From 7397334bfaa5d3a293cb4f6c38d1a5e750095481 Mon Sep 17 00:00:00 2001 From: ilia Date: Sun, 12 Jul 2026 11:24:38 -0400 Subject: [PATCH] Refresh handoff next-steps and harden daily report delivery. --- docs/HANDOFF-2026-05-27.md | 28 ++++++++++------------ scripts/send_daily_report.py | 10 +++++++- src/pote/reporting/report_generator.py | 33 ++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/docs/HANDOFF-2026-05-27.md b/docs/HANDOFF-2026-05-27.md index 2d59e4c..9e7d571 100644 --- a/docs/HANDOFF-2026-05-27.md +++ b/docs/HANDOFF-2026-05-27.md @@ -111,24 +111,22 @@ make beszel-install-agents BESZEL_ONLY=pote-236 # if agent not yet installed --- -## Next steps (priority order) +**Vikunja:** [todo.levkin.ca → Business → POTE](https://todo.levkin.ca) (`POTE`) -### Soon +## Open tasks (source of truth) -- [ ] **Sync server to `main`** — `make deploy-pote` from laptop (server was rsync’d pre-merge; safe to refresh). -- [ ] **Confirm first cron email** — tomorrow 07:00 server time; fix via `crontab -e` / `.env` `REPORT_RECIPIENTS` if needed. -- [ ] **Beszel agent** — `make beszel-install-agents BESZEL_ONLY=pote-236` if not done. -- [ ] **Proxmox backup** — schedule backup for LXC 236 on pve10. -- [ ] **Merge ansible branch** — homelab inventory/deploy scripts if still on `feature/outline-setup-api`. +| P | Task | Owner | Status | +|---|------|-------|--------| +| **P1** | `make deploy-pote` — sync server to `main` | @agent | ⏳ | +| **P1** | Confirm 07:00 cron email delivered | @agent | ⏳ | +| **P1** | Proxmox backup schedule for LXC **236** on pve10 | @you | ⏳ | +| **P3** | Dedicated `pote@levkine.ca` mailbox (vs shared `alerts@`) | @you | optional / low | +| **P3** | Git deploy on LXC (replace rsync-only) | @agent | optional / low | +| **P3** | Kuma LAN health monitor | @agent | optional / low | +| **P3** | Mattermost / webhook alerts (email only today) | @agent | optional / low | +| **P3** | Full history ingest (second data source beyond kadoa cap) | @agent | optional / low | -### Optional enhancements - -- [ ] **Dedicated mailbox** — e.g. `pote@levkine.ca` in Mailcow instead of shared `alerts@` ([smtp-inventory.md](https://git.levkin.ca/ilia/ansible/src/branch/master/docs/guides/smtp-inventory.md) in ansible repo). -- [ ] **Git deploy on LXC** — clone `gitea@git.levkin.ca:ilia/POTE.git` + deploy key; replace rsync-only workflow. -- [ ] **UniFi DNS** — `mail.levkine.ca` → `10.0.10.132` so `.env` can use hostname instead of raw IP ([unifi-static-dhcp.md](https://git.levkin.ca/ilia/ansible/src/branch/master/docs/guides/unifi-static-dhcp.md)). -- [ ] **Full history ingest** — kadoa JSON is capped; add second source (capitol-api, Clerk scrape) for backfill. -- [ ] **Kuma monitor** — LAN HTTP health script or SSH check (no public URL). -- [ ] **Mattermost / webhook alerts** — not implemented; email only today. +**Closed 2026-07-11:** Beszel agent on pote-236 (`beszel-agent` active). ### Not planned (unless you want them) diff --git a/scripts/send_daily_report.py b/scripts/send_daily_report.py index 15c7994..51bbae5 100644 --- a/scripts/send_daily_report.py +++ b/scripts/send_daily_report.py @@ -43,6 +43,12 @@ def main(): action="store_true", help="Test SMTP connection before sending", ) + parser.add_argument( + "--lookback-days", + type=int, + default=1, + help="Include trades filed in the last N days ending on the report date (default: 1)", + ) parser.add_argument( "--save-to-file", help="Also save report to this file path", @@ -81,7 +87,9 @@ def main(): logger.info(f"Generating daily report for {report_date or date.today()}...") with get_session() as session: generator = ReportGenerator(session) - report_data = generator.generate_daily_summary(report_date) + report_data = generator.generate_daily_summary( + report_date, lookback_days=args.lookback_days + ) # Format as text and HTML text_body = generator.format_as_text(report_data, "daily") diff --git a/src/pote/reporting/report_generator.py b/src/pote/reporting/report_generator.py index 698e5fe..096f3cc 100644 --- a/src/pote/reporting/report_generator.py +++ b/src/pote/reporting/report_generator.py @@ -27,13 +27,15 @@ class ReportGenerator: self.detector = PatternDetector(session) def generate_daily_summary( - self, report_date: Optional[date] = None + self, report_date: Optional[date] = None, *, lookback_days: int = 1 ) -> Dict[str, Any]: """ Generate a daily summary report. Args: report_date: Date to generate report for (defaults to today) + lookback_days: Include trades filed in the last N days ending on report_date + (defaults to 1, meaning only filings on report_date). Returns: Dictionary containing report data @@ -41,12 +43,19 @@ class ReportGenerator: if report_date is None: report_date = date.today() + if lookback_days < 1: + raise ValueError("lookback_days must be >= 1") + start_of_day = datetime.combine(report_date, datetime.min.time()) end_of_day = datetime.combine(report_date, datetime.max.time()) - # Count new trades filed today + filing_start_date = report_date - timedelta(days=lookback_days - 1) + + # Trades filed within the lookback window (inclusive) new_trades = ( - self.session.query(Trade).filter(Trade.filing_date == report_date).all() + self.session.query(Trade) + .filter(Trade.filing_date >= filing_start_date, Trade.filing_date <= report_date) + .all() ) # Count market alerts today @@ -71,6 +80,8 @@ class ReportGenerator: return { "date": report_date, + "filing_start_date": filing_start_date, + "lookback_days": lookback_days, "new_trades_count": len(new_trades), "new_trades": [ { @@ -173,13 +184,20 @@ class ReportGenerator: def _format_daily_text(self, data: Dict[str, Any]) -> str: """Format daily report as plain text.""" + if data.get("lookback_days", 1) > 1: + trades_label = ( + f" • Trades Filed (last {data['lookback_days']} days): {data['new_trades_count']}" + ) + else: + trades_label = f" • New Trades Filed: {data['new_trades_count']}" + lines = [ "=" * 70, f"POTE DAILY REPORT - {data['date']}", "=" * 70, "", "📊 SUMMARY", - f" • New Trades Filed: {data['new_trades_count']}", + trades_label, f" • Market Alerts: {data['market_alerts_count']}", f" • Critical Alerts (≥7 severity): {data['critical_alerts_count']}", f" • Suspicious Timing Trades: {data['suspicious_trades_count']}", @@ -287,6 +305,11 @@ class ReportGenerator: def _format_daily_html(self, data: Dict[str, Any]) -> str: """Format daily report as HTML.""" + if data.get("lookback_days", 1) > 1: + new_trades_label = f"Trades Filed (last {data['lookback_days']} days):" + else: + new_trades_label = "New Trades:" + html = f""" @@ -307,7 +330,7 @@ class ReportGenerator:

📊 Summary

-
New Trades: {data['new_trades_count']}
+
{new_trades_label} {data['new_trades_count']}
Market Alerts: {data['market_alerts_count']}
Critical Alerts: {data['critical_alerts_count']}
Suspicious Trades: {data['suspicious_trades_count']}