@piecemaker-legal/piecemaker 2.0.10 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/assets/{FindReplaceDialog-KU5TEIBJ-CQ76sPaJ.js → FindReplaceDialog-KU5TEIBJ-BBxf1he7.js} +1 -1
  2. package/dist/assets/{FootnotePropertiesDialog-W3YHM4BV-BpgYQvvd.js → FootnotePropertiesDialog-W3YHM4BV-BYqp6U4x.js} +1 -1
  3. package/dist/assets/{HyperlinkDialog-ZYH7ZHRF-z08UbYbL.js → HyperlinkDialog-ZYH7ZHRF-BVC9zOMs.js} +4 -4
  4. package/dist/assets/{ImagePositionDialog-HEWYR6Q3-CFzOnzAt.js → ImagePositionDialog-HEWYR6Q3-Xn2mC64y.js} +1 -1
  5. package/dist/assets/{ImagePropertiesDialog-RAB47C6M-D-ThGU-b.js → ImagePropertiesDialog-RAB47C6M-CFcdqEif.js} +1 -1
  6. package/dist/assets/{PageSetupDialog-AIGIXO3E-_PYIO8eb.js → PageSetupDialog-AIGIXO3E-DECaeRS9.js} +1 -1
  7. package/dist/assets/{SplitCellDialog-IFBKYCD6-Cb9uQOB8.js → SplitCellDialog-IFBKYCD6-ByYa04H_.js} +1 -1
  8. package/dist/assets/{TablePropertiesDialog-TMLKGOOW-12vmjb6B.js → TablePropertiesDialog-TMLKGOOW-BuyqZB5A.js} +1 -1
  9. package/dist/assets/{WatermarkDialog-KRL7WMIS-C-d8rzt2.js → WatermarkDialog-KRL7WMIS-ByZ1Acr2.js} +1 -1
  10. package/dist/assets/{abnfDiagram-VCTEODGH-B4OsX7eF.js → abnfDiagram-VCTEODGH-BtttzdZA.js} +1 -1
  11. package/dist/assets/{arc-BK1oUIk7.js → arc-DN4m1tLs.js} +1 -1
  12. package/dist/assets/{architectureDiagram-5GKGNRK7-CfaFI6L7.js → architectureDiagram-5GKGNRK7-Bt-zFumG.js} +1 -1
  13. package/dist/assets/{blockDiagram-I7D4REHJ-DQEI2azY.js → blockDiagram-I7D4REHJ-D14V8v4j.js} +1 -1
  14. package/dist/assets/{c4Diagram-7LVT6UL2-DbravpVi.js → c4Diagram-7LVT6UL2-BynDo1-W.js} +1 -1
  15. package/dist/assets/channel-C4gJXzYa.js +1 -0
  16. package/dist/assets/{chunk-2Q5K7J3B-BLpZ9huC.js → chunk-2Q5K7J3B-B8gKhSj9.js} +1 -1
  17. package/dist/assets/{chunk-5VM5RSS4-BLlEdmX9.js → chunk-5VM5RSS4-GvOpglRR.js} +1 -1
  18. package/dist/assets/{chunk-F27PBJKO-D-ZNhI6q.js → chunk-F27PBJKO-CAgZy0qc.js} +1 -1
  19. package/dist/assets/{chunk-IMKFNOWR-vzSiKnhh.js → chunk-IMKFNOWR-gf8Ux8Zd.js} +1 -1
  20. package/dist/assets/{chunk-JWPE2WC7-DfF0k4nb.js → chunk-JWPE2WC7-D4zFFRMp.js} +1 -1
  21. package/dist/assets/{chunk-POPQ4Y6H-BnwCYsPP.js → chunk-POPQ4Y6H-Ct8X_LaT.js} +1 -1
  22. package/dist/assets/{chunk-SVP7TREG-DRxc8fhq.js → chunk-SVP7TREG-g5iH1bs5.js} +1 -1
  23. package/dist/assets/{chunk-TICWLB2K-Dd2Q-md-.js → chunk-TICWLB2K-89-VFKTn.js} +1 -1
  24. package/dist/assets/{chunk-XXDRQBXY-DBjkEUp_.js → chunk-XXDRQBXY-CaQbdObb.js} +1 -1
  25. package/dist/assets/classDiagram-ZZMXUADV-Cm08FeYk.js +1 -0
  26. package/dist/assets/classDiagram-v2-VYDZK3BY-Cm08FeYk.js +1 -0
  27. package/dist/assets/{cose-bilkent-JH36ORCC-By6PS4oe.js → cose-bilkent-JH36ORCC-BiYOGJAU.js} +1 -1
  28. package/dist/assets/{cynefin-OW5HDTMX-YxWpjc5W.js → cynefin-OW5HDTMX-F6mJ-4tj.js} +1 -1
  29. package/dist/assets/{cynefinDiagram-5FMLGOSQ-DrtXzz3p.js → cynefinDiagram-5FMLGOSQ-q-Rggn6r.js} +1 -1
  30. package/dist/assets/{dagre-GXQ25YYZ-BDpTLKqZ.js → dagre-GXQ25YYZ-COqImSUH.js} +1 -1
  31. package/dist/assets/{diagram-S7CK7UJ4-C4LvqeCs.js → diagram-S7CK7UJ4-BAg1dyEc.js} +1 -1
  32. package/dist/assets/{diagram-UQ7AKVKN-BTpnMeHp.js → diagram-UQ7AKVKN-DFJS8qCV.js} +1 -1
  33. package/dist/assets/{diagram-VSXAHHWV-DucHHWev.js → diagram-VSXAHHWV-BfldL35r.js} +1 -1
  34. package/dist/assets/{diagram-VX7I27RA-DYHZNmmH.js → diagram-VX7I27RA-kLjovpYe.js} +1 -1
  35. package/dist/assets/{diagram-Z3DM3KII-BHIH2BGN.js → diagram-Z3DM3KII-Dhwvi3Iu.js} +1 -1
  36. package/dist/assets/{ebnfDiagram-PWID7BFC-CGiEkwj0.js → ebnfDiagram-PWID7BFC-Bp4YE1i7.js} +1 -1
  37. package/dist/assets/{erDiagram-RLTQ6QDP-AJMHi2Us.js → erDiagram-RLTQ6QDP-BKDdaSWz.js} +1 -1
  38. package/dist/assets/{flowDiagram-HODETNUW-BqzANBh2.js → flowDiagram-HODETNUW-zHProVxL.js} +1 -1
  39. package/dist/assets/{ganttDiagram-EL5Y4UJY-B0xPDY2_.js → ganttDiagram-EL5Y4UJY-B-B-BI6j.js} +1 -1
  40. package/dist/assets/{gitGraphDiagram-WWUBYQGX-CzPX5CNO.js → gitGraphDiagram-WWUBYQGX-BTHMg5CH.js} +1 -1
  41. package/dist/assets/index-C5slQNvE.css +1 -0
  42. package/dist/assets/{index-Bk2iXKnJ.js → index-CJP4dZ-c.js} +146 -146
  43. package/dist/assets/{infoDiagram-27XIBGKW-Cjc44H0O.js → infoDiagram-27XIBGKW-mabp4H9I.js} +1 -1
  44. package/dist/assets/{ishikawaDiagram-5VMMS53U-BWhr2v7r.js → ishikawaDiagram-5VMMS53U-CLNhcCrM.js} +1 -1
  45. package/dist/assets/{journeyDiagram-3NMN7TZE-Bl4r6RrP.js → journeyDiagram-3NMN7TZE-CGEi2RB6.js} +1 -1
  46. package/dist/assets/{kanban-definition-UXKFOSKX-FV-T0Vy1.js → kanban-definition-UXKFOSKX-B2fLX1fK.js} +1 -1
  47. package/dist/assets/{layout-CTfb1sAF.js → layout-CloKWsFq.js} +1 -1
  48. package/dist/assets/{linear-Dfgi6vlo.js → linear-BFyydI0c.js} +1 -1
  49. package/dist/assets/{mermaid.core-DOCGsdlo.js → mermaid.core-Dxl3LVAh.js} +6 -6
  50. package/dist/assets/{mindmap-definition-YA3MSWOX-CymQ86ir.js → mindmap-definition-YA3MSWOX-FT4pm55s.js} +1 -1
  51. package/dist/assets/{pegDiagram-XKGWAZYB-1VMq_vi0.js → pegDiagram-XKGWAZYB-okyIfJWM.js} +1 -1
  52. package/dist/assets/{pieDiagram-E7YTZNPT-C5GgXtJU.js → pieDiagram-E7YTZNPT-NF61CQz8.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-AXDQQJYC-C6zpryYf.js → quadrantDiagram-AXDQQJYC-uVfvZzzt.js} +1 -1
  54. package/dist/assets/{railroadDiagram-O6MQD6OU-D-sv4SWi.js → railroadDiagram-O6MQD6OU-Dp8IpeoH.js} +1 -1
  55. package/dist/assets/{requirementDiagram-BXWQKSXE-CkjY-z4B.js → requirementDiagram-BXWQKSXE-D8VV0oQe.js} +1 -1
  56. package/dist/assets/{sankeyDiagram-P5KCCOFB-De-d8Q6L.js → sankeyDiagram-P5KCCOFB-D-xhxjob.js} +1 -1
  57. package/dist/assets/{sequenceDiagram-WJ2MYXX4-DCsGSJa9.js → sequenceDiagram-WJ2MYXX4-BAQBK95h.js} +1 -1
  58. package/dist/assets/{sizeCapture-INFHLROL-xINZhgER.js → sizeCapture-INFHLROL-C6rk9SVx.js} +1 -1
  59. package/dist/assets/{stateDiagram-D77RDMKH-CuLUTWSL.js → stateDiagram-D77RDMKH-DshqaEOj.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-MP3YSRHH-Cnbs-M92.js → stateDiagram-v2-MP3YSRHH-BohZIrk5.js} +1 -1
  61. package/dist/assets/{swimlanes-42K2YHIH-BwxARD7N.js → swimlanes-42K2YHIH-DgcPDy4l.js} +1 -1
  62. package/dist/assets/swimlanesDiagram-VR7AAH4N-CuM3Su0K.js +8 -0
  63. package/dist/assets/{timeline-definition-24CTP7MA-BIOvnn-l.js → timeline-definition-24CTP7MA-OgpIe-gQ.js} +1 -1
  64. package/dist/assets/{vennDiagram-4TSXK5OY-BSD6Pfmt.js → vennDiagram-4TSXK5OY-BZzGd6jt.js} +1 -1
  65. package/dist/assets/{wardleyDiagram-VM6X3IG4-7Zz2iRkF.js → wardleyDiagram-VM6X3IG4-1tg-H7l_.js} +1 -1
  66. package/dist/assets/{xychartDiagram-S5SC5T6Z-Ctx17Y1D.js → xychartDiagram-S5SC5T6Z-ByTsX8g0.js} +1 -1
  67. package/dist/index.html +2 -2
  68. package/dist/sw.js +1 -1
  69. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +61 -1
  70. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
  71. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +0 -48
  72. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
  73. package/dist-server/server/piecemaker/agent-instructions.js +148 -0
  74. package/dist-server/server/piecemaker/agent-instructions.js.map +1 -0
  75. package/dist-server/server/piecemaker/index.js +4 -0
  76. package/dist-server/server/piecemaker/index.js.map +1 -1
  77. package/dist-server/server/piecemaker/knowledge/pipeline.js +25 -29
  78. package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
  79. package/dist-server/server/piecemaker/knowledge/service.js +1 -16
  80. package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
  81. package/dist-server/server/piecemaker/project-registry.js +5 -5
  82. package/dist-server/server/piecemaker/project-registry.js.map +1 -1
  83. package/package.json +1 -1
  84. package/scripts/piecemaker/cli/lib/plugins.mjs +1 -0
  85. package/server/piecemaker/agent-instructions.ts +148 -0
  86. package/server/piecemaker/anonymizer/dictionary.cjs +34 -3
  87. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +1 -1
  88. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +2 -1
  89. package/server/piecemaker/anonymizer/hudsucker-smoke.cjs +343 -0
  90. package/server/piecemaker/anonymizer/rewrite.cjs +2 -2
  91. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +7 -4
  92. package/server/piecemaker/anonymizer/rewriter-worker.cjs +85 -0
  93. package/server/piecemaker/anonymizer/service.cjs +10 -8
  94. package/server/piecemaker/anonymizer/sqlite-dictionary.cjs +15 -32
  95. package/server/piecemaker/index.ts +5 -1
  96. package/server/piecemaker/knowledge/pipeline.ts +24 -31
  97. package/server/piecemaker/knowledge/service.ts +1 -14
  98. package/server/piecemaker/project-registry.ts +6 -6
  99. package/server/piecemaker/router.cjs +0 -1
  100. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +9 -162
  101. package/server/piecemaker/vendor/installer/lib/commandes.mjs +1 -2
  102. package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +62 -3
  103. package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +5 -5
  104. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +23 -69
  105. package/server/piecemaker/vendor/installer/steps/12-mcp-piecemaker.mjs +3 -3
  106. package/server/piecemaker/vendor/mcp/README.md +1 -1
  107. package/server/piecemaker/vendor/mcp/piecemaker/server.mjs +12 -75
  108. package/server/piecemaker/vendor/piecemaker-plugin/hooks/hooks.json +0 -40
  109. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +2 -2
  110. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +2 -90
  111. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +26 -7
  112. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +0 -28
  113. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/hook-io.mjs +0 -13
  114. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +3 -3
  115. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +10 -143
  116. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +1 -1
  117. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +0 -65
  118. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +189 -41
  119. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +11 -2
  120. package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +4 -18
  121. package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +9 -5
  122. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -179
  123. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +0 -10
  124. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +3 -0
  125. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +23 -372
  126. package/server/piecemaker/vendor/websocket-server/renamed-originals.cjs +136 -0
  127. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +115 -96
  128. package/dist/assets/channel-DVyNr2Yg.js +0 -1
  129. package/dist/assets/classDiagram-ZZMXUADV-2pGPHl5H.js +0 -1
  130. package/dist/assets/classDiagram-v2-VYDZK3BY-2pGPHl5H.js +0 -1
  131. package/dist/assets/index-Dc9wNuS4.css +0 -1
  132. package/dist/assets/swimlanesDiagram-VR7AAH4N-D7p8qZI2.js +0 -8
  133. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +0 -194
  134. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +0 -71
  135. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit-worker.mjs +0 -260
  136. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit.mjs +0 -83
