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
+22
View File
@@ -146,6 +146,17 @@ class Store:
)
return list(cur.fetchall())
def load_quotes_between(self, start: date, end: date) -> list[sqlite3.Row]:
"""Quote rows for every stored session in [start, end], ordered by (symbol, session_date)."""
cur = self._conn.execute(
"""
SELECT * FROM quotes WHERE session_date >= ? AND session_date <= ?
ORDER BY symbol, session_date
""",
(start.isoformat(), end.isoformat()),
)
return list(cur.fetchall())
def _history(self, symbol: str, before: date, n: int) -> list[_Row]:
"""Up to ``n`` stored rows for ``symbol`` strictly before ``before``, oldest first."""
cur = self._conn.execute(
@@ -250,6 +261,17 @@ class Store:
log.info("saved report id=%d krx_date=%s kind=%s degraded=%s", report_id, krx, kind, result.degraded)
return report_id
def reports_between(self, start: date, end: date, kind: str = "briefing") -> list[sqlite3.Row]:
"""(krx_date, session_date, result_json) rows with start <= krx_date <= end, oldest first."""
cur = self._conn.execute(
"""
SELECT krx_date, session_date, result_json FROM reports
WHERE kind = ? AND krx_date >= ? AND krx_date <= ? ORDER BY krx_date
""",
(kind, start.isoformat(), end.isoformat()),
)
return list(cur.fetchall())
def mark_sent(self, report_id: int, channel: str, receipt: str) -> None:
with self._conn:
self._conn.execute(