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:
parent
a2bcb2c053
commit
b47c45e218
4 changed files with 10 additions and 48 deletions
|
|
@ -42,6 +42,7 @@ async def latest_quotes_by_group(session) -> dict[str, list[dict]]:
|
||||||
& (Quote.symbol == sub.c.symbol)
|
& (Quote.symbol == sub.c.symbol)
|
||||||
& (Quote.fetched_at == sub.c.mx),
|
& (Quote.fetched_at == sub.c.mx),
|
||||||
)
|
)
|
||||||
|
.where(Quote.price.is_not(None))
|
||||||
.order_by(Quote.group_name, Quote.symbol)
|
.order_by(Quote.group_name, Quote.symbol)
|
||||||
)
|
)
|
||||||
rows = (await session.execute(stmt)).scalars().all()
|
rows = (await session.execute(stmt)).scalars().all()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from sqlalchemy import desc, func, select
|
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.config import get_settings, load_groups
|
||||||
from app.db import utcnow
|
from app.db import utcnow
|
||||||
from app.jobs._helpers import job_lifecycle, log
|
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.cadence import DEFAULT_POLICY
|
||||||
from app.services.openrouter import (
|
from app.services.openrouter import (
|
||||||
PROMPT_VERSION,
|
PROMPT_VERSION,
|
||||||
|
|
@ -136,40 +136,6 @@ def clean_summary(text: str) -> str:
|
||||||
return out
|
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(
|
async def _generate_one(
|
||||||
session, client: httpx.AsyncClient, group: str, quotes: list[dict],
|
session, client: httpx.AsyncClient, group: str, quotes: list[dict],
|
||||||
|
|
@ -254,13 +220,13 @@ async def run() -> None:
|
||||||
jr.error = reason
|
jr.error = reason
|
||||||
return
|
return
|
||||||
|
|
||||||
spent = await _month_spend(session)
|
spent = await month_spend(session)
|
||||||
if spent >= s.OPENROUTER_MONTHLY_CAP_USD:
|
if spent >= s.OPENROUTER_MONTHLY_CAP_USD:
|
||||||
jr.status = "skipped"
|
jr.status = "skipped"
|
||||||
jr.error = f"monthly cap reached (${spent:.2f})"
|
jr.error = f"monthly cap reached (${spent:.2f})"
|
||||||
return
|
return
|
||||||
|
|
||||||
groups = await _latest_quotes_by_group(session)
|
groups = await latest_quotes_by_group(session)
|
||||||
# Only summarise groups currently configured in TOML — drops stale
|
# Only summarise groups currently configured in TOML — drops stale
|
||||||
# group names (e.g. an old "pie" before T212 sourcing) that still have
|
# group names (e.g. an old "pie" before T212 sourcing) that still have
|
||||||
# quotes in the table but no UI presence.
|
# quotes in the table but no UI presence.
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ from app.auth import require_token, maybe_current_user, CurrentUser
|
||||||
from app.services.i18n import ACTIVE_LANGUAGES
|
from app.services.i18n import ACTIVE_LANGUAGES
|
||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
from app.db import get_session, utcnow
|
from app.db import get_session, utcnow
|
||||||
|
from app.jobs._market_context import REFERENCE_LINE
|
||||||
from app.services.openrouter import (
|
from app.services.openrouter import (
|
||||||
PROMPT_VERSION,
|
PROMPT_VERSION,
|
||||||
build_chat_system_prompt,
|
build_chat_system_prompt,
|
||||||
call_openrouter,
|
call_llm,
|
||||||
month_start,
|
month_start,
|
||||||
)
|
)
|
||||||
from app.templates_env import templates
|
from app.templates_env import templates
|
||||||
|
|
@ -710,10 +711,7 @@ class ChatRequest(BaseModel):
|
||||||
messages: list[ChatMessage]
|
messages: list[ChatMessage]
|
||||||
|
|
||||||
|
|
||||||
CHAT_REFERENCE_LINE = (
|
|
||||||
"S&P 7,501 (ATH) · VIX 18.0 · US 10y 4.45% · HY OAS 279bps · "
|
|
||||||
"Brent $109/bbl · Gold $4,651/oz · CPI 3.8% YoY"
|
|
||||||
)
|
|
||||||
THESIS_KEYWORDS_FALLBACK = [
|
THESIS_KEYWORDS_FALLBACK = [
|
||||||
"hormuz", "iran", "opec", "brent", "wti", "crude", "oil",
|
"hormuz", "iran", "opec", "brent", "wti", "crude", "oil",
|
||||||
"china", "taiwan", "yuan", "fed", "inflation", "cpi", "yield",
|
"china", "taiwan", "yuan", "fed", "inflation", "cpi", "yield",
|
||||||
|
|
@ -822,7 +820,7 @@ async def chat(
|
||||||
log_generated_at=log_row.generated_at if log_row else None,
|
log_generated_at=log_row.generated_at if log_row else None,
|
||||||
quotes_by_group=quotes,
|
quotes_by_group=quotes,
|
||||||
headlines=headlines,
|
headlines=headlines,
|
||||||
reference_line=CHAT_REFERENCE_LINE,
|
reference_line=REFERENCE_LINE,
|
||||||
)
|
)
|
||||||
|
|
||||||
msgs = [{"role": "system", "content": system_prompt}]
|
msgs = [{"role": "system", "content": system_prompt}]
|
||||||
|
|
@ -831,7 +829,7 @@ async def chat(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||||
result = await call_openrouter(client, msgs, model=s.OPENROUTER_MODEL)
|
result = await call_llm(client, msgs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
session.add(AICall(
|
session.add(AICall(
|
||||||
model=s.OPENROUTER_MODEL, status="error", error=str(e)[:500],
|
model=s.OPENROUTER_MODEL, status="error", error=str(e)[:500],
|
||||||
|
|
|
||||||
|
|
@ -775,9 +775,6 @@ async def call_llm(
|
||||||
raise last_exc
|
raise last_exc
|
||||||
|
|
||||||
|
|
||||||
# Back-compat alias for any straggling import sites.
|
|
||||||
call_openrouter = call_llm
|
|
||||||
|
|
||||||
|
|
||||||
def month_window() -> tuple[datetime, datetime]:
|
def month_window() -> tuple[datetime, datetime]:
|
||||||
"""[start, now] in UTC for the current calendar month."""
|
"""[start, now] in UTC for the current calendar month."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue