Traceability

How we handle your data

We walk through every step your data goes through inside Viral. Where the phase corresponds to executable logic, we point to the source file where the implementation lives and to the automated verifier that checks it.

Who owns and runs this technology

Viral is owned by ApisDom Intelligence Group and operated by ApisDom. The mathematical prediction engine is Chronos 2, an open-source model developed by Amazon Science; ApisDom integrates it into its infrastructure and provides the whole service layer (authentication, credits, exports, this site, and the guarantees described below). The Technology section of the How it works page details the engine's capabilities, limits and performance. This page adds what does not fit there: the internal journey of your data within Viral.

How to read this page

The seven points below describe the phases of the treatment. The first five correspond to executable Viral logic and include two references: the source file where the implementation lives and the name of the automated verifier that runs via npm run verify:all. Step 6 points to the single retention constant and the files that use it; it is configuration, not algorithmic logic, so it has no dedicated verifier. Step 7 lists design principles and service policies, not compilable code.

1. Which columns we read from your file

From your CSV, XLSX or XLS we only read what is strictly necessary: the date column, the value column and, optionally, a third column that marks which days your business was open. Any other column in the file is silently ignored; if we detect extra columns we warn you right on the upload screen.

Exact rules:

  • Headers we recognise for the third column (uppercase, lowercase and accents are indifferent): is_open, is open, isopen, open, abierto, abierta, estado, status.
  • Values we interpret as open: true, 1, sí, si, yes, y, abierto, abierta, open, opened, activo, active.
  • Values we interpret as closed: false, 0, no, n, cerrado, cerrada, closed, close, inactivo, inactive.
  • An empty cell is interpreted as closed. Any other unrecognised text is interpreted as open by conservative default (we prefer not to discard an ambiguous day because of a typo).
Implementation: src/lib/file-parser/is-open.ts
Automated verifier: audit:is-open (12 deterministic cases) and audit:pipeline (12 reference files with headers in different languages and common typos)

2. How we clean the texts you type

The free-text fields you send from your browser (name, support message, unsubscribe reason and similar) pass through a central sanitisation function before being stored in the database, included in a download or sent by email. It is an invisible layer whose purpose is to reduce the risk of a raw text reaching a later channel without normalisation.

Exact rules:

  • We strip non-printable control characters: ranges U+0000 to U+0008, U+000B, U+000C, U+000E to U+001F and U+007F. Legitimate line breaks, tabs and carriage returns are preserved for the next step.
  • We collapse runs of whitespace into a single space and apply trim at the start and at the end.
  • We truncate each field to the maximum length defined by its Zod schema (for example 100 characters for a name, 2,000 for a message).
  • We do NOT strip or escape HTML at this step. React already escapes by default when rendering; channel-specific escaping is applied at the point of use (for example when injecting into an email or when rendering a PDF with jsPDF).
Implementation: src/lib/sanitize/text-input.ts
Automated verifier: audit:sanitize (16 deterministic cases including BOM, emojis, embedded HTML, legitimate line breaks and 10,000-character strings)

3. How we detect if your business closes some days

If your file has the open/closed column, we use it as-is, without transforming its values. If it doesn't have it but we detect a clear pattern (for example every Sunday shows a zero for several weeks) we surface a notice on the upload screen, so you can mark those days if your business is indeed closed. We do not modify or add that information on your behalf and the upload continues in any case.

Exact rules:

  • Weekly closure (code weekly_closure): a given weekday shows a zero in at least 80% of its occurrences within the history, with a minimum of 3 occurrences.
  • Long streak (code long_streak): at least one run of 3 or more consecutive zero-valued days exists.
  • High zero ratio (code high_zero_ratio): more than 20% of the history are zeros without either of the previous patterns being present.
  • This heuristic detection ONLY runs when the user has NOT supplied the column. If the user has already told us which days were closed, we respect that decision without overriding or second-guessing it.
Implementation: src/lib/file-parser/zero-pattern.ts
Automated verifier: audit:zero-pattern (8 deterministic cases: the three patterns above and their negative counterparts, including the case where the user already supplied is_open)

4. How your data reaches the prediction engine

