All projects 03 · LLM systems

Metadata Knowledge Base

AI drafts catalog metadata at scale, a deterministic validator checks every verifiable claim against the actual full file, and humans judge what machines can't. 98% verifiable-claim accuracy on 10 real public datasets — and the 2% that failed, the system caught automatically.

Status Shipped Year 2026 Stack Python 3.11 · Claude Sonnet 5 · pytest

Overview

Market data teams sit on hundreds of datasets that all need catalog metadata — titles, field descriptions, likely users, caveats — so people can find and correctly use them. Writing it by hand doesn't scale; letting an LLM write it unchecked is how errors end up in front of traders.

This project takes the middle path, built on a deliberate information asymmetry. Claude drafts metadata seeing only a small sample of each dataset — column names, row count, eight sample rows. A stdlib-only Python profiler reads the full file and derives ground truth the model never saw. Five mechanical checks then compare the model's claims against that ground truth: field names, field types, row count, date range, frequency. A single failed check rejects the entry outright — no partial credit for “mostly right.”

What machines can't verify — descriptions, use cases, caveats — goes to a human review queue. Every knowledge-base entry keeps the two tiers cleanly separated: verified facts are machine-checked, drafted judgments carry an explicit review status. The two are never confused with each other.

Claim accuracy98% — 49/50
Verified into KB9 / 10
Caught by validator1
Offline test suite53 passed

Approach

01

Fetch

Download 10 real public datasets from FRED, ECB, Cboe and US Treasury — snapshots committed to the repo with SHA-256 hashes. The only step that ever touches the network.

02

Profile

A stdlib-only profiler (plain csv/statistics, no pandas) reads the full file and derives ground truth: types, row counts, date ranges, median gap between dates.

03

Generate

Claude drafts metadata via forced tool use, seeing only column names, the row count and 8 sample rows — never the whole file. Failures become an explicit status, never a dropped dataset.

04

Validate

Five deterministic checks compare every verifiable claim against ground truth. One failed check rejects the entire entry.

05

Review

Verified entries land in the knowledge base; drafted judgments await human sign-off via the CLI review workflow. Outcome: 9 verified, 1 rejected, 8 approved by the reviewer.

The audit, live

All 50 mechanical checks from the real validation run — 10 datasets × 5 checks. Click any cell to see exactly what the validator compared: the model’s claim against ground truth derived from the full file. One cell is orange. That’s the mechanism working.

Checks run50
Passed49
Failed1
Column types95 / 96
Check passed Check failed — entry rejected REAL RUN DATA — results/validations/*.json

Three real
findings

REJECTED

The validator caught a real type error

Working from an 8-row sample, the model described the SDMX column UNIT_MULT in ecb_eur_usd as a string. The validator, checking all 500 rows, found it holds integers only — and rejected the entire entry rather than let the wrong type ship. A model that is fluent and confident can still be wrong; only a check against the full file catches it.

PENDING

A human held back an unresolved ambiguity

ecb_gbp_eur passed every mechanical check, but its own drafted caveats flagged that the FX quoting direction was ambiguous — the draft’s first reading contradicted the data. The reviewer declined to approve an unresolved FX direction rather than guess: exactly the judgment call a validator cannot make.

PATTERN

The caveats reveal model reasoning limits

Seven of eight approved entries flag their 2026 end-dates as “beyond the current date” — the model reasoning from its training cutoff instead of the run date. Prose sits outside what the validator can check, so this was the reviewer’s call: harmless conservatism, allowed to stand.

Five
mechanical
checks

Each check runs independently against the profiler’s ground truth — pass, fail or skipped. A single failed check rejects the entry outright.

CheckWhat it verifies
field_namesEvery claimed column name matches the real header exactly
field_typesClaimed type (integer / float / date / string) matches the type inferred from all rows
row_countClaimed row count equals the actual row count
date_rangeClaimed first/last date matches the actual range across the full file
frequencyClaimed frequency matches a band derived from the median day-gap between observations

Result across the run: 49 of 50 checks passed (98.0%), 95 of 96 column types correct (98.96%).

Verification,
not confidence
scores

Its sibling project, the Notification Classifier, trusts the model’s own confidence number to decide what needs human review. This project deliberately takes the opposite approach:

notification-classifiermodel self-reports confidence; low score → human review metadata-kbexternal validator re-derives ground truth from the full file and compares

Don’t ask the model how sure it is — check the claim against reality. A model that is fluent and wrong still gets caught.

Ten real
datasets

All public, keyless CSV endpoints — FRED, ECB Data Portal, Cboe Global Markets and the US Treasury. Snapshots are committed with SHA-256 hashes; everything after fetch reproduces fully offline, including the 53-test suite.

DatasetOrganizationFrequencyStatus
US GDPFREDQuarterlyverified
US CPI (CPIAUCSL)FREDMonthlyverified
Effective Federal Funds RateFREDMonthlyverified
US Unemployment RateFREDMonthlyverified
10-Year Treasury Rate (DGS10)FREDDailyverified
S&P 500 Daily CloseFREDDailyverified
ECB USD/EUR Reference RateECBDailyrejected by validator
ECB GBP/EUR Reference RateECBDailypending human review
Cboe VIX Daily HistoryCboe Global MarketsDailyverified
Treasury Par Yield Curve RatesUS TreasuryDailyverified

Fail-safe invariant, enforced in code: every dataset produces exactly one outcome — verified, rejected or generation_failed — and none can silently vanish from the report.