@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.
- package/README.md +16 -8
- package/build/client/_app/immutable/chunks/CIErFlYG.js +1 -0
- package/build/client/_app/immutable/chunks/CIErFlYG.js.br +0 -0
- package/build/client/_app/immutable/chunks/CIErFlYG.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CVz6aYsT.js → app.mURYm8o_.js} +2 -2
- package/build/client/_app/immutable/entry/app.mURYm8o_.js.br +0 -0
- package/build/client/_app/immutable/entry/app.mURYm8o_.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D4M9ZeGO.js +1 -0
- package/build/client/_app/immutable/entry/start.D4M9ZeGO.js.br +2 -0
- package/build/client/_app/immutable/entry/start.D4M9ZeGO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BBtxY46Q.js → 1.DHKtMeFI.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DHKtMeFI.js.br +1 -0
- package/build/client/_app/immutable/nodes/1.DHKtMeFI.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +4 -4
- package/build/index.js +4 -4
- package/build/server/chunks/chunks/{internal.js-D8EA_he2.js → internal.js-C3tV0XXj.js} +2 -2
- package/build/server/chunks/chunks/{internal.js-D8EA_he2.js.map → internal.js-C3tV0XXj.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/graph/schema-registry/{_server.ts.js-Dyfsc-VA.js → _server.ts.js-CAsXxBRq.js} +7 -2
- package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-CAsXxBRq.js.map +1 -0
- package/build/server/chunks/{handler-BjXBCd1c.js → handler-DlaCCnxx.js} +3 -3
- package/build/server/chunks/{handler-BjXBCd1c.js.map → handler-DlaCCnxx.js.map} +1 -1
- package/build/server/chunks/{index.js-BJYcV8V7.js → index.js-BGfKWHak.js} +2 -2
- package/build/server/chunks/{index.js-BJYcV8V7.js.map → index.js-BGfKWHak.js.map} +1 -1
- package/build/server/chunks/{manifest.js-Ddaot0P4.js → manifest.js-CnmaNf5D.js} +4 -4
- package/build/server/chunks/{manifest.js-Ddaot0P4.js.map → manifest.js-CnmaNf5D.js.map} +1 -1
- package/build/server/chunks/nodes/{1.js-BRigw_9J.js → 1.js-BypjwBYB.js} +2 -2
- package/build/server/chunks/nodes/{1.js-BRigw_9J.js.map → 1.js-BypjwBYB.js.map} +1 -1
- package/examples/immeuble/ACCEPTANCE.yaml +82 -0
- package/examples/immeuble/BIM_LITE.md +15 -0
- package/examples/immeuble/CHECKLIST.md +128 -0
- package/examples/immeuble/README.md +121 -0
- package/examples/immeuble/bundle/immeuble.bundle.json +22786 -0
- package/examples/immeuble/contracts/answer_artifacts.seed.jsonl +34 -0
- package/examples/immeuble/contracts/business_capabilities.seed.jsonl +25 -0
- package/examples/immeuble/contracts/consumer_contract.yaml +131 -0
- package/examples/immeuble/contracts/immeuble_structured_import_model.json +310 -0
- package/examples/immeuble/contracts/mapping_external_to_canonical.json +375 -0
- package/examples/immeuble/contracts/mapping_external_to_canonical.yaml +55 -0
- package/examples/immeuble/contracts/mapping_external_to_canonical_ws.json +65 -0
- package/examples/immeuble/contracts/model_contract.json +943 -0
- package/examples/immeuble/contracts/projection_catalog.yaml +366 -0
- package/examples/immeuble/contracts/scenarios.yaml +27 -0
- package/examples/immeuble/contracts/semantic_proposal.golden.json +1 -0
- package/examples/immeuble/contracts/source_profile.yaml +38 -0
- package/examples/immeuble/fake_data/DeltaFinding.csv +20 -0
- package/examples/immeuble/fake_data/ProjectionResult.csv +13 -0
- package/examples/immeuble/fake_data/ag_meeting.csv +4 -0
- package/examples/immeuble/fake_data/agenda_item.csv +4 -0
- package/examples/immeuble/fake_data/architect.csv +3 -0
- package/examples/immeuble/fake_data/architecture_firm.csv +2 -0
- package/examples/immeuble/fake_data/bank_account.csv +3 -0
- package/examples/immeuble/fake_data/billing_group.csv +41 -0
- package/examples/immeuble/fake_data/block.csv +10 -0
- package/examples/immeuble/fake_data/budget_line.csv +3 -0
- package/examples/immeuble/fake_data/building.csv +6 -0
- package/examples/immeuble/fake_data/cellar.csv +41 -0
- package/examples/immeuble/fake_data/change_order.csv +2 -0
- package/examples/immeuble/fake_data/charge_call.csv +58 -0
- package/examples/immeuble/fake_data/claim.csv +4 -0
- package/examples/immeuble/fake_data/coda_entry.csv +49 -0
- package/examples/immeuble/fake_data/compliance_certificate.csv +10 -0
- package/examples/immeuble/fake_data/contractor.csv +4 -0
- package/examples/immeuble/fake_data/decision.csv +5 -0
- package/examples/immeuble/fake_data/defect_reserve.csv +3 -0
- package/examples/immeuble/fake_data/household.csv +41 -0
- package/examples/immeuble/fake_data/inspection.csv +3 -0
- package/examples/immeuble/fake_data/insurance_policy.csv +4 -0
- package/examples/immeuble/fake_data/intervention.csv +13 -0
- package/examples/immeuble/fake_data/invoice.csv +3 -0
- package/examples/immeuble/fake_data/lease_contract.csv +12 -0
- package/examples/immeuble/fake_data/maintenance_ticket.csv +13 -0
- package/examples/immeuble/fake_data/meter.csv +4 -0
- package/examples/immeuble/fake_data/meter_reading.csv +19 -0
- package/examples/immeuble/fake_data/milestone.csv +3 -0
- package/examples/immeuble/fake_data/organization.csv +12 -0
- package/examples/immeuble/fake_data/parking_space.csv +8 -0
- package/examples/immeuble/fake_data/permit.csv +3 -0
- package/examples/immeuble/fake_data/person.csv +85 -0
- package/examples/immeuble/fake_data/private_garden.csv +7 -0
- package/examples/immeuble/fake_data/progress_event.csv +2 -0
- package/examples/immeuble/fake_data/quote.csv +3 -0
- package/examples/immeuble/fake_data/receipt.csv +2 -0
- package/examples/immeuble/fake_data/reminder.csv +11 -0
- package/examples/immeuble/fake_data/service_contract.csv +4 -0
- package/examples/immeuble/fake_data/shared_equipment.csv +8 -0
- package/examples/immeuble/fake_data/shared_space.csv +10 -0
- package/examples/immeuble/fake_data/unit.csv +41 -0
- package/examples/immeuble/fake_data/work_package.csv +4 -0
- package/examples/immeuble/fake_data/worksite_project.csv +3 -0
- package/examples/immeuble/gap-rules/L0-patrimoine.json +57 -0
- package/examples/immeuble/gap-rules/L1-syndic-naive.json +36 -0
- package/examples/immeuble/gap-rules/L1-syndic.json +61 -0
- package/examples/immeuble/gap-rules/L2-chantier.json +87 -0
- package/examples/immeuble/gap-rules/L2-exploitation.json +87 -0
- package/examples/immeuble/gap-rules/L2-finance.json +48 -0
- package/examples/immeuble/gap-rules/L2-maintenance.json +107 -0
- package/examples/immeuble/gap-rules/L2-syndic-filtered.json +39 -0
- package/examples/immeuble/gap-rules/L3-full.json +332 -0
- package/examples/immeuble/gap-rules/closed-world-contract.md +76 -0
- package/examples/immeuble/gap-rules/demo.json +51 -0
- package/examples/immeuble/gap-rules/expected-findings.yaml +100 -0
- package/examples/immeuble/gap-rules/gap-scenarios.yaml +79 -0
- package/examples/immeuble/gap-rules/motifs.json +38 -0
- package/examples/immeuble/gap-rules/syndic.json +40 -0
- package/examples/immeuble/import_manifest.yaml +25 -0
- package/examples/immeuble/import_ready/graph_edges_import.csv +848 -0
- package/examples/immeuble/import_ready/mfo_facets_import.csv +559 -0
- package/examples/immeuble/index.md +140 -0
- package/examples/immeuble/model/immeuble_model.json +418 -0
- package/examples/immeuble/reports/01-model.validation.json +56 -0
- package/examples/immeuble/reports/02-mapping.validation.json +9 -0
- package/examples/immeuble/reports/acceptance.validation.json +161 -0
- package/examples/immeuble/reports/consumer_contract.validation.json +162 -0
- package/examples/immeuble/reports/graph_edges.jsonl +847 -0
- package/examples/immeuble/reports/graph_nodes.jsonl +558 -0
- package/examples/immeuble/reports/hybrid-compare.json +144 -0
- package/examples/immeuble/reports/immeuble-import-scenario.json +233 -0
- package/examples/immeuble/reports/live-artifacts-refresh.validation.json +51 -0
- package/examples/immeuble/reports/pipeline_audit.json +59 -0
- package/examples/immeuble/reports/projection_audit.json +69 -0
- package/examples/immeuble/reports/projection_audit_immeuble.json +257 -0
- package/examples/immeuble/reports/projection_audit_immeuble.md +122 -0
- package/examples/immeuble/reports/projection_candidates.json +1596 -0
- package/examples/immeuble/reports/projection_candidates.md +117 -0
- package/examples/immeuble/reports/projection_model_validation.md +115 -0
- package/examples/immeuble/reports/reindex.json +4 -0
- package/examples/immeuble/reports/reset-immeuble-workspace.json +32 -0
- package/examples/immeuble/reports/schema-id-prefix-check.json +5 -0
- package/examples/immeuble/scripts/audit-immeuble-projections.mjs +254 -0
- package/examples/immeuble/scripts/build-immeuble-model.mjs +308 -0
- package/examples/immeuble/scripts/compare-immeuble-snapshots.sh +227 -0
- package/examples/immeuble/scripts/enrich-immeuble-demo.mjs +1135 -0
- package/examples/immeuble/scripts/reset-immeuble-workspace.mjs +139 -0
- package/examples/immeuble/scripts/run-immeuble-backend.sh +164 -0
- package/examples/immeuble/scripts/run-immeuble-import.mjs +232 -0
- package/examples/immeuble/scripts/run-immeuble-live-lab.sh +439 -0
- package/examples/immeuble/scripts/seed-immeuble-gap-rules.mjs +69 -0
- package/examples/immeuble/scripts/start-immeuble-demo.sh +52 -0
- package/examples/immeuble/scripts/starterkit/analysis-lenses.mjs +181 -0
- package/examples/immeuble/scripts/starterkit/analyze-projection-candidates.mjs +714 -0
- package/examples/immeuble/scripts/starterkit/audit-ghostcrab-projections.mjs +674 -0
- package/examples/immeuble/scripts/starterkit/facet-prefix.mjs +166 -0
- package/examples/immeuble/scripts/starterkit/sqlite-utils.mjs +131 -0
- package/examples/immeuble/scripts/verify-immeuble-acceptance.mjs +284 -0
- package/examples/immeuble/scripts/verify-immeuble-live-artifacts.mjs +140 -0
- package/examples/immeuble/scripts/yaml-lite.mjs +96 -0
- package/examples/immeuble/sources/agent-prompts/prompts/00-prerequisites-immo-mcp.md +161 -0
- package/examples/immeuble/sources/agent-prompts/prompts/00-prerequisites.md +39 -0
- package/examples/immeuble/sources/agent-prompts/prompts/01-discovery-and-model-proposal.md +42 -0
- package/examples/immeuble/sources/agent-prompts/prompts/02-ontology-register.md +44 -0
- package/examples/immeuble/sources/agent-prompts/prompts/03-gap-rules-design.md +44 -0
- package/examples/immeuble/sources/agent-prompts/prompts/04-document-ingest.md +40 -0
- package/examples/immeuble/sources/agent-prompts/prompts/05-graph-extraction.md +44 -0
- package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare-immo-mcp.md +109 -0
- package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare-test-immo-mcp3.md +30 -0
- package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare.md +63 -0
- package/examples/immeuble/sources/checklists/gap-rules-checklist.md +42 -0
- package/examples/immeuble/sources/checklists/ontology-checklist.md +54 -0
- package/examples/immeuble/sources/documents/README.md +42 -0
- package/examples/immeuble/sources/documents/annexes-caves-garages-jardins.md +8 -0
- package/examples/immeuble/sources/documents/annexes-jardins-garages.md +1 -0
- package/examples/immeuble/sources/documents/baux-erables.md +1 -0
- package/examples/immeuble/sources/documents/baux-locatifs.md +11 -0
- package/examples/immeuble/sources/documents/coda-janvier-2026.md +10 -0
- package/examples/immeuble/sources/documents/composition-menages.md +1 -0
- package/examples/immeuble/sources/documents/composition-occupants.md +18 -0
- package/examples/immeuble/sources/documents/expected-coverage.json +70 -0
- package/examples/immeuble/sources/documents/extrait-coda-janvier-2026.md +1 -0
- package/examples/immeuble/sources/documents/groupes-facturation.md +30 -0
- package/examples/immeuble/sources/documents/manifest.json +79 -0
- package/examples/immeuble/sources/documents/note-architecte-chantier-erables.md +3 -0
- package/examples/immeuble/sources/documents/permis-urbanisme-erables.md +3 -0
- package/examples/immeuble/sources/documents/procedures-operationnelles.md +3 -0
- package/examples/immeuble/sources/documents/pv-ag-budget-2026.md +1 -0
- package/examples/immeuble/sources/documents/pv-reception-reserves-erables.md +3 -0
- package/examples/immeuble/sources/documents/registre-coproprietaires.md +24 -0
- package/examples/immeuble/sources/documents/reglement-copropriete-tilleuls.md +1 -0
- package/examples/immeuble/sources/documents/statuts-erables.md +22 -0
- package/examples/immeuble/sources/documents/statuts-tilleuls.md +19 -0
- package/examples/immeuble/sources/documents/succession-jean-dupont.md +3 -0
- package/examples/immeuble/sources/documents/titre-propriete-tilleuls-a3.md +1 -0
- package/examples/immeuble/sources/documents/trous-pedagogiques.md +3 -0
- package/examples/immeuble/sources/ontology/README.md +1 -0
- package/examples/immeuble/sources/ontology/core.yaml +444 -0
- package/examples/immeuble/success-criteria.yaml +35 -0
- package/fixtures/immeuble-demo.sqlite +0 -0
- package/package.json +16 -3
- package/scripts/lib/sqlite-runtime.mjs +1 -1
- package/scripts/load-immeuble-demo.sh +103 -0
- package/scripts/seed-immeuble-projections.mjs +75 -148
- package/scripts/verify-immeuble-demo-sources.mjs +93 -0
- package/scripts/verify-immeuble-demo.mjs +69 -0
- package/build/client/_app/immutable/chunks/BmeSanva.js +0 -1
- package/build/client/_app/immutable/chunks/BmeSanva.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmeSanva.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CVz6aYsT.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CVz6aYsT.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Bt5tVOz8.js +0 -1
- package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.br +0 -2
- package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.gz +0 -0
- package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-Dyfsc-VA.js.map +0 -1
- package/scripts/build-serenity-v6-concept-review-pack.mjs +0 -493
- package/scripts/build-serenity-v6-review-pack.mjs +0 -479
- package/scripts/create-serenity-production-v6.mjs +0 -627
- package/scripts/export-serenity-v6-backup.mjs +0 -178
- package/scripts/import-serenity-v6-user-decisions.mjs +0 -543
- 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
|