# Flight Pacing Tracker ## Product and engineering specification, V1.1 Hostinger edition **Kari Tech · 12 September 2026** **Route:** /tools/flight-pacing **Scope:** Immediate CSV and manual-entry previews without an account, plus a private, durable saved workspace for one authenticated user. **Status:** This document describes the Hostinger source edition. Deployment and acceptance-test results are maintained separately; this specification does not establish either. This specification describes the Hostinger implementation of V1.1 and its acceptance criteria. It is not a test-results report. Calculation choices are Kari planning rules, not a reproduction of any demand-side platform's delivery algorithm. ## 1. User and jobs The primary user is a programmatic trader or agency operator reviewing several independently budgeted flights across clients and platforms. The tracker supports four recurring jobs: - Find flights with pacing risk or incomplete reporting before deciding what needs attention. - Maintain a daily record from completed reports, including legitimate zero-delivery days and corrected totals. - Compare delivery with a spend or impression plan and understand the remaining daily requirement. - Save review decisions and follow-up actions, then export records for a handoff or backup. Each flight must represent a distinct allocation. The tracker does not identify parent-child budget overlap. Users must avoid adding both an insertion order and all of its constituent line items to the same portfolio total. ## 2. Included scope and boundaries V1.1 includes the Data Interpreter for CSV upload/paste, deterministic column matching, four row types, explicit exclusions, reviewed flight plans, editable temporary previews, preview backup/restore, and a sign-in draft bridge. The underlying tracker includes saved flights, a portfolio register, selected-flight detail, daily delivery, charting, daily or weekday allocation, spend or impression pacing, a planning cap, review states, actions, recent activity, exports, and isolated fictional examples. The interpreter runs locally in the page to prepare a preview. It makes no AI API call and does not claim an active AI service. Initial report interpretation is available without an account; saving to MySQL requires a verified Better Auth session. XLSX files, screenshots, and arbitrary prose are not supported report inputs. Platform names and external IDs are metadata. There is no live DSP synchronization, OAuth connection to a DSP, automated optimization, bid update, budget change, or platform write. A tracker state of Paused does not pause a campaign. An entered daily cap is a planning input, not an enforced platform limit. There are no invited collaborators, shared team roles, approval routing, notifications, or scheduled report ingestion. The current authenticated user's saved records are private to that user. V1.1 does not include intraday/partial-day reporting, foreign-exchange conversion, shared-budget allocation, automatic holiday calendars, custom excluded dates, custom pacing curves, or historical plan-version replay. ## 3. Views and actions ### Portfolio The first viewport is an operational workspace in Kari white, graphite, pale neutral, and restrained lime. It contains the title, refresh control, Use my data, Add flight manually, and the Data Interpreter introduction. Visitors can choose/drop a CSV, paste report data, or try a labeled example immediately. The illustrated workflow has a pause control. Below it are a persistent mode label, filters, four summary cards, and the flight register. | Element | Implemented behavior | |---|---| | Workspace mode | Example flights, My report preview, or My saved workspace. Temporary previews are labeled as not saved yet. | | Bring data | CSV chooser/drop area, paste, example CSV, or manual flight entry. New report onboarding always opens a temporary preview first, including for signed-in users. | | Keep or restore | Download preview backup, Restore preview, and either sign in to keep data or Save to my workspace. | | Scope | One selected currency and cost basis; optional platform filter. | | Search | Matches flight name, advertiser, and platform. | | Filter tabs | All flights, Needs attention, To review, and Archived. All excludes archived records. | | Sort | Attention first, ending soonest, or flight name. | | Summary | Tracked budget, recorded spend, flights needing attention, and open actions, calculated from the filtered rows. | | Register | Flight, status, recorded/goal, pacing percentage, required/day, latest delivery date, and missing-date count. | | Review queue | Up to six currently filtered warning/danger flights with a concrete review prompt and link into detail. | | Export view | Downloads the filtered portfolio as CSV. | Currency and cost basis are exact-match filters before monetary aggregation. No mixed-currency total or currency conversion is produced. Summary budget/spend are monetary totals even when some flights pace against impressions. Flights can have different reporting timezones; the portfolio is a sum of available completed-day records, not a synchronized global end-of-day report. Latest dates and gaps remain visible per flight. Needs attention is driven by delivery/data status. To review is driven by the separate review state. Completing a review does not hide an unresolved pacing warning. ### Flight detail Selecting a flight replaces the portfolio view with its detail; All flights returns to the register. The toolbar provides edit plan, Update report through Data Interpreter, record delivery, export delivery, Flight backup, and removal. A valid last-import snapshot exposes Undo import. Four summary cards show recorded delivery, expected delivery, required/day, and projected finish. The primary goal determines their units. Four tabs organize the work: 1. **Pacing overview:** cumulative actual, plan, projection, schedule, timezone, cutoff, planning cap, tolerance, coverage, unspent monetary budget, and external platform ID. 2. **Daily delivery:** newest-first ledger with spend, optional impressions, source, edit, and confirmed removal. Missing reporting dates offer direct entry shortcuts. 3. **Review & actions:** review state, action text, optional due date, open/completed state, and follow-up controls. 4. **History:** the latest 100 activity entries in the flight's reporting timezone. Saved mutations are server-stamped; temporary preview changes have local timestamps. Editing a plan recalculates the current chart and metrics. The activity log records that a plan changed, not a full before/after snapshot. After any delivery is recorded, currency, timezone, cost basis, platform, and external platform ID are locked against changes. A different reporting identity requires a separate flight. Names, goals, and valid dates remain editable. Changing the primary goal's units requires clearing the prior daily cap; an impression cap must be a whole number. Date edits are revalidated against existing rows and fail if they would leave delivery outside the flight. ### Examples, temporary previews, and empty/error states The sample workspace contains four fictional flights covering underpacing, near-plan delivery, overpacing, and overdue data. Its dates are generated relative to the sample session's current date. Sample flights and actions are read-only, are not written to the user's saved workspace, and are labeled in the UI and export filenames. Unauthenticated visitors can interpret their own CSV/paste data or manually create a flight, then review and edit it in My report preview. No sign-in is needed to begin. New-report onboarding creates a temporary preview even when already signed in. The user must explicitly choose Save to my workspace to persist it. A signed-in user can switch among examples, the current temporary preview, and saved flights. A new empty saved workspace shows Create your first flight; filters with no matches show a separate no-matches state. Temporary previews support plan edits, delivery updates, reviews, actions, and exports. They live in tab memory and are not ordinary autosave. Download backup preserves the preview as JSON. Restore preview accepts a valid preview bundle or an individual flight JSON backup and replaces the temporary preview; it does not overwrite a saved record. Download the current preview first if it needs to be retained. Before navigation to the same-origin /sign-in page, the UI attempts to write a one-hour sessionStorage draft. On return, it restores that draft into the temporary preview and still requires an explicit save. This draft bridges the navigation; later edits are not continuously written back to it. If browser draft storage fails, the interface asks for a downloaded backup before continuing. Saving clears the draft. Session storage is not the durable authority. Loading, saving, failed requests, authentication requirements, missing records, revision conflicts, and deletion confirmation have explicit feedback. A saved notice appears only after server acknowledgment. A failed save leaves the editor open with its draft. Conflict recovery requires refreshing the latest saved record and reviewing the edit again; there is no automatic merge. ## 4. Data model and durability ### Flight aggregate Once saved, one flight is stored as one JSON aggregate in MySQL, accessed through mysql2 against Hostinger's MySQL-compatible MariaDB service. Its arrays are replaced atomically with the enclosing record. Temporary previews use the same flight shape in browser memory, without writing to MySQL until the user saves. | Group | Fields | |---|---| | Identity | id, revision, name, advertiser, platform, externalId | | Reporting context | start, end, timezone, currency, costBasis | | Plan | budget, goalType (spend/impressions), nullable impressionGoal, schedule (daily/weekdays), nullable dailyCap, tolerance | | Tracker state | active, paused, archived; separate reviewState of needs-review, in-progress, or reviewed | | Delivery | date, spend, nullable impressions, source | | Action | id, text, optional due date, done, created timestamp | | Activity | at timestamp, short event text | | Import undo | Optional snapshot containing delivery immediately before the last applied Interpreter update or changed strict CSV import | The database row contains id, owner_id, revision, payload, created_at, and updated_at. Every lookup and mutation is scoped to owner_id. Spend and budget are numeric JSON amounts validated to at most two decimal places. Recorded-spend summation uses rounded cents before converting back to currency units. This is not a persisted micros-based money ledger. Impressions and impression goals are safe whole numbers. Unknown impressions are null; zero is an explicit measurement. Current currencies: USD, GBP, EUR, CAD, AUD, JPY, SGD. Supported reporting zones: UTC, America/New_York, America/Chicago, America/Los_Angeles, Europe/London, Europe/Berlin, Asia/Singapore, Australia/Sydney. ### Authentication and revision checks The server calls getAuth().api.getSession({ headers: request.headers }) to validate the Better Auth session and derives the owner from session.user.id. The old oai-authenticated-user-id header is untrusted and cannot authenticate a request. An owner ID supplied in request JSON is also not proof of identity. Missing account configuration returns 503; an absent or invalid session returns 401. A new flight starts at revision zero. A successful create saves revision one. Subsequent writes include the revision the editor read. The database update is conditional on id, owner_id, and revision; success increments the revision. A changed or missing revision returns a conflict without overwriting the later record. The same revision protection applies to saved-flight Interpreter updates, strict imports, import undo, and deletion. MySQL is authoritative for saved flights. Browser state holds the current view, editable temporary previews, and unsaved form drafts. The optional expiring sessionStorage draft is only a sign-in bridge. The API exposes GET /api/pacing and JSON POST operations create, save, save-preview, interpret, import, undo-import, and delete. Responses are private and not cached. Cross-site mutation requests are rejected. Expected failures include unauthenticated (401), disallowed origin (403), inaccessible flight (404), stale revision or duplicate saved identity (409), oversized request (413), wrong content type (415), and unavailable storage (503). Save-preview accepts 1 to 20 final flight aggregates, including edits made after the initial interpretation. The server validates each flight and batch before insertion. It does not re-parse the original CSV on this path. It resets new saved revisions to 1, creates server-stamped creation history, sets Needs review, and clears import undo. A nonempty external ID already tracked for the same platform, currency, and cost basis is rejected; users update that saved flight instead of creating another budget. Interpret receives the original CSV, selected interpretation options, reviewed plans, completeness confirmation, and, for an update, the target ID and revision. The server re-runs the interpretation and validation. All pacing POST operations run inside withWorkspaceLock(owner): one MySQL transaction inserts or selects the owner's workspace_locks row with FOR UPDATE before checking and changing the workspace. This serializes concurrent mutations for that owner, including capacity checks and preview duplicate-identity checks. Multi-flight inserts use the same transaction and connection; updates save one revision-checked flight aggregate. An error rolls back the transaction, so invalid rows or conflicts commit no part of the operation. The 200-flight limit cannot be exceeded by concurrent creates that both saw the old count. ### Hostinger accounts and setup The app runs as standard Next.js 16.3.5 on Node.js 24. Better Auth serves /api/auth/*, and the /sign-in page provides email/password signup, signin, and configured password recovery. The tracker provides the Sign out action. Signup requires a name, email, and a password of 12 to 128 characters; it signs the new user in automatically. Email verification is not required for this edition. Password-reset delivery is available only when RESEND_API_KEY and a valid INQUIRY_FROM_EMAIL are configured, and must be tested separately. A successful password reset revokes existing sessions. Sessions are stored in MySQL and validated by Better Auth on private API requests. The configured lifetime is seven days with a one-day refresh interval; cookie caching is disabled. Authentication endpoints use a database-backed allowance shared across visitors for each path, because forwarded IP headers are not trusted. The configured window is 60 seconds: 100 requests for other paths, 10 for /sign-in/email, 5 for /sign-up/email, and 3 for /request-password-reset. These account limits are separate from the inquiry form's email-keyed and global limits. Guest interpretation and temporary preview work do not require an account. Set DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, BETTER_AUTH_URL, and a private persistent BETTER_AUTH_SECRET. BETTER_AUTH_URL must match the current HTTPS domain and defines the trusted origin. Import database/setup.sql into the new Kari database through hPanel phpMyAdmin, or run npm run db:setup from a Node.js 24 environment with database access and private .env.local settings. The setup creates the schema and does not transfer data from the former hosting environment. Old accounts and saved workspaces do not migrate automatically. A valid exported preview/flight backup can be restored as temporary data and explicitly saved under the new account. ## 5. Dates, coverage, and pacing formulas ### Reporting and schedule Flight start and end are inclusive YYYY-MM-DD calendar dates. Valid dates begin in 2000. Day arithmetic uses date-only UTC representations so daylight-saving changes do not alter the number of calendar dates. Each flight computes its own current date T in its reporting timezone and cutoff Q = min(T - 1 day, end). A delivery date must lie within the flight and be strictly before T. Today's partial total and future rows are rejected. Daily allocation includes every date. Weekdays allocation includes Monday through Friday and excludes Saturday/Sunday from the plan. It does not remove public holidays. At least one scheduled day is required. Coverage requires a real row for every closed calendar date from flight start through Q, including weekends on a weekday plan. Enter confirmed zeroes for genuinely inactive dates. The allocation schedule changes the plan and rate window, not reporting completeness. Supplied weekend delivery counts toward actuals even though the weekday plan does not allocate to those dates. ### Zero, gaps, and staleness Spend is required on every recorded row; 0 is valid. Impressions may be omitted. A spend-goal flight can be complete without impression values. An impression-goal flight is incomplete if any required calendar row has unknown impressions. The model returns total impressions as unavailable until coverage is complete, alongside knownImpressions and impressionsComplete fields so the known subtotal is not mistaken for a complete total. Data through is the latest recorded date, not a promise of contiguous coverage. The missing-date list is evaluated independently. Data overdue means required calendar dates exist after the latest recorded row. An earlier hole with a later record is Missing data. Uploading a file today does not make an old reporting date fresh. Known amounts are still displayed when data is incomplete, but pace percentage, required delivery, and operational projection are withheld. The actual chart stops at the first unresolved required gap; it does not join later points across unknown delivery. Explicit zero rows restore coverage without fabricating activity. ### Formula contract Apply the following to the primary goal. Spend target G is the monetary budget; impression target G is impressionGoal. Let W be total scheduled days, E be scheduled days through Q, R = W - E, and A be known recorded primary delivery through Q. | Metric | Formula and conditions | |---|---| | Expected by cutoff | G × E / W | | Remaining primary goal | max(0, G - A) | | Pace versus plan | 100 × A / expected, only when coverage is complete and expected > 0 | | Variance | A - expected; derivable from the shown/exported values, not a separate V1 dashboard card | | Required per remaining scheduled day | max(0, G - A) / R when state is active, R > 0, and coverage is complete or no calendar reporting day is due yet | | Recent rate | Mean primary delivery over the last min(7, E) scheduled dates; every date in that window must be known | | Projected finish | A + recent rate × R when the tracker is active and overall coverage is complete | | Planning-cap finding | Required/day exceeds the optional dailyCap, in the units of the primary goal | | Unspent monetary budget | max(0, budget - recorded spend), regardless of primary goal | No estimate is clamped to the goal. V1 uses available short history, including one completed scheduled day; the UI states the number of days used. The projection is a continuation of recent delivery, not an inventory model. The plan chart increments by G/W on scheduled dates and stays flat on non-serving dates. These are ideal fractional planning allocations, not a rounded per-day export that promises to sum to a currency-specific settlement schedule. Monetary display uses locale currency formatting. Ordinary impression amounts round to a whole value; required impression delivery rounds upward to the next whole impression. Required spend remains a displayed planning average, not an automatically executed DSP setting. ### Delivery states and lifecycle New flights default to active, a daily schedule, and ±10% tolerance. Tolerance can be edited from 1% to 50%. Equality at either tolerance boundary remains On track. After lifecycle/data prerequisites, the main pacing states are Underpacing below 100 - tolerance, Overpacing above 100 + tolerance, and On track within the band. Cap too low takes priority over ordinary pacing differences. Recorded monetary overspend produces Over budget even on an impression-goal flight; primary delivery beyond the impression target produces Over goal. Before start, an active record is Upcoming. Before a completed scheduled day is due, an otherwise eligible active record is Awaiting first day. Paused and Archived retain dates and delivery but suppress operational projection/required-day output. Confirmed Over budget or Over goal remains the highest-priority finding even while paused or archived. Otherwise tracker state precedes upcoming/data/pacing labels. Archiving is reversible through Edit plan and removes the record from the default portfolio. After the end date, unresolved reporting remains a missing/overdue data state. With complete final coverage, reaching the goal is Completed; a shortfall is Ended below goal. Overspend/overdelivery remains visible. There is no catch-up number when no scheduled days remain. No lifecycle change writes to an ad platform. ## 6. Daily entry and Data Interpreter reports ### Manual daily entry Record or edit one full-day total per selected flight/date. An existing date is replaced, not incremented. The dialog shows the prior spend before replacement. The date of an existing row is fixed during editing; correcting the date requires a deliberate removal and a new entry. Negative spend, invalid dates, out-of-flight dates, today/future dates, and fractional impressions are rejected. Removing a date requires confirmation and may reopen a coverage gap. Manual flights and delivery can be prepared in the temporary preview before sign-in. ### Bring, match, and review CSV entry now opens Data Interpreter. Its three steps are Bring your report, Match & organize, and Review & track. A readable chosen file goes directly to mapping. CSV paste and the downloadable fictional example are also available. The parser accepts quoted CSV, escaped quotes, a UTF-8 BOM, CRLF, and blank lines. It validates UTF-8 byte size, source row/column limits, nonempty case-insensitively distinct headers, and consistent column counts before interpretation. Deterministic aliases suggest one clear match for date, spend, impressions, flight identity, and optional metadata. Matching lowercases and strips non-alphanumeric header characters. More than one candidate remains unresolved. ID/name suggestions try to use the same campaign or line-item level; users must choose which independent budget to track. A source column can have only one mapped meaning. Date and spend are required. Optional fields are impressions, flight ID/name, advertiser, budget, start/end dates, currency, platform, timezone, and spend basis. Grouping uses the chosen ID, then the chosen name, otherwise one flight. Blank mapped grouping IDs and inconsistent names for one ID are rejected. Unmatched columns are ignored except when explicitly selected as detail identifiers. Users choose date-only ISO, month/day/year, or day/month/year interpretation and dot-decimal or comma-decimal number formats. Year-first slash dates are normalized to ISO. Money accepts matching leading currency symbols/codes and valid grouping separators but no more than two decimals. Ambiguous date order is a user setting; timestamps, percentages, exponent notation, negative amounts, and blank spend are not supported. Date-only reports cannot be shifted between timezones. Mapped budget and other flight metadata must be nonblank and consistent within each group. Budget is kept once per flight and is never summed across report rows. For new flights, the review permits a name, approved budget, and actual start/end dates to be confirmed or supplied. The first report date is only a labeled start suggestion when metadata is absent; the report period is not presumed to be the full flight. The receipt shows source rows, resulting daily totals, flights, and excluded rows. The review shows up to seven daily rows per flight and states when additional validated days will be included. An update also shows prior saved spend or New date. Complete validation and a fresh confirmation are required before Open my tracker or Update this flight. Changing settings, mappings, or plans clears confirmation. ### Four row types and explicit exclusions | Row type | Behavior | |---|---| | One daily total per flight | Requires one included flight/date row. Duplicate dates fail. | | Separate items to add together | Adds distinct complete detail rows into one flight/date total. Every selected detail identifier must be present and the composite key must be unique. | | The same daily total repeated | Keeps one copy only when repeated spend and impressions agree. Conflicts fail without averaging. | | Running totals from flight start | Requires one complete cumulative flight snapshot per date, then converts it using the strict rules below. | The UI requests a row-type choice when a flight/date repeats. For detail aggregation, all recognized breakdown columns such as ad, creative, placement, device, country, or hour must be included in the selected identifiers when not otherwise mapped. Additional source breakdowns must also be chosen when needed. Spend is added in integer cents. If any contributing item's impressions are unknown, the aggregate day's impressions remain unknown. Exact repeated rows and recognized Total/Grand Total/Summary rows are not silently removed. The user may explicitly choose either exclusion. The receipt lists original CSV row numbers and reasons, showing the first 100 exclusions. Recognition is limited to the implemented labels and date/blank-date conditions, not every platform subtotal convention. Other invalid rows stop the interpretation. Every included date must contain all contributing items at the selected flight level. A filtered detail export may be structurally valid yet understate delivery. The user confirms completeness and distinct budgets; the interpreter cannot prove either from the CSV alone. ### Strict cumulative conversion Cumulative imports must begin on the flight start date and contain consecutive calendar dates through their final row, including weekends even for a weekday plan. The final imported date must also cover the latest already-saved date, preventing an old derived tail from surviving a changed cumulative baseline. Mid-flight opening balances, sparse snapshots, and prior-ledger anchors are not supported in V1. Use daily reports for partial updates. With cumulative C(d), derive daily delivery D(d) = C(d) - C(d - 1), using zero immediately before flight start. Raw cumulative rows first pass the normal precision, range, date, and whole-impression checks. Spend conversion subtracts integer cent values; the resulting daily rows pass validation again. If impressions are mapped, they must be present on every cumulative row or omitted entirely. A decrease in either cumulative metric is rejected. Use corrected daily totals for historical restatements. After conversion, the saved authoritative ledger is daily. Daily/detail/repeated Interpreter rows identify their Interpreter row type; cumulative conversion uses the shared CSV cumulative report source. The original uploaded file and its raw cumulative series are not retained as separate database objects. ### Replace, server validation, and undo Update report targets one selected temporary or saved flight and accepts only that flight's report. A supplied flight ID must match. The reviewed currency, timezone, platform, and cost basis must preserve the target context, and mapped report values must agree. Updating delivery retains the saved plan rather than applying incoming budget or flight-date metadata as a plan change. Incoming dates replace matching daily totals and keep saved dates outside the file. Omitting an impression value writes unknown for that incoming date; it does not preserve a previously known impression count. A daily/detail update may cover part of a flight only when each included date is complete. Cumulative replacements must span through the latest existing date. For a saved update, the server retrieves the user's current flight at the supplied revision, re-parses and revalidates the original report and options, reruns conversion and merge, then saves the entire new aggregate with a conditional revision update. Errors or a stale revision commit no part of the update. Temporary updates perform the same interpretation in the page without a server save. Repeating an Interpreter report replaces its dates again, so totals are not doubled. The current Interpreter update still increments the revision, adds Report interpreted, resets review to Needs review, and replaces the undo snapshot even when the values are equal. It is not a no-op write. An applied Interpreter update stores the previous delivery array as lastImport. Undo import restores that array as a new revision, adds an Import undone event, and sets review to Needs review. It does not restore a former review state, rewind the plan, or remove history. Any subsequent saved mutation clears the undo snapshot, including a review or action change. ### Retained strict CSV import API The older import operation remains available in the API and shared logic. It accepts a selected flight's already-daily or strict cumulative report with at most 731 data rows, ISO dates, and plain nonnegative numeric values without symbols or grouping separators. Its recognized identity headers are external_id, platform, currency, timezone, and cost_basis; matching headers are trimmed/lowercased with spaces normalized to underscores, and values must match the saved flight after trimming. It does not use the Interpreter's broader aliases or detail aggregation. This older path returns the unchanged flight when all incoming spend/impression values already match, without a new revision, activity entry, review reset, or undo reset. That unchanged-data shortcut belongs to the strict import API, not the current Interpreter UI. Changed strict imports use the same revision checks and delivery undo contract. ## 7. Review, actions, history, and exports Review is separate from pacing: Needs review, In progress, Reviewed. Creating a flight, changing its plan, recording/removing delivery, applying an Interpreter update, applying a changed strict CSV import, and undoing an import reopen the review. Merely viewing a record does not. Identical Interpreter updates still reopen review; an unchanged strict import API call does not. Actions contain a decision/follow-up note, optional due date, creation timestamp, and open/completed state. Users can complete, reopen, export, or remove them after confirmation. Due dates are organizational metadata; no reminder is scheduled. The activity list retains the latest 100 event names, not a full immutable field-level audit. Saved mutations receive server timestamps; temporary edits receive local timestamps. Saving a preview creates a new saved-record activity entry rather than restoring its temporary history as server history. Current exports are: | Export | Contents and scope | |---|---| | Portfolio CSV | The filtered view: flight identity/context, dates, goal unit/amount, known recorded delivery, recorded spend, expected amount, pace, required/day, projection, latest date, missing count, delivery status, review state, and open-action count. | | Delivery CSV | All selected-flight daily rows, sorted by date, with date, spend, optional impressions, and source. Spend is written with two decimals. | | Actions CSV | Selected-flight action id, action text, due date, done flag, and created timestamp. | | Flight JSON backup | The full currently loaded flight aggregate, including revision, plan, delivery, actions, activity, and any undo snapshot. | | Preview JSON backup | A kari-pacing-preview bundle containing every current temporary flight for restoration and later saving. | Restore preview accepts a valid preview bundle or a single flight JSON backup and opens it as temporary data. It validates 1 to 20 flights, rejects repeated internal IDs, and resets their revisions for new saving. It does not overwrite a saved record or bypass duplicate external-identity checks. Existing saved flights should receive delivery changes through Update report. Downloads use the currently loaded selected mode and flight, including applied temporary edits, not an unsaved editor form. Preview backup includes the whole temporary preview, not only filtered rows. Sample exports include sample in their filenames. CSV cells escape quotes and delimiters and guard leading spreadsheet-formula characters. An action can also be removed after explicit confirmation. Export completed actions before removing them to make room under the per-flight limit. Action removal records an activity event. It does not delete delivery. Removing an entire flight explicitly removes its saved plan, delivery, actions, and activity; there is no recycle-bin restore. Archive remains the reversible organizational alternative. ## 8. Limits and operating constraints | Constraint | V1.1 Hostinger limit | |---|---| | Saved flights | 200 per authenticated user; archive still counts toward this limit | | Flight duration | 1 to 731 inclusive calendar days | | Daily rows | 731 per flight | | Interpreter CSV | 750,000 UTF-8 bytes for uploads and pasted text | | Interpreter source rows and columns | 1 to 5,000 data rows plus a header; at most 64 nonempty, case-insensitively distinct column names | | Interpretation / new-save batch | 1 to 20 distinct flights | | Retained strict import API | At most 731 daily/cumulative data rows; its older text parser retains a 750,000-character limit | | Aggregate payload | Nominal 250 KB cap, implemented as 250,000 serialized JSON characters, checked before and after server metadata/undo history | | Actions | 200 per flight, including completed actions | | Activity | Latest 100 entries per flight | | API request | 2,000,000 declared bytes when Content-Length is present and 2,000,000 decoded text characters | | Preview restore | 2,000,000-byte JSON file; 1 to 20 valid flights with distinct IDs | | Sign-in draft | One-hour expiry; at most 2,000,000 serialized characters, subject to browser sessionStorage availability | | Budget / daily spend | Up to 1 billion; budget positive, recorded daily spend nonnegative; at most two decimals | | Impression target / daily count | Up to 1 trillion, safe whole numbers | | Text | Flight name 120 characters; advertiser 100; platform 80; external ID 100; cost basis 80; action 1,000 | A dense flight can reach the payload limit before the row/action limits, especially while retaining an import undo snapshot. Export before removing records needed elsewhere. The current history is a bounded activity list, not a compliance archive. ## 9. Acceptance criteria These are required verification scenarios, not a claim that testing has already completed. | Area | Observable pass criterion | |---|---| | Durable private records | A saved flight, daily rows, actions, and review state survive refresh and reloading under the same authenticated user. Another user's identity cannot read, overwrite, import into, or delete that flight. | | Concurrent edits | Two sessions read revision n. The first successful write creates n+1. A write/import/delete from the stale session returns 409 and cannot overwrite n+1. | | Daily spend math | Daily Sep 1–30, 2026; budget 30,000 USD; seven complete days of 800 through Sep 7; now Sep 8 in the flight zone. Actual 5,600; expected 7,000; pace 80%; 23 days remain; required 1,060.87 displayed; seven-day projection 24,000; Underpacing at default tolerance. | | Weekday math | Sep 4–8, 2026, weekdays; goal 300; now Sep 7; Friday actual 90, Saturday0, Sunday0. W=3, E=1, R=2; expected100; required105; projection270 from one known scheduled day. Removing either confirmed weekend row blocks complete coverage. A public holiday is not excluded automatically. | | Impression math | Ten scheduled days, impression goal 1,000,000; four complete days of 70,000 each. Actual280,000; expected400,000; pace70%; required120,000; projection700,000. A missing impression value prevents complete impression pacing; known subtotal remains distinguishable from unavailable total impressions. | | Zero and gap | An explicit zero counts as recorded coverage. Removing a required date makes pace, required/day and projection unavailable. A later row cannot bridge the chart across the missing date. | | Staleness | A missing latest reporting day becomes Data overdue. An earlier gap with a later record becomes Missing data. Upload time alone does not change freshness. | | Calendar boundaries | A one-day flight and a DST-crossing flight use inclusive calendar dates without NaN/infinity. Today in the selected timezone cannot be imported as a completed date. | | Reporting identity | Changing currency, timezone, cost basis, platform or external ID after delivery exists fails without mutation. Switching goal units clears the prior cap; fractional impression caps fail. | | Tolerance and cap | Exactly 90% and 110% are within the default 10% band. A required/day above the entered primary-unit cap gives Cap too low when lifecycle and coverage permit evaluation. | | Lifecycle | Upcoming and first-day states do not produce an underpacing alert. Pause/archive suppress forecast but do not hide already-recorded over-budget/over-goal findings; archive is reversible. An ended incomplete flight still asks for data; an ended complete shortfall has no fictional catch-up days. | | Cumulative import | From-start cumulative spend100,240,360 converts to daily100,140,120. Starting mid-flight, skipping a date, ending before the latest saved row, invalid raw precision, or decreasing totals fails before any save. | | Interpreter validation and replacement | Any invalid included row blocks the interpretation. Conflicting repeated totals, repeated detail keys, metadata mismatches, and invalid selected formats fail. A reviewed complete date replaces its saved total; unrelated dates remain intact. | | Immediate guest preview | A CSV upload or paste reaches mapping without sign-in. Ambiguous aliases remain choices. A valid confirmed report opens a labeled editable preview, including for signed-in visitors, without writing to MySQL. | | Row grain and budget | Detail spend120+80 becomes daily200 while a budget9000 repeated on both rows remains9000. Repeated totals require agreement. Exact duplicates and recognized summary rows are removed only by explicit settings with a visible receipt. | | Preview save and restore | Preview and single-flight JSON backups restore temporary data without overwriting saved records. Sign-in draft recovery still requires Save to my workspace. Server validation rejects invalid batches and already tracked external reporting identities. | | Repeated updates and undo | Repeating an Interpreter update does not double totals, but does create a new revision/activity event, reset review, and renew the undo snapshot. Identical strict import API values remain a no-op. Applied updates can be undone until another saved mutation; undo restores prior delivery and reopens review. | | Review and actions | Reviewed remains separate from delivery risk. New data reopens review. Creating/completing/reopening an action persists and updates open-action counts; confirmed removal deletes only that action and logs the change. | | Filtered totals | Currency and cost basis are separated before summation; search/platform/filter changes update cards and exported rows to the same scope. | | Export | Portfolio, delivery and actions CSV match the loaded mode/record, safely escape text, and distinguish samples. Delivery CSV round-trips through a correctly configured update without changing totals. Flight JSON contains the loaded aggregate; preview JSON includes all temporary flights and can be restored. | | UI and accessibility | The responsive workspace, dialogs, tabs and actions remain operable by keyboard, with labels, visible focus, readable errors, and saving feedback. Status meaning is stated in words. The chart has a text summary; the daily ledger provides a numeric alternative. | ## 10. Official references and interpretation The following primary sources informed the product. The formulas and restrictions above remain Kari's implementation choices. - **Budget units, pacing settings, and parent limits:** DV360 supports spend and impression budgets and distinguishes daily/flight pacing modes. Actual delivery depends on additional platform constraints. This supports explicit goal units and a clearly labeled plan, not exact simulation. [Google: Set budgets and control your pacing](https://support.google.com/displayvideo/answer/3114676?hl=en) - **Required daily delivery and performance:** Google describes the remaining-budget/remaining-days calculation and distinguishes budget pacing from outcome performance. Kari applies its own daily or weekday schedule. [Google: Difference between performance and pacing](https://support.google.com/displayvideo/answer/2697800?hl=en) - **Reporting timezone:** Advertiser timezone affects dates and measurement windows; reports can use UTC. Date-only aggregates cannot be reliably shifted into another zone without the underlying hourly data. [Google: How time zones work](https://support.google.com/displayvideo/answer/3184282?hl=en) - **Revised reports:** DV360 data may be updated for up to 31 days. A completed-day upload is a reporting cutoff, not a claim that the platform has permanently finalized the values. [Google: Report data freshness and availability](https://support.google.com/displayvideo/answer/6110224?hl=en) - **Currency and impression caps:** DV360 documents separate daily cap fields and its own currency-micros representation. Kari's entered cap and numeric storage are independent of those API settings. [Google Developers: Pacing](https://developers.google.com/display-video/api/reference/rest/v4/Pacing) - **Operational workflow:** The Trade Desk documents multiple flights in campaign setup and table-based monitoring/editing. Those examples support a register-plus-detail workflow; they do not imply a TTD integration in Kari. [TTD: Campaign workflow](https://www.thetradedesk.com/resources/create-campaigns-faster-with-a-simplified-workflow), [TTD: Operational tables](https://www.thetradedesk.com/resources/manage-campaigns-more-easily-with-in-line-edits) - **Terminology:** TTD publishes its own pacing and forecast terms. Kari's up-to-seven-day continuation estimate should be described by its actual formula rather than as a native platform metric. [The Trade Desk glossary](https://www.thetradedesk.com/glossary) ### Implementation map The Hostinger implementation is organized in lib/pacing-tracker.ts (model, validation, math, strict CSV), lib/data-interpreter.ts (report parsing, aliases, grouping and finalization), lib/pacing-store.ts (session-derived ownership, batches and revisions), lib/database.ts (mysql2 connection, transactions and owner locks), lib/auth.ts and lib/auth-client.ts (Better Auth configuration and client), app/api/auth/[...all]/route.ts (account API), database/setup.sql (schema), app/api/pacing/route.ts (private pacing API), components/site/pacing-tracker.tsx (workspace, preview, restore and detail), components/site/data-interpreter.tsx (report workflow), and components/site/pacing-forms.tsx (editors/confirmation). The companion Data Interpreter Spec provides the full report workflow contract. Publishing and acceptance-test results are maintained separately. This specification does not claim browser/screenshot QA, deployment, or a hosted authenticated round trip. Prior hosting-specific tests do not verify the Hostinger edition; use its current test report and deployment guide.