Skip to content

Latest commit

 

History

History
499 lines (394 loc) · 29.5 KB

File metadata and controls

499 lines (394 loc) · 29.5 KB

CLAUDE.md

Context for AI assistants working on the DebtStack.ai codebase.

IMPORTANT: Before starting work, read WORKPLAN.md for current priorities, active tasks, and session history. Update the session log at the end of each session.

What's Next

See WORKPLAN.md for full session history and priorities.

All major infrastructure complete:

  • Stripe billing ✅ (2026-02-03) — Products, webhooks, checkout flow, 14/14 E2E tests passing
  • Ownership enrichment ✅ (2026-02-24) — 13,113 entities with known parents (32% of non-root), 13,077 ownership links
  • Debt coverage ✅ (2026-02-19) — 314/314 companies accounted for (197 genuinely OK + 94 benchmark-adjusted + 23 new)
  • Document linking ✅ — 96.4% coverage (5,512/5,719 instruments linked)
  • Company expansion ✅ — 211 → 314 → 457 companies (140 S&P 500 additions, Mar 2026)
  • SDK/MCP/Docs ✅ — PyPI v0.1.3, 6 MCP directories, docs.debtstack.ai
  • Chat assistant ✅ → Migrating: Gemini 2.5 Pro → Claude Sonnet 4.6 + 8 skills (19 tools, MCP server, prompt caching)
  • Analytics & alerting ✅ — Vercel, PostHog, Sentry, Slack
  • QC ✅ — 0 critical, 0 errors
  • CDS spreads ✅ (2026-03-05) — Bond spread proxy: ~53K records, 111 companies, 598 days (2023-03-06 to present), 5 tenors. Daily 9:30 PM ET scheduler. Data quality filters: price >= $60, estimated excluded, near-maturity excluded, outlier removal.
  • Credit ratings ✅ (2026-03-16) — 846 ratings across 367 companies. Sources: NRSRO 17g-7(b) disclosures (Fitch + Moody's) + LLM extraction from SEC filings (S&P). Issuer + instrument-level. Monthly auto-refresh (1st Sunday, 2 AM ET).

In Progress:

  1. Medici model migration — Gemini → Claude Sonnet 4.6 + skills architecture (see medici/ROADMAP.md)

Remaining:

  1. Grow usage / marketing

Project Overview

DebtStack.ai is a credit data API for AI agents. It extracts corporate structure and debt information from SEC filings, then serves pre-computed responses via a FastAPI REST API.

Target users: AI agents that need credit analysis data (corporate structure, debt details, structural subordination).

Current Status (March 2026)

Database: 457 companies | 8,658 active debt instruments | 3,857 with CUSIP | 3,093 with pricing | ~39,757 document sections | 7,383 guarantees | 1,403 collateral records | 3,566 covenants | 64,433 entities | ~3,730 financial quarters | ~53K CDS spread proxy records | 846 credit ratings (367 companies) | 442 companies with market data

Deployment: Railway with Neon PostgreSQL + Upstash Redis. Live at https://api.debtstack.ai.

Key Metrics:

  • Document Linkage: 96.4% (5,512/5,719 instruments linked)
  • Pricing Coverage: 3,064 bonds with TRACE pricing (95.8% of CUSIPs). All estimated/synthetic removed — only real TRACE data. Updated 3x daily by APScheduler (11 AM, 3 PM, 9 PM ET).
  • Debt Coverage: 197 genuinely OK + 94 benchmark-adjusted = 314/314 accounted for. Overall $5,838B/$7,980B = 73.2%.
  • Ownership: 13,113 entities with known parents (32% of non-root). Sources: LLM (5,647), GLEIF (3,415), UK Companies House (1,362).
  • Data Quality: QC audit passing — 0 critical, 3 errors (all legitimate), 11 warnings.
  • Eval Suite: 181/206 tests passing (87.9%) across 17 eval files + 36 new market data tests (12 CDS + 12 ETF flows + 12 FRED spreads) across 3 new eval files. 20 primitives in eval runner.
  • CDS Spread Proxy: ~53K records across 111 companies, 598 trading days (2023-03-06 to 2026-03-04). Bond spread-to-treasury proxy. 5 tenors (1Y/3Y/5Y/7Y/10Y). Updated daily at 9:30 PM ET. Data quality: price >= $60, no estimated, no near-maturity, spread range check for small groups, 3-sigma outlier removal. Negative spreads allowed (floor -200 bps) for premium IG bonds.
  • Credit Ratings: 846 rows across 367 companies (80% coverage). Sources: SEC Rule 17g-7(b) NRSRO disclosures (Fitch, Moody's) + LLM extraction from SEC filings (S&P). Issuer-level + instrument-level (senior unsecured, senior secured, subordinated, corporate family). Monthly auto-refresh via scheduler.
  • FRED Spread Indices: ICE BofA OAS indices (13 series: broad IG/HY, 7 rating buckets, 4 maturity buckets). Daily 6:30 PM ET scheduler. ~10K records from 2023-01-01.
  • Market Data: 442/457 companies with market cap, share price, shares outstanding from Yahoo Finance. Updated daily at 5:00 PM ET.
  • Finnhub Discovery: All 314 original companies scanned. Cache: 271 companies, 1,904 bonds. 140 new companies in progress.

What's Working:

  • Chat Assistant: /dashboard/chat — Migrating from Gemini 2.5 Pro to Claude Sonnet 4.6 + 8 skills + 19 tools via SSE streaming
  • Three-Tier Pricing: Pay-as-You-Go ($0 + per-call), Pro ($199/mo), Business ($499/mo)
  • Primitives API: 14 core endpoints optimized for AI agents (field selection, powerful filters)
  • Auth & Credits: API key auth, tier-based access control, usage tracking
  • Legacy REST API: 26 endpoints for detailed company data
  • Observability: Vercel Analytics, PostHog, Sentry, Slack alerts
  • Iterative extraction with QA feedback loop (5 checks, 85% threshold)
  • Gemini for extraction (~$0.008), Claude for escalation

Architecture

SEC-API.io (10-K, 10-Q, 8-K, Exhibits)
    ↓
Gemini Extraction (~$0.008)
    ↓
QA Agent: 5 verification checks (~$0.006)
    ↓
Score >= 85%? → PostgreSQL → Cache → API
    ↓ No
Targeted Fixes → Loop up to 3x → Escalate to Claude

Code Organization

Utilities (stateless helpers — no DB, no API calls):

File Domain
app/services/utils.py Text/parsing: parse_json_robust, normalize_name, parse_date
app/services/extraction_utils.py SEC filings: clean_filing_html, combine_filings, extract_debt_sections
app/services/llm_utils.py LLM clients: get_gemini_model, call_gemini, LLMResponse
app/services/yield_calculation.py Financial math: YTM, duration, treasury yields

Services (orchestrate DB, APIs, workflows):

File Purpose
app/services/sec_client.py SEC filing clients (SecApiClient, SECEdgarClient)
app/services/extraction.py Core extraction + DB persistence
app/services/iterative_extraction.py Main extraction with QA loop (entry point)
app/services/tiered_extraction.py LLM clients, prompts, escalation
app/services/base_extractor.py Base class for LLM extraction services
app/services/section_extraction.py Document section extraction and storage
app/services/amount_extraction.py Indenture principal amount extraction (regex + Gemini)
app/services/document_linking.py Link instruments to source documents
app/services/hierarchy_extraction.py Exhibit 21 + indenture ownership enrichment
app/services/financial_extraction.py TTM financial statements from 10-K/10-Q
app/services/guarantee_extraction.py Guarantee extraction from indentures
app/services/collateral_extraction.py Collateral for secured debt
app/services/covenant_extraction.py Structured covenant data extraction
app/services/metrics.py Credit metrics (leverage, coverage ratios)
app/services/filing_monitor.py SEC EDGAR polling for new filings (FilingMonitor, NewFiling)
app/services/filing_refresh.py Filing refresh orchestrator (FilingRefreshService, RefreshResult)
app/services/qc.py Quality control checks
app/services/qa_agent.py 5-check QA verification
app/services/embedding.py Shared Gemini embedding utilities (embed_texts, embed_query)
app/services/pricing_history.py Bond pricing history backfill and daily snapshots
app/services/treasury_yields.py Treasury yield curve history from Treasury.gov
app/services/credit_rating_extraction.py Credit rating extraction from SEC filings (LLM-based)
app/services/cds_spreads.py CDS spread proxy from bond pricing + DTCC SDR ingestion
app/services/fred_spreads.py FRED ICE BofA credit spread index fetch/store/query
app/services/market_data.py Yahoo Finance market cap, shares outstanding, share price → Company.attributes JSONB

API Layer:

File Purpose
app/api/primitives.py Primitives API — 14 core endpoints for agents
app/api/analysis.py Analysis API — 5 compute endpoints (financials, liquidity, capital structure, valuation, peers)
app/api/auth.py Auth API — signup, user info
app/api/routes.py Legacy FastAPI endpoints (26 routes)
app/core/auth.py API key generation, validation, tier config
app/core/cache.py Redis cache client (Upstash)
app/core/scheduler.py APScheduler: pricing refresh + filing refresh + CDS spread proxy + alert checks
app/core/posthog.py PostHog analytics client
app/core/alerting.py Slack webhook alerts
app/core/monitoring.py Redis-based API metrics

Models & Config:

File Purpose
app/models/schema.py SQLAlchemy models (includes User, UserCredits, UsageLog)
app/models/credit_rating.py Credit rating history model (CreditRatingHistory, VALID_RATING_TYPES)
app/models/cds_spread.py CDS spread models (CdsSpreadHistory, CdsEntityMapping)
app/models/fred_spread.py FRED spread index model (FredSpreadIndex)
app/core/config.py Environment configuration
app/core/database.py Database connection

Scripts (thin CLI wrappers importing from services):

File Purpose
scripts/extract_iterative.py Complete extraction pipeline
scripts/recompute_metrics.py Metrics recomputation
scripts/update_pricing.py Bond pricing updates
scripts/backfill_debt_document_links.py Heuristic multi-strategy document linking
scripts/link_to_base_indenture.py Fallback: link notes → base indenture
scripts/link_to_credit_agreement.py Fallback: link loans → credit agreement
scripts/analyze_gaps_v2.py Debt coverage gap analysis
scripts/populate_benchmark_debt.py Set benchmark_total_debt for banks/utilities
scripts/fetch_prospectus_sections.py Fetch 424B prospectus sections ($0)
scripts/refresh_filings.py Automated SEC filing refresh (--all, --ticker, --dry-run)
scripts/extract_intermediate_ownership.py LLM extraction of intermediate parents
scripts/script_utils.py Shared utilities (DB sessions, parsers, progress, Windows handling)
scripts/embed_document_sections.py Embed document sections into pgvector for semantic search
scripts/ingest_cds_spreads.py CDS spread ingestion (bond proxy backfill, DTCC SDR, stats)
scripts/ingest_nrsro_ratings.py NRSRO credit rating ingestion from SEC Rule 17g-7(b) disclosures
scripts/extract_credit_ratings.py LLM-based credit rating extraction from SEC filings
scripts/ingest_fred_spreads.py FRED ICE BofA credit spread index ingestion (--backfill, --daily, --stats)
scripts/ingest_market_data.py Market data ingestion from Yahoo Finance (--all, --ticker, --stats)
medici/scripts/ingest_knowledge.py RAG knowledge ingestion (see medici/CLAUDE.md)

Database Schema

Core Tables:

  • companies: Master company data (ticker, name, CIK, sector)
  • entities: Corporate entities with hierarchy (parent_id, is_root, VIE tracking)
  • ownership_links: Complex ownership (JVs, partial ownership; ownership_type: "direct"/"indirect"/NULL)
  • debt_instruments: All debt with terms, linked via issuer_id
  • guarantees: Links debt to guarantor entities
  • collateral: Asset-backed collateral for secured debt (type, description, priority)
  • covenants: Structured covenant data from credit agreements/indentures
  • company_financials: Quarterly financial statements (amounts in cents) — see Industry-Specific Metrics below
  • bond_pricing: Current pricing (YTM, spreads in basis points)
  • bond_pricing_history: Historical daily snapshots (Business tier only)
  • treasury_yield_history: Historical US Treasury yield curves (1M-30Y tenors, 2021-present)
  • credit_rating_history: Credit rating snapshots (S&P, Moody's, Fitch) — issuer + instrument-level with rating_type and optional debt_instrument_id FK. Sources: LLM extraction from SEC filings + NRSRO 17g-7(b) disclosures
  • cds_spread_history: Bond spread proxy / CDS spread time series (company_id, spread_date, tenor, spread_bps, trade_count, source)
  • cds_entity_mapping: DTCC entity name → company_id mapping for SDR data matching
  • fred_spread_index: FRED ICE BofA credit spread indices (observation_date, series_id, spread_bps, index_category). 13 series across broad/rating/maturity categories. Daily from FRED API.
  • document_sections: SEC filing sections for full-text search (TSVECTOR + GIN index)
  • document_section_chunks: Embedded chunks for vector semantic search (pgvector HNSW, 768-dim Gemini embeddings)

Auth Tables:

  • users: User accounts (email, api_key_hash, tier, stripe IDs, rate_limit_per_minute)
  • user_credits: Credit balance and billing cycle
  • usage_log: API usage tracking (endpoint, cost_usd, tier_at_time_of_request)

Other Tables: company_cache (pre-computed JSON + extraction_status JSONB), company_metrics, team_members, coverage_requests, knowledge_chunks (pgvector for RAG — see medici/CLAUDE.md)

Industry-Specific Financial Metrics

The company_financials.ebitda field stores the industry-appropriate metric, with ebitda_type indicating which:

ebitda_type Industry Metric
"ebitda" Operating companies EBITDA
"ppnr" Banks Pre-Provision Net Revenue
"ffo" REITs Funds From Operations
"noi" Real Estate Net Operating Income

Banks/Financial Institutions: companies.is_financial_institution = true (12 companies: AXP, BAC, C, COF, GS, JPM, MS, PNC, SCHW, TFC, USB, WFC). Industry-specific fields in company_financials: net_interest_income, non_interest_income, non_interest_expense, provision_for_credit_losses.

To add a new industry type: Add flag to companies → add columns to company_financials → Alembic migration → extraction prompt in financial_extraction.py → calculation logic in save_financials_to_db() → detection in extract_iterative.py.

API Endpoints

Primitives API (14 endpoints — optimized for agents)

All require X-API-Key header (except /v1/coverage/request).

Endpoint Method Cost Tier Purpose
/v1/companies GET $0.05 All Search companies with field selection
/v1/bonds GET $0.05 All Search/screen bonds with pricing
/v1/bonds/resolve GET $0.05 All Map bond identifiers — free-text to CUSIP
/v1/financials GET $0.05 All Quarterly financial statements
/v1/collateral GET $0.05 All Collateral securing debt
/v1/covenants GET $0.05 All Structured covenant data
/v1/ratings GET $0.05 All Credit rating history (S&P, Moody's, Fitch)
/v1/market/cds GET $0.05 All CDS spread proxy (bond spread-to-treasury)
/v1/companies/{ticker}/changes GET $0.10 All Diff against historical snapshots
/v1/entities/traverse POST $0.15 All Graph traversal (guarantors, structure)
/v1/documents/search GET $0.15 All Search SEC filings (keyword, semantic, hybrid)
/v1/batch POST Sum All Execute multiple primitives in parallel
/v1/covenants/compare GET Biz Biz Compare covenants across companies
/v1/market/spreads GET $0.05 All FRED ICE BofA credit spread indices (IG/HY OAS)
/v1/coverage/request POST Free All Request coverage for non-covered companies

Analysis API (5 endpoints — compute tools for Medici)

All require X-API-Key header. $0.10 per call.

Endpoint Method Purpose
/v1/analysis/financials GET Ratio analysis + trends (profitability, solvency, efficiency, cash flow)
/v1/analysis/liquidity GET Cash position, revolver capacity, maturity schedule, runway
/v1/analysis/capital-structure GET Debt stack: seniority, cost of debt, maturity profile, fixed/floating
/v1/analysis/valuation GET Multi-methodology valuation with implied share prices (see below)
/v1/analysis/peers GET Side-by-side metrics + rankings across companies

Valuation methodologies (/v1/analysis/valuation):

  • EV/EBITDA comps: Implied price from sector median EV/EBITDA (or EV/PPNR for banks, EV/FFO for REITs)
  • EV/Revenue comps: Implied price from sector median EV/Revenue
  • P/E comps: Implied price from sector median P/E
  • P/BV comps (P/Book for banks): Implied price from sector median Price/Book Value — primary for banks, useful for capital-intensive sectors
  • P/PPNR comps (banks only): Implied price from sector median Price/Pre-Provision Net Revenue
  • P/FFO comps (REITs only): Implied price from sector median Price/Funds From Operations
  • DCF (5-year): Revenue growth + FCF margin approach. Revenue CAGR from YoY comparison (capped -10% to +25%), FCF margin held constant at TTM level, 2.5% terminal growth, WACC from actual capital structure.

Each method returns implied_share_price_cents and vs_current_pct for easy comparison. valuation_summary array provides all methods side by side. Peer matching broadens sector search when exact match has < 3 peers.

Market data (market cap, shares outstanding, share price) stored in Company.attributes JSONB. Refreshed daily at 5:00 PM ET from Yahoo Finance (v7 quote API with crumb auth, batch 10 tickers/request). 442/457 companies covered (96.7%). Script: scripts/ingest_market_data.py.

Business-Only: /v1/bonds/{cusip}/pricing/history, /v1/covenants/compare, /v1/export, /v1/usage/analytics, /v1/usage/trends

Pricing API (public): /v1/pricing/tiers, /v1/pricing/calculate, /v1/pricing/my-usage, /v1/pricing/purchase-credits, /v1/pricing/upgrade

Auth: /v1/auth/signup (POST), /v1/auth/me (GET)

Legacy REST (26 routes): /v1/companies/{ticker} — overview, structure, hierarchy, debt, metrics, financials, ratios, pricing, guarantees, entities, maturity-waterfall. Search: /v1/search/companies, /v1/search/debt, /v1/search/entities. Analytics: /v1/compare/companies, /v1/analytics/sectors. System: /v1/ping, /v1/health, /v1/status, /v1/sectors.

Query features: Field selection (?fields=ticker,name), sorting (?sort=-net_leverage_ratio), filtering (?min_ytm=8.0&seniority=senior_unsecured).

Key Design Decisions

  1. Amounts in CENTS: $1 billion = 100_000_000_000 cents
  2. Rates in BASIS POINTS: 8.50% = 850 bps
  3. Individual instruments: Extract each bond separately, not totals
  4. Name normalization: Case-insensitive, punctuation-normalized via normalize_name()
  5. Robust JSON parsing: parse_json_robust() handles LLM output issues
  6. Estimated data must be flagged: Always mark estimated/inferred data (e.g., issue_date_estimated: true)
  7. ALWAYS detect scale from source document: Never assume scale — extract from SEC filing header. detect_filing_scale() in financial_extraction.py. Never blindly apply scale fixes — always verify against the source SEC filing first.
  8. Automated filing refresh: New filings detected via EDGAR polling (7:30 AM + 1:00 PM ET). Each filing type triggers specific extraction steps: 10-Q updates financials/docs/metrics; 10-K adds hierarchy/guarantees/collateral/covenants; 8-K updates docs/metrics only. Detection via company_cache.source_filing_date comparison. ~$0.008 per 10-Q, ~$0.04 per 10-K.

Debt Instrument Types

Canonical taxonomy with normalization from 50+ LLM output variants:

Type Description
senior_notes Investment-grade or high-yield bonds
senior_secured_notes Secured bonds with collateral
subordinated_notes Junior/subordinated bonds
convertible_notes Bonds convertible to equity
term_loan Bank term loans (A, B, or generic)
revolver Revolving credit facilities
abl Asset-based lending facilities
commercial_paper Short-term unsecured notes
equipment_trust EETCs, equipment financing
government_loan PSP notes, government-backed loans
finance_lease Capital/finance lease obligations
mortgage Real estate secured debt
bond Generic bonds (municipal, revenue, etc.)
debenture Unsecured debentures
preferred_stock Trust preferred securities
other Catch-all for rare types

The validator in extraction.py normalizes 50+ variants (e.g., eetcequipment_trust, notessenior_notes).

Adding a New Company

# Get CIK from SEC EDGAR, then run complete pipeline:
python scripts/extract_iterative.py --ticker XXXX --cik 0001234567 --save-db

This runs all 11 steps: SEC download → core extraction with QA → save to DB → document sections → document linking → amount backfill → TTM financials → ownership hierarchy → guarantees → collateral → metrics → QC checks.

Options: --core-only, --skip-financials, --skip-enrichment, --threshold 90, --force, --all, --resume, --limit N

Modular execution with --step: Run individual steps for existing companies:

python scripts/extract_iterative.py --ticker AAL --step financials  # Re-extract financials
python scripts/extract_iterative.py --ticker AAL --step cache       # Refresh cache
python scripts/extract_iterative.py --ticker AAL --step metrics     # Update metrics

Available steps: core, financials, hierarchy, guarantees, collateral, documents, amounts, covenants, metrics, finnhub, pricing, cache, market_data.

Idempotent: Tracks extraction status in company_cache.extraction_status (JSONB). Status values: success, no_data (won't retry unless --force), error (will retry). ~2-5 min per company, ~$0.03-0.08.

Individual Service CLIs

Each extraction service has its own CLI for debugging:

python -m app.services.section_extraction --ticker CHTR [--all] [--force]
python -m app.services.document_linking --ticker CHTR [--all --heuristic]
python -m app.services.financial_extraction --ticker CHTR --ttm --save-db
python -m app.services.hierarchy_extraction --ticker CHTR [--all]
python -m app.services.guarantee_extraction --ticker CHTR [--all]
python -m app.services.collateral_extraction --ticker CHTR [--all]
python -m app.services.covenant_extraction --ticker CHTR [--all] [--force]
python -m app.services.metrics --ticker CHTR [--all --dry-run]
python scripts/qc_master.py --ticker CHTR --verbose
python scripts/update_pricing.py --ticker CHTR

Bond Pricing & Market Data

TRACE pricing via Finnhub/FINRA. See docs/BOND_PRICING.md for detailed data flow, tables, and scripts.

Key points:

  • Finnhub requires ISIN, not CUSIP. Convert: US + CUSIP + check digit.
  • 3,064 bonds with TRACE pricing. All estimated/synthetic removed.
  • Historical TRACE fallback when Finnhub returns no current data.
  • Tables: bond_pricing (current), bond_pricing_history (daily snapshots), treasury_yield_history

CDS Spread Proxy (bond spread-to-treasury):

  • Derives credit spreads from TRACE bond pricing data — economically equivalent to CDS spreads (CDS-bond basis ±20 bps for IG).
  • Methodology: select fixed-rate senior unsecured bonds → group by remaining maturity into tenor buckets (1Y/3Y/5Y/7Y/10Y) → report median spread-to-treasury.
  • Coverage: ~111 companies (requires >= 2 TRACE-priced bonds per tenor bucket), 598 trading days (2023-03-06 onward).
  • Data quality filters: (1) price >= $60 (excludes bad TRACE prints), (2) price_source != "estimated", (3) remaining maturity >= 1 year, (4) spread sanity cap at 5000 bps, (5) spread range check for groups <= 3 bonds (>300 bps range → skip), (6) 3-sigma outlier removal for groups >= 4 bonds, (7) negative spreads allowed down to -200 bps (premium IG bonds).
  • Tables: cds_spread_history (daily spreads), cds_entity_mapping (DTCC name → company mapping).
  • DTCC SDR infrastructure also built but entity names are anonymized in CFTC public data.
  • CLI: scripts/ingest_cds_spreads.py (--proxy-backfill, --proxy-daily, --stats, --seed-mapping).

APScheduler Jobs (US/Eastern timezone):

  • 7:30 AM: Check for new SEC filings (EDGAR polling)
  • 11:00 AM: Refresh current bond prices
  • 1:00 PM: Check for new SEC filings
  • 3:00 PM: Refresh current bond prices
  • 5:00 PM: Refresh market data (market cap, shares outstanding, share price from Yahoo Finance)
  • 6:00 PM: Refresh treasury yields
  • 6:30 PM: Refresh FRED credit spread indices (13 ICE BofA OAS series)
  • 9:00 PM: Refresh prices + save daily snapshot to bond_pricing_history
  • 9:30 PM: Compute daily CDS spread proxy from current bond pricing
  • 1st Sunday of month, 2:00 AM: Refresh NRSRO credit ratings (Fitch + Moody's CSVs from ratingshistory.info)
  • Every 15 min: Check and send alerts

Testing

5 test layers. See tests/ directory and docs/EVAL_FRAMEWORK.md.

# Quick: Unit tests only
python -m pytest tests/unit -v -s

# All pytest tests
python -m pytest tests/ -v -s

# Unified runner
python scripts/run_all_tests.py [--quick|--unit|--api|--qc|--json]

# API evaluation
python scripts/run_evals.py [--primitive companies|--update-baseline|--json]

# Data quality
python scripts/qc_master.py [--category integrity]
python scripts/qc_financials.py --verbose

Test counts: Unit (73), API Contract (72+), Eval Suite (242 across 20 files), Edge Cases (50+), QC (30+). Target accuracy: 95%+.

Environment Variables

Variable Required Purpose
DATABASE_URL Yes PostgreSQL (use sslmode=require for Neon)
REDIS_URL Optional Redis cache (Upstash, use rediss:// for TLS)
ANTHROPIC_API_KEY Yes Claude for escalation
GEMINI_API_KEY Recommended Gemini for extraction
SEC_API_KEY Recommended SEC-API.io for filing retrieval
FINNHUB_API_KEY Optional Finnhub for bond pricing
FRED_API_KEY Optional FRED for credit spread indices (free)
SENTRY_DSN Optional Sentry error tracking
SLACK_WEBHOOK_URL Optional Slack alerts
POSTHOG_API_KEY Optional PostHog analytics

Deployment

Railway: railway.json, Dockerfile, health check at /v1/ping or /v1/health.

Local: uvicorn app.main:app --reload

Migrations: alembic upgrade head / alembic revision -m "description" (001 through 031)

Common Issues

Issue Solution
LLM returns debt totals Prompt requires individual instruments
Entity name mismatch normalize_name() handles case/punctuation
Large filing truncation extract_debt_sections() extracts relevant portions
JSON parse errors parse_json_robust() fixes common issues
"I/O operation on closed file" Duplicate sys.stdout wrapping — only wrap in ONE place. If importing script_utils, don't wrap manually.
"Can't reconnect until invalid transaction is rolled back" Neon idle connection drop — use fresh session per company (see pattern below)
Bank EBITDA shows 0 or NULL Banks use PPNR not EBITDA — check is_financial_institution flag
Instrument totals mismatch Check source SEC filing scale first. Banks include deposits in total_debt.

Neon Serverless Connection Pattern

Neon drops idle connections after ~10s. For batch scripts with LLM calls, use fresh session per company:

# Wrong: single session goes idle during LLM calls
async with get_db_session() as session:
    for company in companies:
        result = await slow_operation(session, company)  # FAILS

# Right: fresh session per company
async with get_db_session() as session:
    company_info = [(c.id, c.ticker) for c in await get_companies(session)]

for company_id, ticker in company_info:
    async with get_db_session() as session:  # Fresh per company
        result = await slow_operation(session, company)

Script Utilities (scripts/script_utils.py)

All scripts should use script_utils.py for consistent DB handling and Windows compatibility:

from script_utils import get_db_session, print_header, run_async

async def main():
    print_header("SCRIPT NAME")
    async with get_db_session() as db:
        pass  # Do work

if __name__ == "__main__":
    run_async(main())

Key exports: get_db_session, get_all_companies, get_company_by_ticker, create_base_parser, create_fix_parser, process_companies, run_async, print_header/print_summary/print_progress.

Windows note: script_utils handles stdout UTF-8 encoding and WindowsSelectorEventLoopPolicy automatically. Scripts importing from it must NOT also wrap sys.stdout manually.

Cost

Stage Cost
Gemini extraction ~$0.008
QA checks (5x) ~$0.006
Fix iteration ~$0.01
Claude escalation ~$0.15-0.50

Target: <$0.03 per company with 85%+ QA score.

Reference Documentation

Document Content
docs/BOND_PRICING.md Finnhub integration, data flow, tables, scripts, scheduler details
docs/DEBT_COVERAGE_HISTORY.md Phases 1-9 backfill history, key learnings, structural gap categories
docs/OWNERSHIP_EXTRACTION.md Ownership hierarchy extraction pipeline, Exhibit 21 + prospectus + GLEIF
docs/AGENT_EXAMPLES.md Agent user journey, API usage examples, document search patterns
docs/EVAL_FRAMEWORK.md Eval framework details and ground truth management
docs/api/PRIMITIVES_API_SPEC.md Full Primitives API specification
docs/operations/QA_TROUBLESHOOTING.md QA debugging guides
medici/CLAUDE.md RAG knowledge base management