@@ -6,10 +6,11 @@ This script chains smart_converter.py (conversion to Markdown) and
6
6
  scanner_worker.py (PII scanning) for batch processing of multiple documents.
7
7
 
8
8
  Usage:
9
- python3 convert_and_scan_pipeline.py <file1> [file2 ...] -o <output_dir> [options]
9
+ python3 convert_and_scan_pipeline.py <file1> [file2 ...] -o <output_dir> --database <auth.db> [options] < case-mapping
10
10
 
11
11
  Options:
12
12
  -o, --output DIR Output directory for all generated files
13
+ --database PATH PieceMaker database; codes held by every case are reserved
13
14
  --engine ENGINE Conversion engine: auto|markitdown|mineru (default: auto)
14
15
  --mode MODE MinerU mode: pipeline|hybrid|vlm (default: pipeline)
15
16
  --lang CODE OCR language code for MinerU (default: auto)
@@ -17,17 +18,17 @@ Options:
17
18
  Progress Format:
18
19
  PROGRESS:CONVERT:percentage:current:total (Phase 1: Converting documents)
19
20
  PROGRESS:SCAN:percentage:current:total (Phase 2: Scanning for PII)
21
+ MAPPING:<json> (merged case mapping, written to the database by PieceMaker)
20
22
 
