Context for AI assistants working on the DebtStack.ai codebase.
IMPORTANT: Before starting work, read
WORKPLAN.mdfor current priorities, active tasks, and session history. Update the session log at the end of each session.
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 passingOwnership enrichment✅ (2026-02-24) — 13,113 entities with known parents (32% of non-root), 13,077 ownership linksDebt 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.aiChat assistant✅ → Migrating: Gemini 2.5 Pro → Claude Sonnet 4.6 + 8 skills (19 tools, MCP server, prompt caching)Analytics & alerting✅ — Vercel, PostHog, Sentry, SlackQC✅ — 0 critical, 0 errorsCDS 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:
- Medici model migration — Gemini → Claude Sonnet 4.6 + skills architecture (see
medici/ROADMAP.md)
Remaining:
- Grow usage / marketing
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).
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
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
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) |
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 viaissuer_idguarantees: Links debt to guarantor entitiescollateral: Asset-backed collateral for secured debt (type, description, priority)covenants: Structured covenant data from credit agreements/indenturescompany_financials: Quarterly financial statements (amounts in cents) — see Industry-Specific Metrics belowbond_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 withrating_typeand optionaldebt_instrument_idFK. Sources: LLM extraction from SEC filings + NRSRO 17g-7(b) disclosurescds_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 matchingfred_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 cycleusage_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)
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.
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 |
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).
- Amounts in CENTS:
$1 billion = 100_000_000_000 cents - Rates in BASIS POINTS:
8.50% = 850 bps - Individual instruments: Extract each bond separately, not totals
- Name normalization: Case-insensitive, punctuation-normalized via
normalize_name() - Robust JSON parsing:
parse_json_robust()handles LLM output issues - Estimated data must be flagged: Always mark estimated/inferred data (e.g.,
issue_date_estimated: true) - ALWAYS detect scale from source document: Never assume scale — extract from SEC filing header.
detect_filing_scale()infinancial_extraction.py. Never blindly apply scale fixes — always verify against the source SEC filing first. - 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_datecomparison. ~$0.008 per 10-Q, ~$0.04 per 10-K.
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., eetc → equipment_trust, notes → senior_notes).
# Get CIK from SEC EDGAR, then run complete pipeline:
python scripts/extract_iterative.py --ticker XXXX --cik 0001234567 --save-dbThis 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 metricsAvailable 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.
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 CHTRTRACE 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
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 --verboseTest counts: Unit (73), API Contract (72+), Eval Suite (242 across 20 files), Edge Cases (50+), QC (30+). Target accuracy: 95%+.
| 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 |
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)
| 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 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)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.
| 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.
| 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 |