feat: trading feedback engine — periodic performance reports with AI summarization
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
- Migration 038: trading_reports table + report-summarizer agent seed
- 6 reporting modules: models, collector, sections, validator, summarizer, generator
- API endpoints: GET /api/reports (paginated, filterable), GET /api/reports/{id}
- Frontend hooks: useReports, useReport with TanStack Query
- Scheduler: daily (after 16:30 ET) and weekly (Saturday) report triggers
- Redis queue consumer for async report generation with retry/dedup
- 5 property-based tests (chunking, serialization, validation, accuracy, deltas)
- 109 unit/integration tests across all modules
- 6 frontend hook tests with MSW mocks
This commit is contained in:
@@ -4107,3 +4107,112 @@ async def get_validation_attribution_layers(
|
||||
"lookback": lookback,
|
||||
"horizon": horizon,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Trading Reports
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.get("/api/reports")
|
||||
async def list_reports(
|
||||
report_type: Optional[str] = None,
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
limit: int = Query(default=20, le=100),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
):
|
||||
"""Paginated list of trading reports with optional filtering.
|
||||
|
||||
Query params:
|
||||
- report_type: 'daily' or 'weekly'
|
||||
- start_date: ISO date (YYYY-MM-DD) — filter period_start >= this
|
||||
- end_date: ISO date (YYYY-MM-DD) — filter period_end <= this
|
||||
- limit: max results (default 20, max 100)
|
||||
- offset: pagination offset (default 0)
|
||||
|
||||
Requirements: 5.4, 5.5, 5.6
|
||||
"""
|
||||
conditions: list[str] = []
|
||||
params: list[Any] = []
|
||||
idx = 1
|
||||
|
||||
if report_type:
|
||||
if report_type not in ("daily", "weekly"):
|
||||
raise HTTPException(400, "report_type must be 'daily' or 'weekly'")
|
||||
conditions.append(f"report_type = ${idx}")
|
||||
params.append(report_type)
|
||||
idx += 1
|
||||
|
||||
if start_date:
|
||||
try:
|
||||
from datetime import date as _date
|
||||
_date.fromisoformat(start_date)
|
||||
except ValueError:
|
||||
raise HTTPException(400, "start_date must be YYYY-MM-DD")
|
||||
conditions.append(f"period_start >= ${idx}::date")
|
||||
params.append(start_date)
|
||||
idx += 1
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
from datetime import date as _date
|
||||
_date.fromisoformat(end_date)
|
||||
except ValueError:
|
||||
raise HTTPException(400, "end_date must be YYYY-MM-DD")
|
||||
conditions.append(f"period_end <= ${idx}::date")
|
||||
params.append(end_date)
|
||||
idx += 1
|
||||
|
||||
where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
|
||||
query = f"""
|
||||
SELECT id, report_type, period_start, period_end,
|
||||
validation_status, generated_at
|
||||
FROM trading_reports
|
||||
{where}
|
||||
ORDER BY generated_at DESC
|
||||
LIMIT ${idx} OFFSET ${idx + 1}
|
||||
"""
|
||||
params.extend([limit, offset])
|
||||
|
||||
rows = await pool.fetch(query, *params)
|
||||
return [
|
||||
{
|
||||
"id": str(r["id"]),
|
||||
"report_type": r["report_type"],
|
||||
"period_start": r["period_start"].isoformat(),
|
||||
"period_end": r["period_end"].isoformat(),
|
||||
"validation_status": r["validation_status"],
|
||||
"generated_at": r["generated_at"].isoformat(),
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
|
||||
|
||||
@app.get("/api/reports/{report_id}")
|
||||
async def get_report(report_id: str):
|
||||
"""Fetch a single report including full report_data JSONB.
|
||||
|
||||
Requirements: 5.4, 5.5
|
||||
"""
|
||||
row = await pool.fetchrow(
|
||||
"""SELECT id, report_type, period_start, period_end,
|
||||
report_data, validation_status, generated_at, created_at
|
||||
FROM trading_reports
|
||||
WHERE id = $1::uuid""",
|
||||
report_id,
|
||||
)
|
||||
if row is None:
|
||||
raise HTTPException(404, "Report not found")
|
||||
|
||||
return {
|
||||
"id": str(row["id"]),
|
||||
"report_type": row["report_type"],
|
||||
"period_start": row["period_start"].isoformat(),
|
||||
"period_end": row["period_end"].isoformat(),
|
||||
"report_data": json.loads(row["report_data"]) if isinstance(row["report_data"], str) else row["report_data"],
|
||||
"validation_status": row["validation_status"],
|
||||
"generated_at": row["generated_at"].isoformat(),
|
||||
"created_at": row["created_at"].isoformat(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user