aidentity / index

emmapbingham/agentic-newsroom · MIT

ingest-to-sqlite

Turns a messy structured dataset (JSON, JSONL, XML, CSV, or nested API dumps) into a normalized, queryable, auditable SQLite database. Encodes a verifiable-ingestion methodology — preserve the source's own IDs as keys, store money and numbers both raw and parsed, fold controlled vocabularies into reference tables, add FTS5 for free text, stamp every fact row with a source key or URL, make rebuilds source-scoped and idempotent, and validate by reconciling row counts and foreign keys against the raw source. Use when building a database or ETL pipeline from raw data files, designing a SQLite schema, normalizing nested data, or making query results traceable for journalism, research, or analytics. Includes a generic database verifier.

stable

Contents

Ingest to SQLite (verifiably)

A repeatable way to turn raw data files into a SQLite database you can trust:

cheap to query, normalized, and auditable — every row traceable back to its

source record. Optimized for the case where downstream claims must be defended

(journalism, research, compliance).

Profile first. Don't design a schema blind — run the profile-dataset skill

to learn the keys, fan-out, and sparsity, then come back here.

The methodology

  1. Preserve the source's own IDs. If the data already has stable ids

(registrant.id, a UUID, a filing number), make them your primary/foreign

keys. Never fuzzy-match what is already keyed. Only build a crosswalk for the

fields that genuinely lack ids.

  1. One fact per row. Flatten nested arrays into child tables with a foreign

key back to the parent (M:N → a link table). Keep a seq for array order.

  1. Store money/numbers twice: the raw string exactly as filed (auditable)

and a parsed numeric column (*_amt). Aggregate on the parsed column, quote

the raw one.

  1. Controlled vocabularies → reference tables (ref_*) with foreign keys.

Codes/enums become small lookup tables shared across sources.

  1. Free text → FTS5. Add a full-text index for any body/description field

you'll search. Map the FTS rowid to your surrogate key.

  1. Stamp provenance on every fact row — the filing_uuid / url /

source_file that lets any value resolve to the public record.

  1. Source-scoped, idempotent rebuilds. Split the schema into a shared part

(CREATE ... IF NOT EXISTS for ref_* + an ingest_log with a source

column) and a per-source part (DROP+CREATE only that source's tables).

Re-running one source never touches the others. No whole-file wipes.

  1. Validate by reconciliation, not vibes. After loading, assert the row

count equals the distinct-record count from the raw source, and that there

are zero orphan foreign keys. See "Verify" below.

  1. Log what you dropped. Exact-duplicate source records, unparseable files,

rows missing a key — count them and print the totals. Silent loss reads as

"complete" when it isn't. (Recover where you can: e.g. sanitize bad input and

retry before giving up on a record.)

Concrete SQL/DDL for each pattern is in [reference/patterns.md](reference/patterns.md).

Build hygiene

  • Bulk load with PRAGMA journal_mode=OFF; synchronous=OFF; foreign_keys=OFF,

then turn foreign keys back ON and run the checks. (A rebuildable artifact

doesn't need crash safety mid-build.)

  • Dedupe dimensions in memory on their id; write them once at the end.
  • executemany with batched lists; commit per file.

Verify

python scripts/check_db.py path/to.db \
  --expect main_table=<raw_count> other_table=<raw_count>

Runs quick_check + foreign_key_check, prints every table's row count, and

reconciles the --expect counts against numbers you computed from the raw

source. Exits non-zero if anything fails — wire it into the build.

Worked examples

The project this skill came from — the agentic-newsroom repo

(https://github.com/emmapbingham/agentic-newsroom) — ingests three very

different sources with exactly this methodology. Read them as templates; they

are not bundled with the skill:

  • nested JSON with clean ids → scripts/ingest_senate.py + scripts/schema_senate.sql
  • ~409k XML files, no UUIDs, bad chars recovered → scripts/ingest_house.py
  • flat JSONL + FTS + derived roster → scripts/ingest_press.py

Stdlib only. License: MIT.

Provenance

Authored by emmapbingham in emmapbingham/agentic-newsroom. The content is mirrored here under its upstream license.

Related

fish-for-leads Operates the everyday, cheap go-fish loop over a data-journalism newsroom's sourced SQLite corpus — runs one live screen, gates candidates before they cost editor attention (template collapse, actor test, novelty-lite), surfaces survivors as leads, builds derived tables on demand, and promotes leads into cases. Every state change lands in the actions journal with an actor and review state; agents act provisionally and the editor reviews a ranked queue afterward (acknowledge or overturn, signed). On invocation reads full posture (screens with yield, leads, dispositions, unreviewed actions). Hands off to track-investigation on promotion, and to sweep-for-screens when the live slate is exhausted. Use when running the newsroom, going fishing, surfacing or triaging leads, reviewing autonomous actions, promoting a lead, or building a derived table. Trigger phrases: run the newsroom, newsroom status, go fish, surface leads, triage leads, review actions, promote a lead, check posture. emmapbingham/agentic-newsroom MIT sweep-for-screens Runs the rare, expensive fleet sweep that expands a data-journalism newsroom's fishing capability over a sourced SQLite corpus — designs new deterministic SQL screens and proposes the derived tables they need, without generating leads directly. Reads and writes screens/derived_tables/sweeps in newsroom.db; the fish-for-leads skill runs the screens and surfaces leads. Encodes the contrast taxonomy (six ways a lead can deviate from a baseline, crossed with the corpus's grains) that partitions idea space across a scout fleet, fleet run-economics (pilot-first, probe budgets, tier models by fleet size, hard token ceilings), and the beat-nomination discipline for using outside reporting to aim the corpus without letting it into evidence. Use when sweeping for new screen ideas, designing a screen, expanding fishing capability, proposing a derived table, or running a fleet sweep. Trigger phrases: sweep for leads, design new screens, expand the newsroom, run a sweep, propose a derived table. emmapbingham/agentic-newsroom MIT track-investigation Runs and tracks data-driven investigative-journalism cases across sessions over a SQLite corpus. Starts when a logged lead is promoted from the newsroom (fish-for-leads skill) — opens the case, runs drilldown, compiles evidence, verifies the finding (builder → skeptic → judge), and conducts a novelty scan. Defines durable on-disk case files (hypothesis, sourced evidence, running log), a layered data architecture that keeps the immutable source database, shared rebuildable derived tables, and case-local artifacts separate and reproducible. Built for the db/gain.db corpus but the discipline is general. Use when promoting a lead to a case, starting or resuming an investigation, building or refuting a case from data, managing investigation state across context windows, or verifying a finding. Trigger phrases: promote a lead, open a case, track an investigation, resume the investigation, build a case, verify a finding. emmapbingham/agentic-newsroom MIT