21
23
  Persistent Output Structure:
22
24
  {output_dir}/
23
25
  ├── document1.md
24
26
  ├── document2.md
25
- ├── mapping_default.json
26
27
  └── .piecemaker/anonymization-state.json
27
28
 
28
29
  The per-document sensitive maps are transient scanner payloads. They are
29
- created in a private temporary directory, merged into mapping_default.json,
30
- then removed automatically.
30
+ created in a private temporary directory, merged into the case mapping sent
31
+ back on stdout, then removed automatically. No mapping file is written.
31
32
 
32
33
  Exit Codes:
33
34
  0: Success (at least one file fully processed)
@@ -41,9 +42,11 @@ import argparse
41
42
  import subprocess
42
43
  import json
43
44
  import hashlib
45
+ import sqlite3
44
46
  import tempfile
45
47
  import unicodedata
46
48
  import re
49
+ from contextlib import closing
47
50
  from pathlib import Path
48
51
  from typing import List, Optional, Tuple, Dict, Set
49
52
 
@@ -393,30 +396,30 @@ def convert_file(
393
396
  return False, None
394
397
 
395
398
 
396
- def load_existing_mapping(mapping_path: Path) -> Optional[Dict]:
397
- """Load the case/document mapping file if it exists.
398
-
399
- This is the SAME file that the server uses (server.cjs line 2146),
400
- ensuring single source of truth for mappings.
401
-
402
- Args:
403
- mapping_path: Full path to mapping_default.json (or an explicit target)
404
-
405
- Returns:
406
- Existing mapping data or None if file doesn't exist
407
- """
408
- if not mapping_path.exists():
409
- print("ℹ️ No existing mapping found, will create new one")
410
- return None
411
-
412
- try:
413
- with open(mapping_path, 'r', encoding='utf-8') as f:
414
- data = json.load(f)
415
- print(f"✅ Loaded existing mapping: {len(data.get('mapping', {}))} entries")
416
- return data
417
- except Exception as e:
418
- print(f"⚠️ Error loading existing mapping: {e}", file=sys.stderr)
419
- return None
399
+ def read_mapping_seed() -> Dict:
400
+ """Mapping of the case being scanned, sent by PieceMaker on stdin."""
401
+ if sys.stdin.isatty():
402
+ return {}
403
+ raw = sys.stdin.read().strip()
404
+ seed = json.loads(raw) if raw else {}
405
+ print(f"✅ Loaded case mapping: {len(seed.get('mapping', {}))} entries")
406
+ return seed
407
+
408
+
409
+ def load_reserved_codes(database_path: str) -> Set[str]:
410
+ """Every code already held by any case: a new code must never reuse one."""
411
+ database = Path(database_path).resolve()
412
+ if not database.exists():
413
+ raise FileNotFoundError(f"PieceMaker database not found: {database}")
414
+ with closing(sqlite3.connect(f"{database.as_uri()}?mode=ro", uri=True)) as connection:
415
+ tables = {row[0] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
416
+ codes: Set[str] = set()
417
+ if "piecemaker_mappings" in tables:
418
+ codes |= {row[0] for row in connection.execute("SELECT masked_value FROM piecemaker_mappings")}
419
+ if "piecemaker_nodes" in tables:
420
+ codes |= {row[0][len("entity:"):] for row in connection.execute("SELECT id FROM piecemaker_nodes WHERE id LIKE 'entity:%'")}
421
+ print(f"✅ Codes reserved by every case: {len(codes)}")
422
+ return codes
420
423
 
421
424
 
422
425
  def read_individual_mappings(json_files: List[str]) -> Dict:
@@ -633,6 +636,48 @@ def consolidate_duplicate_entities(entities: List[Dict]) -> List[Dict]:
633
636
  return consolidated
634
637
 
635
638
 
639
+ CIVILITY_TITLE = (
640
+ r"(?:monsieur|madame|mademoiselle|messieurs|mesdames|ma[iî]tre|docteure|doctoresse|docteur|professeure|professeur"
641
+ r"|mmes|mlles|mme|mlle|mm|m|me|mrs|mr|ms|miss|dr|pr|prof)\.?\s+"
642
+ )
643
+ CIVILITY_PREFIX = re.compile(
644
+ rf"^(?:{CIVILITY_TITLE}(?:(?:le|la|les)\s+(?={CIVILITY_TITLE}))?)+",
645
+ re.IGNORECASE,
646
+ )
647
+
648
+
649
+ def strip_civility(text: str) -> str:
650
+ """« Monsieur Jean Dupont » → « Jean Dupont » ; « Maître Prunelle » → « Prunelle »."""
651
+ bare = CIVILITY_PREFIX.sub("", text.strip()).strip()
652
+ if re.match(r"(?:le|la|les)\s|l['’]", bare, re.IGNORECASE):
653
+ return ""
654
+ return bare if re.search(r"[^\W\d_]{2,}", bare) else ""
655
+
656
+
657
+ def _is_person_code(code: str, person_codes: Set[str]) -> bool:
658
+ return code in person_codes or "PERSONNE_PHYSIQUE_" in code or code.startswith(("DIRIGEANT_", "AVOCAT_"))
659
+
660
+
661
+ def add_bare_person_variants(merged_mapping: Dict, merged_reverse: Dict,
662
+ merged_extracted: Dict, ignored_lower: Set[str]) -> None:
663
+ """Every titled name also gets its title-less variant, under the same code."""
664
+ person_entries = merged_extracted.setdefault("personnes_physiques", {})
665
+ person_codes = set(person_entries)
666
+ known_lower = {text.lower() for text in merged_mapping}
667
+ for text, code in list(merged_mapping.items()):
668
+ if not _is_person_code(str(code), person_codes):
669
+ continue
670
+ bare = strip_civility(text)
671
+ if not bare or bare == text or bare.lower() in known_lower or bare.lower() in ignored_lower:
672
+ continue
673
+ merged_mapping[bare] = code
674
+ known_lower.add(bare.lower())
675
+ merged_reverse[code] = list(dict.fromkeys([*(merged_reverse.get(code) or [text]), bare]))
676
+ entry = person_entries.get(code)
677
+ if entry is not None:
678
+ entry["variants"] = list(dict.fromkeys([*(entry.get("variants") or []), bare]))
679
+
680
+
636
681
  def _person_entry_texts(code: str, entry: Dict, reverse_mapping: Dict,
637
682
  mapping_texts: Optional[List[str]] = None) -> List[str]:
638
683
  values = [entry.get("original", ""), *(entry.get("variants") or [])]
@@ -1227,18 +1272,18 @@ def convert_to_anonymization_format(consolidated_entities: Dict) -> Dict:
1227
1272
  }
1228
1273
 
1229
1274
 
1230
- def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Dict]) -> Dict:
1275
+ def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Dict], reserved_codes: Set[str] = frozenset()) -> Dict:
1231
1276
  """Merge new mapping with existing mapping, avoiding duplicates.
1232
1277
 
1233
1278
  Args:
1234
1279
  new_mapping: Newly generated mapping from current batch
1235
1280
  existing_mapping: Existing mapping data (or None)
1281
+ reserved_codes: Codes held by any case, never handed out again
1236
1282
 
1237
1283
  Returns:
1238
1284
  Merged mapping data
1239
1285
  """
