Motore di forecasting e ricalcolo
Input canonici
Per ogni mese/giorno nell'orizzonte il motore usa:
- periodo e domanda ore dei progetti;
- risorse associate e validità dell'associazione;
- compatibilità tra città della sede del progetto e sede della risorsa valida nel periodo;
- calendario lavorativo, orario e periodi di impiego;
- monte ferie e piano ferie, distinguendo ferie possibili e già pianificate/approvate;
- allocazioni su tutti i progetti dell'organizzazione;
- limite ore progetto-risorsa (default 1720) e ore contrattuali dei freelancer;
- costi reali o standard validi nel periodo;
- consuntivi fino al relativo cursore e mesi consegnati immutabili.
Vincoli hard
- Ore assegnate a una risorsa nel periodo ≤ capacità lavorativa disponibile meno ferie pianificate/approvate e altri impegni.
- Somma delle allocazioni della risorsa su tutti i progetti ≤ capacità: nessuna over-allocation.
- Ore di una risorsa su un progetto in un anno solare ≤
max_hours_per_resource. Corretto il 18 agosto 2026: questa riga diceva «ore cumulative risorsa-progetto», cioè sull'intera vita del progetto. Il committente ha confermato che 1720 sono ore/anno/risorsa, e che il valore è un default sovrascrivibile per progetto — su un progetto triennale le due letture davano risultati molto diversi. - Allocazioni solo dentro periodo progetto, rapporto di lavoro e associazione.
- Per un progetto con sede, allocazioni solo a risorse assegnate a una sede della stessa città nel periodo; un progetto senza sede non applica il vincolo geografico.
- Mesi consegnati non modificabili.
- Per costo standard deve esistere una tariffa univoca ente + macro-livello + data; per costo reale una tariffa dipendente univoca. Se manca o è ambigua il calcolo si ferma e riporta un warning: non ricade sul costo reale e non mette zero. Oggi fa entrambe le cose, in silenzio — vedi Difetti noti.
- Freelancer: usare ore contrattuali, senza applicare il normale calcolo predittivo e consuntivo automatico.
Obiettivi
Ordine consigliato, da confermare con il committente:
- massimizzare la copertura della domanda dei progetti;
- evitare over-allocation e violazioni dei limiti;
- minimizzare scostamenti rispetto al piano precedente per stabilità;
- rispettare priorità/scadenze progetto;
- distribuire il carico in modo uniforme, preservando una riserva ferie configurabile;
- minimizzare sforamenti di budget come obiettivo secondario, senza nascondere domanda non coperta.
Se non esiste una soluzione completa, il motore non deve produrre silenziosamente numeri falsamente validi: salva uno scenario INFEASIBLE/PARTIAL con ore scoperte, risorse sature e vincoli responsabili.
Monte ferie e piano ferie
- Ferie pianificate/approvate: riducono direttamente la capacità nei periodi indicati.
- Ferie disponibili ma non pianificate: alimentano una riserva prudenziale distribuita sull'orizzonte secondo policy configurabile.
- Il forecast mostra capacità lorda, ferie pianificate, riserva ferie, capacità netta e allocazione.
- Se il piano ferie cambia, si ricalcola dal primo periodo interessato.
Cosa se ne realizza in Fase 2 — solo il primo punto: il piano mensile riduce la capacità dei mesi indicati. Non esistono stati di approvazione, quindi «pianificate» e «approvate» qui sono la stessa cosa. La riserva per le ferie disponibili ma non pianificate dipende dal modello di capacità, che è rimandato: vedi Fase 2.
Trigger e dirty range
| Evento | Inizio intervallo da ricalcolare |
|---|---|
| Nuovo/chiuso/modificato progetto | Prima data modificata non consegnata |
| Assunzione/cessazione o cambio orario | Data di efficacia |
| Associazione/rimozione risorsa-progetto | Inizio/fine associazione modificata |
| Estensione/riduzione progetto | Prima data coinvolta |
| Costo o tariffa standard | valid_from |
| Ferie/balance/calendario | Prima giornata interessata |
| Modifica retroattiva consuntivo | Mese modificato |
| Limite ore risorsa-progetto | Primo mese futuro/non consegnato |
| Cambio sede progetto o sede storica risorsa | Prima data in cui cambia la compatibilità città |
Gli eventi vengono aggregati nel minimo intervallo dirty. Il job acquisisce un lock per organizzazione/scenario, legge uno snapshot coerente, calcola e pubblica atomicamente una nuova versione. Un evento arrivato durante il calcolo programma una nuova run, senza perdere invalidazioni.
Cosa di questa tabella esiste davvero, al 18 agosto 2026
La tabella qui sopra descrive il disegno di arrivo, non ciò che gira. Leggerla come stato dei fatti porta a credere che una modifica scateni un ricalcolo, e a guardare numeri vecchi convinti che siano freschi.
I trigger reali sono due:
| cosa scatena | cosa ricalcola |
|---|---|
| import del foglio presenze | il consuntivo di quel mese, poi il forecast del periodo successivo |
POST /employees/reconcile | i mesi impattati dalla riconciliazione di un nominativo |
Tutto il resto è manuale, per decisione del committente: un bottone nella scheda del progetto rifà il consuntivo di quel progetto dal suo cursore di consegna al mese consuntivato. Cambiare un'assegnazione o spostare una persona non ricalcola niente da solo, e niente segnala che quei numeri siano vecchi — è il limite noto di questa scelta, e il posto dove il dirty range servirà.
Il primo mese previsto
Il forecast non parte dal mese corrente ma dal mese successivo al cursore del consuntivo. La differenza si vede quando l'import è in ritardo: con il consuntivo a maggio e oggi 18 agosto, giugno e luglio non sono consuntivati — quindi sono previsione, anche se stanno nel passato. Partendo da oggi, il ricalcolo cancellava le loro righe e non le riscriveva: due mesi spariti, senza una riga a dirlo.
Solo in assenza di qualunque import si ricade sul mese corrente, che è l'unico riferimento disponibile.
Versionamento e spiegabilità
Ogni forecast_run conserva:
- versione algoritmo e parametri;
- intervallo, trigger/eventi e utente correlato;
- hash/snapshot logico degli input;
- stato, tempi e metriche;
- risultati per progetto/risorsa/periodo;
- warning, vincoli violati e domanda scoperta;
- riferimento alla versione precedente sostituita.
Sviluppo incrementale
- V1 deterministica: capacità mensile, ferie approvate, vincoli hard e ricalcolo manuale/automatico idempotente.
- V2 globale: ottimizzazione su tutti i progetti, domanda scoperta e stabilità rispetto al piano precedente.
- V3 scenari: simulazioni what-if, priorità, riserva ferie evoluta e confronto/approvazione scenari.
Prima della scelta della libreria/solver creare dataset sintetici “golden” con casi fattibili, impossibili, variazioni retroattive, costi temporali e freelancer.