All projects

NXSelf

A reporting service where the numbers are computed and only the sentences are generated.

2026 · Solo build — the data model, the privacy design, and the generation boundary.

NestJSTypeScriptOpenAIzod

The problem

Generated reports are useful right up until someone checks a total and finds it wrong. A model asked to both count and explain will occasionally do neither reliably, and a confident wrong figure is worse than no report.

What it does

A service that splits the two jobs cleanly: arithmetic is deterministic, prose is generated. Counts, totals, rates, cadence and state transitions are computed in code with no model call; the model receives settled state and turns it into language.

Engineering decisions

Arithmetic is deterministic, prose is generated

Every number in a report is computed before any model is involved, and the model sees the finished state plus a bounded sample of evidence. It can phrase things badly; it cannot get a total wrong. Drawing that line early is what makes the output trustworthy enough to send to anyone.

The identifier is a hash, and the phone number is never the key

Records are joined across sources on a phone number, which makes it the most sensitive value in the system. The subject identifier is a keyed hash of the normalized number; the recoverable value is encrypted separately. Raw numbers are never database keys, log values, URL parameters, or model input — so a leaked log or a stored prompt doesn't leak a contact list.

A closed action space that won't compile if it grows

Classification runs against a fixed set of action types, and adding a new one fails the build until it's handled everywhere. That's deliberate friction: an open-ended category would quietly absorb anything unfamiliar and report it as normal.

Where it landed

  • Report totals are verifiable, not model-generated
  • No raw phone number in any key, log, URL or prompt
  • New categories can't be silently absorbed — they break the build first