3 browser-native chat surfaces (atlas-chat / atlas-hud / atlas-command, no build step) + 11 Python automation scripts (cluster, tray, finance, reminders, autonomy loops) + 2 patterns documented (cluster-then-polish, anti-impulse R1-R10) MIT licensed, anonymized from production stack.
240 lines
10 KiB
Python
240 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
atlas_reminders.py — Operator burden-remover.
|
|
|
|
Fires based on date-pattern matching:
|
|
- 1e van de maand: Inkomsten xlsx update reminder
|
|
- 14d + 3d vóór BTW deadlines: aangifte reminder
|
|
- Friday-end-of-month: personal financial summary
|
|
- Quarterly mid-last-month: plan revision reminder
|
|
- Annual prep windows
|
|
|
|
Each reminder is an email to info@your-domain.tld via TransIP SMTP.
|
|
|
|
Schedule:
|
|
Atlas-Reminders-Daily at 07:45 — checks all conditions, fires what matches.
|
|
"""
|
|
import sys
|
|
import smtplib
|
|
import json
|
|
from pathlib import Path
|
|
from datetime import date, datetime, timedelta
|
|
from email.mime.text import MIMEText
|
|
|
|
SMTP_HOST = "smtp.transip.email"
|
|
SMTP_PORT = 587
|
|
SMTP_USER = "your@your-domain.tld"
|
|
SMTP_PASS = "<YOUR_SMTP_PASSWORD>"
|
|
MAIL_FROM = "your@your-domain.tld"
|
|
MAIL_TO = "info@your-domain.tld"
|
|
|
|
LOG = Path(r"C:\Users\Gebruiker\.config\atlas\atlas_reminders.log")
|
|
|
|
|
|
def log(msg):
|
|
LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(LOG, "a", encoding="utf-8") as f:
|
|
f.write(f"[{datetime.now().isoformat(timespec='seconds')}] {msg}\n")
|
|
|
|
|
|
def send_html(subject, html_body):
|
|
msg = MIMEText(html_body, "html", "utf-8")
|
|
msg["Subject"] = subject
|
|
msg["From"] = MAIL_FROM
|
|
msg["To"] = MAIL_TO
|
|
try:
|
|
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as s:
|
|
s.starttls()
|
|
s.login(SMTP_USER, SMTP_PASS)
|
|
s.send_message(msg)
|
|
log(f"sent: {subject}")
|
|
return True
|
|
except Exception as e:
|
|
log(f"send FAIL: {e}")
|
|
return False
|
|
|
|
|
|
def reminder_card(title, urgency, body_html):
|
|
color = {"high": "#dc2626", "med": "#f59e0b", "low": "#3b82f6"}[urgency]
|
|
return f"""
|
|
<div style="border-left:4px solid {color};padding:14px;background:#f8fafc;border-radius:4px;margin-bottom:14px">
|
|
<div style="font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:{color}">{urgency} priority reminder</div>
|
|
<h2 style="margin:4px 0 8px;font-size:18px;color:#0f172a">{title}</h2>
|
|
<div style="font-size:14px;color:#334155;line-height:1.5">{body_html}</div>
|
|
</div>
|
|
"""
|
|
|
|
|
|
def check_monthly_inkomsten(today):
|
|
"""1e van de maand: update Inkomsten xlsx voor afgesloten maand."""
|
|
if today.day not in (1, 2, 5):
|
|
return None
|
|
last_month = today.replace(day=1) - timedelta(days=1)
|
|
body = f"""
|
|
<p>Vandaag ({today.strftime('%d %B')}) is een check-dag voor de <strong>Inkomsten uit onderneming 2026.xlsx</strong>.</p>
|
|
<p><strong>Actie</strong>: open Knab → download afschrift {last_month.strftime('%B %Y')} →
|
|
folder <code>07_Finance/2026/03_bank/</code> → en update de
|
|
<strong>Betaalcontrole</strong> sheet met:</p>
|
|
<ul>
|
|
<li>Klant + factuurnummer + bedrag voor elke ontvangst</li>
|
|
<li>Knab afschrift-regel als bewijs</li>
|
|
<li>Status "opgenomen" zodra reconciled</li>
|
|
</ul>
|
|
<p>Atlas Command BRIEF tab leest de xlsx automatisch (06:30) → refresht je
|
|
live YTD/MRR/concentration cijfers.</p>
|
|
<p style="color:#64748b;font-size:12px"><em>Reminder doel: voorkom "xlsx niet bijgewerkt sinds vorige maand" patroon (gebeurde mei 2026).</em></p>
|
|
"""
|
|
return ("📊 Monthly: Inkomsten xlsx update", "med",
|
|
reminder_card("Update Inkomsten xlsx voor afgelopen maand", "med", body))
|
|
|
|
|
|
def check_btw_deadline(today):
|
|
"""14d en 3d vóór BTW kwartaal-deadlines."""
|
|
btw_deadlines = [
|
|
date(today.year, 4, 30), # Q1
|
|
date(today.year, 7, 31), # Q2
|
|
date(today.year, 10, 31), # Q3
|
|
date(today.year + 1, 1, 31), # Q4
|
|
]
|
|
quarters = ["Q1", "Q2", "Q3", "Q4"]
|
|
for d, q in zip(btw_deadlines, quarters):
|
|
delta = (d - today).days
|
|
if delta in (14, 3):
|
|
urgency = "high" if delta == 3 else "med"
|
|
body = f"""
|
|
<p><strong>BTW aangifte {q} {today.year} deadline: {d.strftime('%d %B %Y')}</strong>
|
|
— over {delta} dagen.</p>
|
|
<p><strong>Actie</strong>:</p>
|
|
<ol>
|
|
<li>Open Odoo (<a href="http://<YOUR_VPS_IP>:8069"><YOUR_VPS_IP>:8069</a>) → Boekhouding → BTW</li>
|
|
<li>Genereer aangifte-overzicht voor {q}</li>
|
|
<li>Controleer met Knab + ING + Revolut totalen</li>
|
|
<li>Login Belastingdienst (Mijn Belastingdienst Zakelijk) → aangifte indienen</li>
|
|
<li>BTW-bedrag overmaken vanuit Knab vóór {d.strftime('%d %B')}</li>
|
|
</ol>
|
|
<p style="color:#64748b;font-size:12px">
|
|
<em>Boete bij te late aangifte: vanaf €68. Bij te late betaling: 4% rente op jaarbasis.</em>
|
|
</p>
|
|
"""
|
|
return (f"⏳ BTW {q} deadline {delta}d - actie nodig", urgency,
|
|
reminder_card(f"BTW aangifte {q} — {delta} dagen", urgency, body))
|
|
return None
|
|
|
|
|
|
def check_quarterly_review(today):
|
|
"""15e van laatste maand van kwartaal: revise Q+1 plan."""
|
|
quarterly_review_days = [
|
|
date(today.year, 3, 15),
|
|
date(today.year, 6, 15),
|
|
date(today.year, 9, 15),
|
|
date(today.year, 12, 15),
|
|
]
|
|
if today not in quarterly_review_days:
|
|
return None
|
|
quarter = (today.month - 1) // 3 + 1
|
|
body = f"""
|
|
<p>Mid-laatste-maand Q{quarter} — tijd voor Q{quarter+1 if quarter < 4 else 1} planning.</p>
|
|
<p><strong>Acties</strong>:</p>
|
|
<ul>
|
|
<li>Open Atlas Command → BRIEF tab → screenshot finance + concentration + R10</li>
|
|
<li>Open vault note <code>05.5 - Q3 2026 Plan.md</code> (of Q+1 versie)</li>
|
|
<li>Herzie cijfers: YTD vs target, MRR groei, concentration delta</li>
|
|
<li>Definieer 3-5 top prioriteiten voor komend kwartaal</li>
|
|
<li>Schrijf Q+1 versie in <code>00_Atlas_Wiki/</code></li>
|
|
</ul>
|
|
<p>Tip: gebruik [[06.5 - Master Cockpit & 90-day System]] als template.</p>
|
|
"""
|
|
return (f"🔁 Q{quarter+1 if quarter<4 else 1} planning tijd", "med",
|
|
reminder_card(f"Quarterly review — Q{quarter+1 if quarter<4 else 1} plan opstellen", "med", body))
|
|
|
|
|
|
def check_holiday_booking(today):
|
|
"""Vrij van eerste juni-week: reminder om vakantie te boeken voor zomer."""
|
|
triggers = [date(today.year, 6, 5), date(today.year, 11, 25)] # June + late Nov
|
|
if today not in triggers:
|
|
return None
|
|
body = f"""
|
|
<p>Solo founder herinnering: <strong>plan je vakantie zelf in</strong>, anders gebeurt het niet.</p>
|
|
<p><strong>Suggestie 2026 windows</strong> (zie [[06.5 - Master Cockpit & 90-day System]] §7):</p>
|
|
<ul>
|
|
<li>6-12 juli: eerste zomerweek (Bisogno retainer dekt cash)</li>
|
|
<li>17-21 augustus: pre-schoolstart week</li>
|
|
<li>14-20 december: pre-kerst rust</li>
|
|
</ul>
|
|
<p><strong>Actie</strong>: blokkeer in Nextcloud Kalender + Apple Calendar.
|
|
Stuur "out of office" voor Atlas Agency klanten.
|
|
Werkplek-pakket levering = plan voor of na vakantie.</p>
|
|
"""
|
|
return ("🌴 Vakantie boeking reminder", "low",
|
|
reminder_card("Plan je zomer/winter vakantie nu", "low", body))
|
|
|
|
|
|
def check_savings(today):
|
|
"""26e van maand: na vakantiegeld / na payday → savings reminder."""
|
|
if today.day != 26:
|
|
return None
|
|
body = """
|
|
<p>Het is de 26e — payday is geweest (25e is NL salaris-norm).</p>
|
|
<p><strong>Actie</strong>: open Knab → check 3 spaar-pots:</p>
|
|
<ul>
|
|
<li>🚗 <strong>Auto-pot</strong>: doel €6.500 voor december — storting deze maand?</li>
|
|
<li>🛡️ <strong>Buffer-pot</strong>: 3 mnd vaste lasten doel €5.000 — storting deze maand?</li>
|
|
<li>🌴 <strong>Vakantie-pot</strong>: doel €1.500/jaar — €125/mnd</li>
|
|
</ul>
|
|
<p>Niet sparen = niet halen. Auto-transfer instellen via Knab is one-time setup, daarna fire-and-forget.</p>
|
|
<p style="color:#64748b;font-size:12px"><em>Bron: [[06.5 - Master Cockpit]] §7 savings goals tabel.</em></p>
|
|
"""
|
|
return ("💰 Savings reminder (26e)", "low",
|
|
reminder_card("Spaar-pots check", "low", body))
|
|
|
|
|
|
def check_annual_prep(today):
|
|
"""1 maart: IB aangifte voorjaars-window opent. 1 juni: deadline 1 mei is voorbij."""
|
|
if today not in (date(today.year, 3, 1), date(today.year, 4, 15)):
|
|
return None
|
|
body = f"""
|
|
<p>IB aangifte 2025 voor {today.year} — deadline 1 mei {today.year}.</p>
|
|
<p><strong>Acties</strong>:</p>
|
|
<ol>
|
|
<li>Stuur boekhouder Inkomsten + Kosten 2025 xlsx (al beschikbaar in 07_Finance)</li>
|
|
<li>Verzamel BTW aangiften 2025 (4 kwartalen)</li>
|
|
<li>Verzamel zakelijke kosten-bonnetjes 2025 (05_overig/ + 02_uitgaven/)</li>
|
|
<li>Vraag KOR-check (kleine ondernemingsregeling) bij boekhouder</li>
|
|
<li>Plan voorlopige IB-aanslag voor {today.year} samen met boekhouder</li>
|
|
</ol>
|
|
"""
|
|
return ("📋 IB aangifte voorjaarsstart", "high",
|
|
reminder_card("IB aangifte 2025 voorbereiden", "high", body))
|
|
|
|
|
|
def main():
|
|
today = date.today()
|
|
log(f"reminders check for {today.isoformat()}")
|
|
cards = []
|
|
for check in [check_monthly_inkomsten, check_btw_deadline,
|
|
check_quarterly_review, check_holiday_booking,
|
|
check_savings, check_annual_prep]:
|
|
result = check(today)
|
|
if result:
|
|
subject, urgency, html = result
|
|
cards.append((subject, urgency, html))
|
|
log(f" triggered: {subject}")
|
|
|
|
if not cards:
|
|
log(" no reminders today")
|
|
return
|
|
|
|
# Combine all cards into one email
|
|
body_html = f"""<!doctype html><html><body style="font-family:-apple-system,Segoe UI,sans-serif;color:#1e293b;max-width:680px;margin:auto;padding:24px">
|
|
<h1 style="font-size:22px;margin-bottom:18px">🔔 Atlas Reminders — {today.strftime('%A %d %B %Y')}</h1>
|
|
{''.join(c[2] for c in cards)}
|
|
<div style="margin-top:24px;padding-top:14px;border-top:1px solid #e2e8f0;font-size:11px;color:#94a3b8">
|
|
Atlas Reminders · auto-fired by daily 07:45 schtask · disable via Disable-ScheduledTask Atlas-Reminders-Daily
|
|
</div>
|
|
</body></html>"""
|
|
subject = f"🔔 Atlas Reminders ({len(cards)}) — {today.strftime('%d %b')}"
|
|
send_html(subject, body_html)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|