1240
- if not existing_mapping:
1241
- return new_mapping
1286
+ existing_mapping = existing_mapping or {}
1242
1287
 
1243
1288
  # `ignored` is written by the admin editor (originals-pipeline.cjs): entities
1244
1289
  # the lawyer removed by hand are false positives and must not come back.
@@ -1285,7 +1330,10 @@ def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Di
1285
1330
  # URGOT SA), là où un test par sous-chaîne ne reconnaîtrait pas SA_3.
1286
1331
  societe_counters: Dict[str, int] = {}
1287
1332
 
1288
- for code in set(merged_reverse) | set(merged_mapping.values()):
1333
+ used_codes = set(merged_reverse) | set(merged_mapping.values()) | set(reserved_codes)
1334
+ for code in used_codes:
1335
+ if not re.search(r"_\d+$", code):
1336
+ continue
1289
1337
  if "PERSONNE_PHYSIQUE_" in code or code.startswith("DIRIGEANT_"):
1290
1338
  num = int(code.split("_")[-1])
1291
1339
  code_counters["personnes_physiques"] = max(code_counters["personnes_physiques"], num + 1)
@@ -1296,7 +1344,7 @@ def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Di
1296
1344
  num = int(code.split("_")[-1])
1297
1345
  code_counters["siren"] = max(code_counters["siren"], num + 1)
