Data model#
Two surfaces: the in-memory domain models, and the on-disk usage table.
Domain models#
All domain entities live in qfa.domain.models and are Pydantic BaseModel(frozen=True) per ADR-001. One exception is AggregateSummaryResultModel, which is mutable so the orchestrator can attach a quality score after the judge call.
Model |
Purpose |
|---|---|
A single beneficiary feedback record submitted by the CRM. |
|
Request and result for |
|
|
Per-record summarisation. |
Single aggregate summary with judge score. |
|
Hierarchical code assignment. |
|
|
Generic envelope returned from |
One row in |
|
One LLM call’s worth of tracking data — written by |
|
Aggregate views returned by |
Persistence — llm_calls#
When DB_TRACK_USAGE=true, every LLM call appends one row to the llm_calls table. The schema lives in qfa.adapters.db and is managed by Alembic migrations under migrations/.
Roughly:
Column |
Meaning |
|---|---|
|
UUID primary key |
|
Caller, set from the authenticated |
|
One of |
|
The LiteLLM model string used |
|
From the provider response |
|
Computed from LiteLLM’s cost map; zero when the model has no published pricing |
|
Wall-clock duration of the call |
|
Exception class name if the call failed |
|
UTC timestamp |
Migrations#
Run by python -m qfa.cli.migrate (entry point in qfa.cli.migrate). Invoked from entrypoint.sh before uvicorn starts. Uses Alembic with a Postgres advisory lock so concurrent replicas wait for one migrator to finish — see Deployment: runtime overview for the operational story.
What lives outside the domain#
API schemas (
qfa.api.schemas,qfa.api.schemas_usage) — Pydantic models for HTTP request/response shapes. Per ADR-007, API schemas are separated from domain models only when fields differ, shapes are reshaped, or HTTP-only fields are added.Tracking-table rows —
qfa.adapters.dbdefines the SQLAlchemy table mapping; the domain only seesLLMCallRecord.