[DTP-Worker 20260813_020316] Auto exec · session 20260813_020316
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Reproductibilité des artefacts out/ (build == commité) (push) Has been cancelled
CI / Fraîcheur matrice de régression (run == commité) (push) Has been cancelled
CI / Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés) (push) Has been cancelled
CI / Intégrité des chiffres du README (valeur == artefact cité · (push) Has been cancelled
CI / Intégrité mobile-build.yml (gating portable · activation différée · (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / CRM · Financement Bancaire (gate hypothécaire RD) (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / Chat OTOIA · montage par portail (Custom Block) (push) Has been cancelled
CI / QA · Audit 4Big (95+/100 sur 100% deliverables) (push) Has been cancelled
CI / Démo · Scénarios (run-sheet P07 banquier / P05 client) (push) Has been cancelled
CI / QA · Matrice de régression exhaustive (Sprint 8) (push) Has been cancelled
CI / DevOps · Run-book de déploiement VPS unifié (Sprint 8) (push) Has been cancelled
CI / QA · Matrice d'acceptation / traçabilité MVP (Sprint 8) (push) Has been cancelled
CI / Mobile · config app Expo/EAS (navigation par rôle) (push) Has been cancelled
CI / PIE · manifest de dépendances (Annexe 12 · V10.1) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Reproductibilité des artefacts out/ (build == commité) (push) Has been cancelled
CI / Fraîcheur matrice de régression (run == commité) (push) Has been cancelled
CI / Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés) (push) Has been cancelled
CI / Intégrité des chiffres du README (valeur == artefact cité · (push) Has been cancelled
CI / Intégrité mobile-build.yml (gating portable · activation différée · (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / CRM · Financement Bancaire (gate hypothécaire RD) (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / Chat OTOIA · montage par portail (Custom Block) (push) Has been cancelled
CI / QA · Audit 4Big (95+/100 sur 100% deliverables) (push) Has been cancelled
CI / Démo · Scénarios (run-sheet P07 banquier / P05 client) (push) Has been cancelled
CI / QA · Matrice de régression exhaustive (Sprint 8) (push) Has been cancelled
CI / DevOps · Run-book de déploiement VPS unifié (Sprint 8) (push) Has been cancelled
CI / QA · Matrice d'acceptation / traçabilité MVP (Sprint 8) (push) Has been cancelled
CI / Mobile · config app Expo/EAS (navigation par rôle) (push) Has been cancelled
CI / PIE · manifest de dépendances (Annexe 12 · V10.1) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
"""Master Data Model V18 — bibliothèque (Phase 1 · Master Project Intake).
|
||||
|
||||
Débloquée par `V18_GO_PHASE_1_20260812.md` (D-06 APPROVED) +
|
||||
`V18_DECISIONS_D07_D08_20260812.md` (D-08). Voir `../README.md`.
|
||||
|
||||
Expose le contrat en mémoire du Master Dataset (One Master Dataset · Multiple
|
||||
Outputs) : statuts, catégories, DataPoint traçable, registres documentaires.
|
||||
Aucun moteur métier ici (Zéro moteur tant que le data model n'est pas validé au
|
||||
CP0 · audit §11).
|
||||
"""
|
||||
|
||||
from .master_data_model import (
|
||||
CANONICAL,
|
||||
CATEGORIES,
|
||||
STATUSES,
|
||||
DataPoint,
|
||||
MasterDataError,
|
||||
assert_not_canonical,
|
||||
is_filled,
|
||||
is_present,
|
||||
)
|
||||
from .evidence_register import (
|
||||
Document,
|
||||
DocumentRegister,
|
||||
EvidenceRegister,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CANONICAL",
|
||||
"CATEGORIES",
|
||||
"STATUSES",
|
||||
"DataPoint",
|
||||
"MasterDataError",
|
||||
"assert_not_canonical",
|
||||
"is_filled",
|
||||
"is_present",
|
||||
"Document",
|
||||
"DocumentRegister",
|
||||
"EvidenceRegister",
|
||||
]
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Evidence Register V18 · registre documentaire + traçabilité des preuves.
|
||||
|
||||
Phase 1 · brique « Data Register + Evidence Register (traçabilité) » de
|
||||
`V18_GO_PHASE_1_20260812.md` : « Register documents / files / versions ». C'est
|
||||
l'amorce en mémoire du futur moteur 2 (Document / Evidence Engine · audit §11) —
|
||||
ici on livre le REGISTRE et ses invariants d'intégrité, pas encore l'extraction.
|
||||
|
||||
Deux registres complémentaires :
|
||||
|
||||
* DocumentRegister · catalogue des pièces probantes (id · titre · fichier ·
|
||||
version · date). Une pièce = une source citable, versionnée (audit §4.3 :
|
||||
baseline vs actual = vues horodatées, jamais un écrasement).
|
||||
* EvidenceRegister · lie chaque `DataPoint.key` à la (aux) pièce(s) qui la
|
||||
justifie(nt). Ferme la « provenance fantôme » : un DataPoint SOURCE_BASED
|
||||
dont le `source_document` ne résout à AUCUNE pièce enregistrée est un lien
|
||||
pendouillant → détecté ici (`dangling_evidence`).
|
||||
|
||||
Anti-invention (#6) : le registre n'invente aucune pièce ; il ne fait que
|
||||
cataloguer ce qui est déclaré et VÉRIFIER la cohérence référentielle. stdlib pur,
|
||||
aucune écriture VPS (#8).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from .master_data_model import DataPoint, MasterDataError
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Document:
|
||||
"""Une pièce probante cataloguée (audit §18 · Evidence Room)."""
|
||||
|
||||
doc_id: str
|
||||
title: str
|
||||
filename: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
source_date: Optional[str] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not self.doc_id or not str(self.doc_id).strip():
|
||||
raise MasterDataError("Document.doc_id ne peut être vide.")
|
||||
if not self.title or not str(self.title).strip():
|
||||
raise MasterDataError("Document.title ne peut être vide.")
|
||||
|
||||
|
||||
class DocumentRegister:
|
||||
"""Catalogue des pièces probantes, indexé par `doc_id` unique."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._docs: dict[str, Document] = {}
|
||||
|
||||
def add(self, doc: Document) -> Document:
|
||||
if doc.doc_id in self._docs:
|
||||
raise MasterDataError(
|
||||
f"doc_id dupliqué « {doc.doc_id} » — l'id de pièce doit être "
|
||||
f"unique (registre = source citable non ambiguë)."
|
||||
)
|
||||
self._docs[doc.doc_id] = doc
|
||||
return doc
|
||||
|
||||
def get(self, doc_id: str) -> Optional[Document]:
|
||||
return self._docs.get(doc_id)
|
||||
|
||||
def has(self, doc_id: str) -> bool:
|
||||
return doc_id in self._docs
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._docs)
|
||||
|
||||
def ids(self) -> list[str]:
|
||||
return sorted(self._docs)
|
||||
|
||||
|
||||
class EvidenceRegister:
|
||||
"""Lie les DataPoint à leurs pièces + vérifie l'intégrité référentielle."""
|
||||
|
||||
def __init__(self, documents: Optional[DocumentRegister] = None) -> None:
|
||||
self.documents: DocumentRegister = documents or DocumentRegister()
|
||||
# key du DataPoint -> ensemble ordonné de doc_id justificatifs
|
||||
self._links: dict[str, list[str]] = {}
|
||||
|
||||
def link(self, data_key: str, doc_id: str) -> None:
|
||||
"""Rattache la donnée `data_key` à la pièce `doc_id` (déjà cataloguée)."""
|
||||
if not self.documents.has(doc_id):
|
||||
raise MasterDataError(
|
||||
f"lien vers une pièce inconnue « {doc_id} » — la cataloguer "
|
||||
f"d'abord dans le DocumentRegister (pas de preuve fantôme)."
|
||||
)
|
||||
bucket = self._links.setdefault(data_key, [])
|
||||
if doc_id not in bucket:
|
||||
bucket.append(doc_id)
|
||||
|
||||
def evidence_for(self, data_key: str) -> list[str]:
|
||||
"""Pièces justificatives (doc_id) déclarées pour cette donnée."""
|
||||
return list(self._links.get(data_key, ()))
|
||||
|
||||
def dangling_evidence(self, data_points: Iterable[DataPoint]) -> list[str]:
|
||||
"""Clés de DataPoint SOURCE_BASED dont la pièce citée n'est PAS cataloguée.
|
||||
|
||||
C'est le cœur de l'intégrité : un DataPoint qui prétend venir d'un
|
||||
document (`status == SOURCE_BASED`) doit pouvoir résoudre ce document —
|
||||
soit via un lien Evidence, soit via un `source_document` == doc_id connu.
|
||||
Toute clé retournée est une provenance non résoluble (à corriger).
|
||||
"""
|
||||
missing: list[str] = []
|
||||
for dp in data_points:
|
||||
if dp.status != "SOURCE_BASED":
|
||||
continue
|
||||
linked = self.evidence_for(dp.key)
|
||||
resolves = any(self.documents.has(d) for d in linked) or (
|
||||
dp.source_document is not None
|
||||
and self.documents.has(dp.source_document)
|
||||
)
|
||||
if not resolves:
|
||||
missing.append(dp.key)
|
||||
return sorted(missing)
|
||||
@@ -0,0 +1,222 @@
|
||||
"""Master Data Model V18 · statuts de donnée + catégories + DataPoint.
|
||||
|
||||
Phase 1 du moteur V18 (`DIRECTIVE_V18_MASTER_FEASIBILITY_ENGINE_20260810.md`
|
||||
« ORDRE DÉVELOPPEMENT · 1. Master Project Intake / Data Model »), débloquée par
|
||||
`V18_GO_PHASE_1_20260812.md` (D-06 APPROVED) et `V18_DECISIONS_D07_D08_20260812.md`
|
||||
(D-08 : Master Intake A1-A20 = sur-ensemble STRICT de `brief.json` V12).
|
||||
|
||||
Principe « One Master Dataset · Multiple Outputs » : une donnée n'est jamais un
|
||||
chiffre nu. C'est un `DataPoint` qui porte SA PROVENANCE — statut de fiabilité,
|
||||
document source, date, confiance, validateur. Les 3 sorties V18 (Bank Package /
|
||||
Full Feasibility / Ops) sont des PROJECTIONS de ce dataset, jamais des copies
|
||||
éditables (audit §4/§7).
|
||||
|
||||
Anti-invention (CLAUDE.md #6 · directive V18 « NE PAS inventer données ») : le
|
||||
modèle n'attribue AUCUNE valeur ni statut par défaut « optimiste ». Une donnée
|
||||
absente est explicitement `MISSING`/`PENDING` — la complétude devient une mesure
|
||||
honnête (audit §4, « la fraction de champs absents explosera au démarrage… ce
|
||||
n'est pas un bug »).
|
||||
|
||||
Ce module ne FAIT rien d'externe : ni I/O réseau, ni écriture VPS (#8). stdlib
|
||||
pur. Il définit le contrat en mémoire ; le schéma machine miroir est
|
||||
`../master_intake.schema.json` (défs `data_status` / `data_category` /
|
||||
`data_point`), tenu identique par la suite de tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Les 11 STATUTS de donnée (V18_GO_PHASE_1 §« Data status obligatoire »).
|
||||
# ORDRE CANONIQUE = du plus fiable (prouvé/vérifié) au moins engageant
|
||||
# (non applicable). Cet ordre est un CONTRAT : le schéma `data_status.enum` et la
|
||||
# suite de tests le rejouent à l'identique (aucune ré-ordonnance silencieuse).
|
||||
#
|
||||
# VERIFIED · vérifié contre une pièce probante (audit/tiers)
|
||||
# CONFIRMED · confirmé par une partie prenante autorisée
|
||||
# SOURCE_BASED · repris verbatim d'un document source daté
|
||||
# CALCULATED · dérivé par formule traçable d'opérandes sourcés
|
||||
# ESTIMATED · estimation méthodologique déclarée (non un fait)
|
||||
# ASSUMPTION · hypothèse de travail explicite (à valider)
|
||||
# TARGET · objectif fixé (cible), pas une donnée constatée
|
||||
# BANK_REQUIREMENT · exigence imposée par la banque / le prêteur
|
||||
# PENDING · attendu, en cours de collecte
|
||||
# MISSING · absent — devrait exister, non fourni
|
||||
# NOT_APPLICABLE · sans objet pour ce projet
|
||||
# --------------------------------------------------------------------------- #
|
||||
STATUSES: tuple[str, ...] = (
|
||||
"VERIFIED",
|
||||
"CONFIRMED",
|
||||
"SOURCE_BASED",
|
||||
"CALCULATED",
|
||||
"ESTIMATED",
|
||||
"ASSUMPTION",
|
||||
"TARGET",
|
||||
"BANK_REQUIREMENT",
|
||||
"PENDING",
|
||||
"MISSING",
|
||||
"NOT_APPLICABLE",
|
||||
)
|
||||
|
||||
# Statuts qui dénotent une donnée EXPLOITABLE (présente et défendable) vs une
|
||||
# lacune. Sert au futur gate CP0 (Document Completeness, moteur 2 · audit §6).
|
||||
_ABSENT_STATUSES: frozenset[str] = frozenset({"PENDING", "MISSING"})
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Les 5 CATÉGORIES de séparation (V18_GO_PHASE_1 §« Séparation INPUT / TARGET /
|
||||
# ASSUMPTION / BANK_REQUIREMENT / CALCULATED »). La CATÉGORIE dit le RÔLE de la
|
||||
# donnée dans le modèle ; le STATUT dit sa FIABILITÉ. (TARGET / ASSUMPTION /
|
||||
# BANK_REQUIREMENT / CALCULATED existent dans les deux axes à dessein : un même
|
||||
# libellé peut être un rôle ET un état — ils ne sont pas confondus.)
|
||||
# --------------------------------------------------------------------------- #
|
||||
CATEGORIES: tuple[str, ...] = (
|
||||
"INPUT", # donnée d'entrée fournie (le brief V12 vit ici)
|
||||
"TARGET", # objectif projet (ex. absorption cible)
|
||||
"ASSUMPTION", # hypothèse de modélisation
|
||||
"BANK_REQUIREMENT", # contrainte bancaire imposée
|
||||
"CALCULATED", # sortie dérivée du moteur (jamais saisie)
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Paramètres CANONIQUES CLAUDE.md #9/#10 — RÉIMPOSÉS à l'identique du générateur
|
||||
# V12 (`generator/genlib/model.py CANONICAL`), conformément à l'audit §8 non-
|
||||
# négociable #9 : « À réimposer identiques dans le Master Data Model V18 (ne pas
|
||||
# les rendre saisissables) ». Ils ne sont JAMAIS des DataPoint d'intake : ils
|
||||
# sont IMPOSÉS par le moteur pour tous les projets → aucune invention projet par
|
||||
# projet possible. La suite de tests re-vérifie l'égalité stricte avec la valeur
|
||||
# du générateur (single source of truth : toute dérive est rouge).
|
||||
# --------------------------------------------------------------------------- #
|
||||
CANONICAL: dict[str, str] = {
|
||||
"frais_edition_pct": "3 %",
|
||||
"marketing_pct": "8.5 %",
|
||||
"point_equilibre_pct": "52 %",
|
||||
"devises": "USD + DOP",
|
||||
"paiement": "Cardnet",
|
||||
"format_doc": "Letter US",
|
||||
}
|
||||
|
||||
# Clés réservées : ne peuvent JAMAIS être une donnée d'intake saisissable.
|
||||
_CANONICAL_KEYS: frozenset[str] = frozenset(CANONICAL)
|
||||
|
||||
|
||||
class MasterDataError(ValueError):
|
||||
"""Violation du contrat Master Data Model (statut/catégorie/canonique)."""
|
||||
|
||||
|
||||
def is_present(status: str) -> bool:
|
||||
"""True si le statut dénote une donnée exploitable (ni PENDING ni MISSING)."""
|
||||
return status in STATUSES and status not in _ABSENT_STATUSES
|
||||
|
||||
|
||||
def assert_not_canonical(key: str) -> None:
|
||||
"""Rejette toute tentative de saisir un paramètre canonique comme intake.
|
||||
|
||||
Les 6 constantes #9/#10 sont IMPOSÉES par le moteur, jamais fournies par le
|
||||
brief → les capter comme DataPoint rouvrirait la porte à l'invention projet
|
||||
par projet (audit §8 · l'invariant de migration le plus important).
|
||||
"""
|
||||
if key in _CANONICAL_KEYS:
|
||||
raise MasterDataError(
|
||||
f"« {key} » est un paramètre CANONIQUE imposé (#9/#10) — "
|
||||
f"interdit comme donnée d'intake saisissable (audit §8)."
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DataPoint:
|
||||
"""Une donnée du Master Dataset AVEC sa provenance (audit §4/§5).
|
||||
|
||||
Le patron « valeur = {donnée + provenance + statut} » généralise le
|
||||
`{formule + opérandes sourcés/null}` déjà éprouvé dans `banclib/finance.py`
|
||||
(audit §5 : « exactement le bon patron pour un moteur financier auditable »).
|
||||
|
||||
Champs de traçabilité (V18_GO_PHASE_1 §« Data Register + Evidence Register :
|
||||
chaque donnée: source_document · source_date · status · confidence ·
|
||||
validated_by »).
|
||||
"""
|
||||
|
||||
key: str
|
||||
value: Any
|
||||
category: str
|
||||
status: str
|
||||
source_document: Optional[str] = None
|
||||
source_date: Optional[str] = None
|
||||
confidence: Optional[float] = None
|
||||
validated_by: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
note: Optional[str] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not self.key or not str(self.key).strip():
|
||||
raise MasterDataError("DataPoint.key ne peut être vide.")
|
||||
assert_not_canonical(self.key)
|
||||
if self.category not in CATEGORIES:
|
||||
raise MasterDataError(
|
||||
f"catégorie inconnue « {self.category} » "
|
||||
f"(attendu ∈ {CATEGORIES})."
|
||||
)
|
||||
if self.status not in STATUSES:
|
||||
raise MasterDataError(
|
||||
f"statut inconnu « {self.status} » (attendu ∈ {STATUSES})."
|
||||
)
|
||||
if self.confidence is not None:
|
||||
if not isinstance(self.confidence, (int, float)) or isinstance(
|
||||
self.confidence, bool
|
||||
):
|
||||
raise MasterDataError("confidence doit être un nombre 0..1 ou None.")
|
||||
if not 0.0 <= float(self.confidence) <= 1.0:
|
||||
raise MasterDataError("confidence doit être dans [0, 1].")
|
||||
# Cohérence provenance ⟺ statut : une donnée SOURCE_BASED prétend venir
|
||||
# d'un document. Sa provenance peut être fournie SOIT en ligne
|
||||
# (source_document), SOIT par un lien de l'Evidence Register — un
|
||||
# DataPoint isolé ne connaît pas le registre, donc la complétude de
|
||||
# provenance est une propriété du DATASET (EvidenceRegister.
|
||||
# dangling_evidence), pas du point pris seul. On ne la vérifie donc PAS
|
||||
# ici (sinon on interdirait le chemin « preuve par lien »).
|
||||
#
|
||||
# Symétriquement, un trou (PENDING/MISSING) ne peut pas porter une
|
||||
# valeur non nulle — ce serait une donnée déguisée en lacune.
|
||||
if self.status in _ABSENT_STATUSES and is_filled(self.value):
|
||||
raise MasterDataError(
|
||||
f"« {self.key} » est {self.status} mais porte une valeur — "
|
||||
f"une lacune ne peut pas contenir de donnée."
|
||||
)
|
||||
|
||||
@property
|
||||
def present(self) -> bool:
|
||||
"""Raccourci : la donnée est-elle exploitable (pas une lacune) ?"""
|
||||
return is_present(self.status)
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Projection sérialisable (ordre de clés stable pour byte-repro)."""
|
||||
return {
|
||||
"key": self.key,
|
||||
"value": self.value,
|
||||
"category": self.category,
|
||||
"status": self.status,
|
||||
"source_document": self.source_document,
|
||||
"source_date": self.source_date,
|
||||
"confidence": self.confidence,
|
||||
"validated_by": self.validated_by,
|
||||
"unit": self.unit,
|
||||
"note": self.note,
|
||||
}
|
||||
|
||||
|
||||
def is_filled(value: Any) -> bool:
|
||||
"""True si `value` est une donnée réelle (pas None ni placeholder vide).
|
||||
|
||||
Aligné sur `generator/genlib/model.py is_filled` : un vrai 0 numérique est
|
||||
une donnée valide ; None / chaîne vide / liste vide ne le sont pas.
|
||||
"""
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, bool):
|
||||
return True
|
||||
if isinstance(value, (int, float)):
|
||||
return True
|
||||
if isinstance(value, (list, tuple, dict)):
|
||||
return len(value) > 0
|
||||
return bool(str(value).strip())
|
||||
Reference in New Issue
Block a user