GMI · TECHNOLOGY OBSERVATORY // ALL SYSTEMS NOMINAL
ENGINEERED BY LEOPARD DATA

Free Tier Architecture

How GMI serves a no-credit-card free tier at near-zero marginal cost: derived entitlements, a global grade cache, server-side enforcement, and model routing

ENTITLEMENT DERIVATION
rendering diagram…
flowchart TD
    REQ[API request for account] --> ENT[AccountEntitlementService]
    ENT --> STAFF{Staff flag?}
    STAFF -- yes --> S[Staff tier<br/>unlimited, never billed]
    STAFF -- no --> CARD{Active card on file?}
    CARD -- yes --> P[Paid tier<br/>unlimited, usage-billed<br/>Haiku / Sonnet / Opus]
    CARD -- no --> F[Free tier<br/>8 symbols, 1 report/month<br/>Haiku only, never billed]
    F --> WALLS[Enforcement points<br/>symbol add, report create,<br/>AI submit, rate limits]
    P --> WALLS
    S --> WALLS
Tier is computed at request time from existing state — no tier column, no state machine, no partial-upgrade states.

Derived Tiers, Not Stored Tiers

The free tier introduced no new account state. An account's tier is derived on every request from facts that already exist: a staff flag makes it Staff (unlimited, never billed), an active card on file makes it Paid (unlimited, usage-billed), and everything else is Free — capped at 8 tracked symbols, one report per calendar month, and the fastest Claude model tier.

This makes two hard problems disappear. Upgrades are instant and lossless by construction: adding a card flips the derivation, and the user's watchlist, grades, and history were never partitioned by tier in the first place — there is nothing to migrate. And failed upgrades cannot leave partial state: if the card fails, the derivation still evaluates to Free. A single AccountEntitlementService is the only code that answers "what may this account do?", and every enforcement point consults it.

GLOBAL GRADE CACHE
rendering diagram…
flowchart LR
    subgraph Refresh[Earnings-driven refresh]
      E[Earnings calendar scan] -. reported? queue +2d .-> Q
      Q[Refresh job queue] --> B[Grade builder service]
      B --> FMP[Market data fan-out<br/>per ticker]
      FMP --> G[Shared grading model]
      G --> C[(Per-ticker grade cache<br/>grade + full summary)]
    end
    C --> SEO[Public SEO grade page]
    C --> D1[User dashboard A]
    C --> D2[User dashboard B]
    T[User tracks new ticker] -. registers + queues .-> Q
One grade computation per ticker serves the public SEO page and every user tracking that stock. Dashboard views are database reads.

The Global Grade Cache

Free-tier dashboards are fed by the same per-ticker grade snapshots that power the public SEO ticker pages. The grading model was already unified — the SEO builder and the report engine call the identical pure grading function — so the free tier's job was to unify the cache: each ticker row now stores the full per-symbol summary alongside the headline stats, and a user's grade dashboard is assembled from those rows.

  • Viewing a dashboard costs a database read. No market-data API calls, no recomputation — refreshing the page all day costs effectively nothing.
  • Grades refresh on earnings events, never on demand. A background pass watches each tracked ticker's earnings calendar and regrades it a couple of days after the company reports — exactly when the fundamentals actually move — with a staleness cap as a safety net. Only tickers that at least one user actually tracks participate, so data cost scales with real users rather than with the full multi-thousand-ticker SEO universe (which keeps its own slower cadence).
  • New tickers self-register. Track a stock the cache has never seen and it is queued for the next refresh pass — and gains a public SEO grade page as a side effect.
  • One computation, many consumers. A thousand users tracking NVDA share one post-earnings grade generation with each other and with the public NVDA page.

The cache rides the existing refresh-job queue and background service — the free tier added a second, lighter job type rather than a second pipeline.

Server-Side Enforcement, Payment-Grade

Entitlement checks are treated with the same discipline as payment code: the UI renders friendly walls, but every limit is enforced server-side, so a caller bypassing the web client hits the same rules via the API.

  • Symbol cap — enforced at the single service-layer choke point for symbol adds, plus fail-fast pre-checks on every bulk path (list create, list replace, ETF import, template import) so a denied request writes nothing.
  • Monthly report allowance — a month-scoped count over the jobs table (failed runs excluded, so they never burn the allowance), checked in the job-creation transaction and again at the API for a clean HTTP 402 with an upgrade message. The month boundary resets itself; there is no counter to maintain.
  • Model routing — every Claude request's model is validated against the account's allowlist and silently coerced when out of policy; free accounts always run the fast tier. This also closed a pre-existing gap where a client-supplied model string reached the AI API unvalidated.
  • Abuse controls — email verification is required before any generation endpoint runs, verification is token-only (no self-service bypass), and generation endpoints sit behind their own tighter per-account rate bucket.
  • Observability from day one — every denied attempt (cap hit, allowance exhausted, model downgrade) is written to the activity log, powering the admin free-tier dashboard: signups, verification rate, wall hits, free-to-paid conversions, and per-user cost.

Email-Only Signup and the Lossless Upgrade

The old funnel required manual account approval and a card before the dashboard. The free-tier funnel is fully self-serve: sign in with Google, verify your email, land on the dashboard. Email verification doubles as the approval step — the verification handler activates the account, and admin notification became informational rather than a queue to work.

Upgrading is the same page that used to gate the product, reframed as an opt-in unlock: add a card, and because tier is derived, entitlements flip on the next status read. Downgrading is symmetric — remove the card and the account simply derives back to Free. Nothing is copied, archived, or deleted in either direction.

Why Marginal Cost Stays Near Zero

Three properties keep a free user cheap: dashboards are cache reads; grade refreshes are shared across all users of a ticker and triggered by earnings events; and the one monthly AI report runs on the fastest model tier with its cost logged per run. The design goal was that a free account's recurring cost rounds to cents — cheap enough to let the free tier be genuinely free for life, with the paid tier's usage billing untouched alongside it.