1298
1346
 
1299
- for code in merged_extracted.get("societes", {}).keys():
1347
+ for code in set(merged_extracted.get("societes", {}).keys()) | set(reserved_codes):
1300
1348
  match = re.search(r"_(\d+)$", code)
1301
1349
  if not match:
1302
1350
  continue
@@ -1353,27 +1401,27 @@ def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Di
1353
1401
  continue
1354
1402
 
1355
1403
  # Generate new code with incremented counter
1356
- if category == "personnes_physiques":
1357
- new_code = f"PERSONNE_PHYSIQUE_{code_counters[category]:02d}"
1358
- code_counters[category] += 1
1359
- elif category == "societes":
1360
- # Le sigle du code d'origine est préservé : SA_3 → SA_<n>, legacy
1361
- # URGOT SA → SA_<n>, sinon PERS_MORALE_<n>. Compteur par sigle.
1362
- key = _societe_counter_key_of_code(code)
1363
- n = societe_counters.get(key, 0) + 1
1364
- societe_counters[key] = n
1365
- new_code = f"{key}_{n}"
1366
- elif category == "adresses":
1367
- new_code = f"ADRESSE_{code_counters[category]:02d}"
1368
- code_counters[category] += 1
1369
- elif category == "siren":
1370
- new_code = f"SIREN_{code_counters[category]:02d}"
1371
- code_counters[category] += 1
1372
- else:
1373
- # Extract entity type from new code
1374
- entity_type = code.split("_")[0] if "_" in code else "AUTRE"
1375
- new_code = f"{entity_type}_{code_counters[category]:02d}"
1404
+ def next_code() -> str:
1405
+ if category == "societes":
1406
+ # Le sigle du code d'origine est préservé : SA_3 → SA_<n>, legacy
1407
+ # URGOT SA → SA_<n>, sinon PERS_MORALE_<n>. Compteur par sigle.
1408
+ key = _societe_counter_key_of_code(code)
1409
+ n = societe_counters.get(key, 0) + 1
1410
+ societe_counters[key] = n
1411
+ return f"{key}_{n}"
1412
+ prefix = {
1413
+ "personnes_physiques": "PERSONNE_PHYSIQUE",
1414
+ "adresses": "ADRESSE",
1415
+ "siren": "SIREN",
1416
+ }.get(category) or (code.split("_")[0] if "_" in code else "AUTRE")
1417
+ candidate = f"{prefix}_{code_counters[category]:02d}"
1376
1418
  code_counters[category] += 1
