Skip to content

Add public-sector invoice spending domain (buyer-supplier-year aggregates) - #85

Merged
namedgraph merged 2 commits into
masterfrom
feat/public-spending-domain
Oct 26, 2026
Merged

namedgraph merged 2 commits into
masterfrom
feat/public-spending-domain

Conversation

@namedgraph

Copy link
Copy Markdown
Member

Summary

Final Tier 1 source. A new opt-in etl/public-spending/ domain — public-sector invoice totals per (buyer, supplier, year) from the State Treasury (NBFC) open data (nbfc/viesojo_sektoriaus_saskaitos), joined to legal-entities/ by the bare JAR code. Opens the sub-threshold / "money past the procurement register" cases.

Scale & method

  • Source is ~14.4M invoices (Saskaitos) + ~32M party rows (SaskaituSalys). The party rows can't be windowed by invoice year (their data is a 2020-era registration date), so ltlod-public-spending streams both feeds (keyset-paginated) into a temporary on-disk SQLite db and aggregates there — low memory, ~40–90 min fetch.
  • Join (verified): SaskaituSalys.sf_id = Saskaitos.sf_id normalized to an LT prefix; tipas Pirkėjas = buyer, Tiekėjas = supplier, each by validus_jar_kodas. Party names are pseudonymised in the source, so names come from legal-entities/.

Model

  • ltlod:SpendingAggregate per document (public-spending/{buyer}-{supplier}-{year}/): ltlod:spendingBuyer/ltlod:spendingSupplier → legal-entities/{code}/#this, dct:temporal → the calendar year, measures ltlod:invoiceCount / ltlod:billedTotal (incl. VAT) / ltlod:billedNet.
  • app/ns.ttl: the class + both party links (each with its own inverse view — buyer / supplier) + the measures. app/public-spending.ttl container with a top-flows chart (no children listing for the ~432k docs). SHACL in etl/shapes/public-spending.ttl.
  • MIN_TOTAL=1000 EUR floor (like procurement's MIN_VALUE): drops the ~63% zero-total credit-note/VAT pairs and the trivial tail → ~432,570 documents (full set ~3.9M). Years restricted to the calendar's 1977–2027 (28 malformed-date rows dropped).

Verification

  • 432,570 SpendingAggregate docs; riot validates, 0 blank nodes, 0 dangling calendar years, all 60 municipalities N/A (parties are JAR-linked, not refArea). Links into legal-entities/ dangle until the full JAR register is loaded — expected.
  • Not in make all (the ~46M-row fetch is heavy); aggregates.trig is gitignored bulk output (~418 MB).
  • Two cross-domain example queries verified returning real rows (UAB Scandi Standard €81M over 25,406 small invoices with no procurement contract; Vidmantas Kanopa / "Daivida" €51.6M over 30,163 invoices). EXAMPLES.md updated with the two sections only.

🤖 Generated with Claude Code

namedgraph and others added 2 commits October 26, 2026 10:12
…ates)

New opt-in etl/public-spending/ domain: public-sector invoice totals per
(buyer, supplier, year) from the State Treasury (NBFC) open data
(nbfc/viesojo_sektoriaus_saskaitos). ltlod-public-spending streams ~14.4M invoices
(Saskaitos) and ~32M party rows (SaskaituSalys), keyset-paginated, into a temporary
on-disk SQLite db and aggregates there (the party rows cannot be windowed by invoice
year, so the whole table is read — ~40-90 min). Join: SaskaituSalys.sf_id =
Saskaitos.sf_id normalized to an LT prefix; tipas Pirkėjas is the buyer, Tiekėjas the
supplier, each by validus_jar_kodas. Party names are pseudonymised in the source, so
names come from legal-entities/ by the code.

One ltlod:SpendingAggregate per document (public-spending/{buyer}-{supplier}-{year}):
ltlod:spendingBuyer/spendingSupplier -> legal-entities/{code}/#this, dct:temporal ->
the calendar year, measures ltlod:invoiceCount / ltlod:billedTotal (incl. VAT) /
ltlod:billedNet. At the default MIN_TOTAL=1000 EUR floor (drops the ~63% zero-total
credit-note/VAT pairs and the trivial tail) the set is ~432k documents; years are
restricted to the calendar's 1977-2027.

app/ns.ttl: the class + the two party links (each with its own inverse view, buyer /
supplier) + the measure properties. app/public-spending.ttl container with a top-flows
chart (no children listing). SHACL in etl/shapes/public-spending.ttl. NOT in `make all`
(the ~46M-row fetch is heavy); aggregates.trig is gitignored bulk output (~418 MB).

Two cross-domain cases joined on the JAR code: suppliers paid via many sub-threshold
invoices with no procurement contract, and politicians' companies billed by the public
sector. Repayments/irregularities are not modelled here; see the eu-funds note for the
hash-key limitation that also applies conceptually.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
app/stories/ pages (like seimo-turtas-2024) that narrate the investigative cases the
company-side Tier-1 domains open up, each with live charts pinned to the current data:

- rangovai-skolos-bankrotai — public contractors that owe overdue tax or are in the
  insolvency register (procurement × taxes × insolvency).
- es-fondai-politiku-imones — EU structural funds to companies tied to politicians, and
  companies drawing both EU funds and public contracts (eu-funds × PINREG × procurement).
- saskaitu-skaidymas — suppliers paid large sums via many sub-threshold invoices with no
  procurement contract (public-spending × procurement).

All figures and company links are verified against the loaded data. Listed automatically
under the Stories container (app/stories.ttl ChildrenView); installed with `make install`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@namedgraph
namedgraph merged commit 9ff7151 into master Oct 26, 2026
4 checks passed
@namedgraph
namedgraph deleted the feat/public-spending-domain branch October 26, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant