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.
265 lines
10 KiB
Python
265 lines
10 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
atlas_seasonal_strategy.py — Generate context-aware strategic recommendations
|
||
based on calendar season, NL economic windows, local Tilburg events, and
|
||
Google Trends signals where available.
|
||
|
||
Output: atlas-strategy.json — consumed by Atlas Command BRIEF tab "Strategy" card.
|
||
|
||
The engine knows about:
|
||
- Vakantiegeld payouts (25 mei – 5 juni)
|
||
- Belastingteruggaaf windows (Mar-Apr)
|
||
- Schoolstart (late aug / early sept)
|
||
- Sint/Kerst spending (Nov-Dec)
|
||
- Tilburgse Kermis (juli)
|
||
- Q1/Q2/Q3/Q4 BTW aangifte deadlines
|
||
- Ramadan / Suikerfeest (year-variable)
|
||
|
||
Per current window, recommends:
|
||
- Which revenue stream to prioritize
|
||
- Which prospects to push
|
||
- Which framing to use
|
||
- What NOT to do
|
||
"""
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
import urllib.request
|
||
from pathlib import Path
|
||
from datetime import datetime, date, timedelta
|
||
|
||
OUT_LOCAL = Path(r"C:\Users\Gebruiker\AppData\Local\Atlas\analyses\atlas-strategy.json")
|
||
SSH = "root@<YOUR_VPS_IP>"
|
||
REMOTE = "/opt/atlas/dashboard/atlas-strategy.json"
|
||
|
||
|
||
# Calendar windows (year-relative — adjusted to 2026)
|
||
WINDOWS_2026 = [
|
||
{
|
||
"name": "Vakantiegeld",
|
||
"start": "2026-05-25", "end": "2026-06-05",
|
||
"signal": "Consumer cash boost (€1.500-3.000 per consumer)",
|
||
"push": [
|
||
"Werkplek STARTER €799 (consumer-friendly price)",
|
||
"Kapper / horeca consumer-facing zaak-eigenaars (zij ervaren spend-piek)",
|
||
"Atlas Agency retainers (klanten hebben budget voor pakketten)"
|
||
],
|
||
"framing": "\"Vakantiegeld al binnen? Nieuwe werkplek-set is nu beste timing.\"",
|
||
"avoid": ["Geen B2B-koud sales — owners distracted door consumer-rush"],
|
||
"channels": ["WhatsApp", "Instagram", "Tilburg community"],
|
||
},
|
||
{
|
||
"name": "Pre-zomer rust",
|
||
"start": "2026-06-06", "end": "2026-07-05",
|
||
"signal": "Stabiele B2B window, vóór schoolvakantie",
|
||
"push": [
|
||
"Atlas Agency cold-outreach (max response rate)",
|
||
"POS Support Lite contract conversions (existing klanten)",
|
||
"Werkplek PRO + KASSA (besluitvorming-tijd)"
|
||
],
|
||
"framing": "\"Inrichten vóór zomerstop — na zomer = nieuwe kalender, schone start.\"",
|
||
"avoid": ["Geen consumer-spend campagnes — vakantiegeld op"],
|
||
"channels": ["WhatsApp", "Email B2B", "LinkedIn (1×/wk max)"],
|
||
},
|
||
{
|
||
"name": "Tilburgse Kermis",
|
||
"start": "2026-07-17", "end": "2026-07-26",
|
||
"signal": "Lokale horeca-piek (10 dagen)",
|
||
"push": [
|
||
"POS Support Lite NU verkopen voor july readiness",
|
||
"Kermis-tijdelijke POS rental aanbieden (€50/dag) — test market",
|
||
"WhatsApp campagne richting kermis-deelnemers vóór kermis"
|
||
],
|
||
"framing": "\"Kermis = jouw drukste week. Atlas Support Lite zorgt dat je POS niet stilvalt.\"",
|
||
"avoid": ["Geen large-budget B2B proposals — operationele focus"],
|
||
"channels": ["WhatsApp horeca", "Instagram Tilburg-tags"],
|
||
},
|
||
{
|
||
"name": "Schoolvakantie",
|
||
"start": "2026-07-06", "end": "2026-08-17",
|
||
"signal": "MKB-eigenaars meer tijd voor strategie + nieuwe leveranciers",
|
||
"push": [
|
||
"Atlas Agency cold-outreach (BEST window — owners hebben tijd)",
|
||
"Werkplek follow-ups op bestaande quotes",
|
||
"Workshop POS voor Horeca (eerste editie juli)"
|
||
],
|
||
"framing": "\"Tijd voor de zaak achter de zaak — Atlas helpt het systeem rond te krijgen.\"",
|
||
"avoid": ["Geen sales-druk eerste 2 weken juli (vakantie-spirit)"],
|
||
"channels": ["WhatsApp", "Instagram zaak-eigenaar netwerken", "Charif referral"],
|
||
},
|
||
{
|
||
"name": "Back to school + work",
|
||
"start": "2026-08-18", "end": "2026-08-31",
|
||
"signal": "Nieuwe office-setups, MKB equipment-piek",
|
||
"push": [
|
||
"Werkplek PRO + KASSA push (office-upgrade tijd)",
|
||
"Group-buy aanbod (5+ werkplekken voor MKB)",
|
||
"Studenten-werkplekken (TilburgU/Avans semesterstart)"
|
||
],
|
||
"framing": "\"Begin het nieuwe seizoen met systemen die werken.\"",
|
||
"avoid": ["Geen low-budget peripherals — push hoog-marge bundels"],
|
||
"channels": ["WhatsApp MKB", "TilburgU community", "Avans-area outreach"],
|
||
},
|
||
{
|
||
"name": "Najaar groei",
|
||
"start": "2026-09-01", "end": "2026-10-31",
|
||
"signal": "Najaars-momentum, beslissingen-tijd",
|
||
"push": [
|
||
"Atlas Agency retainers (€250/mo) — vol momentum",
|
||
"Bisogno + andere bestaande klanten verdieping",
|
||
"Workshops 2×/mo schaal"
|
||
],
|
||
"framing": "\"Voor december komt: maak je systeem klaar voor jaar-eind drukte.\"",
|
||
"avoid": [],
|
||
"channels": ["Alles open"],
|
||
},
|
||
{
|
||
"name": "Sint/Kerst opmaat",
|
||
"start": "2026-11-01", "end": "2026-11-30",
|
||
"signal": "B2C horeca-piek voorbereiden + boekhouders druk",
|
||
"push": [
|
||
"Werkplek KASSA voor horeca die met sint/kerst druk gaat krijgen",
|
||
"Cross-sell naar boekhouders (referral network — zij zien hun klanten worstelen)",
|
||
"Year-end Atlas Support Lite incentives"
|
||
],
|
||
"framing": "\"December = duurste maand om POS te laten falen. Nu is het tijd.\"",
|
||
"avoid": [],
|
||
"channels": ["Alles open"],
|
||
},
|
||
{
|
||
"name": "Eindejaarsspecial",
|
||
"start": "2026-12-01", "end": "2026-12-23",
|
||
"signal": "Cash + planning + 'kerstcadeau voor jezelf'-framing",
|
||
"push": [
|
||
"Werkplek STARTER €799 als kerstcadeau voor zzp'er",
|
||
"Q4 retainer-vernieuwingen + annual prepay deals",
|
||
"Atlas Support Lite jaarcontracten (12% korting bij vooruitbetaling)"
|
||
],
|
||
"framing": "\"Kerstcadeau aan jezelf én aan je zaak.\"",
|
||
"avoid": ["Geen B2B sales na 23 dec — niemand bereikbaar"],
|
||
"channels": ["WhatsApp warm-list", "Email bestaande klanten", "IG"],
|
||
},
|
||
{
|
||
"name": "Kerst-doodweek",
|
||
"start": "2026-12-24", "end": "2027-01-04",
|
||
"signal": "Holiday lull — minimaal contact",
|
||
"push": ["Eigen administratie afronden", "2027 plan schrijven", "Cluster onderhoud"],
|
||
"framing": "n.v.t. — geen sales",
|
||
"avoid": ["Alle externe outreach"],
|
||
"channels": [],
|
||
},
|
||
{
|
||
"name": "Q1 freshness",
|
||
"start": "2027-01-05", "end": "2027-01-31",
|
||
"signal": "\"Nieuw jaar nieuw systeem\" — annual contract renewals",
|
||
"push": [
|
||
"Werkplek upgrade calls naar Q1 klanten",
|
||
"Atlas Agency jaarcontracten ipv maandelijks",
|
||
"POS Support Lite hernieuwingen + uitbreidingen"
|
||
],
|
||
"framing": "\"Begin 2027 met systemen die in 2026 hebben bewezen te werken.\"",
|
||
"avoid": [],
|
||
"channels": ["Alles open"],
|
||
},
|
||
]
|
||
|
||
|
||
def current_window(today=None):
|
||
today = today or date.today()
|
||
for w in WINDOWS_2026:
|
||
start = date.fromisoformat(w["start"])
|
||
end = date.fromisoformat(w["end"])
|
||
if start <= today <= end:
|
||
days_in = (today - start).days
|
||
days_left = (end - today).days
|
||
return {**w, "days_in": days_in, "days_left": days_left}
|
||
return None
|
||
|
||
|
||
def upcoming_windows(today=None, n=3):
|
||
today = today or date.today()
|
||
upcoming = []
|
||
for w in WINDOWS_2026:
|
||
start = date.fromisoformat(w["start"])
|
||
if start > today:
|
||
upcoming.append({**w, "days_until": (start - today).days})
|
||
if len(upcoming) >= n: break
|
||
return upcoming
|
||
|
||
|
||
def btw_deadline(today=None):
|
||
"""Quarterly NL BTW aangifte deadlines: end of month after quarter end."""
|
||
today = today or date.today()
|
||
q_ends = [
|
||
(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"),
|
||
]
|
||
for d, q in q_ends:
|
||
if d >= today:
|
||
return {"quarter": q, "deadline": d.isoformat(), "days_until": (d - today).days}
|
||
return None
|
||
|
||
|
||
def days_to_next_payday():
|
||
"""Most NL employers pay 25th of month — quick proxy."""
|
||
today = date.today()
|
||
if today.day < 25:
|
||
nxt = date(today.year, today.month, 25)
|
||
else:
|
||
m = today.month + 1
|
||
y = today.year
|
||
if m > 12: m = 1; y += 1
|
||
nxt = date(y, m, 25)
|
||
return (nxt - today).days
|
||
|
||
|
||
def build_summary():
|
||
today = date.today()
|
||
curr = current_window(today)
|
||
upcoming = upcoming_windows(today, 3)
|
||
btw = btw_deadline(today)
|
||
|
||
summary = {
|
||
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
||
"as_of": today.isoformat(),
|
||
"current_window": curr,
|
||
"upcoming_windows": upcoming,
|
||
"btw_aangifte": btw,
|
||
"days_to_next_payday": days_to_next_payday(),
|
||
"weekday": today.strftime("%A"),
|
||
"is_weekend": today.weekday() >= 5,
|
||
# Action triggers
|
||
"primary_recommendation": curr["push"][0] if curr and curr["push"] else "Geen actieve window — gebruik default plan",
|
||
"primary_framing": curr["framing"] if curr else "n.v.t.",
|
||
"avoid_today": curr["avoid"] if curr else [],
|
||
"channels_today": curr["channels"] if curr else [],
|
||
}
|
||
return summary
|
||
|
||
|
||
def push_to_atlas01(summary):
|
||
OUT_LOCAL.parent.mkdir(parents=True, exist_ok=True)
|
||
OUT_LOCAL.write_text(json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8")
|
||
print(f"wrote {OUT_LOCAL}")
|
||
try:
|
||
subprocess.run(["scp", str(OUT_LOCAL), f"{SSH}:{REMOTE}"], check=True, timeout=20)
|
||
print(f"pushed to {SSH}:{REMOTE}")
|
||
except Exception as e:
|
||
print(f"scp fail: {e}")
|
||
|
||
|
||
def main():
|
||
s = build_summary()
|
||
push_to_atlas01(s)
|
||
print(f"\nCurrent window: {s['current_window']['name'] if s['current_window'] else 'none'}")
|
||
if s['current_window']:
|
||
print(f" Days in: {s['current_window']['days_in']}, days left: {s['current_window']['days_left']}")
|
||
print(f" Push: {s['primary_recommendation']}")
|
||
print(f"BTW deadline: {s['btw_aangifte']['quarter']} on {s['btw_aangifte']['deadline']} ({s['btw_aangifte']['days_until']}d)")
|
||
print(f"Next payday (25th): {s['days_to_next_payday']}d")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|