1419
+ return candidate
1420
+
1421
+ new_code = next_code()
1422
+ while new_code in used_codes:
1423
+ new_code = next_code()
1424
+ used_codes.add(new_code)
1377
1425
 
1378
1426
  # Add all variants to merged structures (no lowercase duplicates)
1379
1427
  accepted_variants = [variant for variant in variants if variant.lower() not in ignored_lower]
@@ -1430,28 +1478,17 @@ def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Di
1430
1478
  merged_ignored = list(dict.fromkeys(
1431
1479
  ignored + [str(text).strip() for text in new_mapping.get('ignored', []) if str(text).strip()] + left_visible
1432
1480
  ))
1481
+ add_bare_person_variants(merged_mapping, merged_reverse, merged_extracted, ignored_lower)
1433
1482
  return {
1434
1483
  "mapping": merged_mapping,
1435
1484
  "reverse_mapping": merged_reverse,
1436
1485
  "extracted_data": merged_extracted,
1437
- "informations_dossier": existing_mapping.get("informations_dossier", {}),
1438
1486
  **({"ignored": merged_ignored} if merged_ignored else {}),
1439
1487
  }
1440
1488
 
1441
1489
 
1442
- def save_mapping(mapping_path: Path, mapping_data: Dict) -> Path:
1443
- """Save the merged mapping to the case/document mapping file.
1444
-
1445
- Uses the SAME file as server.cjs (line 2146), ensuring the validated
1446
- mapping can be read by GET /api/anonymize/mapping/:documentId.
1447
-
1448
- Args:
1449
- mapping_path: Full path to the mapping file to write
1450
- mapping_data: Complete mapping data structure
1451
-
1452
- Returns:
1453
- Path to saved file
1454
- """
1490
+ def emit_mapping(mapping_data: Dict) -> None:
1491
+ """Send the merged mapping to PieceMaker, which writes it to its database."""
1455
1492
  payload = {
1456
1493
  **mapping_data,
1457
1494
  "mapping": dict(sorted(
@@ -1459,15 +1496,7 @@ def save_mapping(mapping_path: Path, mapping_data: Dict) -> Path:
1459
1496
  key=lambda item: (-len(item[0]), item[0].casefold()),
1460
1497
  )),
1461
1498
  }
