Financial Statements Preparation
1. Document Control
| Document Title | Singapore Financial Statements Preparation |
|---|---|
| Document ID | SOP-ACC-003 |
| Version | 1.0 (Draft for Automation Build) |
| Effective Date | [Insert Date] |
| Review Date | [Insert Date + 12 months] |
| Document Owner | Head of Reporting & Assurance |
| Approver | Managing Director |
| Status | Draft |
Version History
| Version | Date | Author | Changes | Approved By |
|---|---|---|---|---|
| 1.0 | [Insert Date] | [Insert Author] | Initial SOP for the AI-assisted financial-statements module (BreezyCorp Phase 1) | [Insert Approver] |
Distribution
| Role Title | Access Rights |
|---|---|
| Client Approver | Read-only |
| FS Preparer | Edit |
| FS Reviewer | Edit |
| Senior Accountant | Edit |
| Platform Admin | Edit |
| Quality Reviewer | Read-only |
| AI Workflow Developer | Read-only |
2. Purpose and Scope
2A. Process Overview for New Staff
What is this process?
This SOP defines the controlled annual / interim workflow by which BreezyCorp prepares Singapore-compliant financial statements for a private limited entity, using the AI-assisted Financial Statements Engagement Engine (FSEE). The FSEE orchestrates: trial-balance ingestion, SFRS taxonomy mapping (with AI auto-suggest and mandatory human confirmation), deterministic statement assembly (Balance Sheet, P&L, SOCE, Cash Flow), disclosure interrogation, AI-generated notes and Directors Report, and DOCX export — with human gates at mapping, interrogation, and final review.
When does this process occur?
This process is triggered when a client requires:
- Annual financial statements for an accounting period (typically 12 months ending on a fixed year-end date)
- Interim financial statements for a sub-annual period (less common)
A new FsEngagement is created per client per financial year. Multiple engagements for the same client (e.g. FY2023 and FY2024) coexist; each is independent.
What are the deliverables?
- Validated trial-balance pack stored against the engagement
- Confirmed SFRS taxonomy mapping for every TB row
- Computed BS / P&L / SOCE / Cash Flow with balancing checks and anomaly flags
- Complete disclosure responses for every triggered rule
- AI-generated notes + Directors Report (Section 201 Companies Act)
- Publication-ready DOCX export
- Optional client portal sign-off
- Archived engagement record with immutable audit trail
Who is involved?
- FS Preparer (
FS_PREPARER) - FS Reviewer (
FS_REVIEWER) - Senior Accountant (
SENIOR_ACCOUNTANT) - Client Approver (
CLIENT_APPROVER) - FSEE (orchestration, deterministic statement generator, disclosure rule engine, AI mapping + notes)
- Anthropic Claude Sonnet (taxonomy auto-suggest + notes/Directors Report generation)
2B. Applicable Legal Framework and Statutory Authority
Primary Compliance Areas (Singapore)
- Singapore Financial Reporting Standards for Small Entities (SFRS for Small Entities) — default standard.
- Full SFRS — for entities that do not meet the small-entity thresholds.
- Companies Act 1967 — particularly Section 201 (Directors Report disclosures) and the requirement that financial statements give a true and fair view.
- ACRA filing requirements (informs disclosure rule set, even when XBRL filing is downstream).
- PDPA — directors' personal data appears in financial statements.
Compliance Horizon
This SOP is designed so that no financial statement leaves the system without: a mapped trial balance, a balanced statement set, a complete answer to every triggered disclosure rule, and the verbatim mandatory accounting-policy text (Income Tax, Employee Benefits, Provisions) inserted unaltered into the notes.
3. Definitions and Abbreviations
| Term/Abbreviation | Definition |
|---|---|
| FSEE | Financial Statements Engagement Engine — the automation layer that orchestrates TB upload, mapping, statement generation, disclosure interrogation, notes generation, export |
| SFRS | Singapore Financial Reporting Standards (Full SFRS) |
| SFRS_SE | Singapore Financial Reporting Standards for Small Entities |
| SFRS(I) | Singapore Financial Reporting Standards (International) — IFRS-aligned |
| TB | Trial Balance — the input list of GL account balances at year-end |
| SOFP | Statement of Financial Position (Balance Sheet) |
| SPLOCI | Statement of Profit or Loss and Other Comprehensive Income (P&L) |
| SOCE | Statement of Changes in Equity |
| SOCF | Statement of Cash Flows |
| Constitution | Immutable JSON files in packages/financial-statements/constitution/: sfrs_taxonomy.json, disclosure_rules.json, mandatory_policies/*.md |
| Disclosure Rule | One of ~25 entries in disclosure_rules.json with a trigger_condition (always or tb_contains_any([...])) and a set of prompts |
| Mandatory Policy | Verbatim regulatory wording for Income Tax, Employee Benefits, Provisions; inserted unaltered into notes |
| AL | Audit Logger — AuditEvent rows with actor, action, payload, timestamp |
| SoD | Segregation of duties — the preparer is not the reviewer is not the client approver |
4. Roles and Responsibilities
| Role | Responsibilities | Competency Requirements |
|---|---|---|
| FS Preparer | Upload trial balance, run auto-suggest mapping, override low-confidence rows, generate statements, complete disclosure interrogation, generate notes | SFRS for Small Entities fluency, COA familiarity |
| FS Reviewer | All preparer rights + final review and DOCX export to the client | Senior SFRS judgment, disclosure completeness check |
| Senior Accountant | All FS actions (and all XBRL actions short of MARK_XBRL_FILED) — typically the firm partner-equivalent for cross-product engagements | Full SFRS + Companies Act + ACRA filing experience |
| Client Approver | Review the DOCX preview via the portal magic link; Approve or Request Revision | Familiarity with the entity's operations and prior-year statements |
| FSEE (system) | Trial-balance parsing, AI mapping suggestion, deterministic statement assembly, disclosure rule evaluation, AI notes generation, DOCX rendering | Constitution loaders + status machine + worker handlers + AI plumbing |
Segregation of Duties (SoD) Controls
- The preparer who uploads the TB and overrides mappings must not be the same person who marks the engagement reviewed and exports the final DOCX. The
EXPORT_FS_DOCUMENTaction is reserved toFS_REVIEWER,SENIOR_ACCOUNTANT,PLATFORM_ADMIN. - AI auto-suggested mappings carry
mappingConfidenceandmappingRationale. A reviewer who accepts aLOW-confidence mapping without override is making a judgment that is audited and surfaced in the activity log. - Mandatory policy text is inserted verbatim from the constitution — the LLM is not permitted to rewrite it. Any edit to policy text is a constitution change, requires a PR + qualified-accountant sign-off, and is out of band.
- The Client Approver path is one-way: Approve seals the engagement at
NOTES_COMPLETE; Request Revision routes back to the preparer.
5. Prerequisites and Entry Conditions
Before commencing this process, verify the following conditions are met:
- Client master record exists with
FINANCIAL_STATEMENTSinenabledProducts. Client.fsConfigis populated withdefaultReportingStandard(SFRS_SEorFULL_SFRS),baseCurrency(typicallySGD),fyEndMonth,roundingConvention, and the company'suen,registeredAddress, andprincipalActivities.- Staff users hold the appropriate role:
FS_PREPARERto drive the engagement;FS_REVIEWER(orSENIOR_ACCOUNTANT/PLATFORM_ADMIN) to export. - Prior-year comparatives — if
hasPriorYearwill betrue, the TB upload must carry prior-year balances or a separate prior-year TB must be available.
Entry Checklist
- [ ] Engagement ID convention confirmed (auto-generated UUID; surfaced in URL as
/dashboard/financial-statements/<engagementId>) - [ ] Financial year label confirmed (e.g.
FY2024) - [ ] Year-end date confirmed (
YYYY-MM-DD, must agree withfyConfig.fyEndMonth) - [ ] Reporting standard chosen (
SFRS_SEorFULL_SFRS) - [ ] Functional currency and rounding convention confirmed
- [ ] Audit logging enabled (default — every state transition emits
AuditEvent)
6. Detailed Process Flow
Process Map (high-level phases)
- PREPARE — Engagement creation and configuration
- INGEST — Trial-balance upload and parsing
- MAP — Account → SFRS taxonomy key (AI-suggested, human-confirmed)
- GENERATE — Deterministic BS / P&L / SOCE / Cash Flow
- INTERROGATE — Disclosure rule evaluation and response capture
- AUTHOR — AI-generated notes + Directors Report
- EXPORT — Publication-ready DOCX (and later PDF / XLSX)
- REVIEW — Client portal sign-off (optional)
- ARCHIVE — Sealed engagement, retained per policy
Controlled States (State Machine)
The engagement walks through seven states. See packages/financial-statements/src/status-transitions.ts for the canonical transition matrix.
DRAFT → TB_UPLOADED → TB_MAPPED → STATEMENTS_GENERATED → NOTES_COMPLETE → ARCHIVED
↘ NOTES_ERROR ↺ STATEMENTS_GENERATED (retry)General State Transition Rules
- Re-uploading a trial balance returns the engagement to
DRAFTand invalidates all downstream artifacts. The status badge shows the regression so a reviewer knows everything below TB needs re-running. - Regenerating statements bumps
FsGeneratedStatement.versionNoand stays atSTATEMENTS_GENERATED. Notes generated against the prior version do not automatically invalidate, but a reviewer must confirm they still match the new figures. NOTES_ERRORis a recoverable state. Statements remain valid; only the notes Claude call failed (timeout, JSON parse, schema mismatch).- Every transition is audit-logged with actor, timestamp, and a typed
action.
Step 1.0: Create FS Engagement (S0 → DRAFT)
Process Phase: PREPARE
Actor: FS Preparer
Action Description
Create a new FsEngagement for the client + financial year. Header inputs: financial year label, year-end date, reporting standard, functional currency, prior-year flag.
Statutory Basis
N/A (internal control step — creates the container for the engagement)
Inputs
| Field Name | Format | Source | Validation Rule |
|---|---|---|---|
clientId | UUID | Client master | Must have FINANCIAL_STATEMENTS enabled |
financialYear | String | User input | Free-form (e.g. FY2024) |
yearEndDate | YYYY-MM-DD | User input | Must agree with Client.fsConfig.fyEndMonth |
reportingStandard | SFRS_SE | FULL_SFRS | User input or fsConfig default | One of the two values |
functionalCurrency | ISO 4217 | fsConfig default | Typically SGD |
hasPriorYear | Boolean | User input | Default false |
Micro-Steps
- Open
/dashboard/financial-statements/new(orPOST /ops/financial-statements/engagements). - Fill in header. The reporting-standard dropdown defaults from
Client.fsConfig.defaultReportingStandard. - Submit. Engagement created in
DRAFT.
Validation and Control Checks
- Check 1: Caller holds
MANAGE_FS_ENGAGEMENTfor theFINANCIAL_STATEMENTSproduct. - Check 2: No duplicate
(clientId, financialYear)engagement exists (idempotency). - Check 3: Year-end date is consistent with
fyEndMonth.
Decision Points and Branching Logic
- IF
enabledProductsdoes not includeFINANCIAL_STATEMENTS, THEN request rejected with400. - ELSE engagement created at
DRAFT.
System Interaction
- System: API (
/ops/financial-statements/engagements) - Action: Insert
FsEngagementrow, emitAuditEvent(fs.engagement.created)
Data Storage and Evidence
FsEngagementrow withversionNo = 1,status = DRAFT, timestamps.
Acceptance Criteria
- [ ] Engagement created in
DRAFT - [ ] Audit log shows
fs.engagement.created
Step 2.0: Upload Trial Balance (DRAFT → TB_UPLOADED)
Process Phase: INGEST
Actor: FS Preparer
Action Description
Upload the client's trial balance as CSV or XLSX. The parse-trial-balance worker handler runs smart column detection, sign normalization (debit-positive in storage), year detection (4-digit headers, finds closest prior year), and append-replaces all TB rows for the engagement.
Statutory Basis
N/A (data capture)
Inputs
| Input | Format | Notes |
|---|---|---|
| TB file | CSV / XLSX / XLS | Max 10 MB |
| Column headers | Case-insensitive | Recognised: account_code, description, debit, credit, OR current_year/prior_year |
| Currency | Implicit | Assumed to equal engagement.functionalCurrency |
Micro-Steps
- Open the engagement; click Upload Trial Balance.
- Drag-drop the file. The drag-drop surface accepts a single file per upload.
- The handler:
- Reads the file from S3 (
financial-statements/<clientId>/<engagementId>/tb-<timestamp>.{csv,xlsx}) - Detects columns (case-insensitive, underscore/space-agnostic)
- Normalizes signs (debit-positive)
- Detects year columns (finds the closest 4-digit prior year — sets
hasPriorYear = true) - Replaces all
FsTrialBalanceItemrows for this engagement
- Reads the file from S3 (
- Engagement transitions
DRAFT → TB_UPLOADED. - The UI shows a preview table with row count, balance check (Σ current-year balances must equal zero ±1.0 SGD), and a "Replace upload" button.
Validation and Control Checks
- Check 1: File size ≤ 10 MB; file extension is
.csv,.xls, or.xlsx. - Check 2: Σ of all
currentYearBalancevalues equals zero ±1.0 SGD (TB is balanced). - Check 3: No row has both null account code and null description.
- Check 4: If
hasPriorYearflips totrue, prior-year column was successfully detected.
Decision Points and Branching Logic
- IF the TB is not balanced, THEN flag the engagement with a warning (does not block but appears on the mapping screen).
- IF column detection fails (no recognisable headers), THEN the handler emits an error and the engagement stays at
DRAFT. The reviewer must reformat the file and re-upload.
System Interaction
- System: API (
POST /ops/financial-statements/engagements/:id/trial-balance), worker (parse-trial-balance), S3.
Data Storage and Evidence
- Raw TB file persisted in S3 with the engagement-scoped key prefix.
- One
FsTrialBalanceItemrow per GL account line. AuditEventfs.tb.uploadedwith row count and balance-check result.
Common Mistakes to Avoid
- Do not upload a TB with mixed sign conventions (some columns debit-positive, others credit-positive). The detector flags the second column but cannot infer the intent.
- Do not include subtotal rows. The parser sums every row; subtotals will double-count.
Acceptance Criteria
- [ ] Engagement reaches
TB_UPLOADED - [ ]
FsTrialBalanceItemcount > 0 and balance check passed (or warned)
Step 3.0: Map Trial Balance to SFRS Taxonomy (TB_UPLOADED → TB_MAPPED)
Process Phase: MAP
Actor: FS Preparer (with Claude assist)
Action Description
Map every TB row to an SFRS taxonomy key from sfrs_taxonomy.json. Use the AI auto-suggest path for the bulk, then manually override low-confidence rows.
Statutory Basis
The taxonomy aligns to SFRS for Small Entities primary mapping and Full SFRS alternate references. Misclassification at this step propagates into the BS / P&L sections — this is the most important human-judgment gate in the workflow.
Micro-Steps
- Open the Mapping tab. The table shows every TB row with its current taxonomy key (empty initially).
- Click Auto Suggest. The handler enqueues
auto-suggest-mapping. Claude Sonnet receives the TB rows + the full flattened SFRS taxonomy and returns per-row{taxonomyKey, taxonomyLabel, confidence, rationale}. - The table refreshes with confidence badges (HIGH / MEDIUM / LOW). A tooltip on each badge shows the AI rationale.
- Walk every
LOWandMEDIUMrow. Decide: accept, or open the dropdown and pick a different taxonomy key. - Manual overrides set
isOverride = trueandmappingConfidence = MANUAL. The override is audited. - When every row has a
taxonomyKey, the engagement transitionsTB_UPLOADED → TB_MAPPED(handler-side when auto-suggest leaves nothing unmapped; otherwise on the final manual confirmation).
Validation and Control Checks
- Check 1: Every row has a non-null
taxonomyKeybefore transitioning. - Check 2: Every assigned key exists in the taxonomy constitution (
findTaxonomyItem(key) != undefined). - Check 3: Sign consistency — credit-side keys (revenue, equity, liabilities) should pair with credit balances in the TB. The auto-suggest prompt is sign-aware but a manual override could mis-sign. The statement generator surfaces a sign-flip anomaly downstream.
Decision Points and Branching Logic
- IF auto-suggest returns nothing for a row (Claude responded
unmapped), the row stays unmapped and is highlighted withUNCLASSIFIED. Preparer must override manually. - IF the preparer re-uploads a fresh TB at any time, mappings are dropped and the engagement returns to
TB_UPLOADED.
System Interaction
- System: API (
POST /ops/financial-statements/engagements/:id/mapping/auto-suggest,PATCH /ops/financial-statements/engagements/:id/mapping/:tbItemId), worker (auto-suggest-mapping), Claude Sonnet, constitution loaders.
Data Storage and Evidence
- Each
FsTrialBalanceItemcarriestaxonomyKey,taxonomyLabel,mappingConfidence,mappingRationale,isOverride. AuditEventfs.mapping.auto_suggested(one event per run; payload includes input + output token counts).AuditEventfs.mapping.overridden(one event per manual override).
Common Mistakes to Avoid
- Do not auto-accept a
LOW-confidence row without reading the rationale. The cost of misclassification is a mis-stated note disclosure later. - Do not invent a taxonomy key. The dropdown is constrained to the constitution.
Acceptance Criteria
- [ ] Every TB row carries a valid
taxonomyKey - [ ] Engagement reaches
TB_MAPPED
Step 4.0: Generate Statements (TB_MAPPED → STATEMENTS_GENERATED)
Process Phase: GENERATE
Actor: FS Preparer
Action Description
Run the deterministic statement generator. There is no AI in this step — every figure is a pure function of the mapped TB.
Statutory Basis
The statement assembly follows SFRS for Small Entities presentation requirements:
- SOFP: Non-current assets → Current assets → Total assets ‖ Equity → Non-current liabilities → Current liabilities → Total equity & liabilities. Balance check ±1.0 SGD.
- SPLOCI: Revenue (sign-flipped from credit) − COGS = Gross Profit; + Other Income − Selling/Admin/Finance/Other = Profit Before Tax; − Tax = Profit After Tax.
- SOCE: opening equity + PAT + share movements = closing equity; columns with no movement are omitted.
- SOCF (indirect): Operating = PBT + D&A + working-capital movements − tax paid; Investing = PPE / ROU / intangibles / investment movements; Financing = share / loan / lease movements. Reconciles to opening + closing cash.
Micro-Steps
- Open the Statements tab; click Generate.
- The
generate-statementshandler runs synchronously (no LLM). Inputs: everyFsTrialBalanceItemfor the engagement; the SFRS taxonomy; engagement metadata. - Output: a new
FsGeneratedStatementrow withversionNobumped,statementsJsonpopulated,isBalancedset,anomaliespopulated. - The UI renders BS / P&L / SOCE / Cash Flow as four tabs. A red banner appears if
isBalanced = false. - Engagement transitions
TB_MAPPED → STATEMENTS_GENERATED.
Anomaly Detection
The generator flags:
- Unbalanced —
total_assets != total_equity_and_liabilities(±1.0 SGD). - Negative working capital — current liabilities exceed current assets.
- Zero revenue — revenue side is empty.
- Profit but no tax — PBT > 0 but no income-tax expense.
- Sign flip — a credit-side taxonomy key resolved to a debit balance after sign normalisation (or vice versa). Surfaces a mapping question the reviewer must answer.
Acceptance Criteria
- [ ]
FsGeneratedStatementrow created withisBalanced = true(or a knowingly-tolerated anomaly noted in the activity log) - [ ] Engagement reaches
STATEMENTS_GENERATED
Step 5.0: Disclosure Interrogation (STATEMENTS_GENERATED)
Process Phase: INTERROGATE
Actor: FS Preparer
Action Description
Walk every triggered disclosure rule and provide responses. The set of triggered rules is computed live by evaluateDisclosureGaps(presentKeys) against the engagement's mapped TB keys.
Statutory Basis
The rule library encodes mandatory and conditional disclosures from SFRS for Small Entities and the Companies Act. Mandatory rules (always trigger) fire for every engagement; conditional rules fire when their tb_contains_any([...]) clause matches the mapped TB.
Rule Inventory (excerpt)
| Priority | Rule ID | Name | Trigger |
|---|---|---|---|
mandatory | DISC-001 | Company General Information | always |
mandatory | DISC-002 | Reporting Standard & Functional Currency | always |
mandatory | DISC-003 | Going Concern | always |
mandatory | DISC-017 | Income Tax (mandatory verbatim policy) | always |
mandatory | DISC-020 | Directors Remuneration / Auditors | always |
mandatory | DISC-021 | Employee Benefits (mandatory verbatim policy) | always |
mandatory | DISC-024 | Events After Reporting Period | always |
mandatory | DISC-025 | Dividends | always |
conditional | DISC-010 | PPE | tb_contains_any(['ppe']) |
conditional | DISC-011 | Leases & ROU | tb_contains_any(['rou_assets','lease_liabilities']) |
conditional | DISC-012 | Inventories | tb_contains_any(['inventory']) |
conditional | DISC-013 | Trade Receivables | tb_contains_any(['trade_receivables']) |
conditional | DISC-014 | Cash and Bank | tb_contains_any(['cash']) |
conditional | DISC-015 | Share Capital | tb_contains_any(['share_capital']) |
conditional | DISC-016 | Borrowings | tb_contains_any(['borrowings']) |
conditional | DISC-018 | Revenue | tb_contains_any(['revenue']) |
conditional | DISC-022 | Finance Costs | tb_contains_any(['finance_costs']) |
recommended | DISC-019 | Related Party Transactions | always |
recommended | DISC-023 | Commitments & Contingencies | always |
The full library lives in packages/financial-statements/constitution/disclosure_rules.json. The loader caches after first read; tests clear via clearDisclosureCache().
Prompt input types
input_type | Renders as |
|---|---|
text | Single-line text |
date | Date picker (YYYY-MM-DD) |
select | Dropdown (uses options[]) |
boolean | Yes/No |
multi_text | Repeating text rows |
table | Editable table (e.g. directors list) |
number | Number with decimal control |
Prompts may carry visible_when predicates referencing a sibling prompt (e.g. DISC-003-01 == 'no'). The UI shows or hides accordingly.
Micro-Steps
- Open Interrogation. The accordion lists triggered rules sorted by priority (mandatory → conditional → recommended), then by
rule_id. - For each rule, expand and answer every required prompt. Conditional prompts appear as triggered.
- Click Save. The handler upserts one
FsDisclosureResponserow per(ruleId, promptId). - The progress bar tracks
complete_rules / triggered_rules. When 100% the engagement is interrogation-complete (no separate state — this is gating for the notes step).
Validation and Control Checks
- Mandatory prompts must be answered before notes generation. The handler refuses to enqueue
generate-notesif any mandatory prompt is empty. - Standard references (e.g.
SFRS-SE Section 29) are surfaced as inline regulatory callouts in the accordion.
Data Storage and Evidence
FsDisclosureResponserows keyed(engagementId, ruleId, promptId).responseDatais JSONB{value: any}.AuditEventfs.disclosure.respondedper save.
Acceptance Criteria
- [ ] Every triggered mandatory rule has every required prompt answered
- [ ] Conditional rules whose triggers fire are answered or knowingly skipped (the activity log captures the skip)
Step 6.0: Generate Notes & Directors Report (STATEMENTS_GENERATED → NOTES_COMPLETE)
Process Phase: AUTHOR
Actor: FS Preparer (with Claude)
Action Description
Run the AI notes generator. A single Claude Sonnet 16K-token call combines: the generated statements JSON; every disclosure response; the verbatim mandatory policies from packages/financial-statements/constitution/mandatory_policies/; engagement and client metadata. The model returns structured note blocks; deterministic post-processing replaces any financial_results_placeholder block with a computed table.
Statutory Basis
The mandatory policy texts (Income Tax — Section 29 SFRS_SE; Employee Benefits; Provisions) are verbatim regulated content. They are inserted into the notes from the constitution and the LLM is instructed not to rewrite them. The Directors Report follows Companies Act Section 201.
Micro-Steps
- Open Notes. Click Generate notes.
- The handler enqueues
generate-notes(background; UI shows a polling indicator). - Background job:
- Loads disclosure responses, generated statements, engagement + client metadata, mandatory policies
- Builds one consolidated 16K-token prompt
- Calls Claude Sonnet (single call — cost-optimised vs per-note loops)
- Receives
{notes: [{note_key, title, blocks}], directors_report: {blocks}} - Replaces
financial_results_placeholderblocks with Python-built financial-results tables (port: TS equivalent) - Upserts
FsNoterows keyed(engagementId, noteKey)
- On success, engagement transitions
STATEMENTS_GENERATED → NOTES_COMPLETE. - On failure (timeout, JSON parse, schema mismatch), engagement transitions to
NOTES_ERROR. A reviewer reads the failure record and either retries (NOTES_ERROR → STATEMENTS_GENERATED) or adjusts upstream inputs first.
Block Types
type | Renders as |
|---|---|
paragraph | Body text |
subheading | Inline subheading inside the note |
table | {headers: string[], rows: string[][]} right-aligned amounts |
financial_results_placeholder | Replaced post-LLM by the computed financial-results table |
Mandatory Notes
The following notes are always generated:
directors_report— Section 201 Companies Actnote_general— name, UEN, registered office, principal activitiesnote_basis— basis of preparationnote_policies— significant accounting policies (uses the verbatim mandatory policies)
Dynamic notes per TB content: note_ppe, note_trade_receivables, note_revenue, note_rpt (related party transactions), etc.
Validation and Control Checks
- Mandatory policy text must appear verbatim in the rendered note. A reviewer can verify by string-matching against the constitution.
- Every
note_keyreturned by the LLM must exist as anote_triggerflag in the taxonomy (or be a mandatory always-present note). Unknownnote_keys are rejected at parse time.
Data Storage and Evidence
FsNoterows keyed(engagementId, noteKey).contentis JSONB array of block objects.AuditEventfs.notes.generatedwith input/output token counts, model id, Claude request id.
Common Mistakes to Avoid
- Do not edit the mandatory policy text in the note. Edit the constitution file via a PR with qualified-accountant sign-off.
- Do not regenerate notes after the client has signed off. Once
NOTES_COMPLETEhas been promoted into a client review, regeneration invalidates the signed artifact.
Acceptance Criteria
- [ ]
FsNoterow exists for every mandatory note + every dynamic note triggered by the TB - [ ] Engagement reaches
NOTES_COMPLETE
Step 7.0: Export DOCX (NOTES_COMPLETE)
Process Phase: EXPORT
Actor: FS Reviewer / Senior Accountant / Platform Admin
Action Description
Render the publication-ready DOCX. Action gated to EXPORT_FS_DOCUMENT permission (FS_REVIEWER, SENIOR_ACCOUNTANT, PLATFORM_ADMIN).
Statutory Basis
The DOCX layout follows Singapore market convention for SFRS for Small Entities:
- Cover page (company name, title, year-end date, UEN)
- Running header (company left, year-end right, thin bottom border) and centred 8 pt page numbers
- Directors Report (Section 201) — with the Financial Results table injected at the placeholder position
- SOFP (4-col: Description / Note / CY / PY)
- SPLOCI (2-col)
- SOCE (zero-movement columns auto-omitted)
- SOCF (Operating → Investing → Financing)
- Notes (rendered from JSON blocks, paragraphs / subheadings / right-aligned amount tables; auto-numbered "1. General Information", "2. Basis", …)
Micro-Steps
- Open the engagement; click Export Word.
- The handler enqueues
export-statements-docx. The renderer reads the latestFsGeneratedStatementand allFsNoterows. - The DOCX lands in S3 at
financial-statements/<clientId>/<engagementId>/<filename>.docxand a signed URL is returned. - Engagement stays at
NOTES_COMPLETE— export does not advance status. Multiple export versions are allowed; each is audit-logged.
Validation and Control Checks
- Statement must be
isBalanced = true(or the export emits a banner warning on the cover). - All mandatory disclosure rules must be answered. The handler refuses to render otherwise.
- Mandatory policy verbatim text is asserted by the renderer against the constitution before write.
Acceptance Criteria
- [ ] DOCX persisted to S3
- [ ] Signed URL returned to the staff user
- [ ]
AuditEventfs.export.generated
Step 8.0: Client Review via Portal (Optional, NOTES_COMPLETE)
Process Phase: REVIEW
Actor: Client Approver
Action Description
Optionally send the engagement to the client for sign-off via the magic-link portal. The portal renders a read-only preview and offers Approve / Request Revision.
Micro-Steps
- Staff clicks Send to client for review. The handler creates a
PortalInvitationwithresourceType = FS_ENGAGEMENT,token.product = FINANCIAL_STATEMENTS, expiry 30 days. - Email sent to the client approver: subject "Financial statements ready for review", body links to
/portal/<token>. - The portal verifies the token, loads the engagement summary + statements preview, and shows two CTAs:
- Approve — captures decision, audits, notifies staff.
- Request revision — captures a comment, audits, notifies staff. The engagement stays at
NOTES_COMPLETE; the preparer addresses the comment and re-runs upstream steps as needed.
Acceptance Criteria
- [ ] Portal token verified
- [ ] Client decision captured with timestamp and (if any) revision comment
- [ ]
AuditEventfs.client.approvedorfs.client.revision_requested
Step 9.0: Archive (NOTES_COMPLETE → ARCHIVED)
Process Phase: ARCHIVE
Actor: FS Reviewer / Senior Accountant / Platform Admin
Action Description
Seal the engagement at the end of the audit cycle. No further edits.
Micro-Steps
- From the engagement detail page, click Archive.
- Engagement transitions
NOTES_COMPLETE → ARCHIVED. - Retention policy applies (default 7 years per accountancy norms; configurable).
Acceptance Criteria
- [ ] Engagement is
ARCHIVEDand read-only - [ ]
AuditEventfs.engagement.archived
7. System Architecture for AI Implementation
Process Input Schema (High Level)
| Field Name | Data Type | Validation Rules | Required/Optional | Source System |
|---|---|---|---|---|
clientId | UUID | FINANCIAL_STATEMENTS in enabledProducts | Required | Client master |
financialYear | String | Free-form; usually FY<year> | Required | Engagement create |
yearEndDate | YYYY-MM-DD | Agrees with fsConfig.fyEndMonth | Required | Engagement create |
reportingStandard | Enum | SFRS_SE or FULL_SFRS | Required | Engagement create |
tbFile | CSV / XLSX | Max 10 MB; balanced ±1.0 SGD | Required | TB upload |
taxonomyKey (per row) | String | Member of sfrs_taxonomy.json | Required after mapping | Mapping step |
disclosureResponses | JSONB | One per (ruleId, promptId) | Required for mandatory | Interrogation step |
Process Output Schema (High Level)
| Output | Data Type | Format Specification | Destination | Retention |
|---|---|---|---|---|
FsGeneratedStatement | JSONB | Nested BS / P&L / SOCE / CF | DB + versioned in DB | Per policy |
FsNote | JSONB blocks | {type, ...} block array | DB | Per policy |
| DOCX export | File | Word document | S3 + signed URL | Per policy |
| Audit events | Append-only | AuditEvent rows | DB | Immutable |
Integration Points
| Integration | Protocol | Direction | Error Handling |
|---|---|---|---|
| Claude Sonnet (mapping) | Anthropic SDK | Outbound | Retry with backoff; persist on first success |
| Claude Sonnet (notes) | Anthropic SDK | Outbound | On failure → NOTES_ERROR; manual retry path |
| S3 (TB / DOCX) | S3 client | In/Outbound | Standard SDK retry; checksum on download |
| Email (client portal) | SMTP adapter | Outbound | Outbox-poller with retry; manual reissue path |
Constitution & Loader Contract
loadSfrsTaxonomy()— readssfrs_taxonomy.json. Cached on first call. Clear viaclearTaxonomyCache()in tests.loadDisclosureRules()— readsdisclosure_rules.json. Cached. Clear viaclearDisclosureCache().evaluateTriggerCondition(condition, presentKeys)— evaluatesalwaysortb_contains_any([...])joined by" OR ".evaluateDisclosureGaps(presentKeys)— returns triggered rules sorted by priority then rule_id.findTaxonomyItem(key)— returns the SFRS taxonomy item orundefined. Use this instead of dipping into the JSON.
8. Quality Controls and Compliance Checkpoints
| Checkpoint | What to Check | Who Checks | Frequency | Record |
|---|---|---|---|---|
| TB balance | Σ currentYearBalance = 0 ±1.0 SGD | FSEE, Preparer | Per upload | AuditEvent |
| Mapping completeness | Every TB row has a taxonomyKey | FSEE, Preparer | Pre-statement-gen | FsTrialBalanceItem |
| Statement balancing | total_assets = total_equity_and_liabilities ±1.0 SGD | FSEE | Per generation | FsGeneratedStatement.isBalanced |
| Mandatory disclosures | Every mandatory rule has every required prompt answered | FSEE, Reviewer | Pre-notes | FsDisclosureResponse |
| Verbatim policy assertion | Mandatory policy text appears unchanged in note_policies | DOCX renderer | Per export | Renderer-side assert |
| Reviewer sign-off | Reviewer ran the export — EXPORT_FS_DOCUMENT action is in the audit trail | Reviewer | Per engagement | AuditEvent |
| Client approval | Client decision captured before archiving (when portal review used) | Staff | Per engagement | AuditEvent |
9. Regulatory and Legal References
- SFRS for Small Entities (Section 29 — Income Taxes; Section 28 — Employee Benefits; Section 21 — Provisions and Contingencies)
- Full SFRS (FRS 16 — PPE; FRS 116 — Leases; FRS 115 — Revenue; FRS 12 — Income Taxes; FRS 19 — Employee Benefits)
- Companies Act 1967 (Section 201 — Directors Report)
- ACRA filing requirements (informs disclosure set; XBRL filing is downstream)
- Personal Data Protection Act (PDPA) — directors' personal data appears in financial statements
10. Records Management and Retention
Records to store for each engagement
- Raw trial-balance file (CSV/XLSX) — S3
FsTrialBalanceItemrows (every TB row + mapping history)FsGeneratedStatementrows (every version)FsNoterowsFsDisclosureResponserows- DOCX exports (every version)
- Client portal sign-off evidence (if used)
AuditEventrows — append-only
Retention
Minimum 7 years from the engagement year-end, consistent with Singapore accountancy norms. Configurable per client jurisdiction.
11. Risk Assessment and Mitigation
| Risk Description | Likelihood (1-5) | Impact (1-5) | Risk Score | Category |
|---|---|---|---|---|
| Misclassification of TB row to wrong SFRS taxonomy key | 3 | 4 | 12 | Reporting |
| Mandatory disclosure missed (rule trigger fired but not answered) | 2 | 5 | 10 | Compliance |
| Mandatory policy text edited inadvertently | 1 | 5 | 5 | Compliance |
| Claude notes-generation hallucination contradicting disclosure responses | 2 | 4 | 8 | AI / Reporting |
| Statement unbalanced shipped to client | 1 | 5 | 5 | Reporting |
| Stale notes against regenerated statements | 3 | 3 | 9 | Operational |
Preventive Controls
- AI mapping suggestions are advisory; every row carries
mappingConfidenceand the reviewer is prompted to confirmLOW/MEDIUMrows. - Disclosure rule engine fires deterministically against the constitution; mandatory rules cannot be silenced.
- Mandatory policy texts live in immutable Markdown files; the LLM is instructed not to paraphrase, and the DOCX renderer asserts the text against the constitution before write.
- Notes generator runs as a single 16K-token consolidated call with the statement JSON in-prompt, so the LLM cannot drift from the figures.
- Statement balancer flags any unbalance with a red banner and prevents silent export.
Escalation
- Mapping override on a
mandatory-rule-triggering taxonomy key escalates to Senior Accountant for review. NOTES_ERRORrecovery: if the same engagement re-fails three times, escalate to Senior Accountant; the failure record contains the model id, request id, and prompt fingerprint.
12. Process Re-engineering and Continual Improvement
- Quarterly review of
mappingConfidencedistribution and override rate. A drift towardLOWconfidence indicates a taxonomy gap. - After every ACRA / SFRS update, the constitution files are versioned by PR and the disclosure rule library is re-checked against the changes.
- Track Claude token consumption per engagement; the notes prompt is the largest contributor and is a prompt-caching candidate.
- Track DOCX visual-regression test outcomes against
financial_statements_final3.docx(port baseline from thefinstatementsource repo).
13. Training and Competency Requirements
Minimum Competency Standards
- Working knowledge of SFRS for Small Entities (or Full SFRS for engagements that require it).
- Familiarity with the disclosure rule library and the Companies Act Section 201 Directors Report.
- Understanding of the export gate: a
FS_PREPARERcannot export — onlyFS_REVIEWER/SENIOR_ACCOUNTANT/PLATFORM_ADMIN.
Training Program
- New preparers shadow two complete engagements end-to-end before independent ownership.
- Reviewers spend one cycle pairing with the platform team to walk every audit event for a sample engagement.
- Annual refresher on SFRS / Companies Act updates that affect the taxonomy or disclosure rules.
14. Related Documents and Cross-References
- Architecture — XBRL & FS integration plan
- Status machine — FS engagement
- Quick start — financial statements
- Userflows — Financial Statements
- SOP — XBRL Filing — the downstream filing this engagement may feed
- Bookkeeping SOP — the upstream source for the trial balance (when Phase 3 cross-product wiring lands)
- RBAC matrix
15. Appendices
Appendix A: Process Flow (Swimlane Description)
- FS Preparer creates the engagement and uploads the trial balance.
- FSEE parses TB, normalises signs, persists rows.
- FS Preparer runs auto-suggest mapping; reviews and overrides low-confidence rows.
- FSEE runs deterministic statement assembly; flags anomalies if any.
- FS Preparer walks the disclosure interrogation accordion.
- FSEE runs the Claude notes call; on success persists notes; on failure transitions to
NOTES_ERRORfor recovery. - FS Reviewer exports DOCX.
- Optional: staff dispatches the portal magic link; client approves or requests revision.
- FS Reviewer archives the engagement at engagement close.
Appendix B: Worked Example (Fictional Data — AURORA)
ClientCode: AURORA (seeded as a financial-statements-only client)
Engagement: AURORA / FY2024 (year-end 2024-12-31, SFRS for Small Entities, SGD)
- 13 TB rows uploaded, every row mapped to an SFRS taxonomy key (10 HIGH, 2 MEDIUM, 1 LOW —
accumulated_depreciationmapped toppe). - Statements generated v1,
is_balanced = true. Profit after tax: SGD 73,900 (PY: SGD 55,400). - Mandatory disclosures DISC-001 / DISC-002 / DISC-003 pre-filled in seed. Conditional rules DISC-010 (PPE), DISC-013 (Trade Receivables), DISC-014 (Cash), DISC-018 (Revenue) fire automatically.
- Notes generation pending (service layer ships in Phase 1).
- DOCX export pending.
Appendix C: Junior Staff Decision Guide
| If You See This... | Then Do This... | Escalate? |
|---|---|---|
| TB upload returns "not balanced" warning | Open the TB file, find the unbalanced rows, fix at source, re-upload | No |
Auto-suggest returns LOW confidence on > 30% of rows | The TB column structure may have confused the parser; check sign normalisation and re-run | Yes, to SA |
| Mandatory rule has no answer after interrogation | Refuse to enqueue notes generation; revisit interrogation | No |
Statement says total_assets != total_equity_and_liabilities | Open the mapping table, look for sign-flip flags, correct the affected row, regenerate | Yes if recurs |
NOTES_ERROR after a generate attempt | Open the failure record; check the model id and request id; retry if the error was transient | Yes if 2+ |
| DOCX export refuses with "mandatory policy mismatch" | A note row has been edited away from the constitution text; regenerate notes | Yes, to SA |
Appendix D: Escalation Matrix
- Preparer to Reviewer: any
LOWorUNCLASSIFIEDtaxonomy mapping; sign-flip anomaly on the statement. - Reviewer to Senior Accountant: mandatory disclosure rule answered with non-trivial assumptions; mandatory policy text dispute.
- Senior Accountant to Managing Director: SFRS / Companies Act interpretation that affects multiple clients.
Appendix E: Completion Checklist
- [ ] Engagement created in
DRAFT - [ ] Trial balance uploaded; engagement reached
TB_UPLOADED - [ ] Every TB row mapped to an SFRS taxonomy key; engagement reached
TB_MAPPED - [ ] Statements generated;
is_balanced = true - [ ] Every triggered mandatory disclosure rule answered
- [ ] Notes generated; engagement reached
NOTES_COMPLETE - [ ] DOCX exported
- [ ] Optional: client portal approval captured
- [ ] Engagement archived; retention applied
End of SOP