How MELMAP's financial side reads Companies House filings and turns them into the dashboards at views.melmap.uk/finance — data sources, the extraction pipeline, scoring formulas, and trust model. Every number below is real output from the two production runs (310 WM manufacturers; 440 WM construction/MMC firms, 2,168 filings, August 2026). Companion page: MELMAP Skills Views — Supply↔Demand Matching Logic.

All code lives in one deliberately self-contained pipeline: Test.py (~10,200 lines). Line references below are to that file.


0. Plain-English guide

Every UK limited company must file annual accounts at Companies House, and those filings are public. MELMAP downloads them for every active firm in chosen sectors (SIC codes) in the West Midlands, reads the numbers out of each filing — preferring machine-readable formats, falling back to OCR for scanned paper — sanity-checks what it read, and computes health scores per firm: resilience (can the balance sheet absorb a shock?), renewal (is the firm investing in its capital base or letting it shrink?), and an academic distress index. Every figure carries a trust tier, and when a filing legally omits something (most small companies don't publish a profit & loss), the pipeline records “not filed” rather than guessing. The result: a sector's financial health, firm by firm, on a map — built entirely from free public data.


1. Data sources

Source What we take Cost
Companies House Advanced Search API Every active company per SIC code (fetch_all_sic_companies, Test.py:4248; API caps each query at 5,000 — oversized SICs are honestly recorded as samples in the set manifest) Free (API key)
Companies House Filing History + Document API Each firm's accounts filings and the actual documents — iXBRL/XHTML preferred, PDF fallback (get_filing_history :9221, get_all_accounts_filings :9415, choose_best_document_content_type :9469) Free
postcodes.io Bulk geocoding of registered-office postcodes — the geographic West Midlands filter and the dashboard map (geocode_postcodes :4266). Used because the CH API's own location filter is silently ignored Free
PSC register (CH) Corporate-parent links → corporate-group tagging (assign_corporate_groups :7692) Free
Tesseract OCR + PyMuPDF Text from scanned PDF filings Free, local
Claude vision (claude-sonnet-5) Optional last-resort extraction for low-trust filings only (llm_vision_extract_balance_fields :3803) Paid, capped, off by default
IN05 literature Neumaier & Neumaierová's industrial-distress index; validated on engineering firms (Lukáč et al., PLOS ONE 2022)

2. The pipeline in one line

Discover firms → select accounts filings → extract (iXBRL → OCR → LLM) → validate & tier → derive ratios & scores → aggregate & visualise. Orchestrated by main() (Test.py:9517); the construction run drove it through run_wm_construction_financials.py (an exec-pattern driver that swaps SIC codes and the company set without touching Test.py).


3. Extraction: three tiers, cheapest first

The project rule is exhaust the free layers first:

  1. iXBRL (extract_ixbrl_financials :4522) — modern filings embed machine-tagged facts (NetAssetsLiabilities, FixedAssets…). Exact by construction; adopted when ≥2 tagged concepts are present (IXBRL_MIN_FIELDS_TO_ADOPT, :58).
  2. Text / layout / OCR (extract_financial_fields_from_content :6865) — for PDFs: native text first, Tesseract OCR for scans, with fuzzy label matching, unit scaling and bracketed-negative handling. Scans up to 80 pages (:63) because large filers bury the balance sheet behind narrative.
  3. LLM vision (maybe_apply_llm_fallback :3874) — only for filings already flagged suspect/warned, capped at 200 calls (:47-51).

Real distribution — construction/MMC run (2,168 filings): iXBRL 1,873, traditional/OCR 295, LLM 0 (fallback off; total cost of the run: £0 beyond API-free tiers). Manufacturing run: iXBRL 1,123, traditional 147, llm_vision 76 — e.g. VAUXHALL MOTORS LIMITED's 2024 scanned filing, where vision extraction promoted the record to the verified tier.


4. Validation and the trust model