1462
- mapping_path.parent.mkdir(parents=True, exist_ok=True)
1463
- temporary = mapping_path.with_name(f"{mapping_path.name}.piecemaker-{os.getpid()}.tmp")
1464
- with open(temporary, 'w', encoding='utf-8') as f:
1465
- json.dump(payload, f, indent=2, ensure_ascii=False)
1466
- f.write("\n")
1467
- os.chmod(temporary, 0o600)
1468
- os.replace(temporary, mapping_path)
1469
-
1470
- return mapping_path
1499
+ print("MAPPING:" + json.dumps(payload, ensure_ascii=False, separators=(",", ":")), flush=True)
1471
1500
 
1472
1501
 
1473
1502
  def load_anonymization_state(state_path: Path) -> Dict:
@@ -1757,7 +1786,7 @@ def run_pipeline(resources: PipelineResources):
1757
1786
  "-o",
1758
1787
  "--output",
1759
1788
  required=True,
1760
- help="Output directory for Markdown files and mapping_default.json",
1789
+ help="Output directory for Markdown files",
1761
1790
  )
1762
1791
  parser.add_argument(
1763
1792
  "--engine",
@@ -1777,14 +1806,9 @@ def run_pipeline(resources: PipelineResources):
1777
1806
  help=argparse.SUPPRESS,
1778
1807
  )
1779
1808
  parser.add_argument(
1780
- "--mapping-dir",
1781
- default=None,
1782
- help="Directory for mapping_default.json (default: same as --output)",
1783
- )
1784
- parser.add_argument(
1785
- "--mapping-file",
1786
- default=None,
1787
- help="Exact mapping file to read and rewrite (overrides --mapping-dir)",
1809
+ "--database",
1810
+ required=True,
1811
+ help="PieceMaker database (auth.db): codes held by every case are reserved; the case mapping comes on stdin",
1788
1812
  )
