Refresh handoff next-steps and harden daily report delivery.
CI / skip-ci-check (pull_request) Successful in 9s
CI / secret-scan (pull_request) Successful in 8s
CI / python-ci (pull_request) Successful in 36s

This commit is contained in:
2026-07-12 11:24:50 -04:00
parent be74902234
commit 7397334bfa
3 changed files with 50 additions and 21 deletions
+13 -15
View File
@@ -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 rsyncd 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)
+9 -1
View File
@@ -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")
+28 -5
View File
@@ -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"""
<html>
<head>
@@ -307,7 +330,7 @@ class ReportGenerator:
<div class="summary">
<h2>📊 Summary</h2>
<div class="stat"><strong>New Trades:</strong> {data['new_trades_count']}</div>
<div class="stat"><strong>{new_trades_label}</strong> {data['new_trades_count']}</div>
<div class="stat"><strong>Market Alerts:</strong> {data['market_alerts_count']}</div>
<div class="stat"><strong>Critical Alerts:</strong> {data['critical_alerts_count']}</div>
<div class="stat"><strong>Suspicious Trades:</strong> {data['suspicious_trades_count']}</div>