Yard StudioJamaica-first · scam-skeptical
Back to Tidewatt

Tidewatt — Technical Specification

Constraint file: `../JAMAICA_TRUST.md` Launch geography: Jamaica, JPS Rate 10 households (post-paid and prepaid). Forbidden: collecting JPS credentials or JPS payments.


System architecture

[PWA] camera → bill/voucher image IndexedDB: last extract + tariff snapshot

v [API] POST /bills/extract GET /tariff/current ← public OUR/JPS table we maintain POST /watch/plan ← this-week actions (no actuation required)

+---> Official pay HINTS only (URLs/text for jpsco.com, Paymaster, Bill Express)

X---> JPS customer login X---> Card checkout for "clear your balance"

Optional phase 2: smart-plug actuation, Rate 40 TOU for shops. Not in the launch binary.


Core APIs (v1)

MethodPathPurpose
GET/tariff/currentDated fuel, energy, customer charge, GCT — sourced from public PDFs
POST/billsImage of bill or prepaid receipt
POST/bills/{id}/extractLine items + userMustConfirm: true
PUT/bills/{id}Corrected kWh, remaining credit, meter type
GET/watchDays-of-light estimate + confidence
POST/watch/planThree actions for *this* week; comfort locks
POST/alerts/whatsapp-selfOpt-in “credit low” to a number the user typed (not harvested)
GET/receiptsWhat we stored, when
DELETE/householdPurge

Pay links are static official URLs, not open redirects.


Data models

type MeterKind = "postpaid_rt10" | "prepaid_rt10";

interface TariffSnapshot { id: string; effectiveDate: string; sourceUrl: string; fuelJmdPerKwh: number; energyJmdPerKwh: number; customerChargeJmd: number | null; gctRate: number; notes: string; }

interface BillExtract { id: string; meterKind: MeterKind; kwh: number | null; remainingCreditJmd: number | null; remainingKwh: number | null; fuelLineJmd: number | null; confidence: number; confirmedByUser: boolean; }

interface WeekPlan { id: string; actions: { text: string; savesEstimate: "unknown" | "small" | "medium"; unsafe: boolean }[]; daysOfLight: number | null; daysOfLightConfidence: number; }

We never store a JPS password. Account number is optional and shown as “only if you want a reminder of *your* number — we cannot pay this.”


Security framework

  • DPA 2020 notice. Bills are financial + household data.
  • Extract models isolated; no training on customer bills without a separate written opt-in (default off).
  • Anti-clone: official origin listed on JPS-adjacent partner desks; 876 to report fake “Tidewatt pay” pages.
  • CSP; no skimmers; no third-party payment iframe.

PWA specification

ItemSpec
Manifestname: Tidewatt, start_url: /watch, display: standalone
OfflineLast bill math + last plan
Low dataOne photo, aggressive compression
Footer“We never take JPS payments.” + official pay names
SMS/WhatsAppDouble opt-in; stop keyword

Scalability

Tariff table is tiny. OCR is the cost. Cache snapshots by hash of the image so retries are cheap. 100k prepaid-curious households still fit a boring Postgres + object store.

Honest limit

If JPS changes bill layout, extract breaks. Correction UI is the product, not a perfect model.