No US session closes into Monday KST, so Mondays (default 07:00, cron in the daemon with restart catch-up) now publish a weekly review built entirely from stored data: the quotes table collapsed into week-over- week changes and the week's daily briefing headlines as the narrative. The recap is a BriefingResult kind="weekly" reusing MarketPayload, so the store, verification pass, all delivery channels (Blogger title "미국 주간 리뷰 <monday>") and the dashboard (주간 리뷰 badge) work unchanged. Manual runs via `run-once --weekly [--date <monday>]`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
"""Glue between the pipeline and the LLM layer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from us2kr.config import Settings
|
|
from us2kr.llm.base import LLMError, LLMProvider
|
|
from us2kr.llm.minimax import MiniMaxProvider
|
|
from us2kr.models import MarketPayload, Report, Verification
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def build_provider(settings: Settings) -> LLMProvider:
|
|
"""Construct the configured LLM provider (MiniMax only for now)."""
|
|
return MiniMaxProvider(settings)
|
|
|
|
|
|
def _merge_gaps(payload: MarketPayload, report: Report) -> Report:
|
|
"""Guarantee `report.data_gaps` includes every payload gap (deduplicated, payload first)."""
|
|
merged: list[str] = []
|
|
for gap in [*payload.data_gaps, *report.data_gaps]:
|
|
if gap and gap not in merged:
|
|
merged.append(gap)
|
|
if merged != list(report.data_gaps):
|
|
report = report.model_copy(update={"data_gaps": merged})
|
|
return report
|
|
|
|
|
|
def summarize_payload(payload: MarketPayload, provider: LLMProvider) -> tuple[Report, str]:
|
|
"""Summarise via the provider and guarantee `report.data_gaps` includes every payload gap."""
|
|
report, model_used = provider.summarize(payload)
|
|
report = _merge_gaps(payload, report)
|
|
log.info("summary produced by %s (%d data gaps)", model_used, len(report.data_gaps))
|
|
return report, model_used
|
|
|
|
|
|
def summarize_weekly_payload(
|
|
payload: MarketPayload, week_sessions: list, provider: LLMProvider
|
|
) -> tuple[Report, str]:
|
|
"""Weekly-recap variant of summarize_payload."""
|
|
report, model_used = provider.summarize_weekly(payload, week_sessions)
|
|
report = _merge_gaps(payload, report)
|
|
log.info("weekly summary produced by %s (%d data gaps)", model_used, len(report.data_gaps))
|
|
return report, model_used
|
|
|
|
|
|
def _consistent(verification: Verification) -> Verification:
|
|
"""Force `ok` to mean "no major issues" - verifier models are inconsistent about minor-only cases."""
|
|
ok = not verification.has_major
|
|
return verification if verification.ok == ok else verification.model_copy(update={"ok": ok})
|
|
|
|
|
|
def verify_and_revise(
|
|
payload: MarketPayload,
|
|
report: Report,
|
|
provider: LLMProvider,
|
|
*,
|
|
data_message: str | None = None,
|
|
system_prompt: str | None = None,
|
|
) -> tuple[Report, Verification]:
|
|
"""Fact-check the report; on major issues ask for one revision and re-check it.
|
|
|
|
`data_message`/`system_prompt` override the default daily-briefing serialisation and author persona
|
|
(used by the weekly recap). Returns (possibly revised report, verification of the returned report).
|
|
Raises LLMError only when the *initial* verification cannot be produced; a failed revision keeps
|
|
the original report.
|
|
"""
|
|
raw_verification, model = provider.verify(payload, report, data_message)
|
|
verification = _consistent(raw_verification)
|
|
log.info(
|
|
"verification by %s: ok=%s issues=%d (major=%s)",
|
|
model,
|
|
verification.ok,
|
|
len(verification.issues),
|
|
verification.has_major,
|
|
)
|
|
if not verification.has_major:
|
|
return report, verification
|
|
|
|
for issue in verification.issues:
|
|
log.warning("verification issue [%s] %s -> %s", issue.severity, issue.claim, issue.problem)
|
|
try:
|
|
revised, model = provider.revise(
|
|
payload, report, verification.issues, system_prompt=system_prompt, data_message=data_message
|
|
)
|
|
log.info("report revised by %s after verification issues", model)
|
|
re_verification, model = provider.verify(payload, revised, data_message)
|
|
re_verification = _consistent(re_verification)
|
|
log.info(
|
|
"re-verification by %s: ok=%s issues=%d", model, re_verification.ok, len(re_verification.issues)
|
|
)
|
|
return revised, re_verification
|
|
except LLMError as exc:
|
|
log.warning("revision failed; keeping the original report and verdict: %s", exc)
|
|
return report, verification
|