AI personality profiling with a deterministic core: computation strictly separated from interpretation
A B2C profiling product where every number is computed by code and the LLM is only allowed to interpret what was actually calculated — never to invent.
Context
Consumer profiling products on numerology, arcana, and astrology are a proven B2C market — and almost all share the same engineering flaw: the LLM is handed a birth date and asked for the entire reading. The result: unfalsifiable text that invents planetary positions, contradicts itself between sessions, and leaks method jargon.
The methods themselves are deterministic: numerological reductions are arithmetic, the arcana matrix is a fixed formula set over the birth date, and planetary positions are an ephemeris lookup. The model's legitimate role is narrow: turning computed values into fluent, personal prose. The framing is familiar engineering: a deterministic core, a schema with per-field provenance, and a generation layer with bounded inputs.
The task
Build a B2C profiling product on one non-negotiable rule: the AI interprets only fields with status calculated. Every module carries an explicit status; an unimplemented method returns not_connected, and the synthesis layer must acknowledge the gap rather than fill it. Fabrication is a defect class.
Two surfaces share one backend: a Telegram bot collecting name, birth date, and optionally time and place, and a web product with shareable profile pages. Formula tables live in config files, so methodologists tune the system without touching code. v0.4 expanded the brief to presentation — natal map, rendered chart, cover art, audio reading — under the same no-invention rule.
Architecture
The system is a single FastAPI process: HTTP API, server-rendered web product, and Telegram bot polling inside the app lifespan. The computation core lives in an isolated calc package — numerology and the 22-arcana matrix in pure Python over JSON formula tables, natal astrology and synastry via the Swiss Ephemeris, with birthplace geocoding to coordinates and an IANA timezone. The core runs standalone from the CLI, independently testable by construction.
The synthesis layer is a single choke point to OpenRouter: it receives the computed profile, filters to calculated fields, and produces the portrait; the status enum is the contract that keeps it honest. Storage is Supabase, five tables under row-level security, server-side access only. The error log is a product feature: AI failures, exceptions, validation errors, and lang_leak — jargon in consumer text — are recorded with context for admin review.
The visual layer
v0.4 added the presentation layer. The centerpiece is an animated canvas natal map: planet glyphs fly onto their real computed ecliptic positions against a starfield of real asterisms, with aspect lines. Every coordinate comes from the ephemeris — the map is a visualization of the calculation, not an illustration beside it.
Each profile also gets a matplotlib-rendered chart image, AI cover art prompted from computed themes, and a synthesized voice track — attached to a shareable page at a stable URL. The web product is self-contained down to a 24-section investor deck served at /deck. The visual direction converged over 43 commits on a restrained monochrome style with a single accent color.
Result
The full loop is operational: intake via bot or web form; the core computes numerology, arcana, and — given birth time and place — a full natal chart; the AI synthesizes from calculated fields only into a persistent profile page carrying the dashboard and all four media assets. Compatibility reports run the same pipeline over two people via synastry; event-date analysis applies it to a chosen date.
The architectural bet held: the model cannot state a planetary position it was never given, and the error log distinguishes AI failures from code exceptions from language leaks, so each failure class has an owner. Config-driven formula tables absorbed methodology tuning without a code change. The case proves an LLM product can be built so the model is structurally unable to invent facts.
What we built
Deterministic computation core
Numerology, the 22-arcana matrix, natal astrology and synastry computed in plain Python over JSON formula tables and the Swiss Ephemeris; runs standalone from the CLI with zero service dependencies.
Calculation-status contract
Every profile field carries an explicit status; the synthesis layer interprets calculated fields only, and unconnected methods return a status instead of invented values.
Telegram bot with saved identities
Conversational intake of name, birth date, time and place; users and partners persist in Supabase so repeat readings and compatibility reports skip re-entry.
Animated canvas natal map
Planet glyphs fly onto real computed ecliptic positions against a constellation starfield with aspect connections — a full-screen visualization driven entirely by ephemeris output.
Per-profile media pipeline
Server-rendered chart PNG via matplotlib, AI cover art via Replicate prompted from computed themes, and an edge-tts voice track — all attached to a shareable profile URL.
Structured error and leak log
AI failures, exceptions, validation errors and consumer-facing jargon leaks recorded as typed events with context and reviewed in an admin view.
Self-hosted investor deck
A 24-section pitch deck rendered by the product itself at /deck, sharing the design system of the live profile pages.
Engineering challenges
Keeping the LLM inside its lane
A synthesis model given a birth date will happily compute — wrongly. The profile schema's per-field status enum is enforced at the synthesis boundary: the prompt receives only calculated values, and unconnected modules arrive as explicit gaps the model must acknowledge.
Correct charts for arbitrary birthplaces
A natal chart is only as good as its timezone handling — a birth in 1975 in a city with shifted DST rules can move planets by degrees. Birthplace resolves to coordinates plus an IANA timezone via timezonefinder, and the ephemeris is queried in properly converted UT.
Jargon leaking into consumer text
Early outputs occasionally exposed method internals — term-level jargon that breaks the consumer illusion. Leaks were made a first-class defect category (lang_leak) in the error log with context capture, turning a vague quality concern into a countable, fixable defect stream.
Emotional product, restrained design
The niche's default aesthetic is mystical kitsch, which undermines credibility with exactly the paying audience. The visual layer went through 43 rapid iterations toward an editorial monochrome style where the only spectacle — the particle natal map — is a rendering of real computed data.