1789
1813
  parser.add_argument(
1790
1814
  "--state-file",
@@ -1822,11 +1846,8 @@ def run_pipeline(resources: PipelineResources):
1822
1846
 
1823
1847
  # Ensure output directory exists
1824
1848
  os.makedirs(args.output, exist_ok=True)
1825
- mapping_target = (
1826
- Path(args.mapping_file)
1827
- if args.mapping_file
1828
- else Path(args.mapping_dir or args.output) / "mapping_default.json"
1829
- )
1849
+ reserved_codes = load_reserved_codes(args.database)
1850
+ current_mapping = read_mapping_seed()
1830
1851
  state_target = Path(args.state_file) if args.state_file else Path(args.output) / ".piecemaker" / "anonymization-state.json"
1831
1852
  # Base pour les clés du manifeste : le dossier juridique, découplé de --output
1832
1853
  # (désormais un sous-dossier). Les sources vivent sous le dossier, pas sous la
@@ -1961,7 +1982,6 @@ def run_pipeline(resources: PipelineResources):
1961
1982
 
1962
1983
  scan_success_count = 0
1963
1984
  final_mapping_data = None
1964
- mapping_path = None
1965
1985
  index_path = state_target.parent / "document-index.json"
1966
1986
  # Raw detections contain PII. They live only in a private OS temporary
1967
1987
  # directory and are deleted individually as each file is merged.
@@ -2011,9 +2031,9 @@ def run_pipeline(resources: PipelineResources):
2011
2031
  source = md_sources[md_file]
2012
2032
  consolidated = read_individual_mappings([str(json_path)])
2013
2033
  new_mapping_data = convert_to_anonymization_format(consolidated)
2014
- existing_mapping = load_existing_mapping(mapping_target)
2015
- final_mapping_data = merge_with_existing_mapping(new_mapping_data, existing_mapping)
2016
- mapping_path = save_mapping(mapping_target, final_mapping_data)
2034
+ final_mapping_data = merge_with_existing_mapping(new_mapping_data, current_mapping, reserved_codes)
2035
+ current_mapping = final_mapping_data
2036
+ emit_mapping(final_mapping_data)
2017
2037
  update_anonymization_state(state_target, state_case_root, [source])
2018
2038
 
2019
2039
  record = {
@@ -2046,7 +2066,7 @@ def run_pipeline(resources: PipelineResources):
2046
2066
  print()
2047
2067
  return 1
2048
2068
 
2049
- print(f"✅ Mapping saved to: {mapping_path}")
2069
+ print("✅ Mapping sent to PieceMaker")
2050
2070
  print(f" • Total entities: {len(final_mapping_data['mapping'])} variants")
2051
2071
  print(f" • Unique codes: {len(final_mapping_data['reverse_mapping'])}")
2052
2072
  print()
@@ -2061,7 +2081,6 @@ def run_pipeline(resources: PipelineResources):
2061
2081
  print(f" • Entities mapped: {len(final_mapping_data['mapping'])} variants")
2062
2082
  print(f" • Unique entities: {len(final_mapping_data['reverse_mapping'])} codes")
2063
2083
  print(f"📂 Output directory: {args.output}")
2064
- print(f"📋 Mapping file: {mapping_path}")
2065
2084
  print()
2066
2085
 
2067
2086
  # Exit with success if at least one file was fully processed
@@ -1 +0,0 @@
1
- import{U as a,Q as n}from"./mermaid.core-DOCGsdlo.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-TICWLB2K-Dd2Q-md-.js";import{_ as i}from"./mermaid.core-DOCGsdlo.js";import"./chunk-5VM5RSS4-BLlEdmX9.js";import"./chunk-XXDRQBXY-DBjkEUp_.js";import"./chunk-POPQ4Y6H-BnwCYsPP.js";import"./chunk-F27PBJKO-D-ZNhI6q.js";import"./index-Bk2iXKnJ.js";import"./vendor-codemirror-CN-sJePf.js";import"./vendor-react-B22wqN3J.js";import"./vendor-xterm-BWUgpGtR.js";var c={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{c as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-TICWLB2K-Dd2Q-md-.js";import{_ as i}from"./mermaid.core-DOCGsdlo.js";import"./chunk-5VM5RSS4-BLlEdmX9.js";import"./chunk-XXDRQBXY-DBjkEUp_.js";import"./chunk-POPQ4Y6H-BnwCYsPP.js";import"./chunk-F27PBJKO-D-ZNhI6q.js";import"./index-Bk2iXKnJ.js";import"./vendor-codemirror-CN-sJePf.js";import"./vendor-react-B22wqN3J.js";import"./vendor-xterm-BWUgpGtR.js";var c={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{c as diagram};