8.2 KiB
Handoff — 3D Pricing App
Ce document permet de reprendre le développement dans un nouveau contexte de conversation.
Lire aussi GUIDE.md pour la documentation fonctionnelle complète.
État du projet (07/07/2026)
Application en production sur le réseau local. Tous les bugs critiques connus sont résolus.
Stack
- Python 3.12 / Flask / SQLite (WAL mode)
- Bootstrap 5 + vis.js Timeline
- Docker (port 5010 → 5000 interne)
- Données persistantes dans volume Docker
/data/pricing.db
Dépôt
C:\Users\jbper\Claude\3d-pricing\
├── app.py # ~2557 lignes — backend complet
├── templates/ # 27 templates Jinja2
├── GUIDE.md # Documentation fonctionnelle
├── HANDOFF.md # Ce fichier
├── Dockerfile
├── docker-compose.yml
└── requirements.txt # flask, authlib, requests
Règle absolue : ne jamais éditer app.py avec l'outil Edit
Le fichier app.py est sur un montage NTFS (Windows) — l'outil Edit tronque le fichier silencieusement.
Méthode obligatoire pour modifier app.py :
# Toujours passer par un script Python et écrire directement via le chemin bash
python3 - << 'EOF'
with open('/sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py', 'r') as f:
content = f.read()
content = content.replace(old, new, 1)
with open('/sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py', 'w') as f:
f.write(content)
EOF
La même règle s'applique aux templates si besoin (mais ils sont plus petits, moins risqués).
Migrations DB
Les migrations sont dans init_db() sous forme de liste migrations avec try/except par commande. Ne jamais supprimer une migration existante — l'ordre est cumulatif.
Dernières migrations ajoutées (à la fin de la liste) :
'ALTER TABLE printers ADD COLUMN ha_entity_prefix TEXT DEFAULT ""'
'ALTER TABLE print_slots ADD COLUMN pieces_ignored INTEGER DEFAULT 0'
'ALTER TABLE print_slots ADD COLUMN pieces_override INTEGER DEFAULT NULL'
'ALTER TABLE calendar_blocks ADD COLUMN date_end TEXT DEFAULT NULL'
'ALTER TABLE jobs ADD COLUMN marge_pct_on_ht REAL DEFAULT 0'
'ALTER TABLE jobs ADD COLUMN source_nc_path TEXT DEFAULT ""'
Fonctionnalités clés et leur implémentation
Moteur de pricing (calc(), ligne ~458)
Calcul par pièce. weight_g et print_time_s = valeurs du plateau complet Bambu, divisées par pieces_per_plate. Les charges fiscales (cotisations, VFL, autres taxes) sont incluses dans le prix via prix_ht_net = prix_ht / (1 - total_charges).
Thread Home Assistant (_ha_sync_once(), ligne ~2103)
- Poll toutes les N secondes (setting
ha_poll_interval, défaut 60s) - Connexions courtes par imprimante avec
isolation_level=None(autocommit) pour éviterdatabase is locked - Entités surveillées :
sensor.{prefix}_etat_de_l_impression,binary_sensor.{prefix}_erreur_d_impression,sensor.{prefix}_objets_ignores,sensor.{prefix}_avancement_de_l_impression
Multi-plateau
pieces_per_platesur le job = pièces par plateau physiqueorder_qty= quantité totale commandéetotal_plates_needed = ceil(order_qty / pieces_per_plate)(division entière ceiling)- "À planifier" sur le planning : affiche les jobs tant que
scheduled_plates < total_plates_needed - Compensation : si HA signale
pieces_ignored > 0sur un slot, les autres slots du job affichent un badge amber pour override le nb de pièces
Déjà imprimé (already_done)
Dans POST /api/slots/new : si already_done=true, les checks capacité/overlap sont sautés entièrement, slot inséré avec status='done'. L'ordre du check est important : already_done est testé avant _check_overlap et _check_capacity.
Gantt (planning.html, ligne ~366)
start: new Date(now.getTime() - 12 * 3600000), // -12h pour voir les impressions en cours
end: new Date(now.getTime() + 14 * 86400000),
Indisponibilités par plage (calendar_blocks)
Colonne date_end optionnelle. Requêtes avec COALESCE(date_end, date) pour compatibilité ascendante.
Duplication avec re-parse Nextcloud (source_nc_path)
- Nouveau champ DB
source_nc_path: chemin WebDAV complet du fichier.3mf - À la création d'un job depuis Nextcloud, le path est sauvegardé dans un hidden input
- Au chargement de
new_jobavecclone_from,autoParseOnClone()appelle/api/nextcloud/parse?path=...et reconstitue le sélecteur de plateaux
Bugs résolus dans cette session
| Bug | Fix |
|---|---|
database is locked lors de la création de job |
WAL mode + connexions autocommit par imprimante dans le thread HA |
| Slot HA-synced invisible dans le Gantt | Gantt démarre 12h avant maintenant |
| Vérification capacité bloquait "Déjà imprimé" | Check already_done déplacé avant _check_capacity |
| Vérification capacité bloquait sur impressions en cours | Filtre AND planned_end > datetime('now') |
| "À planifier" ne montrait qu'un seul plateau | Requête compare scheduled_plates < total_plates_needed |
datetime.utcnow() deprecated |
Remplacé par datetime.now(timezone.utc).replace(tzinfo=None) |
INSERT jobs : 36 ? pour 37 colonnes |
Ajout du ? manquant pour marge_pct_on_ht |
| jobs.html tronqué après écriture Python | Toujours partir d'une version git saine avant modification |
Points d'attention / dette technique
Ce qui est propre
- WAL mode SQLite : pas de blocage lecture/écriture
- Thread HA : connexions courtes, autocommit, pas de transaction longue
- Migrations cumulatives dans
init_db()avectry/exceptpar ligne login_requireddécorateur +require_login_globalpour protection globale si OIDC activé
À surveiller
- jobs.html : toujours utiliser le script Python pour modifier, vérifier la syntaxe Jinja2 après chaque modif avec
python3 -c "import jinja2; ..." - app.py : après ajout de docstrings avec des accents, vérifier
python3 -m py_compile app.py— les chaînes avec apostrophes dans les docstrings"""sont valides mais certaines substitutions de texte peuvent créer des conflits - INSERT jobs : 38 colonnes = 38
?. Si on ajoute une colonne, ajouter aussi le?ET la valeur dans le tuple
Fonctionnalité pas encore implémentée
- Recalcul de
marge_pct_on_htpour les jobs existants (valeur = 0 pour les anciens jobs) - Stats : le gaspillage filament (
total_wasted_g) n'est pas encore affiché dans les stats globales - Page mobile : n'enregistre pas
source_nc_path(pas critique)
Commandes utiles
# Vérifier la syntaxe Python
python3 -m py_compile /sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py
# Vérifier la syntaxe Jinja2 d'un template
python3 -c "
import jinja2
env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'))
env.parse(open('templates/jobs.html').read())
print('OK')
"
# Voir les routes Flask
grep -n "^@app.route" /sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py
# Chercher dans app.py sans tronquer
grep -n "mot_clé" /sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py
Git
Le dépôt est sur Windows — les commandes git doivent être exécutées depuis un terminal Windows, pas depuis le shell Linux (le fichier git/index.lock bloque les commits depuis Linux).
cd C:\Users\jbper\Claude\3d-pricing
git add -A
git commit -m "description"
Derniers commits :
3da19f5 feat: auto-reparse Nextcloud 3mf on job clone
0069ab2 fix: INSERT jobs missing ? for marge_pct_on_ht
80307ac feat: plate_name and total_marge in jobs list
f51a789 fix: HA sync per-printer autocommit connections
522a0c5 fix: WAL mode SQLite
b55edce fix: skip capacity/overlap when already_done=true
7824124 fix: replace deprecated utcnow()
1899165 fix: gantt starts 12h before now
Chemins importants
| Quoi | Chemin Windows | Chemin Linux (bash) |
|---|---|---|
| Dossier projet | C:\Users\jbper\Claude\3d-pricing\ |
/sessions/festive-sharp-bardeen/mnt/3d-pricing/ |
| app.py | C:\Users\jbper\Claude\3d-pricing\app.py |
/sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py |
| Templates | C:\Users\jbper\Claude\3d-pricing\templates\ |
/sessions/festive-sharp-bardeen/mnt/3d-pricing/templates/ |
| Base de données | Dans le container Docker : /data/pricing.db |
— |