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 are 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.