--- aliases: ["Frank"] type: progetto --- # CLAUDE.md — Frank (ex Scheduler) **Ultimo aggiornamento:** 2026-07-27 ## Contesto Daemon di automazione moderno per Windows — orchestratore job ufficio. **Versione:** 1.3.0 | **Status:** Production Ready (4+ mesi, 99%+ uptime) Funzionalità principali: - **Job Scheduling**: cron expressions, batch/Python/VBS, job chaining, profili per hostname - **Backup Automation**: profili hard-link deduplication, retention, restore granulare con tree selection, Run Now manuale da GUI **Rename completato: Scheduler → Frank** (2026-03-13). Frank è il "referente operativo in ufficio" nell'ecosistema Albert/Mike/Frank. **Ecosistema AI:** - **Albert** — assistente personale di Mauro (Linux, casa) — il "direttore" - **Mike** — sub-agente di Albert - **Frank** — daemon Windows in ufficio, esecutore operativo locale - Integrazione Frank ↔ Albert pianificata (protocollo cross-machine da definire) ## Come avviare ```bash # Avvio manuale (uso quotidiano — autostart bloccato da policy IT) RUN.bat # Diretto (con venv condiviso) python\python.exe start_gui.py python\python.exe start_daemon.py ``` ## Tech stack e vincoli specifici - **Python**: 3.11.9 embedded in `python\` (consigliato) oppure venv - **APScheduler** 3.10.4 — scheduler engine - **PyQt6** 6.7.0 PINNED (standard ecosistema) - **PyQt6-Charts** 6.7.0 - **Robocopy**: per sync multi-thread (integrazione Windows nativa) - **loguru** 0.7.3 - **pywin32** + **win10toast** per integrazione Windows (system tray, notifiche) ## Architettura ``` Frank/ ├── core/ │ ├── database.py # SQLite manager (singleton) → frank.db (su B via UNC) │ ├── config.py # Config loader JSON (singleton) │ ├── cron_utils.py # Parsing cron expressions │ └── logger.py # Setup logging ├── daemon/ # Automation daemon ├── gui/ # PyQt6 GUI (jobs + backup, read-only a tendere) │ └── main_window.py ├── modules/ │ ├── backup/ # Backup Automation │ └── outlook/ # Outlook classifier ├── config/ │ └── frank_config.json # Config locale (paths, notifiche) ├── logs/ # Log esecuzioni job ├── python/ # Python 3.11.9 embedded (NON in git) ├── start_daemon.py # Entry daemon ├── start_gui.py # Entry GUI ├── deploy_DEV_LOCAL.bat # Deploy A → B └── RUN.bat # Launcher manuale (stop + avvio daemon + GUI) ``` ## Database - **`frank.db`**: SQLite su B (`\\mediaset.it\share\...\sync_db\frank.db`) — config source of truth (jobs, schedules, backup profiles). Condivide la directory `sync_db` con `mediatrack.db` e altri DB. - **`frank_local.db`**: SQLite locale (`C:\PYTHON_LOCAL\Frank\data\frank_local.db`) — DB operativo del daemon. Contiene copia config (sincronizzata da B) + job_executions (solo locale). - WAL mode abilitato su entrambi — accesso multi-PC via UNC per frank.db - Ogni job ha campo `hostname` — il daemon esegue solo i job del suo host - `Database` è un **singleton** — non istanziare più volte - `frank.db` **NON viene copiato dal deploy** — vive permanentemente su B - `frank_local.db` **NON viene copiato dal deploy** — creato automaticamente al primo avvio del daemon ## Gotcha critici **Backup su drive di rete:** usare sempre path UNC (`\\server\share\...`) nelle sorgenti backup — le lettere di drive (I:, W:, X:) sono mappate per sessione utente e NON sono accessibili al daemon in background. Path UNC funzionano senza mapping. **SQLite cross-thread:** il DB singleton usa connessioni per thread. Dopo un commit da worker thread, attendere ~300ms prima di rileggere dal main thread (WAL checkpoint visibility). **frank.db via UNC:** WAL mode abilitato. Write occasionali (start/stop job) — nessun problema di concorrenza per 2-3 PC. **Schema frank_local.db:** creato da `_create_tables()` + sync da B. Se B ha colonne aggiuntive non presenti nello schema locale, il sync fallisce silenziosamente per quella tabella. In caso di errori `no such column`, aggiornare `_create_tables()` e cancellare `frank_local.db` per ricrearlo. **deploy_DEV_LOCAL.bat da Claude Code:** usare `Start-Process cmd -ArgumentList "/c","path\to\bat","silent" -Wait -NoNewWindow` — `cmd /c "bat"` diretto non funziona (Git Bash intercetta alcuni comandi). ## Stato attuale / Roadmap - **v1.2.3** (2026-02-27): Run Now backup da GUI, fix UNC paths, Outlook module - **v1.3.0** (2026-03-13): Rename Scheduler → Frank, rimozione Sync e Version, Zero-Env architecture, hostname-based job filtering, frank.db centralizzato su B — **COMPLETATO** - **v1.4.0** (2026-03-19): DB split locale/remoto — daemon usa `frank_local.db` a runtime (zero dipendenza rete), sync config da B all'avvio, polling `config_version` ogni 60s per reload live — **COMPLETATO** ## Deployment attuale (2026-07-27) Frank gira **solo su A** (W1251000160 — macchina di Mauro). **Definitivo — non girerà mai su altre macchine.** - Gira direttamente da `Dropbox\adrian\PYTHON\Frank\` con venv condiviso `C:\PYTHON_OLD\_runtimes\venv\` - **Avvio manuale quotidiano**: `RUN.bat` (autostart bloccato da policy IT — Startup folder e Task Scheduler disabilitati) - Lock file `data/daemon.lock` previene istanze multiple - B (server): `frank.db` su `sync_db\frank.db` — config source of truth. Il daemon usa `data/frank_local.db` a runtime Job attivi su A: | Job | Schedule | |---|---| | DataHub v2 Weekly | lunedì 10:00 (`C:\PYTHON\DataHub_v2\`) | | DataHub v2 Daily | martedì-venerdì 10:00 (`C:\PYTHON\DataHub_v2\`) | | OMDB Batch Enrich | lun-ven 13:30 (`C:\PYTHON\MyICR_Suite\frank_jobs\`) | | Check doppi Gemma | lun-ven 11:30 | | Check prodotti acquistati | mercoledì 12:30 | | ImdbUpdate | giovedì 14:00 | | IntegrazioneUpdateDay | lun-ven 13:00 | | ReportPdf | lun-ven 16:00 | | UPDATE_DAY | lun-ven 10:00 | | Outlook Frank filtro | ogni 15 min | | GemmaReport | mercoledì 11:00 (`C:\PYTHON\GemmaReport\`) | | OsservatorioReport | mercoledì 11:15 (`C:\PYTHON\GemmaReport\`) | ## Dipendenze - Orchestratore di **DataHub v2** (ETL settimanali/giornalieri) - Accede a **frank.db** su B via UNC ## Non fare mai - Non eliminare `frank.db` — contiene tutti i job e la history - Non avviare più istanze del daemon contemporaneamente sullo stesso host