Skip to content

FS-05 · Disclosure interrogation ​

SOP: SOP_Financial_Statements.md §6 / Step 5.0Actors: FS Preparer (fs-preparer@spade.local) — holds RESPOND_DISCLOSURE. Pre-state: Engagement at STATEMENTS_GENERATED. Post-state: FsDisclosureResponse rows populated for every triggered mandatory rule (and as many conditional / recommended rules as the engagement requires).

0. Prerequisites ​

  • Engagement at STATEMENTS_GENERATED (see FS-04).
  • Disclosure rules constitution loaded fine (run pnpm --filter @breezycorp/financial-statements test to confirm).

1. Steps ​

1.1 Read the live gap list ​

http
GET /ops/financial-statements/engagements/<engagementId>/disclosures/gaps
Authorization: Bearer <fs-preparer-jwt>

The handler:

  1. Reads every FsTrialBalanceItem.taxonomyKey and builds a Set<string> of present keys.
  2. Calls evaluateDisclosureGaps(presentKeys):
    • Walks every rule in disclosure_rules.json
    • Evaluates each rule's trigger_condition (always or tb_contains_any([...]), joined by " OR ")
    • Returns triggered rules sorted by priority (mandatory → conditional → recommended), then rule_id
  3. Returns the list, annotated with which prompts already have responses.

1.2 Answer a rule ​

In the UI: expand the accordion for a rule; fill the prompt inputs; click Save.

Or via API:

http
POST /ops/financial-statements/engagements/<engagementId>/disclosures/respond
Content-Type: application/json
Authorization: Bearer <fs-preparer-jwt>

{
  "ruleId": "DISC-001",
  "responses": [
    { "promptId": "DISC-001-01", "value": "Aurora Holdings Pte. Ltd." },
    { "promptId": "DISC-001-02", "value": "202412345A" },
    { "promptId": "DISC-001-03", "value": "10 Anson Road #20-01, International Plaza, Singapore 079903" },
    { "promptId": "DISC-001-04", "value": "Wholesale trade in industrial machinery and parts" },
    { "promptId": "DISC-001-05", "value": "2024-12-31" }
  ]
}

The handler:

  1. Asserts RESPOND_DISCLOSURE.
  2. Looks up the rule in the constitution; asserts each promptId exists.
  3. Validates each value against the prompt's input_type (text / date / select / boolean / table / multi_text / number).
  4. Upserts one FsDisclosureResponse per (engagementId, ruleId, promptId).
  5. Emits AuditEvent fs.disclosure.responded (one event per rule, payload lists prompt ids and timestamps).

1.3 Conditional prompt visibility ​

The UI hides any prompt whose visible_when predicate is false. Example: in DISC-003 (Going Concern), the prompt DISC-003-02 ("Describe the exit strategy") is visible_when: "DISC-003-01 == 'no'". If the answer to DISC-003-01 is yes, the exit-strategy prompt is hidden and not required.

2. Verification ​

Database ​

sql
SELECT rule_id, count(*) AS prompt_responses
  FROM fs_disclosure_responses
  WHERE engagement_id = '<engagementId>'
  GROUP BY rule_id
  ORDER BY rule_id;

Gap check ​

http
GET /ops/financial-statements/engagements/<engagementId>/disclosures/gaps

Mandatory rules should have triggered: true, complete: true. Conditional rules whose trigger fired should have complete: true if you intend to ship; otherwise the notes generator will refuse.

Audit log ​

Event typeNotes
fs.disclosure.respondedOne per save action; payload identifies the rule + prompts

3. Negative & edge cases ​

  • Unknown ruleId or promptId — 400 Bad Request with the offending id; safer than silently dropping.
  • Wrong value shape for input_type — e.g. a string passed to a date field. Server-side validation rejects with 400.
  • Reading gaps before mapping is complete — handler returns the gap list but flags presentKeys as partial. Useful for early scoping but should not be relied on for completeness.
  • Mandatory rule unanswered when notes generation is enqueued — generate-notes handler refuses (409 Conflict) and returns the list of missing mandatory prompt ids.
  • Reopening a previously-answered prompt — the upsert replaces the value. The audit log captures both the before and after.

Next ​

Proceed to FS-06 · Generate notes & Directors Report.

Internal use only — BreezyCorp