Feature: Tagesmail — Action Items + neue Nutzer heute

This commit is contained in:
rene 2026-05-01 19:31:42 +02:00
parent d04110c2ae
commit df8c4cc279

View file

@ -650,6 +650,34 @@ async def _job_ki_health_report():
# ------------------------------------------------------------------
def _action_items_html(metrics: dict) -> str:
items = [
("jobs_pending", "Bewerbungen offen"),
("breeder_pending", "Züchter-Anträge"),
("reports_open", "Forum-Meldungen"),
("fotos_pending", "Foto-Einreichungen"),
("poi_edits_pending", "POI-Korrekturen"),
]
open_items = [(label, metrics.get(key, 0)) for key, label in items if metrics.get(key, 0) > 0]
if not open_items:
body = '<span style="color:#16a34a;font-weight:700">✅ Alles erledigt — nichts offen</span>'
else:
pills = "".join(
f'<span style="display:inline-block;background:#fff3e0;color:#c45000;border:1px solid #e8a857;'
f'border-radius:999px;padding:3px 12px;font-size:12px;font-weight:700;margin:2px 4px 2px 0">'
f'{label} <strong style="background:#c45000;color:#fff;border-radius:999px;'
f'padding:0 7px;margin-left:4px">{count}</strong></span>'
for label, count in open_items
)
body = f'<div style="font-size:13px;font-weight:600;color:#c45000;margin-bottom:8px">⚠️ {len(open_items)} Punkt{"e" if len(open_items)!=1 else ""} brauchen deine Aufmerksamkeit</div>{pills}'
link = '<div style="margin-top:10px"><a href="https://banyaro.app/app/admin" style="font-size:12px;color:#C4843A">→ Admin-Panel öffnen</a></div>'
return f'<div style="padding:20px 28px;border-bottom:2px solid #e8a857;background:#fffbf5">' \
f'<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#C4843A;margin-bottom:10px">Heute zu erledigen</div>' \
f'{body}{link}</div>'
# JOB: Status-Report per Mail (täglich 06:00 Uhr)
# ------------------------------------------------------------------
async def _job_status_report():
@ -677,6 +705,7 @@ async def _job_status_report():
# Community
metrics["users"] = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
metrics["users_today"] = conn.execute("SELECT COUNT(*) FROM users WHERE DATE(created_at)=DATE('now')").fetchone()[0]
metrics["dogs"] = conn.execute("SELECT COUNT(*) FROM dogs").fetchone()[0]
metrics["diary_entries"] = conn.execute("SELECT COUNT(*) FROM diary").fetchone()[0]
metrics["poison_active"] = conn.execute("SELECT COUNT(*) FROM poison WHERE geloest=0").fetchone()[0]
@ -685,6 +714,28 @@ async def _job_status_report():
except Exception:
metrics["lost_active"] = 0
# Action Items
try:
metrics["jobs_pending"] = conn.execute("SELECT COUNT(*) FROM job_applications WHERE status IN ('pending','reviewing')").fetchone()[0]
except Exception:
metrics["jobs_pending"] = 0
try:
metrics["breeder_pending"] = conn.execute("SELECT COUNT(*) FROM users WHERE breeder_status='pending'").fetchone()[0]
except Exception:
metrics["breeder_pending"] = 0
try:
metrics["reports_open"] = conn.execute("SELECT COUNT(*) FROM forum_reports WHERE resolved=0").fetchone()[0]
except Exception:
metrics["reports_open"] = 0
try:
metrics["fotos_pending"] = conn.execute("SELECT COUNT(*) FROM wiki_foto_submissions WHERE status='pending'").fetchone()[0]
except Exception:
metrics["fotos_pending"] = 0
try:
metrics["poi_edits_pending"] = conn.execute("SELECT COUNT(*) FROM osm_poi_edits WHERE status='pending'").fetchone()[0]
except Exception:
metrics["poi_edits_pending"] = 0
# Wiki-Interesse
try:
metrics["interesse_hat"] = conn.execute("SELECT COUNT(*) FROM wiki_breed_interest WHERE typ='hat'").fetchone()[0]
@ -736,6 +787,9 @@ async def _job_status_report():
<div style="opacity:.88;font-size:13px">{now_str} Uhr</div>
</div>
<!-- Action Items -->
{_action_items_html(metrics)}
<!-- Scheduler-Status -->
<div style="padding:20px 28px;border-bottom:1px solid #f0e8dc">
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#C4843A;margin-bottom:10px">Scheduler-Jobs</div>
@ -749,14 +803,14 @@ async def _job_status_report():
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#C4843A;margin-bottom:10px">Community</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
{"".join(f'<div style="background:#fdf6ef;border-radius:8px;padding:10px 14px"><div style="font-size:20px;font-weight:800;color:#C4843A">{v}</div><div style="font-size:11px;color:#888">{k}</div></div>' for k,v in [
("Nutzer",metrics["users"]),
("Nutzer gesamt",metrics["users"]),
("Neue Nutzer heute",metrics["users_today"]),
("Hunde",metrics["dogs"]),
("Tagebuch-Einträge",metrics["diary_entries"]),
("Aktive Giftköder",metrics["poison_active"]),
("Vermisste Hunde",metrics["lost_active"]),
("'So einen hab ich'",metrics["interesse_hat"]),
("'Interessiert mich'",metrics["interesse_will"]),
("Züchter (pending)",metrics["zuchter_pending"]),
])}
</div>
</div>
@ -770,19 +824,28 @@ async def _job_status_report():
</body>
</html>"""
action_open = [l for k,l in [
("jobs_pending","Bewerbungen"),("breeder_pending","Züchter-Anträge"),
("reports_open","Meldungen"),("fotos_pending","Fotos"),("poi_edits_pending","POI-Korrekturen"),
] if metrics.get(k,0) > 0]
plain = f"""Ban Yaro Status-Report — {now_str}
=== HEUTE ZU ERLEDIGEN ===
{"✅ Alles erledigt" if not action_open else "⚠️ " + ", ".join(f"{l} ({metrics[k]})" for k,l in [
("jobs_pending","Bewerbungen"),("breeder_pending","Züchter-Anträge"),
("reports_open","Meldungen"),("fotos_pending","Fotos"),("poi_edits_pending","POI-Korrekturen"),
] if metrics.get(k,0) > 0)}
=== Scheduler-Jobs ===
{job_rows_txt}
=== Community ===
Nutzer: {metrics['users']}
Nutzer gesamt: {metrics['users']} (+{metrics['users_today']} heute)
Hunde: {metrics['dogs']}
Tagebuch-Einträge: {metrics['diary_entries']}
Aktive Giftköder: {metrics['poison_active']}
Vermisste Hunde: {metrics['lost_active']}
'So einen hab ich': {metrics['interesse_hat']}
'Interessiert mich': {metrics['interesse_will']}
Züchter (pending): {metrics['zuchter_pending']}
"""
try: