COTexplorer/app/api/main.py
Greg 90c2ae3f35 Expose disaggregated COT data in the UI (wheat focus)
The disagg dataset (2019–2026, 468 markets) was in the DB but invisible.
This wires it into every layer of the app:

Backend:
- models.py: add has_disagg to CommodityMeta; add DisaggPositionPoint,
  DisaggHistoryResponse, DisaggScreenerRow models
- commodities.py: join disagg_reports to populate has_disagg flag and
  correct first/last dates; HAVING filter removes markets with no data
- disagg.py (new): /api/disagg/{code}/history, /api/disagg/screener,
  /api/disagg/{code}/net-position-percentile, /api/disagg/compare
- main.py: register disagg router

Frontend:
- Metric selector shows Disaggregated optgroup (Managed Money, Prod/Merchant,
  Swap Dealer, Other Rept) when market has has_disagg=true, hides Legacy group
- Detail view auto-switches to disagg endpoint and defaults to m_money_net
  for disagg markets; shows green "Disaggregated" badge
- Screener always uses disagg endpoint (Managed Money percentile rank)
- Compare uses /api/disagg/compare for disagg metrics
- style.css: add .badge-disagg green variant

Result: wheat markets (SRW, HRW, HRSpring, Black Sea) now show 7 years of
disaggregated positioning data with Managed Money as the default metric.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:22:16 +01:00

40 lines
1.2 KiB
Python

from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pathlib import Path
from app.api.routes import commodities, positions, analytics, reports, disagg
app = FastAPI(
title="CFTC COT Explorer",
description="Explore CFTC Commitments of Traders positioning data",
version="1.0.0",
)
app.include_router(commodities.router)
app.include_router(positions.router)
app.include_router(analytics.router)
app.include_router(reports.router)
app.include_router(disagg.router)
FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend"
if FRONTEND_DIR.exists():
app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="static")
@app.get("/", include_in_schema=False)
async def root():
index = FRONTEND_DIR / "index.html"
if index.exists():
return FileResponse(str(index))
return {"message": "CFTC COT Explorer API", "docs": "/docs"}
@app.get("/health", include_in_schema=False)
async def health():
from app.db import get_db
with get_db() as conn:
count = conn.execute("SELECT COUNT(*) FROM reports").fetchone()[0]
return {"status": "ok", "reports": count}