Refresh handoff next-steps and harden daily report delivery.
This commit is contained in:
+13
-15
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user