X-05 · Export BizFinx XLSX and XBRL XML
SOP:
SOP_XBRL_Filing.md§6 / Step 5.0 (VALIDATION_PASSED → EXPORTED)Actors: XBRL Reviewer (xbrl-reviewer@spade.local), Senior Accountant, or Platform Admin —EXPORT_BIZFINX/EXPORT_XBRL_XML. Pre-state: Filing atVALIDATION_PASSED. Every mapping confirmed (asserted by the export gate). NoERROR-severity unpassed validation result (asserted by the export gate). Post-state:XbrlOutputArtifactrow inserted. Filing atEXPORTED(first time only; subsequent exports add new artifact versions but the status remainsEXPORTED).
0. Prerequisites
- Filing at
VALIDATION_PASSED(see X-04). - Caller holds
EXPORT_BIZFINXfor the XLSX path,EXPORT_XBRL_XMLfor the XML path.XBRL_REVIEWERandSENIOR_ACCOUNTANThold both;XBRL_PREPARERholds neither.
1. Steps
1.1 Generate BizFinx XLSX
POST /ops/xbrl/filings/<filingId>/exports/bizfinx
Authorization: Bearer <xbrl-reviewer-jwt>(Or click Generate BizFinx on the Exports tab.)
The handler:
Asserts
EXPORT_BIZFINX.Loads the filing's confirmed mappings, validation results, and line item count.
Calls
assertExportAllowed()(the export gate). Four possible blocks:NO_LINE_ITEMS(filing has no line items)NO_VALIDATION_RUN(no validation results exist)VALIDATION_ERROR_PRESENT(severity = ERROR AND passed = falserow exists)UNCONFIRMED_TAG_MAPPING(confirmedById IS NULLrow exists)
On any block: the handler raises
XbrlExportBlockedError(reason, message, details). The HTTP layer translates this to422 Unprocessable Entitywith the typed reason. No artifact is produced.On pass: enqueues
generate-bizfinx-xlsxwith{xbrlFilingId, requestedBy}. Returns202 Accepted.
1.2 Worker — generate-bizfinx-xlsx
The worker re-runs assertExportAllowed() before writing any bytes (defence in depth). On pass:
- Builds a multi-sheet
exceljsworkbook:- Income Statement —
period_type = duration, P&L section - Balance Sheet —
period_type = instant, SOFP section - Cash Flow —
period_type = duration, SOCF section - Equity Changes — SOCE section
- Filing Information —
sg-dei_*elements (name, UEN, period, entry point)
- Income Statement —
- Persists to S3 at
xbrl/<clientId>/<filingId>/bizfinx-v<n>.xlsxvia theFilemodel withfileKind = XBRL_BIZFINX_EXPORT. - Inserts
XbrlOutputArtifactrow{kind: 'BIZFINX_XLSX', fileId, versionNo: <next>, generatedAt}. - Transitions filing
VALIDATION_PASSED → EXPORTED(first time only). - Emits
AuditEventxbrl.export.generated.
1.3 Generate XBRL XML
POST /ops/xbrl/filings/<filingId>/exports/xbrl-xml
Authorization: Bearer <xbrl-reviewer-jwt>Same gate. The worker generate-xbrl-xml builds an XBRL instance document:
- Root
<xbrl>with namespace declarations for the pinned taxonomy version - One
<context>per(period, entity)—<instant>for balance-sheet elements,<startDate>/<endDate>for flow - One
<unit>for SGD (<measure>iso4217:SGD</measure>) - One element fact per confirmed mapping with
contextRef+unitRef(for monetary types)
Persists to S3 at xbrl/<clientId>/<filingId>/xbrl-v<n>.xml. Inserts XbrlOutputArtifact row {kind: 'XBRL_XML', ...}.
2. Verification
Database
SELECT kind, version_no, generated_at FROM xbrl_output_artifacts
WHERE xbrl_filing_id = '<filingId>' ORDER BY generated_at DESC;SELECT status FROM xbrl_filings WHERE id = '<filingId>';
-- expect 'EXPORTED'S3
docker exec breezycorp-monorepo-minio-1 mc ls local/breezycorp/xbrl/<clientId>/<filingId>/
# expect bizfinx-v<n>.xlsx and/or xbrl-v<n>.xmlAudit log
| Event type | Notes |
|---|---|
xbrl.export.requested | API hit |
xbrl.export.blocked | Gate refused (if applicable); payload contains the typed reason |
xbrl.export.generated | Background success; payload includes kind + versionNo + signed URL |
Exercise the gate against seed data (manual)
node --experimental-repl-awaitconst { PrismaClient } = await import('@prisma/client');
const xbrl = await import('@breezycorp/xbrl');
const prisma = new PrismaClient();
// STELLAR's MAPPING_IN_PROGRESS filing — should refuse with UNCONFIRMED_TAG_MAPPING
const filing = await prisma.xbrlFiling.findFirst({
where: { status: 'MAPPING_IN_PROGRESS' },
include: { tagMappings: true, validationResults: true, lineItems: { select: { id: true } } },
});
try {
xbrl.assertExportAllowed({
validationResults: filing.validationResults,
tagMappings: filing.tagMappings.map((m) => ({ lineItemId: m.lineItemId, confirmedById: m.confirmedById })),
hasLineItems: filing.lineItems.length > 0,
});
} catch (e) {
console.log(e.reason, '—', e.message);
}
// → NO_VALIDATION_RUN — Validation has not been run for this filing.3. Negative & edge cases
UNCONFIRMED_TAG_MAPPING— HTTP 422 withdetails.lineItemIds. Open the Mappings tab; filter for unconfirmed; confirm each.VALIDATION_ERROR_PRESENT— HTTP 422 withdetails.count. Open the Validation tab; address each ERROR-severity row; re-run validation.NO_VALIDATION_RUN— HTTP 422. Run validation first.NO_LINE_ITEMS— HTTP 422. Re-upload the source document.- Caller without
EXPORT_BIZFINX—403 Forbidden.XBRL_PREPARERis rejected. - Re-export — allowed; each call produces a new artifact with
versionNobumped. The status stays atEXPORTED. - Validation result becomes stale — if a mapping is changed after
VALIDATION_PASSED, the filing implicitly returns toMAPPING_IN_PROGRESS. Re-validate. - Gate bypass via worker direct invocation — the worker re-runs
assertExportAllowed(). Defence in depth: there is no path that produces an artifact without the gate.
Next
Proceed to X-06 · Client portal sign-off (optional) or directly to X-07 · Mark filed with ACRA.