X-02 · Upload source document (DOCX / PDF)
SOP:
SOP_XBRL_Filing.md§6 / Step 2.0 (DRAFT → DOCUMENTS_INGESTED → MAPPING_IN_PROGRESS)Actors: XBRL Preparer (xbrl-preparer@spade.local) — holdsINGEST_XBRL_DOCUMENT. Pre-state: Filing atDRAFT. Post-state: Filing atMAPPING_IN_PROGRESS.XbrlSourceDocument+XbrlLineItemrows persisted.XbrlTagMappingrows withconfirmedById = NULL+alternatives[].
0. Prerequisites
- Filing at
DRAFT(see X-01). - A financial-statement document — DOCX or PDF. Textual tables required; rasterised PDFs will produce zero line items.
TIP
For dev / smoke testing without a real client file, use a DOCX generated from the FS module (see FS-07). For a real fixture, see the xbrl-saas source repo's apps/backend/tests/fixtures/ (port these into packages/xbrl/__tests__/fixtures/).
1. Steps
1.1 Upload via Web UI
- Open the filing detail page; switch to Documents tab.
- Click Upload source document.
- Drag-drop the DOCX or PDF.
- The UI shows an upload progress bar; on completion the document appears with
parseStatus = PENDING. - Within ~30–60 seconds the status flips through
PARSED, line items appear on the Documents tab, then mapping suggestions populate the Mappings tab.
1.2 Upload via API
http
POST /ops/xbrl/filings/<filingId>/documents
Content-Type: multipart/form-data
Authorization: Bearer <xbrl-preparer-jwt>
(form-data: file=<statements.docx>)The handler:
- Asserts
INGEST_XBRL_DOCUMENT. - Persists the bytes to S3 at
xbrl/<clientId>/<filingId>/source.{docx,pdf}via theFilemodel withfileKind = XBRL_SOURCE_DOCUMENT. - Inserts an
XbrlSourceDocumentrow withparseStatus = PENDING,sha256,mimeType,pageCount(best-effort). - Enqueues
ingest-fs-document. - Returns
202 Acceptedwith the source-document id.
1.3 Worker chain
ingest-fs-document— confirms the file is readable; transitionsparseStatus = PENDING → READY; enqueuesextract-line-items.extract-line-items— reads the file:- DOCX: uses
mammothto parse tables and headings; identifies statement sections by heading text (Statement of Financial Position, etc.). - PDF: uses
pdf-parsewith table heuristics; falls back to flat-text extraction. - Normalises numbers (strip commas; parentheses → negative; currency symbols).
- Inserts
XbrlLineItemrows(statement, label, valueCurrent, valuePrior, sourcePage, sourceLine). - Updates
parseStatus = PARSED(orFAILED). - Transitions filing
DRAFT → DOCUMENTS_INGESTED. - Enqueues
suggest-tag-mappings.
- DOCX: uses
suggest-tag-mappings— for each line item without a mapping:- Exact-match against
mapping_library.jsonsynonyms (case-insensitive). - On miss, fuzzy-match against taxonomy element labels filtered by
applicable_entry_points(matchesfiling.entryPoint) andtaxonomy_section(matches line itemstatement). - Inserts
XbrlTagMappingwithelementId = <top-suggestion>,confidence,alternatives[](top-N),explanation,confirmedById = NULL. - Transitions filing
DOCUMENTS_INGESTED → MAPPING_IN_PROGRESS.
- Exact-match against
2. Verification
Database
sql
SELECT parse_status, page_count FROM xbrl_source_documents WHERE xbrl_filing_id = '<filingId>';
-- expect parse_status = 'PARSED'
SELECT statement, count(*) FROM xbrl_line_items
WHERE xbrl_filing_id = '<filingId>' GROUP BY statement;
SELECT count(*) AS total,
count(*) FILTER (WHERE confirmed_by_id IS NULL) AS unconfirmed,
count(*) FILTER (WHERE confidence = 'HIGH') AS high,
count(*) FILTER (WHERE confidence = 'MEDIUM') AS medium,
count(*) FILTER (WHERE confidence = 'LOW') AS low
FROM xbrl_tag_mappings WHERE xbrl_filing_id = '<filingId>';
-- expect total > 0, unconfirmed = total (nothing is human-confirmed yet)sql
SELECT status FROM xbrl_filings WHERE id = '<filingId>';
-- expect 'MAPPING_IN_PROGRESS'S3
bash
docker exec breezycorp-monorepo-minio-1 mc ls local/breezycorp/xbrl/<clientId>/<filingId>/
# expect source.{docx,pdf}Audit log
| Event type | Notes |
|---|---|
xbrl.document.uploaded | API hit; payload includes file id + sha256 |
xbrl.document.parsed | extract-line-items succeeded; line-item count payload |
xbrl.mappings.suggested | suggest-tag-mappings produced N rows; payload includes confidence histogram |
3. Negative & edge cases
- Rasterised PDF (no textual layer) —
extract-line-itemsyields zero line items;parseStatus = FAILED; filing stays atDRAFT. Ask the client for a DOCX or a text-extractable PDF, or use X-02b · Pull from FS engagement. - DOCX without tables — yields a small number of text-only line items at low confidence. Reviewer may discard the upload and request a properly-formatted document.
- Duplicate upload — sha256 deduplication: the same bytes idempotently return the existing
XbrlSourceDocumentid; no new line items are inserted. - Upload from
MAPPING_IN_PROGRESSor later — replacing the source document drops all line items and mappings and resets toDRAFT. Confirms a UI "Are you sure?" prompt; the regression is by design. extract-line-itemsfails after success of upload —parseStatus = FAILED. Reviewer either retries (transient parser issue) or re-uploads a cleaner source.
Next
Proceed to X-03 · Confirm tag mappings.