Add a Monday-morning weekly recap briefing

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>
This commit is contained in:
jsahn
2026-09-12 23:52:31 +09:00
co-authored by Claude Fable 5
parent 1e6272da74
commit f5fa7ec70f
22 changed files with 995 additions and 33 deletions
+35 -10
View File
@@ -17,16 +17,32 @@ def build_provider(settings: Settings) -> LLMProvider:
return MiniMaxProvider(settings)
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)
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})
log.info("summary produced by %s (%d data gaps)", model_used, len(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
@@ -37,14 +53,21 @@ def _consistent(verification: Verification) -> Verification:
def verify_and_revise(
payload: MarketPayload, report: Report, provider: LLMProvider
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.
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.
`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)
raw_verification, model = provider.verify(payload, report, data_message)
verification = _consistent(raw_verification)
log.info(
"verification by %s: ok=%s issues=%d (major=%s)",
@@ -59,9 +82,11 @@ def verify_and_revise(
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)
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)
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)