To the Chronos 2 engine we send only what it needs for your prediction: dates, values, number of days to forecast and, if you supplied the list of open days, we propagate it following four strict rules. No other data of yours (not your email, not your IP, not your previous history) reaches the engine.

Exact rules:

  • Rule 1: if you don't have the open/closed column, the field is not sent. The engine treats it as optional and works the same way it did before this feature existed.
  • Rule 2: if its length doesn't match the number of dates, the field is not sent. This is a defence in depth to avoid an engine rejection and protect your request.
  • Rule 3: if all values are open, the field is not sent. It is equivalent to not sending it and saves bandwidth.
  • Rule 4: if it contains at least one closed day, it is sent as-is. The engine substitutes that day with missing data (NaN) and does not learn it as a real sale of zero euros.
  • These four rules are backed by an additional static rule in the error verifier (MISSING_IS_OPEN_PROPAGATION), which requires the adapter to keep referencing input.is_open. If that reference is removed, the static rule fails when verify:errors is executed.
Implementation: src/adapters/prediction/ApisdomAdapter.ts (buildPayload method)
Automated verifier: audit:is-open-adapter (6 cases covering the 4 rules and their edge cases) plus the MISSING_IS_OPEN_PROPAGATION static rule in verify:errors

5. How we compare figures for the percentage cards

The cards you see with percentages (for example your sales will grow by 12%) compare the sum of the predicted days against the sum of the same number of previous real days. If you supplied the closed days, we exclude them from that comparison so the percentage isn't biased downwards or upwards.

Exact rules:

  • Denominator (baseline): sum of the last N historical values, where N is the number of days you asked us to predict.
  • If the open/closed column is available, within that N-day window only the values marked as open are summed. Closed days do not count in the baseline.
  • Numerator: sum of the engine's predictions for each of the three scenarios (conservative, central, optimistic).
  • Percentage = (numerator - denominator) / denominator × 100.
  • If the denominator is zero or the history is empty, the card shows 0 instead of a misleading percentage or an infinity symbol.
Implementation: src/lib/prediction-stats/pop.ts (calculatePoP function)
Automated verifier: audit:pop (5 synthetic cases with different combinations of open/closed days and one case with real data from a business with weekly closure)

6. How long we keep your prediction

We keep your prediction for 90 days from the moment you unlock it. After that period it is automatically deleted. You can download it as PDF or JSON at any time before it expires, and the JSON includes everything you need to reproduce it without depending on our servers.

Exact rules:

  • The 90-day period is a single constant called PREDICTION_TTL_DAYS. It changes in one place and takes effect on every user-facing message and on the actual expiry. There are no two competing periods in the app.
  • Trial users (no account) are subject to the same period. Anonymous identifiers used to prevent abuse are deleted after 90 days of inactivity.
  • The download JSON includes the schema version (schemaVersion) and engine version (engineVersion) used to generate it. Even if the format changes in the future, your download stays interpretable because it carries its version inside.
Implementation: src/config/constants.ts (PREDICTION_TTL_DAYS = 90 constant), src/app/api/credits/spend/route.ts (sets expiresAt), src/lib/trials.ts (same TTL for trials)

7. What we NEVER do

The Chronos 2 engine is a model pretrained by Amazon Science, with zero-shot inference: it predicts on your time series without having been retrained on your data. Neither we nor ApisDom retrain the model, and by design there is no point in the system where your data becomes part of a collective dataset.

Exact rules:

  • We do not fine-tune or incrementally train with your data. Inference is zero-shot by design.
  • We do not sell nor transfer your data to third parties for marketing or advertising purposes.
  • We do not cross-reference your data with that of other users to build aggregate statistics or benchmarks.
  • We do not keep your original file after computing the prediction. We only store the result (dates, values, scenarios and metadata needed to reproduce it during the 90-day window).
  • We do not send commercial email without your explicit consent. The newsletter is opt-in with double confirmation.

About the automated checks

The npm run verify:all command groups the project's automated checks: static verifiers (they detect dangerous patterns in the code), dynamic auditors (they exercise critical functions with real data) and type consistency. The pipeline distinguishes between blocking failures, explicitly accepted known behaviours and still-pending tests: the first stop the deploy, the others are recorded within the pipeline itself for later review. For the full legal terms of processing, see the privacy policy and the terms of service.