Skip to content

FS engagement status machine

A financial-statements engagement is one annual or interim engagement for a single client (e.g. ACME's FY2024 statements). It moves through seven states.

What each state means

StateMeaningLock semantics
DRAFTEngagement created. Header (year-end, reporting standard, currency) editable. No TB yetTB not loaded; nothing to compute
TB_UPLOADEDTrial balance ingested (CSV/XLSX). Sign normalized. Awaiting taxonomy mappingRe-uploading replaces all TB rows; mapping work, statements, and notes invalidated
TB_MAPPEDEvery TB row carries an SFRS taxonomy key (auto-suggested by Claude or manually overridden)Statements can now be generated
STATEMENTS_GENERATEDDeterministic Balance Sheet, P&L, SOCE, Cash Flow computed and persisted as the latest versionDisclosure interrogation now meaningful (rule triggers fire on the mapped TB keys)
NOTES_COMPLETEClaude generated all notes + Directors Report in a single 16K-token call. Mandatory policies insertedEngagement is review-ready; staff exports the DOCX from here
NOTES_ERRORNotes generation failed (LLM error, JSON parse, schema mismatch). RecoverableStatements are still valid; retry the notes job
ARCHIVEDEngagement closed at the end of the audit cycleSealed; no further edits

Why TB upload returns to DRAFT

A trial balance is the foundation. A new TB upload replaces all TB rows and invalidates every downstream artifact (mappings, statements, notes). Returning to DRAFT makes that invalidation visible in the status badge — a reviewer who sees TB_MAPPED → DRAFT knows the upload happened and the rest needs re-running.

Why notes have an explicit error state

The Claude call for notes is one of the longest single requests in the system (16K tokens). When it fails, the staff user must know whether to wait, retry, or fix the input. NOTES_ERROR is the state where the engagement sits while a Senior Accountant looks at the failure record and decides; NOTES_ERROR → STATEMENTS_GENERATED re-enables the retry button.

Where these transitions happen

TransitionTriggered by
DRAFT → TB_UPLOADEDparse-trial-balance worker handler completing (POST /ops/financial-statements/engagements/:id/trial-balance)
TB_UPLOADED → TB_MAPPEDauto-suggest-mapping handler when no rows remain unmapped, or final manual override
TB_MAPPED → STATEMENTS_GENERATEDgenerate-statements handler
STATEMENTS_GENERATED → NOTES_COMPLETE / NOTES_ERRORgenerate-notes handler outcome
NOTES_ERROR → STATEMENTS_GENERATEDStaff-triggered retry of generate-notes
NOTES_COMPLETE → ARCHIVEDStaff-triggered archive on the engagement detail page
STATEMENTS_GENERATED → STATEMENTS_GENERATEDSelf-loop: re-running generate-statements bumps version_no on FsGeneratedStatement

Implementation

State transitions live in packages/financial-statements/src/status-transitions.ts and are enforced by canTransitionFsEngagement(from, to). The valid set per current state is queryable via getNextFsEngagementStatuses(from) for UI rendering.

Internal use only — BreezyCorp