@mindflight/mindbrain-personal-studio 0.6.1 → 0.6.3

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 (212) hide show
  1. package/README.md +16 -8
  2. package/build/client/_app/immutable/chunks/CIErFlYG.js +1 -0
  3. package/build/client/_app/immutable/chunks/CIErFlYG.js.br +0 -0
  4. package/build/client/_app/immutable/chunks/CIErFlYG.js.gz +0 -0
  5. package/build/client/_app/immutable/entry/{app.CVz6aYsT.js → app.mURYm8o_.js} +2 -2
  6. package/build/client/_app/immutable/entry/app.mURYm8o_.js.br +0 -0
  7. package/build/client/_app/immutable/entry/app.mURYm8o_.js.gz +0 -0
  8. package/build/client/_app/immutable/entry/start.D4M9ZeGO.js +1 -0
  9. package/build/client/_app/immutable/entry/start.D4M9ZeGO.js.br +2 -0
  10. package/build/client/_app/immutable/entry/start.D4M9ZeGO.js.gz +0 -0
  11. package/build/client/_app/immutable/nodes/{1.BBtxY46Q.js → 1.DHKtMeFI.js} +1 -1
  12. package/build/client/_app/immutable/nodes/1.DHKtMeFI.js.br +1 -0
  13. package/build/client/_app/immutable/nodes/1.DHKtMeFI.js.gz +0 -0
  14. package/build/client/_app/version.json +1 -1
  15. package/build/client/_app/version.json.br +0 -0
  16. package/build/client/_app/version.json.gz +0 -0
  17. package/build/handler.js +4 -4
  18. package/build/index.js +4 -4
  19. package/build/server/chunks/chunks/{internal.js-D8EA_he2.js → internal.js-C3tV0XXj.js} +2 -2
  20. package/build/server/chunks/chunks/{internal.js-D8EA_he2.js.map → internal.js-C3tV0XXj.js.map} +1 -1
  21. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/{_server.ts.js-Dyfsc-VA.js → _server.ts.js-CAsXxBRq.js} +7 -2
  22. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-CAsXxBRq.js.map +1 -0
  23. package/build/server/chunks/{handler-BjXBCd1c.js → handler-DlaCCnxx.js} +3 -3
  24. package/build/server/chunks/{handler-BjXBCd1c.js.map → handler-DlaCCnxx.js.map} +1 -1
  25. package/build/server/chunks/{index.js-BJYcV8V7.js → index.js-BGfKWHak.js} +2 -2
  26. package/build/server/chunks/{index.js-BJYcV8V7.js.map → index.js-BGfKWHak.js.map} +1 -1
  27. package/build/server/chunks/{manifest.js-Ddaot0P4.js → manifest.js-CnmaNf5D.js} +4 -4
  28. package/build/server/chunks/{manifest.js-Ddaot0P4.js.map → manifest.js-CnmaNf5D.js.map} +1 -1
  29. package/build/server/chunks/nodes/{1.js-BRigw_9J.js → 1.js-BypjwBYB.js} +2 -2
  30. package/build/server/chunks/nodes/{1.js-BRigw_9J.js.map → 1.js-BypjwBYB.js.map} +1 -1
  31. package/examples/immeuble/ACCEPTANCE.yaml +82 -0
  32. package/examples/immeuble/BIM_LITE.md +15 -0
  33. package/examples/immeuble/CHECKLIST.md +128 -0
  34. package/examples/immeuble/README.md +121 -0
  35. package/examples/immeuble/bundle/immeuble.bundle.json +22786 -0
  36. package/examples/immeuble/contracts/answer_artifacts.seed.jsonl +34 -0
  37. package/examples/immeuble/contracts/business_capabilities.seed.jsonl +25 -0
  38. package/examples/immeuble/contracts/consumer_contract.yaml +131 -0
  39. package/examples/immeuble/contracts/immeuble_structured_import_model.json +310 -0
  40. package/examples/immeuble/contracts/mapping_external_to_canonical.json +375 -0
  41. package/examples/immeuble/contracts/mapping_external_to_canonical.yaml +55 -0
  42. package/examples/immeuble/contracts/mapping_external_to_canonical_ws.json +65 -0
  43. package/examples/immeuble/contracts/model_contract.json +943 -0
  44. package/examples/immeuble/contracts/projection_catalog.yaml +366 -0
  45. package/examples/immeuble/contracts/scenarios.yaml +27 -0
  46. package/examples/immeuble/contracts/semantic_proposal.golden.json +1 -0
  47. package/examples/immeuble/contracts/source_profile.yaml +38 -0
  48. package/examples/immeuble/fake_data/DeltaFinding.csv +20 -0
  49. package/examples/immeuble/fake_data/ProjectionResult.csv +13 -0
  50. package/examples/immeuble/fake_data/ag_meeting.csv +4 -0
  51. package/examples/immeuble/fake_data/agenda_item.csv +4 -0
  52. package/examples/immeuble/fake_data/architect.csv +3 -0
  53. package/examples/immeuble/fake_data/architecture_firm.csv +2 -0
  54. package/examples/immeuble/fake_data/bank_account.csv +3 -0
  55. package/examples/immeuble/fake_data/billing_group.csv +41 -0
  56. package/examples/immeuble/fake_data/block.csv +10 -0
  57. package/examples/immeuble/fake_data/budget_line.csv +3 -0
  58. package/examples/immeuble/fake_data/building.csv +6 -0
  59. package/examples/immeuble/fake_data/cellar.csv +41 -0
  60. package/examples/immeuble/fake_data/change_order.csv +2 -0
  61. package/examples/immeuble/fake_data/charge_call.csv +58 -0
  62. package/examples/immeuble/fake_data/claim.csv +4 -0
  63. package/examples/immeuble/fake_data/coda_entry.csv +49 -0
  64. package/examples/immeuble/fake_data/compliance_certificate.csv +10 -0
  65. package/examples/immeuble/fake_data/contractor.csv +4 -0
  66. package/examples/immeuble/fake_data/decision.csv +5 -0
  67. package/examples/immeuble/fake_data/defect_reserve.csv +3 -0
  68. package/examples/immeuble/fake_data/household.csv +41 -0
  69. package/examples/immeuble/fake_data/inspection.csv +3 -0
  70. package/examples/immeuble/fake_data/insurance_policy.csv +4 -0
  71. package/examples/immeuble/fake_data/intervention.csv +13 -0
  72. package/examples/immeuble/fake_data/invoice.csv +3 -0
  73. package/examples/immeuble/fake_data/lease_contract.csv +12 -0
  74. package/examples/immeuble/fake_data/maintenance_ticket.csv +13 -0
  75. package/examples/immeuble/fake_data/meter.csv +4 -0
  76. package/examples/immeuble/fake_data/meter_reading.csv +19 -0
  77. package/examples/immeuble/fake_data/milestone.csv +3 -0
  78. package/examples/immeuble/fake_data/organization.csv +12 -0
  79. package/examples/immeuble/fake_data/parking_space.csv +8 -0
  80. package/examples/immeuble/fake_data/permit.csv +3 -0
  81. package/examples/immeuble/fake_data/person.csv +85 -0
  82. package/examples/immeuble/fake_data/private_garden.csv +7 -0
  83. package/examples/immeuble/fake_data/progress_event.csv +2 -0
  84. package/examples/immeuble/fake_data/quote.csv +3 -0
  85. package/examples/immeuble/fake_data/receipt.csv +2 -0
  86. package/examples/immeuble/fake_data/reminder.csv +11 -0
  87. package/examples/immeuble/fake_data/service_contract.csv +4 -0
  88. package/examples/immeuble/fake_data/shared_equipment.csv +8 -0
  89. package/examples/immeuble/fake_data/shared_space.csv +10 -0
  90. package/examples/immeuble/fake_data/unit.csv +41 -0
  91. package/examples/immeuble/fake_data/work_package.csv +4 -0
  92. package/examples/immeuble/fake_data/worksite_project.csv +3 -0
  93. package/examples/immeuble/gap-rules/L0-patrimoine.json +57 -0
  94. package/examples/immeuble/gap-rules/L1-syndic-naive.json +36 -0
  95. package/examples/immeuble/gap-rules/L1-syndic.json +61 -0
  96. package/examples/immeuble/gap-rules/L2-chantier.json +87 -0
  97. package/examples/immeuble/gap-rules/L2-exploitation.json +87 -0
  98. package/examples/immeuble/gap-rules/L2-finance.json +48 -0
  99. package/examples/immeuble/gap-rules/L2-maintenance.json +107 -0
  100. package/examples/immeuble/gap-rules/L2-syndic-filtered.json +39 -0
  101. package/examples/immeuble/gap-rules/L3-full.json +332 -0
  102. package/examples/immeuble/gap-rules/closed-world-contract.md +76 -0
  103. package/examples/immeuble/gap-rules/demo.json +51 -0
  104. package/examples/immeuble/gap-rules/expected-findings.yaml +100 -0
  105. package/examples/immeuble/gap-rules/gap-scenarios.yaml +79 -0
  106. package/examples/immeuble/gap-rules/motifs.json +38 -0
  107. package/examples/immeuble/gap-rules/syndic.json +40 -0
  108. package/examples/immeuble/import_manifest.yaml +25 -0
  109. package/examples/immeuble/import_ready/graph_edges_import.csv +848 -0
  110. package/examples/immeuble/import_ready/mfo_facets_import.csv +559 -0
  111. package/examples/immeuble/index.md +140 -0
  112. package/examples/immeuble/model/immeuble_model.json +418 -0
  113. package/examples/immeuble/reports/01-model.validation.json +56 -0
  114. package/examples/immeuble/reports/02-mapping.validation.json +9 -0
  115. package/examples/immeuble/reports/acceptance.validation.json +161 -0
  116. package/examples/immeuble/reports/consumer_contract.validation.json +162 -0
  117. package/examples/immeuble/reports/graph_edges.jsonl +847 -0
  118. package/examples/immeuble/reports/graph_nodes.jsonl +558 -0
  119. package/examples/immeuble/reports/hybrid-compare.json +144 -0
  120. package/examples/immeuble/reports/immeuble-import-scenario.json +233 -0
  121. package/examples/immeuble/reports/live-artifacts-refresh.validation.json +51 -0
  122. package/examples/immeuble/reports/pipeline_audit.json +59 -0
  123. package/examples/immeuble/reports/projection_audit.json +69 -0
  124. package/examples/immeuble/reports/projection_audit_immeuble.json +257 -0
  125. package/examples/immeuble/reports/projection_audit_immeuble.md +122 -0
  126. package/examples/immeuble/reports/projection_candidates.json +1596 -0
  127. package/examples/immeuble/reports/projection_candidates.md +117 -0
  128. package/examples/immeuble/reports/projection_model_validation.md +115 -0
  129. package/examples/immeuble/reports/reindex.json +4 -0
  130. package/examples/immeuble/reports/reset-immeuble-workspace.json +32 -0
  131. package/examples/immeuble/reports/schema-id-prefix-check.json +5 -0
  132. package/examples/immeuble/scripts/audit-immeuble-projections.mjs +254 -0
  133. package/examples/immeuble/scripts/build-immeuble-model.mjs +308 -0
  134. package/examples/immeuble/scripts/compare-immeuble-snapshots.sh +227 -0
  135. package/examples/immeuble/scripts/enrich-immeuble-demo.mjs +1135 -0
  136. package/examples/immeuble/scripts/reset-immeuble-workspace.mjs +139 -0
  137. package/examples/immeuble/scripts/run-immeuble-backend.sh +164 -0
  138. package/examples/immeuble/scripts/run-immeuble-import.mjs +232 -0
  139. package/examples/immeuble/scripts/run-immeuble-live-lab.sh +439 -0
  140. package/examples/immeuble/scripts/seed-immeuble-gap-rules.mjs +69 -0
  141. package/examples/immeuble/scripts/start-immeuble-demo.sh +52 -0
  142. package/examples/immeuble/scripts/starterkit/analysis-lenses.mjs +181 -0
  143. package/examples/immeuble/scripts/starterkit/analyze-projection-candidates.mjs +714 -0
  144. package/examples/immeuble/scripts/starterkit/audit-ghostcrab-projections.mjs +674 -0
  145. package/examples/immeuble/scripts/starterkit/facet-prefix.mjs +166 -0
  146. package/examples/immeuble/scripts/starterkit/sqlite-utils.mjs +131 -0
  147. package/examples/immeuble/scripts/verify-immeuble-acceptance.mjs +284 -0
  148. package/examples/immeuble/scripts/verify-immeuble-live-artifacts.mjs +140 -0
  149. package/examples/immeuble/scripts/yaml-lite.mjs +96 -0
  150. package/examples/immeuble/sources/agent-prompts/prompts/00-prerequisites-immo-mcp.md +161 -0
  151. package/examples/immeuble/sources/agent-prompts/prompts/00-prerequisites.md +39 -0
  152. package/examples/immeuble/sources/agent-prompts/prompts/01-discovery-and-model-proposal.md +42 -0
  153. package/examples/immeuble/sources/agent-prompts/prompts/02-ontology-register.md +44 -0
  154. package/examples/immeuble/sources/agent-prompts/prompts/03-gap-rules-design.md +44 -0
  155. package/examples/immeuble/sources/agent-prompts/prompts/04-document-ingest.md +40 -0
  156. package/examples/immeuble/sources/agent-prompts/prompts/05-graph-extraction.md +44 -0
  157. package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare-immo-mcp.md +109 -0
  158. package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare-test-immo-mcp3.md +30 -0
  159. package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare.md +63 -0
  160. package/examples/immeuble/sources/checklists/gap-rules-checklist.md +42 -0
  161. package/examples/immeuble/sources/checklists/ontology-checklist.md +54 -0
  162. package/examples/immeuble/sources/documents/README.md +42 -0
  163. package/examples/immeuble/sources/documents/annexes-caves-garages-jardins.md +8 -0
  164. package/examples/immeuble/sources/documents/annexes-jardins-garages.md +1 -0
  165. package/examples/immeuble/sources/documents/baux-erables.md +1 -0
  166. package/examples/immeuble/sources/documents/baux-locatifs.md +11 -0
  167. package/examples/immeuble/sources/documents/coda-janvier-2026.md +10 -0
  168. package/examples/immeuble/sources/documents/composition-menages.md +1 -0
  169. package/examples/immeuble/sources/documents/composition-occupants.md +18 -0
  170. package/examples/immeuble/sources/documents/expected-coverage.json +70 -0
  171. package/examples/immeuble/sources/documents/extrait-coda-janvier-2026.md +1 -0
  172. package/examples/immeuble/sources/documents/groupes-facturation.md +30 -0
  173. package/examples/immeuble/sources/documents/manifest.json +79 -0
  174. package/examples/immeuble/sources/documents/note-architecte-chantier-erables.md +3 -0
  175. package/examples/immeuble/sources/documents/permis-urbanisme-erables.md +3 -0
  176. package/examples/immeuble/sources/documents/procedures-operationnelles.md +3 -0
  177. package/examples/immeuble/sources/documents/pv-ag-budget-2026.md +1 -0
  178. package/examples/immeuble/sources/documents/pv-reception-reserves-erables.md +3 -0
  179. package/examples/immeuble/sources/documents/registre-coproprietaires.md +24 -0
  180. package/examples/immeuble/sources/documents/reglement-copropriete-tilleuls.md +1 -0
  181. package/examples/immeuble/sources/documents/statuts-erables.md +22 -0
  182. package/examples/immeuble/sources/documents/statuts-tilleuls.md +19 -0
  183. package/examples/immeuble/sources/documents/succession-jean-dupont.md +3 -0
  184. package/examples/immeuble/sources/documents/titre-propriete-tilleuls-a3.md +1 -0
  185. package/examples/immeuble/sources/documents/trous-pedagogiques.md +3 -0
  186. package/examples/immeuble/sources/ontology/README.md +1 -0
  187. package/examples/immeuble/sources/ontology/core.yaml +444 -0
  188. package/examples/immeuble/success-criteria.yaml +35 -0
  189. package/fixtures/immeuble-demo.sqlite +0 -0
  190. package/package.json +16 -3
  191. package/scripts/lib/sqlite-runtime.mjs +1 -1
  192. package/scripts/load-immeuble-demo.sh +103 -0
  193. package/scripts/seed-immeuble-projections.mjs +75 -148
  194. package/scripts/verify-immeuble-demo-sources.mjs +93 -0
  195. package/scripts/verify-immeuble-demo.mjs +69 -0
  196. package/build/client/_app/immutable/chunks/BmeSanva.js +0 -1
  197. package/build/client/_app/immutable/chunks/BmeSanva.js.br +0 -0
  198. package/build/client/_app/immutable/chunks/BmeSanva.js.gz +0 -0
  199. package/build/client/_app/immutable/entry/app.CVz6aYsT.js.br +0 -0
  200. package/build/client/_app/immutable/entry/app.CVz6aYsT.js.gz +0 -0
  201. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js +0 -1
  202. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.br +0 -2
  203. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.gz +0 -0
  204. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.br +0 -0
  205. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.gz +0 -0
  206. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-Dyfsc-VA.js.map +0 -1
  207. package/scripts/build-serenity-v6-concept-review-pack.mjs +0 -493
  208. package/scripts/build-serenity-v6-review-pack.mjs +0 -479
  209. package/scripts/create-serenity-production-v6.mjs +0 -627
  210. package/scripts/export-serenity-v6-backup.mjs +0 -178
  211. package/scripts/import-serenity-v6-user-decisions.mjs +0 -543
  212. package/scripts/materialize-serenity-v6-snapshots.mjs +0 -675
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Idempotent Immeuble model builder (MVP_Serenity_2 style).
4
+ *
5
+ * Reads bundle/immeuble.bundle.json, emits:
6
+ * - model/immeuble_model.json
7
+ * - fake_data/*.csv (one per entity_type)
8
+ * - import_ready/mfo_facets_import.csv + graph_edges_import.csv
9
+ * - contracts/mapping_external_to_canonical.json
10
+ * - reports/*.json / *.jsonl
11
+ */
12
+
13
+ import {
14
+ mkdirSync,
15
+ readFileSync,
16
+ readdirSync,
17
+ writeFileSync,
18
+ rmSync
19
+ } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+
23
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
24
+ const WS = "immeuble";
25
+ const ONT = "immeuble::core";
26
+ const SCHEMA_PREFIX = "immeuble:core:";
27
+
28
+ const PATHS = {
29
+ bundle: join(ROOT, "bundle", "immeuble.bundle.json"),
30
+ model: join(ROOT, "model", "immeuble_model.json"),
31
+ mapping: join(ROOT, "contracts", "mapping_external_to_canonical.json"),
32
+ fakeData: join(ROOT, "fake_data"),
33
+ importReady: join(ROOT, "import_ready"),
34
+ reports: join(ROOT, "reports"),
35
+ successCriteria: join(ROOT, "success-criteria.yaml")
36
+ };
37
+
38
+ const CLOSED_EDGE_LABELS = [
39
+ "contains", "owns", "occupies", "household_member", "primary_residence_of",
40
+ "leases", "rented_to", "assigned_cellar", "assigned_garage", "uses_exclusive",
41
+ "uses_common", "matched_to", "allocated_to", "requires_review", "documents",
42
+ "member_of", "has_member", "bills_to", "linked_to", "manages", "represents",
43
+ "records", "triggered", "decided_by", "manages_project", "designs_for",
44
+ "has_work_package", "executes_work_package", "has_milestone", "authorized_by",
45
+ "based_on_quote", "has_budget_line", "invoiced_by", "affects_asset",
46
+ "reported_by", "has_reserve", "evidenced_by", "covers_asset", "provided_by",
47
+ "concerns_asset", "has_intervention", "covered_by", "certifies_asset",
48
+ "measures_asset", "records_reading", "has_agenda_item", "flags_entity"
49
+ ];
50
+
51
+ function schemaId(entityType) {
52
+ return `${SCHEMA_PREFIX}${entityType}`;
53
+ }
54
+
55
+ function recordId(entityType, entityId) {
56
+ return `${entityType}:${entityId}`;
57
+ }
58
+
59
+ function sourceRef(entityType, entityId) {
60
+ return `${entityType}:${entityType}:${entityId}`;
61
+ }
62
+
63
+ function csvEscape(value) {
64
+ const text = value == null ? "" : String(value);
65
+ if (/[",\n\r]/.test(text)) {
66
+ return `"${text.replaceAll('"', '""')}"`;
67
+ }
68
+ return text;
69
+ }
70
+
71
+ function writeCsv(path, headers, rows) {
72
+ const lines = [headers.join(",")];
73
+ for (const row of rows) {
74
+ lines.push(headers.map((h) => csvEscape(row[h])).join(","));
75
+ }
76
+ writeFileSync(path, lines.join("\n") + "\n", "utf8");
77
+ }
78
+
79
+ function parseMeta(raw) {
80
+ if (!raw) return {};
81
+ try {
82
+ return JSON.parse(raw);
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
87
+
88
+ function transformBundleIds(bundle) {
89
+ const text = JSON.stringify(bundle)
90
+ .replaceAll("immeuble-demo-llm", WS)
91
+ .replaceAll("immeuble-demo::docs", `${WS}::docs`)
92
+ .replaceAll("immeuble-demo::core", ONT)
93
+ .replaceAll("immeuble-demo", WS)
94
+ .replaceAll("immeuble_demo", "immeuble")
95
+ .replaceAll("agent:immeuble-demo", `agent:${WS}`);
96
+ return JSON.parse(text);
97
+ }
98
+
99
+ function buildModel(entityTypes) {
100
+ return {
101
+ workspace_id: WS,
102
+ ontology_id: ONT,
103
+ label: "Immeuble syndic reference model",
104
+ version: "2026-06-14",
105
+ closed_edge_labels: CLOSED_EDGE_LABELS.map((l) => l.toUpperCase()),
106
+ entity_types: entityTypes.map((name) => ({
107
+ name,
108
+ label: name,
109
+ schema_id: schemaId(name),
110
+ ontology: "core",
111
+ node_type: name,
112
+ record_id_pattern: `${name}:<stable-key>`
113
+ })),
114
+ contract_relations: []
115
+ };
116
+ }
117
+
118
+ function entityToRow(entity) {
119
+ const meta = parseMeta(entity.metadata_json);
120
+ return {
121
+ record_id: recordId(entity.entity_type, entity.entity_id),
122
+ name: entity.name ?? "",
123
+ entity_type: entity.entity_type,
124
+ entity_id: String(entity.entity_id),
125
+ confidence: entity.confidence ?? 1,
126
+ ...meta
127
+ };
128
+ }
129
+
130
+ function facetContent(entity, row) {
131
+ const facets = { ...row, source: "fake_data", ontology: "core" };
132
+ delete facets.record_id;
133
+ return facets;
134
+ }
135
+
136
+ function main() {
137
+ mkdirSync(PATHS.fakeData, { recursive: true });
138
+ mkdirSync(PATHS.importReady, { recursive: true });
139
+ mkdirSync(PATHS.reports, { recursive: true });
140
+
141
+ let bundle = JSON.parse(readFileSync(PATHS.bundle, "utf8"));
142
+ bundle = transformBundleIds(bundle);
143
+ writeFileSync(PATHS.bundle, JSON.stringify(bundle, null, 2) + "\n", "utf8");
144
+
145
+ const entities = bundle.entities_raw ?? [];
146
+ const relations = bundle.relations_raw ?? [];
147
+ const byType = new Map();
148
+ const idToEntity = new Map();
149
+
150
+ for (const entity of entities) {
151
+ idToEntity.set(entity.entity_id, entity);
152
+ const list = byType.get(entity.entity_type) ?? [];
153
+ list.push(entity);
154
+ byType.set(entity.entity_type, list);
155
+ }
156
+
157
+ const entityTypes = [...byType.keys()].sort();
158
+ const model = buildModel(entityTypes);
159
+ writeFileSync(PATHS.model, JSON.stringify(model, null, 2) + "\n", "utf8");
160
+
161
+ for (const old of readdirSync(PATHS.fakeData).filter((f) => f.endsWith(".csv"))) {
162
+ rmSync(join(PATHS.fakeData, old));
163
+ }
164
+
165
+ const facetRows = [];
166
+ for (const [entityType, list] of byType.entries()) {
167
+ const rows = list.map(entityToRow);
168
+ const headers = [...new Set(rows.flatMap((r) => Object.keys(r)))];
169
+ const ordered = ["record_id", "name", "entity_type", "entity_id", ...headers.filter((h) => !["record_id", "name", "entity_type", "entity_id"].includes(h))];
170
+ writeCsv(join(PATHS.fakeData, `${entityType}.csv`), ordered, rows);
171
+
172
+ for (const entity of list) {
173
+ const row = entityToRow(entity);
174
+ const facets = facetContent(entity, row);
175
+ facetRows.push({
176
+ content: entity.name ?? row.record_id,
177
+ facets: JSON.stringify(facets),
178
+ schema_id: schemaId(entityType),
179
+ source_ref: sourceRef(entityType, entity.entity_id),
180
+ workspace_id: WS
181
+ });
182
+ }
183
+ }
184
+
185
+ writeCsv(
186
+ join(PATHS.importReady, "mfo_facets_import.csv"),
187
+ ["content", "facets", "schema_id", "source_ref", "workspace_id"],
188
+ facetRows
189
+ );
190
+
191
+ const edgeRows = relations.map((rel) => {
192
+ const source = idToEntity.get(rel.source_entity_id);
193
+ const target = idToEntity.get(rel.target_entity_id);
194
+ const label = (rel.edge_type ?? "linked_to").toUpperCase();
195
+ return {
196
+ confidence: rel.confidence ?? 0.85,
197
+ label,
198
+ metadata_json: JSON.stringify({
199
+ source: "fake_data",
200
+ relation_contract: {
201
+ edge_label: label,
202
+ source_entity_id: rel.source_entity_id,
203
+ target_entity_id: rel.target_entity_id
204
+ }
205
+ }),
206
+ source: source ? sourceRef(source.entity_type, source.entity_id) : String(rel.source_entity_id),
207
+ target: target ? sourceRef(target.entity_type, target.entity_id) : String(rel.target_entity_id),
208
+ workspace_id: WS
209
+ };
210
+ });
211
+
212
+ writeCsv(
213
+ join(PATHS.importReady, "graph_edges_import.csv"),
214
+ ["confidence", "label", "metadata_json", "source", "target", "workspace_id"],
215
+ edgeRows
216
+ );
217
+
218
+ const mappingEntities = {};
219
+ for (const entityType of entityTypes) {
220
+ mappingEntities[entityType] = {
221
+ csv: `fake_data/${entityType}.csv`,
222
+ schema_id: schemaId(entityType),
223
+ record_id_column: "record_id",
224
+ content_columns: ["name"]
225
+ };
226
+ }
227
+
228
+ const mapping = {
229
+ workspace_id: WS,
230
+ ontology_id: ONT,
231
+ source_tag: "structured_import",
232
+ data_plane: "import_ready",
233
+ import_ready: {
234
+ facets_csv: "import_ready/mfo_facets_import.csv",
235
+ edges_csv: "import_ready/graph_edges_import.csv"
236
+ },
237
+ entities: mappingEntities,
238
+ contract_relations: [],
239
+ edges_mode: "import_ready",
240
+ notes: "Generated by build-immeuble-model.mjs from bundle/immeuble.bundle.json"
241
+ };
242
+ writeFileSync(PATHS.mapping, JSON.stringify(mapping, null, 2) + "\n", "utf8");
243
+
244
+ const counts = Object.fromEntries([...byType.entries()].map(([k, v]) => [k, v.length]));
245
+ const nodeLines = entities.map((e) =>
246
+ JSON.stringify({
247
+ record_id: recordId(e.entity_type, e.entity_id),
248
+ entity_type: e.entity_type,
249
+ name: e.name,
250
+ schema_id: schemaId(e.entity_type)
251
+ })
252
+ );
253
+ writeFileSync(join(PATHS.reports, "graph_nodes.jsonl"), nodeLines.join("\n") + "\n", "utf8");
254
+
255
+ const edgeLines = edgeRows.map((e) => JSON.stringify(e));
256
+ writeFileSync(join(PATHS.reports, "graph_edges.jsonl"), edgeLines.join("\n") + "\n", "utf8");
257
+
258
+ const prefixViolations = facetRows.filter((r) => !r.schema_id.startsWith("immeuble:"));
259
+ const modelValidation = {
260
+ ok: prefixViolations.length === 0,
261
+ workspace_id: WS,
262
+ ontology_id: ONT,
263
+ entity_type_count: entityTypes.length,
264
+ entity_types: entityTypes,
265
+ schema_id_prefix: "immeuble:",
266
+ prefix_violations: prefixViolations.length,
267
+ generated_at: new Date().toISOString()
268
+ };
269
+ writeFileSync(join(PATHS.reports, "01-model.validation.json"), JSON.stringify(modelValidation, null, 2) + "\n", "utf8");
270
+
271
+ const mappingValidation = {
272
+ ok: true,
273
+ workspace_id: WS,
274
+ mapped_entity_types: entityTypes.length,
275
+ facet_rows: facetRows.length,
276
+ edge_rows: edgeRows.length,
277
+ all_schema_ids_prefixed: prefixViolations.length === 0,
278
+ generated_at: new Date().toISOString()
279
+ };
280
+ writeFileSync(join(PATHS.reports, "02-mapping.validation.json"), JSON.stringify(mappingValidation, null, 2) + "\n", "utf8");
281
+
282
+ const pipelineAudit = {
283
+ ok: modelValidation.ok && mappingValidation.ok,
284
+ gates: {
285
+ model: modelValidation.ok,
286
+ mapping: mappingValidation.ok,
287
+ facets: facetRows.length > 0,
288
+ edges: edgeRows.length > 0
289
+ },
290
+ counts,
291
+ facet_rows: facetRows.length,
292
+ edge_rows: edgeRows.length,
293
+ generated_at: new Date().toISOString()
294
+ };
295
+ writeFileSync(join(PATHS.reports, "pipeline_audit.json"), JSON.stringify(pipelineAudit, null, 2) + "\n", "utf8");
296
+
297
+ console.log(JSON.stringify({
298
+ ok: pipelineAudit.ok,
299
+ workspace_id: WS,
300
+ entity_types: entityTypes.length,
301
+ facet_rows: facetRows.length,
302
+ edge_rows: edgeRows.length,
303
+ fake_data_csv: entityTypes.length,
304
+ reports: readdirSync(PATHS.reports).length
305
+ }, null, 2));
306
+ }
307
+
308
+ main();
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env bash
2
+ set -eu
3
+
4
+ FROM_DB=""
5
+ TO_DB=""
6
+ WORKSPACE_ID="immeuble"
7
+ FROM_LABEL=""
8
+ TO_LABEL=""
9
+ CUSTOM_TABLES=""
10
+ JSON_OUTPUT=0
11
+ OUTPUT_FILE=""
12
+
13
+ usage() {
14
+ cat <<EOF
15
+ Usage: examples/immeuble/scripts/compare-immeuble-snapshots.sh [options]
16
+
17
+ Compare two SQLite snapshots from Immeuble and show row-count deltas.
18
+
19
+ Options:
20
+ --from <path> Snapshot A path (required)
21
+ --to <path> Snapshot B path (required)
22
+ --label-a <text> Optional label for snapshot A
23
+ --label-b <text> Optional label for snapshot B
24
+ --workspace <id> Workspace filter for workspace-scoped tables (default: immeuble)
25
+ --tables <t1,t2,...> Override the default table set
26
+ --json Emit JSON output
27
+ --out <path> Write output to a file (human or JSON)
28
+ -h, --help Show this help
29
+ EOF
30
+ }
31
+
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --from)
35
+ FROM_DB="${2:?--from requires a value}"
36
+ shift 2
37
+ ;;
38
+ --to)
39
+ TO_DB="${2:?--to requires a value}"
40
+ shift 2
41
+ ;;
42
+ --label-a)
43
+ FROM_LABEL="${2:?--label-a requires a value}"
44
+ shift 2
45
+ ;;
46
+ --label-b)
47
+ TO_LABEL="${2:?--label-b requires a value}"
48
+ shift 2
49
+ ;;
50
+ --workspace)
51
+ WORKSPACE_ID="${2:?--workspace requires a value}"
52
+ shift 2
53
+ ;;
54
+ --tables)
55
+ CUSTOM_TABLES="${2:?--tables requires a value}"
56
+ shift 2
57
+ ;;
58
+ --json)
59
+ JSON_OUTPUT=1
60
+ shift
61
+ ;;
62
+ --out)
63
+ OUTPUT_FILE="${2:?--out requires a value}"
64
+ shift 2
65
+ ;;
66
+ -h|--help)
67
+ usage
68
+ exit 0
69
+ ;;
70
+ *)
71
+ echo "[immeuble-compare] Unknown argument: $1" >&2
72
+ usage
73
+ exit 1
74
+ ;;
75
+ esac
76
+ done
77
+
78
+ if [[ -z "$FROM_DB" || -z "$TO_DB" ]]; then
79
+ echo "[immeuble-compare] --from and --to are required" >&2
80
+ usage
81
+ exit 1
82
+ fi
83
+
84
+ if [[ ! -f "$FROM_DB" ]]; then
85
+ echo "[immeuble-compare] snapshot does not exist: $FROM_DB" >&2
86
+ exit 1
87
+ fi
88
+
89
+ if [[ ! -f "$TO_DB" ]]; then
90
+ echo "[immeuble-compare] snapshot does not exist: $TO_DB" >&2
91
+ exit 1
92
+ fi
93
+
94
+ if ! command -v sqlite3 >/dev/null 2>&1; then
95
+ echo "[immeuble-compare] sqlite3 is required in PATH" >&2
96
+ exit 1
97
+ fi
98
+
99
+ if [[ -z "$FROM_LABEL" ]]; then
100
+ FROM_LABEL="$(basename "$FROM_DB")"
101
+ fi
102
+ if [[ -z "$TO_LABEL" ]]; then
103
+ TO_LABEL="$(basename "$TO_DB")"
104
+ fi
105
+
106
+ DEFAULT_TABLES="agent_facts graph_entity graph_relation entities_raw relations_raw documents_raw facet_assignments_raw facet_assignments_lookup_raw"
107
+ if [[ -n "$CUSTOM_TABLES" ]]; then
108
+ IFS=',' read -r -a TABLES <<< "$CUSTOM_TABLES"
109
+ else
110
+ read -r -a TABLES <<< "$DEFAULT_TABLES"
111
+ fi
112
+
113
+ workspacesql_escape() {
114
+ printf "%s" "$1" | sed "s/'/''/g"
115
+ }
116
+ WORKSPACE_SQL_SAFE="$(workspacesql_escape "$WORKSPACE_ID")"
117
+
118
+ table_exists() {
119
+ local db="$1"
120
+ local table="$2"
121
+ sqlite3 "$db" "SELECT COUNT(1) FROM sqlite_master WHERE type='table' AND name='$table';" | tr -d '[:space:]'
122
+ }
123
+
124
+ has_workspace_column() {
125
+ local db="$1"
126
+ local table="$2"
127
+ sqlite3 "$db" "PRAGMA table_info('$table');" | awk -F'|' 'BEGIN{RS="\n"}{print $2}' | grep -qx "workspace_id" && echo yes
128
+ }
129
+
130
+ count_rows() {
131
+ local db="$1"
132
+ local table="$2"
133
+ local sql="SELECT COUNT(1) FROM \"$table\";"
134
+ if [[ "$WORKSPACE_ID" != "all" ]] && [[ "$(has_workspace_column "$db" "$table")" == "yes" ]]; then
135
+ sql="SELECT COUNT(1) FROM \"$table\" WHERE workspace_id='$WORKSPACE_SQL_SAFE';"
136
+ fi
137
+ sqlite3 "$db" "$sql"
138
+ }
139
+
140
+ collect_line() {
141
+ local db="$1"
142
+ local table="$2"
143
+ if [[ "$(table_exists "$db" "$table")" != "1" ]]; then
144
+ echo "missing"
145
+ return
146
+ fi
147
+ local cnt
148
+ cnt="$(count_rows "$db" "$table" | tr -d '[:space:]')"
149
+ if [[ -z "$cnt" ]]; then
150
+ echo "0"
151
+ return
152
+ fi
153
+ echo "$cnt"
154
+ }
155
+
156
+ json_escape() {
157
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
158
+ }
159
+
160
+ declare -a DIFF_LINES=()
161
+ declare -a JSON_TABLES=()
162
+
163
+ for table in "${TABLES[@]}"; do
164
+ table="${table#"${table%%[![:space:]]*}"}"
165
+ table="${table%"${table##*[![:space:]]}"}"
166
+ if [[ -z "$table" ]]; then
167
+ continue
168
+ fi
169
+ c1="$(collect_line "$FROM_DB" "$table")"
170
+ c2="$(collect_line "$TO_DB" "$table")"
171
+ if [[ "$c1" == "missing" || "$c2" == "missing" ]]; then
172
+ if [[ "$c1" == "missing" && "$c2" == "missing" ]]; then
173
+ delta="-"
174
+ else
175
+ delta="new_table"
176
+ fi
177
+ else
178
+ delta="$((c2 - c1))"
179
+ fi
180
+ from_json="$(json_escape "$c1")"
181
+ to_json="$(json_escape "$c2")"
182
+ delta_json="$(json_escape "$delta")"
183
+ DIFF_LINES+=("$table|$c1|$c2|$delta")
184
+ JSON_TABLES+=("{\"table\":\"$table\",\"from\":\"$from_json\",\"to\":\"$to_json\",\"delta\":\"$delta_json\"}")
185
+ done
186
+
187
+ rows_changed=0
188
+ for line in "${DIFF_LINES[@]}"; do
189
+ IFS='|' read -r table from to delta <<< "$line"
190
+ if [[ "$delta" != "-" && "$delta" != "0" && "$delta" != "new_table" ]]; then
191
+ rows_changed=1
192
+ fi
193
+ if [[ "$delta" == "new_table" ]]; then
194
+ rows_changed=1
195
+ fi
196
+ done
197
+
198
+ if (( JSON_OUTPUT == 1 )); then
199
+ if [[ ${#JSON_TABLES[@]} -eq 0 ]]; then
200
+ json="[{\"table\":\"(none)\",\"from\":\"missing\",\"to\":\"missing\",\"delta\":\"-\"}]"
201
+ else
202
+ json="$(printf '%s\n' "${JSON_TABLES[@]}" | awk 'BEGIN{printf "["} {printf "%s%s", sep, $0; sep=","} END{printf "]"}')"
203
+ fi
204
+
205
+ payload="{\"from\":\"$FROM_LABEL\",\"to\":\"$TO_LABEL\",\"from_db\":\"$FROM_DB\",\"to_db\":\"$TO_DB\",\"workspace\":\"$WORKSPACE_ID\",\"rows_changed\":$rows_changed,\"tables\":$json}"
206
+ else
207
+ payload="$(printf 'Comparing snapshots\\nFrom: %s (%s)\\nTo: %s (%s)\\nWorkspace filter: %s\\n\\n' "$FROM_DB" "$FROM_LABEL" "$TO_DB" "$TO_LABEL" "$WORKSPACE_ID")"
208
+ payload="$payload$(printf '%-26s %-14s %-14s %-14s\\n' 'table' "from:$FROM_LABEL" "to:$TO_LABEL" "delta")"
209
+ payload="$payload$(printf '%-26s %-14s %-14s %-14s\\n' '-----' '-------------' '-----------' '-----')"
210
+ for line in "${DIFF_LINES[@]}"; do
211
+ IFS='|' read -r table from to delta <<< "$line"
212
+ payload="$payload$(printf '%-26s %-14s %-14s %-14s\\n' "$table" "$from" "$to" "$delta")"
213
+ done
214
+ payload="$payload\\n"
215
+ if [[ "$rows_changed" -eq 0 ]]; then
216
+ payload="${payload}No row-change on the selected tables."
217
+ else
218
+ payload="${payload}Diff detected on at least one table."
219
+ fi
220
+ fi
221
+
222
+ if [[ -n "$OUTPUT_FILE" ]]; then
223
+ printf '%b' "$payload" > "$OUTPUT_FILE"
224
+ echo "[immeuble-compare] wrote: $OUTPUT_FILE"
225
+ else
226
+ printf '%b' "$payload"
227
+ fi