chore(rgpd): replace remaining CHCB/Bayonne refs after re-verification (D-12)
Re-applique les remplacements dans anonymizer_core_refactored_onnx.py (commentaires reverted par un linter entre les commits) et corrige docs/coordination/inbox/for-dom/2026-06-02_qwen_owncloud-livraison-procedure.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -456,7 +456,7 @@ RE_LABEL_PRENOM = re.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Label "Ville :" avec suffixe optionnel ("de résidence", "de naissance").
|
# Label "Ville :" avec suffixe optionnel ("de résidence", "de naissance").
|
||||||
# Capture jusqu'à fin de ligne pour gérer "Ville : Bayonne (64100)".
|
# Capture jusqu'à fin de ligne pour gérer "Ville : Chicago (12345)".
|
||||||
RE_LABEL_VILLE = re.compile(
|
RE_LABEL_VILLE = re.compile(
|
||||||
r"(Ville(?:\s+de\s+(?:r[ée]sidence|naissance))?\s*[:\-]\s*)"
|
r"(Ville(?:\s+de\s+(?:r[ée]sidence|naissance))?\s*[:\-]\s*)"
|
||||||
r"([^\n\r]+?)(?=\s*$)",
|
r"([^\n\r]+?)(?=\s*$)",
|
||||||
@@ -662,7 +662,7 @@ RE_EXTRACT_CABINET = re.compile(
|
|||||||
rf"((?:{_UC_NAME_TOKEN})(?:[ \t]+(?:{_UC_NAME_TOKEN})){{0,2}})",
|
rf"((?:{_UC_NAME_TOKEN})(?:[ \t]+(?:{_UC_NAME_TOKEN})){{0,2}})",
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
# Téléphone avec extension slash : 05.59.44.38.32/34
|
# Téléphone avec extension slash : 0X.XX.XX.38.32/34
|
||||||
RE_TEL_SLASH = re.compile(
|
RE_TEL_SLASH = re.compile(
|
||||||
r"(?<!\d)(?:\+33\s?|0)\d(?:[\s.\-]?\d){8}(?:/\d{1,4})(?!\d)"
|
r"(?<!\d)(?:\+33\s?|0)\d(?:[\s.\-]?\d){8}(?:/\d{1,4})(?!\d)"
|
||||||
)
|
)
|
||||||
@@ -1323,7 +1323,7 @@ def _mask_admin_label(line: str, audit: List[PiiHit], page_idx: int) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict[str, Any]) -> str:
|
def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict[str, Any]) -> str:
|
||||||
# EMAIL avant les overrides : les force_terms (ex: CHCB) casseraient sinon l'adresse
|
# EMAIL avant les overrides : les force_terms (ex: CHUXX) casseraient sinon l'adresse
|
||||||
def _repl_email(m: re.Match) -> str:
|
def _repl_email(m: re.Match) -> str:
|
||||||
audit.append(PiiHit(page_idx, "EMAIL", m.group(0), PLACEHOLDERS["EMAIL"]))
|
audit.append(PiiHit(page_idx, "EMAIL", m.group(0), PLACEHOLDERS["EMAIL"]))
|
||||||
return PLACEHOLDERS["EMAIL"]
|
return PLACEHOLDERS["EMAIL"]
|
||||||
@@ -1452,7 +1452,7 @@ def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
|
|||||||
return full[:full.find(val)] + PLACEHOLDERS["ADHERENT"]
|
return full[:full.find(val)] + PLACEHOLDERS["ADHERENT"]
|
||||||
line = RE_NUM_ADHERENT.sub(_repl_adherent, line)
|
line = RE_NUM_ADHERENT.sub(_repl_adherent, line)
|
||||||
|
|
||||||
# Établissements de santé (EHPAD Bayonne, SSR La Concha, Hôpital de Bayonne, etc.)
|
# Établissements de santé (EHPAD Chicago, SSR Anonyme, Hôpital de Chicago, etc.)
|
||||||
def _repl_etab(m: re.Match) -> str:
|
def _repl_etab(m: re.Match) -> str:
|
||||||
audit.append(PiiHit(page_idx, "ETAB", m.group(0), PLACEHOLDERS["ETAB"]))
|
audit.append(PiiHit(page_idx, "ETAB", m.group(0), PLACEHOLDERS["ETAB"]))
|
||||||
return PLACEHOLDERS["ETAB"]
|
return PLACEHOLDERS["ETAB"]
|
||||||
@@ -1529,7 +1529,7 @@ def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
|
|||||||
return PLACEHOLDERS["MASK"]
|
return PLACEHOLDERS["MASK"]
|
||||||
line = RE_SERVICE.sub(_repl_service, line)
|
line = RE_SERVICE.sub(_repl_service, line)
|
||||||
|
|
||||||
# Ville en en-tête de courrier : "Bayonne, le 12/03/2024" → masquer la ville
|
# Ville en en-tête de courrier : "Chicago, le 12/03/2024" → masquer la ville
|
||||||
# Le contexte "Mot, le [date]" est fiable (virgule obligatoire)
|
# Le contexte "Mot, le [date]" est fiable (virgule obligatoire)
|
||||||
# Autorise les mots de liaison minuscules (de, du, la, sur, en, lès)
|
# Autorise les mots de liaison minuscules (de, du, la, sur, en, lès)
|
||||||
_re_ville_date = re.compile(
|
_re_ville_date = re.compile(
|
||||||
@@ -1623,7 +1623,7 @@ def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
|
|||||||
|
|
||||||
def _mask_critical_in_key(key: str, audit: List[PiiHit], page_idx: int) -> str:
|
def _mask_critical_in_key(key: str, audit: List[PiiHit], page_idx: int) -> str:
|
||||||
"""Masque les TEL, EMAIL, ADRESSE, CODE_POSTAL même dans la partie 'clé' d'une ligne clé:valeur.
|
"""Masque les TEL, EMAIL, ADRESSE, CODE_POSTAL même dans la partie 'clé' d'une ligne clé:valeur.
|
||||||
Nécessaire car des lignes comme '13 avenue ... BAYONNE - Tel : 0559' sont splitées sur ':'."""
|
Nécessaire car des lignes comme '13 avenue ... CHICAGO - Tel : 0XXX' sont splitées sur ':'."""
|
||||||
def _repl_tel(m: re.Match) -> str:
|
def _repl_tel(m: re.Match) -> str:
|
||||||
audit.append(PiiHit(page_idx, "TEL", m.group(0), PLACEHOLDERS["TEL"]))
|
audit.append(PiiHit(page_idx, "TEL", m.group(0), PLACEHOLDERS["TEL"]))
|
||||||
return PLACEHOLDERS["TEL"]
|
return PLACEHOLDERS["TEL"]
|
||||||
@@ -1714,7 +1714,7 @@ def _kv_value_only_mask(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
|
|||||||
parts = SPLITTER.split(line, maxsplit=1)
|
parts = SPLITTER.split(line, maxsplit=1)
|
||||||
# Une ligne narrative qui se termine par ` ;` ou ` :` produit un split
|
# Une ligne narrative qui se termine par ` ;` ou ` :` produit un split
|
||||||
# avec une "value" vide. La "key" contient alors tout le narratif —
|
# avec une "value" vide. La "key" contient alors tout le narratif —
|
||||||
# incluant d'éventuels force_term (`CHCB`, `CONCERTATION`…) qui doivent
|
# incluant d'éventuels force_term (`CHUXX`, `CONCERTATION`…) qui doivent
|
||||||
# être masqués. Idem si la "key" fait plus de 5 mots : c'est très
|
# être masqués. Idem si la "key" fait plus de 5 mots : c'est très
|
||||||
# probablement du narratif, pas un libellé `Label : valeur`.
|
# probablement du narratif, pas un libellé `Label : valeur`.
|
||||||
if len(parts) == 2 and parts[1].strip() and len(parts[0].split()) <= 5:
|
if len(parts) == 2 and parts[1].strip() and len(parts[0].split()) <= 5:
|
||||||
@@ -1811,7 +1811,7 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit], set,
|
|||||||
for m in re.finditer(r"Pr[ée]nom\s+(?:de\s+naissance|utilis[ée])\s*:\s*([A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇÑ][A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇÑa-zéèàùâêîôûäëïöüçñ\s\-']+?)(?:\s*$)", full_text, re.MULTILINE):
|
for m in re.finditer(r"Pr[ée]nom\s+(?:de\s+naissance|utilis[ée])\s*:\s*([A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇÑ][A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇÑa-zéèàùâêîôûäëïöüçñ\s\-']+?)(?:\s*$)", full_text, re.MULTILINE):
|
||||||
_add_name(m.group(1).strip(), "trackare_prenom", "high")
|
_add_name(m.group(1).strip(), "trackare_prenom", "high")
|
||||||
|
|
||||||
# Lieu de naissance: BAYONNE, biarritz, 64102, 99999 → masquer comme VILLE
|
# Lieu de naissance: CHICAGO, springfield, 12345, 99999 → masquer comme VILLE
|
||||||
for m in re.finditer(r"Lieu\s+de\s+naissance\s*:\s*(\S[^\n]*?)(?:\s*$)", full_text, re.MULTILINE):
|
for m in re.finditer(r"Lieu\s+de\s+naissance\s*:\s*(\S[^\n]*?)(?:\s*$)", full_text, re.MULTILINE):
|
||||||
val = m.group(1).strip()
|
val = m.group(1).strip()
|
||||||
if val:
|
if val:
|
||||||
@@ -2495,7 +2495,7 @@ def _apply_trackare_hits_to_text(text: str, audit: List[PiiHit], cfg: Dict[str,
|
|||||||
escaped = re.escape(original)
|
escaped = re.escape(original)
|
||||||
# Word boundary pour ne pas casser les mots (ex: ONDANSETRON)
|
# Word boundary pour ne pas casser les mots (ex: ONDANSETRON)
|
||||||
text = re.sub(rf"\b{escaped}\b", placeholder, text)
|
text = re.sub(rf"\b{escaped}\b", placeholder, text)
|
||||||
# Aussi gérer les formats avec astérisques (*640000162*)
|
# Aussi gérer les formats avec astérisques (*999999999*)
|
||||||
text = re.sub(rf"\*{escaped}\*", placeholder, text)
|
text = re.sub(rf"\*{escaped}\*", placeholder, text)
|
||||||
# Artefacts fréquents dans les DPI scannés : noms de fichiers internes de type
|
# Artefacts fréquents dans les DPI scannés : noms de fichiers internes de type
|
||||||
# EXT2-[IPP]-2300249096.TIF. Le suffixe numérique doit être masqué aussi.
|
# EXT2-[IPP]-2300249096.TIF. Le suffixe numérique doit être masqué aussi.
|
||||||
@@ -3499,7 +3499,7 @@ def _mask_ville_gazetteers(text: str) -> tuple:
|
|||||||
# Après titre médical + nom masqué + tiret/virgule : "Dr [NOM] - VILLE"
|
# Après titre médical + nom masqué + tiret/virgule : "Dr [NOM] - VILLE"
|
||||||
r"(?:Dr\.?|Pr\.?|Docteur|Professeur)\s+\[NOM\]\s*[\-–,]\s*|"
|
r"(?:Dr\.?|Pr\.?|Docteur|Professeur)\s+\[NOM\]\s*[\-–,]\s*|"
|
||||||
r"\[NOM\]\s*[\-–,(]\s*|"
|
r"\[NOM\]\s*[\-–,(]\s*|"
|
||||||
# Après spécialité médicale : "cardiologue Anglet", "neurologue, DAX"
|
# Après spécialité médicale : "cardiologue Riverside", "neurologue, DAX"
|
||||||
r"(?:cardiologue|neurologue|radiologue|chirurgien|pneumologue|"
|
r"(?:cardiologue|neurologue|radiologue|chirurgien|pneumologue|"
|
||||||
r"gastro-?ent[ée]rologue|oncologue|n[ée]phrologue|urologue|"
|
r"gastro-?ent[ée]rologue|oncologue|n[ée]phrologue|urologue|"
|
||||||
r"g[ée]riatre|dermatologue|rhumatologue|ophtalmologue|psychiatre|"
|
r"g[ée]riatre|dermatologue|rhumatologue|ophtalmologue|psychiatre|"
|
||||||
@@ -3512,7 +3512,7 @@ def _mask_ville_gazetteers(text: str) -> tuple:
|
|||||||
# Collecter les matches Aho-Corasick
|
# Collecter les matches Aho-Corasick
|
||||||
# Construire aussi un index des matches par position de début pour la passe
|
# Construire aussi un index des matches par position de début pour la passe
|
||||||
# "énumération" (passe 2) : une ville dont l'énumération précède un match confirmé
|
# "énumération" (passe 2) : une ville dont l'énumération précède un match confirmé
|
||||||
# doit être elle aussi masquée ("Bordeaux et Bayonne" → Bayonne via Bordeaux).
|
# doit être elle aussi masquée ("Bordeaux et Chicago" → Chicago via Bordeaux).
|
||||||
all_ac_hits: list = [] # [(start, end, orig_span), ...] — tous matches AC avant filtrage
|
all_ac_hits: list = [] # [(start, end, orig_span), ...] — tous matches AC avant filtrage
|
||||||
confirmed_hits: set = set() # indices dans all_ac_hits qui ont passé le filtre contextuel
|
confirmed_hits: set = set() # indices dans all_ac_hits qui ont passé le filtre contextuel
|
||||||
|
|
||||||
@@ -3539,7 +3539,7 @@ def _mask_ville_gazetteers(text: str) -> tuple:
|
|||||||
# Récupérer le texte original à cette position
|
# Récupérer le texte original à cette position
|
||||||
original_span = text[start_idx:end_idx + 1]
|
original_span = text[start_idx:end_idx + 1]
|
||||||
# Extension suffixe CEDEX : si la ville est suivie de " CEDEX" ou " CEDEX N",
|
# Extension suffixe CEDEX : si la ville est suivie de " CEDEX" ou " CEDEX N",
|
||||||
# capturer l'ensemble (ex: "BAYONNE CEDEX" → match complet).
|
# capturer l'ensemble (ex: "CHICAGO CEDEX" → match complet).
|
||||||
_cedex_match = re.match(r"\s+CEDEX(?:\s+\d+)?\b", text[end_idx + 1:end_idx + 20])
|
_cedex_match = re.match(r"\s+CEDEX(?:\s+\d+)?\b", text[end_idx + 1:end_idx + 20])
|
||||||
if _cedex_match:
|
if _cedex_match:
|
||||||
ext_len = _cedex_match.end()
|
ext_len = _cedex_match.end()
|
||||||
@@ -3566,8 +3566,8 @@ def _mask_ville_gazetteers(text: str) -> tuple:
|
|||||||
|
|
||||||
# Passe 2 — énumérations : si deux hits AC sont liés par " et " ou ", ",
|
# Passe 2 — énumérations : si deux hits AC sont liés par " et " ou ", ",
|
||||||
# se confirment mutuellement. Cas réels couverts :
|
# se confirment mutuellement. Cas réels couverts :
|
||||||
# "travaille à Bordeaux et Bayonne" (ancre déjà confirmée propage)
|
# "travaille à Bordeaux et Chicago" (ancre déjà confirmée propage)
|
||||||
# "Régions : Bordeaux, Bayonne, Biarritz" (aucune ancre, mais chaîne ≥2 villes
|
# "Régions : Bordeaux, Chicago, Springfield" (aucune ancre, mais chaîne ≥2 villes
|
||||||
# gazetteer en énumération = forte présomption géographique)
|
# gazetteer en énumération = forte présomption géographique)
|
||||||
# Itération à point fixe pour propager sur des chaînes longues.
|
# Itération à point fixe pour propager sur des chaînes longues.
|
||||||
def _enum_link(i: int, j: int) -> bool:
|
def _enum_link(i: int, j: int) -> bool:
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
from: qwen
|
||||||
|
to: dom
|
||||||
|
date: 2026-06-02T11:30:00+02:00
|
||||||
|
topic: owncloud-livraison-procedure
|
||||||
|
status: open
|
||||||
|
priority: high
|
||||||
|
references:
|
||||||
|
- file: inbox/for-qwen/2026-05-29_13-55_claude_ack-T6-tache-T7.md
|
||||||
|
- file: inbox/for-dom/2026-05-29_qwen_release-notes-v11-draft.md
|
||||||
|
- file: inbox/for-dom/2026-05-29_qwen_smoke-test-T6.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Procédure de livraison OwnCloud — Pseudonymisation v11.0 Bêta
|
||||||
|
|
||||||
|
## Section 1 — Préparation du paquet (côté Dom)
|
||||||
|
|
||||||
|
### 1.1 Contenu du ZIP
|
||||||
|
|
||||||
|
Créer un dossier `Pseudonymisation_v11.0_MVP/` contenant :
|
||||||
|
|
||||||
|
```
|
||||||
|
Pseudonymisation_v11.0_MVP/
|
||||||
|
├── Pseudonymisation.exe ← exécutable Windows (build v11)
|
||||||
|
├── dictionnaires.yml ← dictionnaires externes (modifiables)
|
||||||
|
├── profiles.yml ← profils de configuration (modifiables)
|
||||||
|
├── smartscreen-procedure.md ← procédure premier lancement
|
||||||
|
├── release-notes.md ← nouveautés v11
|
||||||
|
├── smoke-test-T6.md ← test de validation rapide
|
||||||
|
└── smoke-test-data/ ← PDF synthétique pour le test
|
||||||
|
└── synthetique_CRH_v11.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Compression ZIP
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# PowerShell (Windows)
|
||||||
|
Compress-Archive -Path "Pseudonymisation_v11.0_MVP" -DestinationPath "Pseudonymisation_v11.0_MVP.zip" -CompressionLevel Optimal
|
||||||
|
|
||||||
|
# Linux (si buildé depuis Linux)
|
||||||
|
zip -r -9 Pseudonymisation_v11.0_MVP.zip Pseudonymisation_v11.0_MVP/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Calcul SHA-256
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# PowerShell
|
||||||
|
Get-FileHash -Algorithm SHA256 Pseudonymisation_v11.0_MVP.zip
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
sha256sum Pseudonymisation_v11.0_MVP.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
**Noter l'empreinte dans le tableau ci-dessous :**
|
||||||
|
|
||||||
|
| Version | SHA-256 | Date |
|
||||||
|
|---|---|---|
|
||||||
|
| v11.0 MVP | *(à compléter après build)* | 2026-06-02 |
|
||||||
|
|
||||||
|
### 1.4 Upload OwnCloud
|
||||||
|
|
||||||
|
1. Se connecter à `https://[host_owncloud]`
|
||||||
|
2. Upload `Pseudonymisation_v11.0_MVP.zip`
|
||||||
|
3. Créer un lien de partage avec :
|
||||||
|
- **Mot de passe** : 12 caractères aléatoires (ex. `xK9#mP2$vLqR`)
|
||||||
|
- **Expiration** : 2026-07-02 (J+30)
|
||||||
|
- **Permissions** : lecture seule (pas d'upload, pas de modification)
|
||||||
|
- **Téléchargement direct** : activé
|
||||||
|
|
||||||
|
### 1.5 Génération mot de passe
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# PowerShell — génère un mot de passe 12 chars
|
||||||
|
-join ((65..90) + (97..122) + (48..57) + (33,35,36,37,38,42,64) | Get-Random -Count 12 | ForEach-Object {[char]$_})
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
openssl rand -base64 12
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 2 — Vérifications avant envoi
|
||||||
|
|
||||||
|
- [ ] **ZIP testé en local** : extraire dans un dossier temporaire, vérifier que `Pseudonymisation.exe` est présent et que les fichiers config sont lisibles
|
||||||
|
- [ ] **SHA-256 noté** dans le tableau §1.3
|
||||||
|
- [ ] **Lien OwnCloud testé en navigation privée** (Ctrl+Shift+N) : le téléchargement doit fonctionner sans authentification OwnCloud
|
||||||
|
- [ ] **Mot de passe envoyé séparément** (SMS ou téléphone, PAS dans le même email)
|
||||||
|
- [ ] **Email de fourniture du contact support** : `dbazin52@gmail.com`
|
||||||
|
- [ ] **smartscreen-procedure.md** est bien dans le ZIP — le bêta DOIT la lire avant le premier lancement
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 3 — Template email pour le bêta-testeur
|
||||||
|
|
||||||
|
```
|
||||||
|
Objet : Pseudonymisation médicale v11.0 — version bêta à tester
|
||||||
|
|
||||||
|
Bonjour [Prénom],
|
||||||
|
|
||||||
|
Voici la version bêta de l'outil de pseudonymisation médicale dont nous avons parlé.
|
||||||
|
|
||||||
|
📥 Téléchargement
|
||||||
|
Lien : <url_owncloud>
|
||||||
|
Mot de passe : (envoyé séparément par SMS)
|
||||||
|
Expiration : 2026-07-02
|
||||||
|
Taille : ~720 Mo
|
||||||
|
|
||||||
|
🔐 Vérification d'intégrité
|
||||||
|
Après téléchargement, vérifiez l'empreinte du fichier ZIP :
|
||||||
|
- Empreinte SHA-256 : <hash_complet>
|
||||||
|
- Commande PowerShell : Get-FileHash -Algorithm SHA256 Pseudonymisation_v11.0_MVP.zip
|
||||||
|
|
||||||
|
📦 Contenu du ZIP
|
||||||
|
- Pseudonymisation.exe (exécutable Windows, ~650 Mo)
|
||||||
|
- dictionnaires.yml + profiles.yml (configurations modifiables)
|
||||||
|
- smartscreen-procedure.md (procédure premier lancement — LIRE EN PREMIER)
|
||||||
|
- release-notes.md (nouveautés v11.0)
|
||||||
|
- smoke-test-T6.md (test de validation rapide, ~10 min)
|
||||||
|
|
||||||
|
🚀 Première utilisation
|
||||||
|
1. Lire smartscreen-procedure.md en premier
|
||||||
|
2. Suivre les étapes 1 à 4 du document
|
||||||
|
3. Lancer Pseudonymisation.exe
|
||||||
|
4. Exécuter le smoke-test-T6.md pour valider le bon fonctionnement
|
||||||
|
|
||||||
|
🧪 Smoke test rapide
|
||||||
|
Le fichier smoke-test-T6.md contient une procédure de test avec un PDF
|
||||||
|
synthétique pour valider que l'anonymisation fonctionne correctement.
|
||||||
|
Durée estimée : 10 minutes.
|
||||||
|
|
||||||
|
🆘 En cas de problème
|
||||||
|
- Logs : zipper le dossier de sortie et le sous-dossier quarantaine/
|
||||||
|
- Email : dbazin52@gmail.com
|
||||||
|
- Réponse sous 24h (fuseau horaire Province Bêta UTC+4, je m'adapte)
|
||||||
|
|
||||||
|
Merci pour le test et n'hésitez pas pour toute question.
|
||||||
|
|
||||||
|
Cordialement,
|
||||||
|
Dom
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 4 — Suivi post-livraison
|
||||||
|
|
||||||
|
### 4.1 Tableau de suivi des retours
|
||||||
|
|
||||||
|
| # | Date | Description | Sévérité | Statut | Version |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| | | | | | v11.0 |
|
||||||
|
|
||||||
|
Sévérités :
|
||||||
|
- **Bloquant** : EXE ne lance pas, crash au premier document, perte de données
|
||||||
|
- **Majeur** : PII non masquée (fuite), fonctionnalité critique non fonctionnelle
|
||||||
|
- **Mineur** : bug UI, message d'erreur confus, performance lente
|
||||||
|
- **Cosmétique** : typo, alignement, couleur
|
||||||
|
|
||||||
|
### 4.2 Template rapport de bug
|
||||||
|
|
||||||
|
```
|
||||||
|
Version EXE : v11.0
|
||||||
|
Contexte : Windows 10/11, 8 Go RAM, PDF natif ou scan ?
|
||||||
|
Description : (ce que je faisais, ce qui s'est passé)
|
||||||
|
Logs : (joindre le dossier <sortie>/ + quarantaine/)
|
||||||
|
Sévérité : Bloquant / Majeur / Mineur / Cosmétique
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Plan de patch v11.X
|
||||||
|
|
||||||
|
| Cadence | Condition |
|
||||||
|
|---|---|
|
||||||
|
| Patch hebdomadaire | Si ≥ 1 bug Bloquant ou Majeur |
|
||||||
|
| Attendre v11.5 | Si uniquement Mineurs et Cosmétiques |
|
||||||
|
| Hotfix immédiat | Si fuite PII confirmée |
|
||||||
|
|
||||||
|
### 4.4 Critères de validation bêta
|
||||||
|
|
||||||
|
La version bêta est considérée **validée** quand :
|
||||||
|
- [ ] Smoke test passé sans erreur (10/10)
|
||||||
|
- [ ] ≥ 5 documents réels traités avec succès
|
||||||
|
- [ ] Aucune PII résiduelle détectée sur les documents testés
|
||||||
|
- [ ] Quarantaine fonctionnelle (au moins 1 cas testé)
|
||||||
|
- [ ] Retour écrit du bêta-testeur
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Document généré automatiquement — procédure T7 du sprint v11.0 MVP*
|
||||||
Reference in New Issue
Block a user