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
+8 -1
View File
@@ -17,6 +17,7 @@ from us2kr.deliver import build_deliverers
from us2kr.logging_setup import setup_logging
from us2kr.market_time import MarketClock
from us2kr.pipeline import SKIP_ALREADY_SENT, SKIP_KRX_CLOSED, delivery_failed, run_briefing
from us2kr.weekly import run_weekly
log = logging.getLogger(__name__)
@@ -77,6 +78,11 @@ def build_parser() -> argparse.ArgumentParser:
once.add_argument("--dry-run", action="store_true", help="print markdown, do not save or deliver")
once.add_argument("--force", action="store_true", help="re-run (resend) even if already sent")
once.add_argument("--no-llm", action="store_true", help="skip the LLM (tables only)")
once.add_argument(
"--weekly",
action="store_true",
help="build the Monday weekly recap instead (--date must be a Monday KST; default: latest Monday)",
)
sub.add_parser("daemon", help="run the resident scheduler")
sub.add_parser("next-run", help="show the next scheduled briefing")
@@ -107,7 +113,8 @@ def build_parser() -> argparse.ArgumentParser:
# ---------------------------------------------------------------------------- commands
def cmd_run_once(args: argparse.Namespace, settings: Settings) -> int:
result = run_briefing(args.date, settings, dry_run=args.dry_run, force=args.force, no_llm=args.no_llm)
run = run_weekly if args.weekly else run_briefing
result = run(args.date, settings, dry_run=args.dry_run, force=args.force, no_llm=args.no_llm)
if result.error in (SKIP_ALREADY_SENT, SKIP_KRX_CLOSED):
print(f"skipped: {result.error} (KRX date {result.payload.krx_date})", file=sys.stderr)
return EXIT_SKIPPED