backend: dedupe shared logic (indicator_summary_job, CHAT_REFERENCE_LINE, call_openrouter alias)

- indicator_summary_job.py imported its own copies of _month_spend and
  _latest_quotes_by_group; _market_context.py already exposes these.
  Switched to the canonical imports. Also fixed _market_context's
  latest_quotes_by_group to actually filter null prices (it claimed to
  in its docstring but lacked the WHERE clause).
- api.py duplicated REFERENCE_LINE as CHAT_REFERENCE_LINE — same string,
  two sources of truth. Now imports REFERENCE_LINE.
- Chat endpoint used the deprecated `call_openrouter` alias and passed
  an explicit `model=` that bypassed the provider chain. Switched to
  `call_llm` with default model selection, then removed the alias.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-27 19:30:11 +02:00
parent a2bcb2c053
commit b47c45e218
4 changed files with 10 additions and 48 deletions

View file

@ -42,6 +42,7 @@ async def latest_quotes_by_group(session) -> dict[str, list[dict]]:
& (Quote.symbol == sub.c.symbol)
& (Quote.fetched_at == sub.c.mx),
)
.where(Quote.price.is_not(None))
.order_by(Quote.group_name, Quote.symbol)
)
rows = (await session.execute(stmt)).scalars().all()

View file

@ -5,7 +5,6 @@ from __future__ import annotations
import asyncio
import re
from collections import defaultdict
import httpx
from sqlalchemy import desc, func, select
@ -13,7 +12,8 @@ from sqlalchemy import desc, func, select
from app.config import get_settings, load_groups
from app.db import utcnow
from app.jobs._helpers import job_lifecycle, log
from app.models import AICall, IndicatorSummary, JobRun, Quote
from app.jobs._market_context import latest_quotes_by_group, month_spend
from app.models import AICall, IndicatorSummary, JobRun
from app.services.cadence import DEFAULT_POLICY
from app.services.openrouter import (
PROMPT_VERSION,
@ -136,40 +136,6 @@ def clean_summary(text: str) -> str:
return out
async def _latest_quotes_by_group(session) -> dict[str, list[dict]]:
"""Latest non-null quote per (group, symbol). Drops error rows."""
sub = (
select(Quote.group_name, Quote.symbol,
func.max(Quote.fetched_at).label("mx"))
.group_by(Quote.group_name, Quote.symbol)
.subquery()
)
rows = (await session.execute(
select(Quote).join(
sub,
(Quote.group_name == sub.c.group_name)
& (Quote.symbol == sub.c.symbol)
& (Quote.fetched_at == sub.c.mx),
).where(Quote.price.is_not(None))
.order_by(Quote.group_name, Quote.symbol)
)).scalars().all()
by_group: dict[str, list[dict]] = defaultdict(list)
for q in rows:
by_group[q.group_name].append({
"symbol": q.symbol, "label": q.label,
"price": q.price, "currency": q.currency,
"as_of": q.as_of, "changes": q.changes,
})
return by_group
async def _month_spend(session) -> float:
total = (await session.execute(
select(func.coalesce(func.sum(AICall.cost_usd), 0.0))
.where(AICall.called_at >= month_start())
)).scalar()
return float(total or 0.0)
async def _generate_one(
session, client: httpx.AsyncClient, group: str, quotes: list[dict],
@ -254,13 +220,13 @@ async def run() -> None:
jr.error = reason
return
spent = await _month_spend(session)
spent = await month_spend(session)
if spent >= s.OPENROUTER_MONTHLY_CAP_USD:
jr.status = "skipped"
jr.error = f"monthly cap reached (${spent:.2f})"
return
groups = await _latest_quotes_by_group(session)
groups = await latest_quotes_by_group(session)
# Only summarise groups currently configured in TOML — drops stale
# group names (e.g. an old "pie" before T212 sourcing) that still have
# quotes in the table but no UI presence.