@mindflight/mindbrain-personal-studio 0.6.2 → 0.6.4
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/{D0UIlUGZ.js → BjfgiHck.js} +1 -1
- package/build/client/_app/immutable/chunks/BjfgiHck.js.br +0 -0
- package/build/client/_app/immutable/chunks/BjfgiHck.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CR-imLox.js → app.XgS1PoV0.js} +2 -2
- package/build/client/_app/immutable/entry/app.XgS1PoV0.js.br +0 -0
- package/build/client/_app/immutable/entry/app.XgS1PoV0.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Dtz8dQFZ.js +1 -0
- package/build/client/_app/immutable/entry/start.Dtz8dQFZ.js.br +2 -0
- package/build/client/_app/immutable/entry/start.Dtz8dQFZ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.CTEedoSY.js → 1.Cs3BmvHO.js} +1 -1
- package/build/client/_app/immutable/nodes/1.Cs3BmvHO.js.br +3 -0
- package/build/client/_app/immutable/nodes/1.Cs3BmvHO.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-WOmQXGMa.js → internal.js-BnebQ91H.js} +2 -2
- package/build/server/chunks/chunks/{internal.js-WOmQXGMa.js.map → internal.js-BnebQ91H.js.map} +1 -1
- package/build/server/chunks/{handler-BIDedSZq.js → handler-B8M0DnFi.js} +3 -3
- package/build/server/chunks/{handler-BIDedSZq.js.map → handler-B8M0DnFi.js.map} +1 -1
- package/build/server/chunks/{index.js-YVPJa0so.js → index.js-qz9YZN7m.js} +2 -2
- package/build/server/chunks/{index.js-YVPJa0so.js.map → index.js-qz9YZN7m.js.map} +1 -1
- package/build/server/chunks/{manifest.js-aGRKuiqF.js → manifest.js-BxEwHoCX.js} +3 -3
- package/build/server/chunks/{manifest.js-aGRKuiqF.js.map → manifest.js-BxEwHoCX.js.map} +1 -1
- package/build/server/chunks/nodes/{1.js-Dhh3ErZY.js → 1.js-D3KAPgsF.js} +2 -2
- package/build/server/chunks/nodes/{1.js-Dhh3ErZY.js.map → 1.js-D3KAPgsF.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 +103 -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 +11 -2
- package/scripts/lib/sqlite-runtime.mjs +1 -1
- package/scripts/load-immeuble-demo.sh +179 -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/D0UIlUGZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/D0UIlUGZ.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CR-imLox.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CR-imLox.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DV-AjeAB.js +0 -1
- package/build/client/_app/immutable/entry/start.DV-AjeAB.js.br +0 -2
- package/build/client/_app/immutable/entry/start.DV-AjeAB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.CTEedoSY.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.CTEedoSY.js.gz +0 -0
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const WS = "immeuble";
|
|
8
|
+
const ONT = "immeuble::core";
|
|
9
|
+
const GENERATED_TAG = "portfolio_v2";
|
|
10
|
+
const GENERATED_MIN_ID = 2000;
|
|
11
|
+
const GENERATED_REL_MIN_ID = 10000;
|
|
12
|
+
const NOW = 1760000000;
|
|
13
|
+
|
|
14
|
+
const paths = {
|
|
15
|
+
bundle: join(ROOT, "bundle", "immeuble.bundle.json"),
|
|
16
|
+
projectionCatalog: join(ROOT, "contracts", "projection_catalog.yaml"),
|
|
17
|
+
answerSeed: join(ROOT, "contracts", "answer_artifacts.seed.jsonl"),
|
|
18
|
+
businessSeed: join(ROOT, "contracts", "business_capabilities.seed.jsonl"),
|
|
19
|
+
consumerContract: join(ROOT, "contracts", "consumer_contract.yaml"),
|
|
20
|
+
acceptance: join(ROOT, "ACCEPTANCE.yaml"),
|
|
21
|
+
successCriteria: join(ROOT, "success-criteria.yaml"),
|
|
22
|
+
gapRules: join(ROOT, "gap-rules"),
|
|
23
|
+
docs: join(ROOT, "sources", "documents")
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function meta(value = {}) {
|
|
27
|
+
return JSON.stringify({ ...value, generated_by: GENERATED_TAG });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseMeta(raw) {
|
|
31
|
+
if (!raw) return {};
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function slug(value) {
|
|
40
|
+
return String(value)
|
|
41
|
+
.normalize("NFD")
|
|
42
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
45
|
+
.replace(/^_+|_+$/g, "")
|
|
46
|
+
.replace(/_+/g, "_");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function yamlScalar(value) {
|
|
50
|
+
if (value == null) return "";
|
|
51
|
+
const text = String(value);
|
|
52
|
+
if (/^[A-Za-z0-9_.:/-]+$/.test(text)) return text;
|
|
53
|
+
return JSON.stringify(text);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function yamlList(values) {
|
|
57
|
+
return `[${values.map((value) => yamlScalar(value)).join(", ")}]`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function entity(id, type, name, metadata = {}) {
|
|
61
|
+
return {
|
|
62
|
+
workspace_id: WS,
|
|
63
|
+
ontology_id: ONT,
|
|
64
|
+
entity_id: id,
|
|
65
|
+
entity_type: type,
|
|
66
|
+
name,
|
|
67
|
+
confidence: 1,
|
|
68
|
+
metadata_json: meta(metadata)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function relation(id, edge, source, target, metadata = {}) {
|
|
73
|
+
return {
|
|
74
|
+
workspace_id: WS,
|
|
75
|
+
ontology_id: ONT,
|
|
76
|
+
relation_id: id,
|
|
77
|
+
edge_type: edge,
|
|
78
|
+
source_entity_id: source,
|
|
79
|
+
target_entity_id: target,
|
|
80
|
+
valid_from: null,
|
|
81
|
+
valid_to: null,
|
|
82
|
+
confidence: 1,
|
|
83
|
+
metadata_json: meta(metadata)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function doc(id, title, content, documentType, domain = "syndic") {
|
|
88
|
+
const source = `examples/immeuble/sources/documents/generated-${slug(title)}.md`;
|
|
89
|
+
return {
|
|
90
|
+
workspace_id: WS,
|
|
91
|
+
collection_id: "immeuble::docs",
|
|
92
|
+
doc_id: id,
|
|
93
|
+
doc_nanoid: `immeuble-doc-${id}`,
|
|
94
|
+
content,
|
|
95
|
+
language: "fr",
|
|
96
|
+
source_ref: source,
|
|
97
|
+
summary: title,
|
|
98
|
+
metadata_json: meta({ title, document_type: documentType, domain, synthetic: true })
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function chunkFor(document) {
|
|
103
|
+
return {
|
|
104
|
+
workspace_id: WS,
|
|
105
|
+
collection_id: document.collection_id,
|
|
106
|
+
doc_id: document.doc_id,
|
|
107
|
+
chunk_index: 0,
|
|
108
|
+
content: document.content,
|
|
109
|
+
language: document.language,
|
|
110
|
+
offset_start: 0,
|
|
111
|
+
offset_end: document.content.length,
|
|
112
|
+
strategy: "document",
|
|
113
|
+
token_count: Math.max(20, Math.ceil(document.content.length / 5)),
|
|
114
|
+
parent_chunk_index: null,
|
|
115
|
+
metadata_json: document.metadata_json
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function artifact(kind, name, label, payload, lifecycle = "stale", state = "dirty", legacyRef = null) {
|
|
120
|
+
const prefix = kind === "analysis_plan" ? "analysis_plan" : kind;
|
|
121
|
+
return {
|
|
122
|
+
artifact_id: `${prefix}__${name}`,
|
|
123
|
+
slug: name,
|
|
124
|
+
workspace_id: WS,
|
|
125
|
+
agent_id: kind === "analysis_plan" ? "agent:immeuble" : null,
|
|
126
|
+
scope: kind === "analysis_plan" ? WS : null,
|
|
127
|
+
artifact_kind: kind,
|
|
128
|
+
public_label_key: `${kind}.immeuble.${name}`,
|
|
129
|
+
public_label: label,
|
|
130
|
+
lifecycle,
|
|
131
|
+
state,
|
|
132
|
+
current_version: 1,
|
|
133
|
+
payload_json: JSON.stringify(payload),
|
|
134
|
+
legacy_ref: legacyRef,
|
|
135
|
+
created_at_unix: NOW,
|
|
136
|
+
updated_at_unix: NOW
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function addType(bundle, type, label = type, layer = "operations") {
|
|
141
|
+
bundle.ontology_entity_types ??= [];
|
|
142
|
+
if (!bundle.ontology_entity_types.some((row) => row.entity_type === type)) {
|
|
143
|
+
bundle.ontology_entity_types.push({
|
|
144
|
+
ontology_id: ONT,
|
|
145
|
+
entity_type: type,
|
|
146
|
+
label,
|
|
147
|
+
metadata_json: JSON.stringify({
|
|
148
|
+
linkml_class: type,
|
|
149
|
+
abstract: false,
|
|
150
|
+
ghostcrab: {
|
|
151
|
+
"ghostcrab.native_entity_type": type,
|
|
152
|
+
"ghostcrab.layer": layer
|
|
153
|
+
},
|
|
154
|
+
generated_by: GENERATED_TAG
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function addEdgeType(bundle, edge, source, target) {
|
|
161
|
+
bundle.ontology_edge_types ??= [];
|
|
162
|
+
if (!bundle.ontology_edge_types.some((row) => row.edge_type === edge && row.source_entity_type === source && row.target_entity_type === target)) {
|
|
163
|
+
bundle.ontology_edge_types.push({
|
|
164
|
+
ontology_id: ONT,
|
|
165
|
+
edge_type: edge,
|
|
166
|
+
directed: true,
|
|
167
|
+
source_entity_type: source,
|
|
168
|
+
target_entity_type: target,
|
|
169
|
+
metadata_json: meta({ source_entity_type: source, target_entity_type: target })
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function addEntityDocument(bundle, entityId, docId, role = "evidence") {
|
|
175
|
+
bundle.entity_documents_raw.push({
|
|
176
|
+
workspace_id: WS,
|
|
177
|
+
entity_id: entityId,
|
|
178
|
+
collection_id: "immeuble::docs",
|
|
179
|
+
doc_id: docId,
|
|
180
|
+
role,
|
|
181
|
+
confidence: 0.9
|
|
182
|
+
});
|
|
183
|
+
bundle.entity_chunks_raw.push({
|
|
184
|
+
workspace_id: WS,
|
|
185
|
+
entity_id: entityId,
|
|
186
|
+
collection_id: "immeuble::docs",
|
|
187
|
+
doc_id: docId,
|
|
188
|
+
chunk_index: 0,
|
|
189
|
+
role,
|
|
190
|
+
confidence: 0.9
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function nextFactory(start) {
|
|
195
|
+
let current = start;
|
|
196
|
+
return () => current++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function buildPortfolioData(bundle) {
|
|
200
|
+
const nextEntity = nextFactory(GENERATED_MIN_ID);
|
|
201
|
+
const nextRel = nextFactory(GENERATED_REL_MIN_ID);
|
|
202
|
+
const nextDoc = nextFactory(100);
|
|
203
|
+
const entities = [];
|
|
204
|
+
const relations = [];
|
|
205
|
+
const docs = [];
|
|
206
|
+
const entityDocs = [];
|
|
207
|
+
const aliases = [];
|
|
208
|
+
|
|
209
|
+
const generatedBuildings = [
|
|
210
|
+
{ name: "Residence Les Platanes", address: "4 avenue des Platanes, 1300 Wavre", city: "Wavre" },
|
|
211
|
+
{ name: "Residence Le Canal", address: "18 quai du Canal, 1000 Bruxelles", city: "Bruxelles" },
|
|
212
|
+
{ name: "Residence Saint-Lambert", address: "7 rue Saint-Lambert, 4000 Liege", city: "Liege" }
|
|
213
|
+
];
|
|
214
|
+
const quotaSets = [
|
|
215
|
+
[120, 110, 115, 105, 120, 110, 115, 105, 100],
|
|
216
|
+
[115, 115, 110, 110, 115, 115, 110, 110, 100],
|
|
217
|
+
[125, 105, 120, 100, 125, 105, 120, 100, 100]
|
|
218
|
+
];
|
|
219
|
+
const firstNames = ["Anne", "Karim", "Lucie", "Noah", "Maya", "Thomas", "Sofia", "Hugo", "Lina", "Nora", "Elias", "Eva"];
|
|
220
|
+
const lastNames = ["Mercier", "Simon", "Lambert", "Petit", "Dubois", "Nguyen", "Rossi", "Peeters", "Colin", "Jacobs"];
|
|
221
|
+
|
|
222
|
+
for (const [buildingIndex, buildingInput] of generatedBuildings.entries()) {
|
|
223
|
+
const buildingId = nextEntity();
|
|
224
|
+
const building = entity(buildingId, "building", buildingInput.name, {
|
|
225
|
+
address: buildingInput.address,
|
|
226
|
+
city: buildingInput.city,
|
|
227
|
+
quota_basis: 1000,
|
|
228
|
+
portfolio_segment: "medium_demo"
|
|
229
|
+
});
|
|
230
|
+
entities.push(building);
|
|
231
|
+
|
|
232
|
+
const acpId = nextEntity();
|
|
233
|
+
entities.push(entity(acpId, "organization", `ACP ${buildingInput.name}`, {
|
|
234
|
+
role: "association_coproprietaires",
|
|
235
|
+
building_id: buildingId
|
|
236
|
+
}));
|
|
237
|
+
relations.push(relation(nextRel(), "represents", acpId, buildingId, { role: "acp" }));
|
|
238
|
+
relations.push(relation(nextRel(), "manages", 201, buildingId, { role: "syndic_reference" }));
|
|
239
|
+
|
|
240
|
+
const buildingDoc = doc(
|
|
241
|
+
nextDoc(),
|
|
242
|
+
`Registre operationnel ${buildingInput.name}`,
|
|
243
|
+
`${buildingInput.name} est ajoutee au portefeuille demo avec blocs, lots, coproprietaires, charges, contrats, releves et anomalies pedagogiques.`,
|
|
244
|
+
"registre_operationnel"
|
|
245
|
+
);
|
|
246
|
+
docs.push(buildingDoc);
|
|
247
|
+
entityDocs.push([buildingId, buildingDoc.doc_id, "portfolio_register"]);
|
|
248
|
+
|
|
249
|
+
const blockIds = [];
|
|
250
|
+
for (const blockName of ["A", "B"]) {
|
|
251
|
+
const blockId = nextEntity();
|
|
252
|
+
blockIds.push(blockId);
|
|
253
|
+
entities.push(entity(blockId, "block", `${buildingInput.name} Bloc ${blockName}`, {
|
|
254
|
+
building_id: buildingId,
|
|
255
|
+
building: buildingInput.name,
|
|
256
|
+
block: blockName
|
|
257
|
+
}));
|
|
258
|
+
relations.push(relation(nextRel(), "contains", buildingId, blockId, { level: "block" }));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const sharedSpaceId = nextEntity();
|
|
262
|
+
const equipmentId = nextEntity();
|
|
263
|
+
const meterId = nextEntity();
|
|
264
|
+
entities.push(entity(sharedSpaceId, "shared_space", `${buildingInput.name} Local technique`, {
|
|
265
|
+
building_id: buildingId,
|
|
266
|
+
usage: "technical_room"
|
|
267
|
+
}));
|
|
268
|
+
entities.push(entity(equipmentId, "shared_equipment", `${buildingInput.name} Chaufferie collective`, {
|
|
269
|
+
building_id: buildingId,
|
|
270
|
+
equipment_type: "heating",
|
|
271
|
+
status: buildingIndex === 1 ? "maintenance_due" : "active"
|
|
272
|
+
}));
|
|
273
|
+
entities.push(entity(meterId, "meter", `${buildingInput.name} Compteur eau general`, {
|
|
274
|
+
building_id: buildingId,
|
|
275
|
+
meter_type: "water",
|
|
276
|
+
serial: `EAU-${buildingId}`
|
|
277
|
+
}));
|
|
278
|
+
relations.push(relation(nextRel(), "contains", buildingId, sharedSpaceId, { level: "shared_space" }));
|
|
279
|
+
relations.push(relation(nextRel(), "contains", sharedSpaceId, equipmentId, { level: "equipment" }));
|
|
280
|
+
relations.push(relation(nextRel(), "measures_asset", meterId, buildingId, { meter_type: "water" }));
|
|
281
|
+
|
|
282
|
+
const contractId = nextEntity();
|
|
283
|
+
const contractorId = nextEntity();
|
|
284
|
+
entities.push(entity(contractorId, "organization", `Maintenance ${buildingInput.city} Services`, {
|
|
285
|
+
role: "maintenance_provider",
|
|
286
|
+
city: buildingInput.city
|
|
287
|
+
}));
|
|
288
|
+
entities.push(entity(contractId, "service_contract", `Contrat maintenance ${buildingInput.name}`, {
|
|
289
|
+
building_id: buildingId,
|
|
290
|
+
starts_on: "2026-01-01",
|
|
291
|
+
expires_on: buildingIndex === 0 ? "2026-05-31" : "2026-12-31",
|
|
292
|
+
status: buildingIndex === 0 ? "renewal_due" : "active"
|
|
293
|
+
}));
|
|
294
|
+
relations.push(relation(nextRel(), "covers_asset", contractId, equipmentId, { scope: "technical_equipment" }));
|
|
295
|
+
if (buildingIndex !== 2) {
|
|
296
|
+
relations.push(relation(nextRel(), "provided_by", contractId, contractorId, { role: "maintenance_provider" }));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const policyId = nextEntity();
|
|
300
|
+
entities.push(entity(policyId, "insurance_policy", `Police assurance ${buildingInput.name}`, {
|
|
301
|
+
building_id: buildingId,
|
|
302
|
+
policy_ref: `POL-${buildingId}-2026`,
|
|
303
|
+
status: buildingIndex === 2 ? "missing_annex" : "active"
|
|
304
|
+
}));
|
|
305
|
+
relations.push(relation(nextRel(), "covers_asset", policyId, buildingId, { scope: "building" }));
|
|
306
|
+
|
|
307
|
+
for (let unitIndex = 0; unitIndex < 9; unitIndex++) {
|
|
308
|
+
const local = unitIndex + 1;
|
|
309
|
+
const unitId = nextEntity();
|
|
310
|
+
const cellarId = nextEntity();
|
|
311
|
+
const billingId = nextEntity();
|
|
312
|
+
const householdId = nextEntity();
|
|
313
|
+
const ownerId = nextEntity();
|
|
314
|
+
const occupantId = nextEntity();
|
|
315
|
+
const blockId = blockIds[unitIndex < 5 ? 0 : 1];
|
|
316
|
+
const lot = `${unitIndex < 5 ? "A" : "B"}${local}`;
|
|
317
|
+
const status =
|
|
318
|
+
unitIndex % 5 === 0 ? "tenant_occupied" :
|
|
319
|
+
unitIndex % 7 === 0 ? "vacant_works" :
|
|
320
|
+
"owner_occupied";
|
|
321
|
+
const unitName = `${buildingInput.name} Appartement ${lot}`;
|
|
322
|
+
const ownerName = `${firstNames[(buildingIndex * 3 + unitIndex) % firstNames.length]} ${lastNames[(buildingIndex + unitIndex) % lastNames.length]}`;
|
|
323
|
+
const occupantName = status === "tenant_occupied"
|
|
324
|
+
? `${firstNames[(buildingIndex * 5 + unitIndex + 4) % firstNames.length]} ${lastNames[(buildingIndex * 2 + unitIndex + 3) % lastNames.length]}`
|
|
325
|
+
: ownerName;
|
|
326
|
+
|
|
327
|
+
entities.push(entity(unitId, "unit", unitName, {
|
|
328
|
+
building_id: buildingId,
|
|
329
|
+
building: buildingInput.name,
|
|
330
|
+
block_id: blockId,
|
|
331
|
+
block: unitIndex < 5 ? "A" : "B",
|
|
332
|
+
floor: Math.floor(unitIndex / 2),
|
|
333
|
+
lot,
|
|
334
|
+
door_label: `${lot}-2026`,
|
|
335
|
+
bedrooms: 1 + (unitIndex % 3),
|
|
336
|
+
tantiemes: quotaSets[buildingIndex][unitIndex],
|
|
337
|
+
quota_basis: 1000,
|
|
338
|
+
usage_status: status
|
|
339
|
+
}));
|
|
340
|
+
entities.push(entity(cellarId, "cellar", `${buildingInput.name} Cave ${lot}`, {
|
|
341
|
+
building_id: buildingId,
|
|
342
|
+
lot
|
|
343
|
+
}));
|
|
344
|
+
entities.push(entity(billingId, "billing_group", `Groupe facturation ${buildingInput.name} ${lot}`, {
|
|
345
|
+
unit_id: unitId,
|
|
346
|
+
status: unitIndex === 4 ? "missing_recipient" : "active"
|
|
347
|
+
}));
|
|
348
|
+
entities.push(entity(householdId, "household", `Menage ${occupantName} ${lot}`, {
|
|
349
|
+
unit_id: unitId,
|
|
350
|
+
household_type: status === "tenant_occupied" ? "tenant" : "owner"
|
|
351
|
+
}));
|
|
352
|
+
entities.push(entity(ownerId, "person", ownerName, {
|
|
353
|
+
role: "coproprietaire",
|
|
354
|
+
age_band: unitIndex % 3 === 0 ? "45-64" : "25-44"
|
|
355
|
+
}));
|
|
356
|
+
entities.push(entity(occupantId, "person", occupantName, {
|
|
357
|
+
role: status === "tenant_occupied" ? "locataire" : "occupant_proprietaire",
|
|
358
|
+
age_band: "25-44"
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
relations.push(relation(nextRel(), "contains", buildingId, unitId, { level: "unit" }));
|
|
362
|
+
relations.push(relation(nextRel(), "contains", blockId, unitId, { level: "unit" }));
|
|
363
|
+
relations.push(relation(nextRel(), "contains", buildingId, cellarId, { level: "cellar" }));
|
|
364
|
+
if (!(buildingIndex === 0 && unitIndex === 6)) {
|
|
365
|
+
relations.push(relation(nextRel(), "assigned_cellar", unitId, cellarId, { lot }));
|
|
366
|
+
}
|
|
367
|
+
if (!(buildingIndex === 1 && unitIndex === 2)) {
|
|
368
|
+
relations.push(relation(nextRel(), "owns", ownerId, unitId, { share: 1 }));
|
|
369
|
+
}
|
|
370
|
+
if (!(buildingIndex === 2 && unitIndex === 3)) {
|
|
371
|
+
relations.push(relation(nextRel(), "occupies", occupantId, unitId, { status }));
|
|
372
|
+
relations.push(relation(nextRel(), "household_member", householdId, occupantId, { role: "occupant" }));
|
|
373
|
+
}
|
|
374
|
+
relations.push(relation(nextRel(), "primary_residence_of", unitId, householdId, { status }));
|
|
375
|
+
if (!(buildingIndex === 1 && unitIndex === 4)) {
|
|
376
|
+
relations.push(relation(nextRel(), "bills_to", billingId, unitId, { mode: "charges" }));
|
|
377
|
+
}
|
|
378
|
+
relations.push(relation(nextRel(), "has_member", billingId, ownerId, { role: "recipient" }));
|
|
379
|
+
|
|
380
|
+
if (status === "tenant_occupied") {
|
|
381
|
+
const leaseId = nextEntity();
|
|
382
|
+
entities.push(entity(leaseId, "lease_contract", `Bail ${unitName}`, {
|
|
383
|
+
unit_id: unitId,
|
|
384
|
+
validFrom: "2025-09-01",
|
|
385
|
+
validTo: unitIndex === 0 ? "2026-08-31" : "2028-08-31",
|
|
386
|
+
monthly_rent: 780 + unitIndex * 25,
|
|
387
|
+
status: "active"
|
|
388
|
+
}));
|
|
389
|
+
if (!(buildingIndex === 2 && unitIndex === 0)) {
|
|
390
|
+
relations.push(relation(nextRel(), "leases", leaseId, unitId, { status: "active" }));
|
|
391
|
+
}
|
|
392
|
+
relations.push(relation(nextRel(), "rented_to", leaseId, householdId, { status: "active" }));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const period of ["2026-01", "2026-02"]) {
|
|
396
|
+
const amount = 280 + unitIndex * 11 + buildingIndex * 30;
|
|
397
|
+
const chargeId = nextEntity();
|
|
398
|
+
entities.push(entity(chargeId, "charge_call", `Appel charges ${buildingInput.name} ${lot} ${period}`, {
|
|
399
|
+
unit_id: unitId,
|
|
400
|
+
period,
|
|
401
|
+
amount,
|
|
402
|
+
currency: "EUR",
|
|
403
|
+
status: period === "2026-02" && unitIndex % 4 === 0 ? "open" : "issued"
|
|
404
|
+
}));
|
|
405
|
+
relations.push(relation(nextRel(), "bills_to", chargeId, billingId, { period }));
|
|
406
|
+
if (!(period === "2026-02" && unitIndex % 4 === 0)) {
|
|
407
|
+
const codaId = nextEntity();
|
|
408
|
+
entities.push(entity(codaId, "coda_entry", `CODA ${buildingInput.name} ${lot} ${period}`, {
|
|
409
|
+
amount,
|
|
410
|
+
currency: "EUR",
|
|
411
|
+
communication: `${lot} ${period}`,
|
|
412
|
+
status: unitIndex === 8 ? "manual_review" : "matched"
|
|
413
|
+
}));
|
|
414
|
+
relations.push(relation(nextRel(), "matched_to", codaId, chargeId, { period }));
|
|
415
|
+
relations.push(relation(nextRel(), "allocated_to", codaId, billingId, { period }));
|
|
416
|
+
} else {
|
|
417
|
+
const reminderId = nextEntity();
|
|
418
|
+
entities.push(entity(reminderId, "reminder", `Relance ${buildingInput.name} ${lot} ${period}`, {
|
|
419
|
+
level: 1,
|
|
420
|
+
amount_due: amount,
|
|
421
|
+
currency: "EUR",
|
|
422
|
+
status: buildingIndex === 0 ? "missing_send_proof" : "draft"
|
|
423
|
+
}));
|
|
424
|
+
if (buildingIndex !== 0) {
|
|
425
|
+
relations.push(relation(nextRel(), "triggered", chargeId, reminderId, { reason: "open_charge" }));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (let readingIndex = 0; readingIndex < 6; readingIndex++) {
|
|
432
|
+
const readingId = nextEntity();
|
|
433
|
+
entities.push(entity(readingId, "meter_reading", `${buildingInput.name} releve eau ${readingIndex + 1}`, {
|
|
434
|
+
meter_id: meterId,
|
|
435
|
+
reading_date: `2026-0${readingIndex + 1}-28`,
|
|
436
|
+
value_m3: 1000 + buildingIndex * 200 + readingIndex * 18,
|
|
437
|
+
status: readingIndex === 5 && buildingIndex === 1 ? "missing_validation" : "validated"
|
|
438
|
+
}));
|
|
439
|
+
relations.push(relation(nextRel(), "records_reading", meterId, readingId, { cadence: "monthly" }));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (let ticketIndex = 0; ticketIndex < 4; ticketIndex++) {
|
|
443
|
+
const ticketId = nextEntity();
|
|
444
|
+
const interventionId = nextEntity();
|
|
445
|
+
entities.push(entity(ticketId, "maintenance_ticket", `${buildingInput.name} ticket maintenance ${ticketIndex + 1}`, {
|
|
446
|
+
building_id: buildingId,
|
|
447
|
+
priority: ticketIndex === 0 ? "high" : "normal",
|
|
448
|
+
status: ticketIndex === 3 ? "late" : "open"
|
|
449
|
+
}));
|
|
450
|
+
entities.push(entity(interventionId, "intervention", `${buildingInput.name} intervention ${ticketIndex + 1}`, {
|
|
451
|
+
planned_on: `2026-04-${10 + ticketIndex}`,
|
|
452
|
+
status: ticketIndex === 2 && buildingIndex === 1 ? "provider_missing" : "planned"
|
|
453
|
+
}));
|
|
454
|
+
relations.push(relation(nextRel(), "concerns_asset", ticketId, ticketIndex % 2 === 0 ? equipmentId : sharedSpaceId, { origin: "resident_report" }));
|
|
455
|
+
relations.push(relation(nextRel(), "has_intervention", ticketId, interventionId, { status: "planned" }));
|
|
456
|
+
if (!(ticketIndex === 2 && buildingIndex === 1)) {
|
|
457
|
+
relations.push(relation(nextRel(), "provided_by", interventionId, contractorId, { role: "field_operator" }));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const claimId = nextEntity();
|
|
462
|
+
entities.push(entity(claimId, "claim", `Sinistre infiltration ${buildingInput.name}`, {
|
|
463
|
+
building_id: buildingId,
|
|
464
|
+
status: buildingIndex === 2 ? "missing_policy_link" : "open",
|
|
465
|
+
declared_on: "2026-03-18"
|
|
466
|
+
}));
|
|
467
|
+
relations.push(relation(nextRel(), "concerns_asset", claimId, buildingId, { damage: "water" }));
|
|
468
|
+
if (buildingIndex !== 2) {
|
|
469
|
+
relations.push(relation(nextRel(), "covered_by", claimId, policyId, { claim_status: "declared" }));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const cert of ["electricite", "incendie", "ascenseur"]) {
|
|
473
|
+
const certId = nextEntity();
|
|
474
|
+
entities.push(entity(certId, "compliance_certificate", `${buildingInput.name} certificat ${cert}`, {
|
|
475
|
+
building_id: buildingId,
|
|
476
|
+
certificate_type: cert,
|
|
477
|
+
expires_on: cert === "incendie" && buildingIndex === 0 ? "2026-02-15" : "2027-12-31",
|
|
478
|
+
status: cert === "incendie" && buildingIndex === 0 ? "expired" : "valid"
|
|
479
|
+
}));
|
|
480
|
+
if (!(cert === "ascenseur" && buildingIndex === 1)) {
|
|
481
|
+
relations.push(relation(nextRel(), "certifies_asset", certId, buildingId, { certificate_type: cert }));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const agId = nextEntity();
|
|
486
|
+
const agendaId = nextEntity();
|
|
487
|
+
const decisionId = nextEntity();
|
|
488
|
+
entities.push(entity(agId, "ag_meeting", `AG ordinaire ${buildingInput.name} 2026`, {
|
|
489
|
+
held_on: "2026-05-20",
|
|
490
|
+
status: "scheduled"
|
|
491
|
+
}));
|
|
492
|
+
entities.push(entity(agendaId, "agenda_item", `Point AG maintenance ${buildingInput.name}`, {
|
|
493
|
+
topic: "maintenance",
|
|
494
|
+
status: buildingIndex === 2 ? "without_decision" : "open"
|
|
495
|
+
}));
|
|
496
|
+
entities.push(entity(decisionId, "decision", `Decision maintenance ${buildingInput.name}`, {
|
|
497
|
+
date: "2026-05-20",
|
|
498
|
+
status: buildingIndex === 2 ? "missing_action" : "approved"
|
|
499
|
+
}));
|
|
500
|
+
relations.push(relation(nextRel(), "has_agenda_item", agId, agendaId, { order: 3 }));
|
|
501
|
+
if (buildingIndex !== 2) {
|
|
502
|
+
relations.push(relation(nextRel(), "decided_by", agendaId, decisionId, { status: "approved" }));
|
|
503
|
+
}
|
|
504
|
+
relations.push(relation(nextRel(), "concerns_asset", agendaId, buildingId, { topic: "maintenance" }));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const extraWorksiteId = nextEntity();
|
|
508
|
+
const extraArchitectId = nextEntity();
|
|
509
|
+
const extraPackageId = nextEntity();
|
|
510
|
+
const extraContractorId = nextEntity();
|
|
511
|
+
const extraPermitId = nextEntity();
|
|
512
|
+
const extraInvoiceId = nextEntity();
|
|
513
|
+
const extraBudgetId = nextEntity();
|
|
514
|
+
const extraInspectionId = nextEntity();
|
|
515
|
+
const extraReserveId = nextEntity();
|
|
516
|
+
entities.push(entity(extraWorksiteId, "worksite_project", "Chantier Platanes ventilation parking", {
|
|
517
|
+
status: "active",
|
|
518
|
+
phase: "preparation",
|
|
519
|
+
planned_start: "2026-06-01",
|
|
520
|
+
planned_end: "2026-09-30",
|
|
521
|
+
profile: "construction_lite"
|
|
522
|
+
}));
|
|
523
|
+
entities.push(entity(extraArchitectId, "architect", "Nadia Claes", { role: "architecte_responsable", registration: "A-BE-2201" }));
|
|
524
|
+
entities.push(entity(extraPackageId, "work_package", "Lot ventilation parking Platanes", { discipline: "ventilation", status: "planned", planned_budget_eur: 41000 }));
|
|
525
|
+
entities.push(entity(extraContractorId, "contractor", "Ventibel Techniques", { specialty: "ventilation", approval: "classe_2" }));
|
|
526
|
+
entities.push(entity(extraPermitId, "permit", "Autorisation ventilation Platanes", { issued_on: "2026-05-05", expires_on: "2027-05-05" }));
|
|
527
|
+
entities.push(entity(extraBudgetId, "budget_line", "Budget ventilation Platanes", { approved_amount_eur: 39000, forecast_amount_eur: 41000, status: "over_forecast" }));
|
|
528
|
+
entities.push(entity(extraInvoiceId, "invoice", "Facture acompte ventilation Platanes", { amount_eur: 15000, status: "received" }));
|
|
529
|
+
entities.push(entity(extraInspectionId, "inspection", "Inspection ventilation Platanes", { date: "2026-06-15", result: "reserve" }));
|
|
530
|
+
entities.push(entity(extraReserveId, "defect_reserve", "Reserve gaine coupe-feu parking", { severity: "error", status: "open", due_date: "2026-06-30" }));
|
|
531
|
+
relations.push(relation(nextRel(), "manages_project", 201, extraWorksiteId, { role: "syndic" }));
|
|
532
|
+
relations.push(relation(nextRel(), "designs_for", extraArchitectId, extraWorksiteId, { appointment: "architecte_responsable" }));
|
|
533
|
+
relations.push(relation(nextRel(), "has_work_package", extraWorksiteId, extraPackageId, { order: 1 }));
|
|
534
|
+
relations.push(relation(nextRel(), "executes_work_package", extraContractorId, extraPackageId, { contract_ref: "VENT-2026-001" }));
|
|
535
|
+
relations.push(relation(nextRel(), "authorized_by", extraWorksiteId, extraPermitId, { permit_scope: "ventilation" }));
|
|
536
|
+
relations.push(relation(nextRel(), "has_budget_line", extraWorksiteId, extraBudgetId, { approved_by: "AG-2026-PLATANES" }));
|
|
537
|
+
relations.push(relation(nextRel(), "invoiced_by", extraPackageId, extraInvoiceId, { invoice_type: "acompte" }));
|
|
538
|
+
relations.push(relation(nextRel(), "affects_asset", extraPackageId, 2002, { asset_scope: "parking" }));
|
|
539
|
+
relations.push(relation(nextRel(), "has_reserve", extraInspectionId, extraReserveId, { reserve_status: "open" }));
|
|
540
|
+
|
|
541
|
+
const allEntityIds = new Set([...bundle.entities_raw, ...entities].map((row) => Number(row.entity_id)));
|
|
542
|
+
const projectionDefinitions = buildProjectionDefinitions();
|
|
543
|
+
for (const projection of projectionDefinitions.snapshots) {
|
|
544
|
+
const id = nextEntity();
|
|
545
|
+
entities.push(entity(id, "ProjectionResult", projection.label, {
|
|
546
|
+
projection_id: projection.name,
|
|
547
|
+
collection_id: WS,
|
|
548
|
+
metric: projection.metric,
|
|
549
|
+
summary: projection.summary
|
|
550
|
+
}));
|
|
551
|
+
const evidenceId = allEntityIds.has(Number(projection.evidenceId)) ? projection.evidenceId : 1000;
|
|
552
|
+
relations.push(relation(nextRel(), "evidenced_by", id, evidenceId, { projection_id: projection.name }));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
for (const finding of buildDeltaFindings()) {
|
|
556
|
+
const id = nextEntity();
|
|
557
|
+
entities.push(entity(id, "DeltaFinding", finding.label, {
|
|
558
|
+
finding_id: finding.id,
|
|
559
|
+
rule_id: finding.rule_id,
|
|
560
|
+
severity: finding.severity,
|
|
561
|
+
expected: finding.expected,
|
|
562
|
+
observed: finding.observed,
|
|
563
|
+
procedure_id: finding.procedure_id,
|
|
564
|
+
status: "open"
|
|
565
|
+
}));
|
|
566
|
+
if (finding.entityId && allEntityIds.has(Number(finding.entityId))) {
|
|
567
|
+
relations.push(relation(nextRel(), "flags_entity", id, finding.entityId, { rule_id: finding.rule_id }));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const operationalDocs = [
|
|
572
|
+
["Procedure controle proprietaires", "Chaque lot actif doit avoir un proprietaire et un groupe de facturation. Les exceptions sont les lots communs et les lots neutralises.", "procedure_syndic"],
|
|
573
|
+
["Procedure rapprochement CODA", "Les virements CODA sont rapproches aux appels de charges. Les lignes inconnues et paiements partiels partent en revue.", "procedure_finance"],
|
|
574
|
+
["Procedure relances impayes", "Les appels ouverts apres echeance declenchent une relance avec preuve d'envoi.", "procedure_finance"],
|
|
575
|
+
["Procedure AG decisions", "Chaque point d'ordre du jour approuve doit produire une decision et une action de suivi.", "procedure_gouvernance"],
|
|
576
|
+
["Procedure contrats maintenance", "Un contrat de maintenance actif doit couvrir chaque equipement technique critique et nommer un prestataire.", "procedure_exploitation"],
|
|
577
|
+
["Procedure interventions", "Chaque ticket de maintenance doit avoir une intervention planifiee puis un prestataire.", "procedure_maintenance"],
|
|
578
|
+
["Procedure certificats", "Les certificats electricite, incendie et ascenseur doivent rester valides et lies aux assets.", "procedure_conformite"],
|
|
579
|
+
["Procedure sinistres", "Chaque sinistre doit etre relie a une police et a une preuve documentaire.", "procedure_sinistre"],
|
|
580
|
+
["Procedure chantier permis", "Chaque chantier actif doit avoir un architecte, au moins un lot de travaux, un permis et un budget.", "procedure_chantier"],
|
|
581
|
+
["Procedure reception reserves", "Chaque reserve de reception doit etre reliee a une inspection et une preuve documentaire.", "procedure_chantier"]
|
|
582
|
+
];
|
|
583
|
+
for (const [title, content, type] of operationalDocs) {
|
|
584
|
+
docs.push(doc(nextDoc(), title, content, type));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
bundle.entities_raw.push(...entities);
|
|
588
|
+
bundle.relations_raw.push(...relations);
|
|
589
|
+
for (const document of docs) {
|
|
590
|
+
bundle.documents_raw.push(document);
|
|
591
|
+
bundle.chunks_raw.push(chunkFor(document));
|
|
592
|
+
}
|
|
593
|
+
for (const [entityId, docId, role] of entityDocs) addEntityDocument(bundle, entityId, docId, role);
|
|
594
|
+
for (const e of entities) {
|
|
595
|
+
if (["building", "unit", "worksite_project", "maintenance_ticket", "claim", "service_contract"].includes(e.entity_type)) {
|
|
596
|
+
aliases.push({ workspace_id: WS, entity_id: e.entity_id, term: e.name, confidence: 0.95 });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
bundle.entity_aliases_raw.push(...aliases);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function buildProjectionDefinitions() {
|
|
603
|
+
const live = [
|
|
604
|
+
["annuaire_coproprietes", "Annuaire immeubles geres", "Quels immeubles sont geres et ou sont-ils situes ?", ["building", "block"], ["CONTAINS"], "procedure_portefeuille"],
|
|
605
|
+
["lots_sans_proprietaire", "Lots sans proprietaire", "Quels lots actifs n'ont pas de proprietaire declare ?", ["unit", "person"], ["OWNS"], "procedure_proprietaires"],
|
|
606
|
+
["occupants_sans_titre", "Occupants sans titre", "Quels lots sont occupes sans occupant, bail ou residence principale coherente ?", ["unit", "person", "household", "lease_contract"], ["OCCUPIES", "LEASES", "PRIMARY_RESIDENCE_OF"], "procedure_occupation"],
|
|
607
|
+
["baux_actifs", "Baux locatifs actifs", "Quels appartements sont loues, a qui, depuis quelle date et par quel bailleur ?", ["unit", "lease_contract", "person"], ["LEASES", "RENTED_TO", "OCCUPIES"], "procedure_baux"],
|
|
608
|
+
["baux_echeance_90j", "Baux a echeance 90 jours", "Quels baux arrivent a echeance dans les 90 prochains jours ?", ["lease_contract", "unit"], ["LEASES"], "procedure_baux"],
|
|
609
|
+
["quotites_par_immeuble", "Quotites par immeuble", "Les quotites de chaque immeuble totalisent-elles 1000 ?", ["building", "unit"], ["CONTAINS"], "procedure_quotites"],
|
|
610
|
+
["annexes_par_lot", "Annexes par lot", "Quelle cave, garage ou jardin est rattache a chaque lot ?", ["unit", "cellar", "parking_space", "private_garden"], ["ASSIGNED_CELLAR", "ASSIGNED_GARAGE", "USES_EXCLUSIVE"], "procedure_annexes"],
|
|
611
|
+
["appels_charges_ouverts", "Appels de charges ouverts", "Quels appels de charges restent ouverts ou partiellement payes ?", ["charge_call", "billing_group"], ["BILLS_TO"], "procedure_finance"],
|
|
612
|
+
["coda_a_rapprocher", "CODA a rapprocher", "Quelles lignes CODA doivent etre investiguees ?", ["coda_entry", "charge_call"], ["MATCHED_TO", "REQUIRES_REVIEW"], "procedure_coda"],
|
|
613
|
+
["relances_a_lancer", "Relances a lancer", "Quelles relances doivent etre envoyees aujourd'hui ?", ["charge_call", "reminder"], ["TRIGGERED"], "procedure_relances"],
|
|
614
|
+
["soldes_coproprietaires", "Soldes coproprietaires", "Quels coproprietaires ont des impayes ou relances actives ?", ["person", "unit", "charge_call", "reminder"], ["OWNS", "BILLS_TO", "TRIGGERED"], "procedure_finance"],
|
|
615
|
+
["decisions_ag_a_executer", "Decisions AG a executer", "Quelles decisions AG attendent une action de suivi ?", ["ag_meeting", "agenda_item", "decision"], ["HAS_AGENDA_ITEM", "DECIDED_BY"], "procedure_ag"],
|
|
616
|
+
["documents_obligatoires_manquants", "Documents obligatoires manquants", "Quels documents obligatoires manquent par immeuble ou procedure ?", ["document", "building"], ["EVIDENCED_BY"], "procedure_documents"],
|
|
617
|
+
["contrats_maintenance_echeance", "Contrats maintenance a echeance", "Quels contrats de maintenance expirent ou n'ont pas de prestataire ?", ["service_contract", "organization", "shared_equipment"], ["COVERS_ASSET", "PROVIDED_BY"], "procedure_maintenance"],
|
|
618
|
+
["interventions_en_retard", "Interventions en retard", "Quelles interventions sont en retard ou sans prestataire ?", ["maintenance_ticket", "intervention", "organization"], ["HAS_INTERVENTION", "PROVIDED_BY"], "procedure_interventions"],
|
|
619
|
+
["certificats_conformite", "Certificats conformite", "Quels certificats sont expires, manquants ou non relies a un asset ?", ["compliance_certificate", "building"], ["CERTIFIES_ASSET"], "procedure_conformite"],
|
|
620
|
+
["sinistres_ouverts", "Sinistres ouverts", "Quels sinistres sont ouverts et quelles preuves ou polices manquent ?", ["claim", "insurance_policy", "document"], ["COVERED_BY", "EVIDENCED_BY"], "procedure_sinistres"],
|
|
621
|
+
["compteurs_releves", "Compteurs et releves", "Quels compteurs ont des releves manquants ou non valides ?", ["meter", "meter_reading"], ["RECORDS_READING"], "procedure_compteurs"],
|
|
622
|
+
["chantier_actif", "Chantier actif", "Quel est l'etat des chantiers actifs et qui en est responsable ?", ["worksite_project", "architect", "contractor", "work_package"], ["MANAGES_PROJECT", "DESIGNS_FOR", "HAS_WORK_PACKAGE", "EXECUTES_WORK_PACKAGE"], "procedure_chantier"],
|
|
623
|
+
["planning_chantier", "Planning chantier", "Quels jalons chantier sont en retard ou ouverts ?", ["worksite_project", "milestone", "progress_event"], ["HAS_MILESTONE"], "procedure_chantier"],
|
|
624
|
+
["budget_travaux", "Budget travaux", "Le budget travaux vote couvre-t-il les devis, factures et avenants ?", ["budget_line", "quote", "invoice", "change_order"], ["HAS_BUDGET_LINE", "BASED_ON_QUOTE", "INVOICED_BY"], "procedure_chantier"],
|
|
625
|
+
["lots_impactes", "Lots et espaces impactes", "Quels batiments, blocs, lots ou espaces sont impactes par les travaux ?", ["work_package", "building", "block", "unit", "shared_space"], ["AFFECTS_ASSET"], "procedure_chantier"],
|
|
626
|
+
["permis_reglementaires", "Permis et pieces reglementaires", "Quelles pieces reglementaires autorisent le chantier ?", ["permit", "worksite_project", "document"], ["AUTHORIZED_BY", "EVIDENCED_BY"], "procedure_chantier"],
|
|
627
|
+
["reserves_reception", "Reserves de reception", "Quelles reserves restent ouvertes apres inspection ?", ["inspection", "defect_reserve", "document"], ["HAS_RESERVE", "REPORTED_BY", "EVIDENCED_BY"], "procedure_chantier"],
|
|
628
|
+
["priorites_gestionnaire", "Priorites gestionnaire", "Quelles 3 a 5 actions doivent remonter au gestionnaire aujourd'hui ?", ["DeltaFinding", "maintenance_ticket", "charge_call"], ["FLAGS_ENTITY"], "procedure_priorites"]
|
|
629
|
+
].map(([name, label, question, schemas, edges, procedureId]) => ({
|
|
630
|
+
name,
|
|
631
|
+
label,
|
|
632
|
+
description: question,
|
|
633
|
+
business_question: question,
|
|
634
|
+
artifact_kind: "live_answer_view",
|
|
635
|
+
scope: `immeuble:operations:${name}`,
|
|
636
|
+
procedure_id: procedureId,
|
|
637
|
+
procedure_area: procedureId.replace("procedure_", ""),
|
|
638
|
+
required_schemas: schemas,
|
|
639
|
+
required_facets: schemas.map((schema) => `${schema}.name`),
|
|
640
|
+
required_edges: edges,
|
|
641
|
+
gap_rule_ids: [],
|
|
642
|
+
retrieval_jobs: ["summary", "graph_focus"],
|
|
643
|
+
expected_output: "Tableau operationnel avec entites sources, anomalies ouvertes et action conseillee."
|
|
644
|
+
}));
|
|
645
|
+
|
|
646
|
+
const snapshots = [
|
|
647
|
+
["portfolio_health", "Sante portefeuille", "portfolio_health", "Vue consolidee portefeuille, charges, maintenance et conformite.", 2000],
|
|
648
|
+
["owner_gaps", "Trous proprietaires", "owner_gap_count", "Lots sans proprietaire ou occupant detectes.", 2018],
|
|
649
|
+
["finance_risk", "Risque finance", "open_finance_risk", "Appels ouverts, relances et CODA a rapprocher.", 2020],
|
|
650
|
+
["maintenance_backlog", "Backlog maintenance", "maintenance_backlog", "Tickets en retard et prestataires manquants.", 2030],
|
|
651
|
+
["compliance_risk", "Risque conformite", "compliance_risk", "Certificats expires ou non rattaches.", 2040],
|
|
652
|
+
["claims_open", "Sinistres ouverts", "claims_open", "Sinistres sans police ou preuve documentaire.", 2050],
|
|
653
|
+
["chantier_erables_synthese", "Synthese chantier Erables", "worksite_summary", "Chantier Erables avec budget, planning et reserves.", 1000],
|
|
654
|
+
["chantier_platanes_budget", "Budget chantier Platanes", "worksite_budget", "Ventilation parking Platanes au-dessus du budget vote.", 2447]
|
|
655
|
+
].map(([name, label, metric, summary, evidenceId]) => ({ name, label, metric, summary, evidenceId }));
|
|
656
|
+
|
|
657
|
+
return { live, snapshots };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function buildDeltaFindings() {
|
|
661
|
+
return [
|
|
662
|
+
["df_unit_owner_missing", "Lot sans proprietaire", "unit-has-owner", "error", "1 owner", "0 owner", "procedure_proprietaires", 2062],
|
|
663
|
+
["df_unit_occupant_missing", "Lot occupe sans occupant", "occupied-unit-has-occupant", "error", "1 occupant", "0 occupant", "procedure_occupation", 2127],
|
|
664
|
+
["df_lease_missing", "Lot loue sans bail lie", "tenant-occupied-has-lease", "warning", "1 lease", "0 lease", "procedure_baux", 2391],
|
|
665
|
+
["df_billing_missing", "Lot sans groupe facturation", "billing-group-bills-unit", "warning", "1 billing group", "0 billing group", "procedure_finance", 2099],
|
|
666
|
+
["df_cellar_missing", "Cave non assignee", "unit-one-cellar", "error", "1 cellar", "0 cellar", "procedure_annexes", 2092],
|
|
667
|
+
["df_coda_unknown", "CODA en revue manuelle", "coda-entry-reviewed", "warning", "matched or reviewed", "manual_review", "procedure_coda", 2123],
|
|
668
|
+
["df_reminder_missing", "Relance sans preuve", "open-charge-has-reminder", "warning", "sent reminder", "missing_send_proof", "procedure_relances", 2085],
|
|
669
|
+
["df_ag_action_missing", "Decision AG sans action", "agenda-item-has-decision", "warning", "decision/action", "missing_action", "procedure_ag", 2444],
|
|
670
|
+
["df_contract_provider_missing", "Contrat sans prestataire", "service-contract-has-provider", "error", "provider", "0 provider", "procedure_maintenance", 2278],
|
|
671
|
+
["df_intervention_provider_missing", "Intervention sans prestataire", "intervention-has-provider", "warning", "provider", "0 provider", "procedure_interventions", 2288],
|
|
672
|
+
["df_certificate_expired", "Certificat incendie expire", "certificate-valid", "error", "valid certificate", "expired", "procedure_conformite", 2080],
|
|
673
|
+
["df_claim_policy_missing", "Sinistre sans police", "claim-covered-by-policy", "error", "policy link", "0 policy", "procedure_sinistres", 2441],
|
|
674
|
+
["df_worksite_budget_over", "Budget chantier depasse", "worksite-has-budget-line", "warning", "forecast <= approved", "forecast > approved", "procedure_chantier", 2453],
|
|
675
|
+
["df_invoice_budget_missing", "Facture chantier sans budget", "invoice-linked-to-work-package", "warning", "work package link", "partial link only", "procedure_chantier", 2454],
|
|
676
|
+
["df_reserve_open", "Reserve reception ouverte", "reserve-linked-to-inspection", "error", "closed reserve", "open", "procedure_chantier", 2457],
|
|
677
|
+
["df_meter_reading_unvalidated", "Releve compteur non valide", "meter-has-recent-reading", "warning", "validated", "missing_validation", "procedure_compteurs", 2296]
|
|
678
|
+
].map(([id, label, rule_id, severity, expected, observed, procedure_id, entityId]) => ({
|
|
679
|
+
id, label, rule_id, severity, expected, observed, procedure_id, entityId
|
|
680
|
+
}));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function buildProjectionCatalog() {
|
|
684
|
+
const { live } = buildProjectionDefinitions();
|
|
685
|
+
const allQuestions = live.map((entry) => ({
|
|
686
|
+
id: entry.procedure_id,
|
|
687
|
+
title: entry.label,
|
|
688
|
+
competency_question: entry.business_question
|
|
689
|
+
}));
|
|
690
|
+
const entries = [
|
|
691
|
+
{
|
|
692
|
+
name: "immeuble_competency_questions",
|
|
693
|
+
label: "Plan d'analyse immeuble - questions operationnelles",
|
|
694
|
+
business_question: "Quelles procedures operationnelles le workspace immeuble doit-il couvrir ?",
|
|
695
|
+
description: "Quelles procedures operationnelles le workspace immeuble doit-il couvrir ?",
|
|
696
|
+
artifact_kind: "analysis_plan",
|
|
697
|
+
scope: "immeuble:competency:questions",
|
|
698
|
+
procedure_id: "procedure_catalogue",
|
|
699
|
+
procedure_area: "pilotage",
|
|
700
|
+
required_schemas: ["building", "unit", "charge_call", "maintenance_ticket", "worksite_project"],
|
|
701
|
+
required_facets: ["building.name", "unit.usage_status", "charge_call.status"],
|
|
702
|
+
required_edges: ["CONTAINS", "OWNS", "BILLS_TO", "HAS_WORK_PACKAGE"],
|
|
703
|
+
retrieval_jobs: ["summary"],
|
|
704
|
+
expected_output: "Catalogue des questions et projections attendues.",
|
|
705
|
+
competency_questions: allQuestions
|
|
706
|
+
},
|
|
707
|
+
...live
|
|
708
|
+
];
|
|
709
|
+
const lines = ["workspace_id: immeuble", "projections:"];
|
|
710
|
+
for (const entry of entries) {
|
|
711
|
+
lines.push(` - name: ${entry.name}`);
|
|
712
|
+
lines.push(` label: ${yamlScalar(entry.label)}`);
|
|
713
|
+
lines.push(` description: ${yamlScalar(entry.description)}`);
|
|
714
|
+
lines.push(` business_question: ${yamlScalar(entry.business_question)}`);
|
|
715
|
+
lines.push(` artifact_kind: ${entry.artifact_kind}`);
|
|
716
|
+
lines.push(` scope: ${entry.scope}`);
|
|
717
|
+
lines.push(` procedure_id: ${entry.procedure_id}`);
|
|
718
|
+
lines.push(` procedure_area: ${entry.procedure_area}`);
|
|
719
|
+
lines.push(` expected_output: ${yamlScalar(entry.expected_output)}`);
|
|
720
|
+
lines.push(` required_schemas: ${yamlList(entry.required_schemas)}`);
|
|
721
|
+
lines.push(` required_facets: ${yamlList(entry.required_facets)}`);
|
|
722
|
+
lines.push(` required_edges: ${yamlList(entry.required_edges)}`);
|
|
723
|
+
lines.push(` gap_rule_ids: ${yamlList(entry.gap_rule_ids ?? [])}`);
|
|
724
|
+
lines.push(` retrieval_jobs: ${yamlList(entry.retrieval_jobs)}`);
|
|
725
|
+
}
|
|
726
|
+
return { entries, text: `${lines.join("\n")}\n` };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function buildArtifacts(catalogEntries) {
|
|
730
|
+
const { snapshots } = buildProjectionDefinitions();
|
|
731
|
+
const analysis = catalogEntries.find((entry) => entry.artifact_kind === "analysis_plan");
|
|
732
|
+
const artifacts = [
|
|
733
|
+
artifact("analysis_plan", analysis.name, "Plan d'analyse immeuble", {
|
|
734
|
+
competency_questions: analysis.competency_questions,
|
|
735
|
+
expected_focus: ["syndic", "finance", "maintenance", "conformite", "chantier"],
|
|
736
|
+
refresh_checks: ["projection catalog >= 25", "gap rules >= 25", "expected findings >= 12"]
|
|
737
|
+
}, "active", "open")
|
|
738
|
+
];
|
|
739
|
+
for (const entry of catalogEntries.filter((row) => row.artifact_kind === "live_answer_view")) {
|
|
740
|
+
artifacts.push(artifact("live_answer_view", entry.name, entry.label, {
|
|
741
|
+
source_plan_id: "analysis_plan__immeuble_competency_questions",
|
|
742
|
+
procedure_id: entry.procedure_id,
|
|
743
|
+
procedure_area: entry.procedure_area,
|
|
744
|
+
business_question: entry.business_question,
|
|
745
|
+
summary: entry.business_question,
|
|
746
|
+
description: entry.business_question,
|
|
747
|
+
expected_output: entry.expected_output,
|
|
748
|
+
refresh_checks: [
|
|
749
|
+
`${entry.required_schemas[0]} records present`,
|
|
750
|
+
`required edges: ${entry.required_edges.join(", ")}`
|
|
751
|
+
]
|
|
752
|
+
}));
|
|
753
|
+
}
|
|
754
|
+
for (const snapshot of snapshots) {
|
|
755
|
+
artifacts.push(artifact("answer_snapshot", snapshot.name, snapshot.label, {
|
|
756
|
+
source_plan_id: "analysis_plan__immeuble_competency_questions",
|
|
757
|
+
projection_id: snapshot.name,
|
|
758
|
+
metric: snapshot.metric,
|
|
759
|
+
summary: snapshot.summary
|
|
760
|
+
}, "active", "ready", `projection:${snapshot.name}`));
|
|
761
|
+
}
|
|
762
|
+
return artifacts;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function writeAnswerSeeds(artifacts) {
|
|
766
|
+
const lines = artifacts.map((row) => JSON.stringify({
|
|
767
|
+
kind: "answer_artifact",
|
|
768
|
+
profile_id: WS,
|
|
769
|
+
artifact: row
|
|
770
|
+
}));
|
|
771
|
+
writeFileSync(paths.answerSeed, `${lines.join("\n")}\n`, "utf8");
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function writeBusinessCapabilities(catalogEntries) {
|
|
775
|
+
const lines = catalogEntries
|
|
776
|
+
.filter((entry) => entry.artifact_kind === "live_answer_view")
|
|
777
|
+
.map((entry) => JSON.stringify({
|
|
778
|
+
kind: "remember",
|
|
779
|
+
profile_id: WS,
|
|
780
|
+
schema_id: "ghostcrab:business-capability",
|
|
781
|
+
content: `Capacite metier immeuble: ${entry.label}.`,
|
|
782
|
+
facets: {
|
|
783
|
+
capability_id: entry.name,
|
|
784
|
+
workspace_id: WS,
|
|
785
|
+
availability: "live_answer_view",
|
|
786
|
+
activation_status: "active",
|
|
787
|
+
label: entry.label,
|
|
788
|
+
business_question: entry.business_question,
|
|
789
|
+
example_queries: [entry.label, entry.business_question],
|
|
790
|
+
required_schemas: entry.required_schemas.map((schema) => `immeuble:core:${schema}`),
|
|
791
|
+
required_facets: entry.required_facets,
|
|
792
|
+
artifact_id: `live_answer_view__${entry.name}`,
|
|
793
|
+
artifact_kind: "live_answer_view",
|
|
794
|
+
procedure_id: entry.procedure_id,
|
|
795
|
+
source: "business-capability"
|
|
796
|
+
}
|
|
797
|
+
}));
|
|
798
|
+
writeFileSync(paths.businessSeed, `${lines.join("\n")}\n`, "utf8");
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function writeConsumerContract(catalogEntries) {
|
|
802
|
+
const live = catalogEntries.filter((entry) => entry.artifact_kind === "live_answer_view");
|
|
803
|
+
const lines = [
|
|
804
|
+
"workspace_id: immeuble",
|
|
805
|
+
"",
|
|
806
|
+
"orphan_entities:",
|
|
807
|
+
" max_ratio: 0.50",
|
|
808
|
+
" exempt_entity_types: [person, organization, role]",
|
|
809
|
+
"",
|
|
810
|
+
"facet_queries:",
|
|
811
|
+
" - name: search_building",
|
|
812
|
+
" tool: ghostcrab_search",
|
|
813
|
+
" schema_id: immeuble:core:building",
|
|
814
|
+
" min_rows: 5",
|
|
815
|
+
" - name: search_unit",
|
|
816
|
+
" tool: ghostcrab_search",
|
|
817
|
+
" schema_id: immeuble:core:unit",
|
|
818
|
+
" min_rows: 40",
|
|
819
|
+
" - name: search_maintenance_ticket",
|
|
820
|
+
" tool: ghostcrab_search",
|
|
821
|
+
" schema_id: immeuble:core:maintenance_ticket",
|
|
822
|
+
" min_rows: 10",
|
|
823
|
+
"",
|
|
824
|
+
"graph_traversals:",
|
|
825
|
+
" - name: building_contains_units",
|
|
826
|
+
" start_entity_type: immeuble:building",
|
|
827
|
+
" edge_label: contains",
|
|
828
|
+
" min_edges: 40",
|
|
829
|
+
" - name: worksite_has_packages",
|
|
830
|
+
" start_entity_type: immeuble:worksite_project",
|
|
831
|
+
" edge_label: has_work_package",
|
|
832
|
+
" min_edges: 3",
|
|
833
|
+
"",
|
|
834
|
+
"answer_artifacts:"
|
|
835
|
+
];
|
|
836
|
+
for (const entry of live) {
|
|
837
|
+
lines.push(` - name: ${entry.name}`);
|
|
838
|
+
lines.push(" artifact_kind: live_answer_view");
|
|
839
|
+
lines.push(" workspace_id: immeuble");
|
|
840
|
+
lines.push(" min_items: 1");
|
|
841
|
+
}
|
|
842
|
+
writeFileSync(paths.consumerContract, `${lines.join("\n")}\n`, "utf8");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function rule(rule_id, entity_type, relation_type, direction, target_entity_type, min_count, severity, label, metadata = {}) {
|
|
846
|
+
const row = { rule_id, entity_type, relation_type, direction, target_entity_type, min_count, severity, label };
|
|
847
|
+
if (metadata && Object.keys(metadata).length) row.metadata_json = JSON.stringify(metadata);
|
|
848
|
+
return row;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function writeRulePack(name, rules, replace = false) {
|
|
852
|
+
writeFileSync(join(paths.gapRules, name), JSON.stringify({
|
|
853
|
+
ontology_id: ONT,
|
|
854
|
+
workspace_id: WS,
|
|
855
|
+
replace,
|
|
856
|
+
rules
|
|
857
|
+
}, null, 2) + "\n", "utf8");
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function buildRules() {
|
|
861
|
+
const activeUnitFilter = { entity_filter: { metadata: { usage_status: { not_one_of: ["vacant_works", "vacant"] } } } };
|
|
862
|
+
const patrimoine = [
|
|
863
|
+
rule("unit-one-cellar", "unit", "assigned_cellar", "out", "cellar", 1, "error", "Chaque lot doit avoir une cave assignee"),
|
|
864
|
+
rule("unit-in-building", "unit", "contains", "in", null, 1, "error", "Chaque lot doit etre rattache a un immeuble ou bloc"),
|
|
865
|
+
rule("building-has-block", "building", "contains", "out", "block", 1, "error", "Chaque immeuble doit contenir au moins un bloc"),
|
|
866
|
+
rule("building-has-shared-space", "building", "contains", "out", "shared_space", 1, "warning", "Chaque immeuble doit declarer au moins une partie commune"),
|
|
867
|
+
rule("meter-measures-asset", "meter", "measures_asset", "out", null, 1, "warning", "Chaque compteur doit mesurer un asset")
|
|
868
|
+
];
|
|
869
|
+
const syndic = [
|
|
870
|
+
rule("unit-has-owner", "unit", "owns", "in", null, 1, "error", "Chaque lot actif doit avoir un coproprietaire", activeUnitFilter),
|
|
871
|
+
rule("occupied-unit-has-occupant", "unit", "occupies", "in", "person", 1, "error", "Lot occupe sans occupant declare", activeUnitFilter),
|
|
872
|
+
rule("tenant-occupied-has-lease", "unit", "leases", "in", "lease_contract", 1, "warning", "Lot loue sans contrat de bail lie", { entity_filter: { metadata: { usage_status: { one_of: ["tenant_occupied", "owner_abroad_tenant"] } } } }),
|
|
873
|
+
rule("billing-group-bills-unit", "unit", "bills_to", "in", "billing_group", 1, "warning", "Chaque lot actif doit avoir un groupe de facturation", activeUnitFilter),
|
|
874
|
+
rule("household-has-member", "household", "household_member", "out", "person", 1, "warning", "Chaque menage doit avoir au moins un membre")
|
|
875
|
+
];
|
|
876
|
+
const finance = [
|
|
877
|
+
rule("charge-call-has-billing-group", "charge_call", "bills_to", "out", "billing_group", 1, "error", "Chaque appel de charges doit cibler un groupe de facturation"),
|
|
878
|
+
rule("matched-coda-has-charge-call", "coda_entry", "matched_to", "out", "charge_call", 1, "warning", "Chaque CODA rapproche doit pointer vers un appel"),
|
|
879
|
+
rule("open-charge-has-reminder", "charge_call", "triggered", "out", "reminder", 1, "warning", "Un appel ouvert doit declencher une relance", { entity_filter: { metadata: { status: { one_of: ["open", "overdue"] } } } }),
|
|
880
|
+
rule("reminder-has-charge-source", "reminder", "triggered", "in", "charge_call", 1, "warning", "Chaque relance doit provenir d'un appel ouvert")
|
|
881
|
+
];
|
|
882
|
+
const exploitation = [
|
|
883
|
+
rule("service-contract-covers-asset", "service_contract", "covers_asset", "out", null, 1, "error", "Chaque contrat de maintenance doit couvrir un asset"),
|
|
884
|
+
rule("service-contract-has-provider", "service_contract", "provided_by", "out", "organization", 1, "error", "Chaque contrat de maintenance doit avoir un prestataire"),
|
|
885
|
+
rule("ticket-concerns-asset", "maintenance_ticket", "concerns_asset", "out", null, 1, "warning", "Chaque ticket doit concerner un asset"),
|
|
886
|
+
rule("ticket-has-intervention", "maintenance_ticket", "has_intervention", "out", "intervention", 1, "warning", "Chaque ticket doit avoir une intervention planifiee"),
|
|
887
|
+
rule("intervention-has-provider", "intervention", "provided_by", "out", "organization", 1, "warning", "Chaque intervention doit avoir un prestataire"),
|
|
888
|
+
rule("claim-covered-by-policy", "claim", "covered_by", "out", "insurance_policy", 1, "error", "Chaque sinistre doit etre relie a une police"),
|
|
889
|
+
rule("certificate-certifies-asset", "compliance_certificate", "certifies_asset", "out", null, 1, "error", "Chaque certificat doit certifier un asset"),
|
|
890
|
+
rule("meter-has-recent-reading", "meter", "records_reading", "out", "meter_reading", 1, "warning", "Chaque compteur doit avoir au moins un releve")
|
|
891
|
+
];
|
|
892
|
+
const gouvernance = [
|
|
893
|
+
rule("ag-has-agenda-item", "ag_meeting", "has_agenda_item", "out", "agenda_item", 1, "warning", "Chaque AG doit avoir un ordre du jour"),
|
|
894
|
+
rule("agenda-item-has-decision", "agenda_item", "decided_by", "out", "decision", 1, "warning", "Chaque point approuve doit avoir une decision")
|
|
895
|
+
];
|
|
896
|
+
const chantier = [
|
|
897
|
+
rule("worksite-has-architect", "worksite_project", "designs_for", "in", "architect", 1, "error", "Chaque chantier doit avoir un architecte responsable"),
|
|
898
|
+
rule("worksite-has-work-package", "worksite_project", "has_work_package", "out", "work_package", 1, "error", "Chaque chantier doit contenir au moins un lot de travaux"),
|
|
899
|
+
rule("work-package-affects-asset", "work_package", "affects_asset", "out", null, 1, "warning", "Chaque lot de travaux doit affecter au moins un asset"),
|
|
900
|
+
rule("work-package-has-contractor", "work_package", "executes_work_package", "in", "contractor", 1, "error", "Chaque lot de travaux doit etre lie a une entreprise"),
|
|
901
|
+
rule("worksite-has-permit", "worksite_project", "authorized_by", "out", "permit", 1, "warning", "Chaque chantier doit etre autorise par un permis ou une decision"),
|
|
902
|
+
rule("worksite-has-budget-line", "worksite_project", "has_budget_line", "out", "budget_line", 1, "warning", "Chaque chantier doit avoir une ligne budget"),
|
|
903
|
+
rule("reserve-linked-to-inspection", "defect_reserve", "has_reserve", "in", "inspection", 1, "error", "Chaque reserve doit etre liee a une inspection"),
|
|
904
|
+
rule("invoice-linked-to-work-package", "invoice", "invoiced_by", "in", "work_package", 1, "warning", "Chaque facture chantier doit etre rattachee a un lot de travaux")
|
|
905
|
+
];
|
|
906
|
+
writeRulePack("L0-patrimoine.json", patrimoine);
|
|
907
|
+
writeRulePack("L1-syndic.json", syndic);
|
|
908
|
+
writeRulePack("L2-finance.json", finance);
|
|
909
|
+
writeRulePack("L2-exploitation.json", exploitation);
|
|
910
|
+
writeRulePack("L2-maintenance.json", [...exploitation, ...gouvernance]);
|
|
911
|
+
writeRulePack("L2-chantier.json", chantier);
|
|
912
|
+
writeRulePack("L3-full.json", [...patrimoine, ...syndic, ...finance, ...exploitation, ...gouvernance, ...chantier], true);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function writeExpectedFindings() {
|
|
916
|
+
const findings = buildDeltaFindings();
|
|
917
|
+
const lines = [
|
|
918
|
+
"workspace_id: immeuble",
|
|
919
|
+
"ontology_id: immeuble::core",
|
|
920
|
+
"profile: pedagogical",
|
|
921
|
+
"expected_findings:"
|
|
922
|
+
];
|
|
923
|
+
for (const finding of findings) {
|
|
924
|
+
lines.push(` - id: ${finding.id}`);
|
|
925
|
+
lines.push(` rule_id: ${finding.rule_id}`);
|
|
926
|
+
lines.push(` severity: ${finding.severity}`);
|
|
927
|
+
lines.push(` label: ${yamlScalar(finding.label)}`);
|
|
928
|
+
lines.push(` procedure_id: ${finding.procedure_id}`);
|
|
929
|
+
lines.push(` entity_id: ${finding.entityId}`);
|
|
930
|
+
}
|
|
931
|
+
writeFileSync(join(paths.gapRules, "expected-findings.yaml"), `${lines.join("\n")}\n`, "utf8");
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function writeAcceptance() {
|
|
935
|
+
const text = `# Definition of Done - workspace immeuble
|
|
936
|
+
workspace_id: immeuble
|
|
937
|
+
ontology_id: immeuble::core
|
|
938
|
+
schema_id_prefix: "immeuble:"
|
|
939
|
+
|
|
940
|
+
import_ready:
|
|
941
|
+
facet_rows_min: 400
|
|
942
|
+
edge_rows_min: 800
|
|
943
|
+
fake_data_csv_min: 35
|
|
944
|
+
entity_type_counts_min:
|
|
945
|
+
building: 5
|
|
946
|
+
block: 9
|
|
947
|
+
unit: 40
|
|
948
|
+
household: 40
|
|
949
|
+
cellar: 40
|
|
950
|
+
person: 80
|
|
951
|
+
charge_call: 50
|
|
952
|
+
coda_entry: 35
|
|
953
|
+
maintenance_ticket: 10
|
|
954
|
+
worksite_project: 2
|
|
955
|
+
|
|
956
|
+
db_after_import:
|
|
957
|
+
agent_facts_min: 400
|
|
958
|
+
relations_raw_min: 800
|
|
959
|
+
graph_entity_min: 400
|
|
960
|
+
schema_id_violations: 0
|
|
961
|
+
|
|
962
|
+
hybrid_compare:
|
|
963
|
+
required: false
|
|
964
|
+
max_deltas:
|
|
965
|
+
facets_inserted: 0
|
|
966
|
+
edges_inserted: 0
|
|
967
|
+
entities_upserted: 0
|
|
968
|
+
|
|
969
|
+
projections:
|
|
970
|
+
analysis_plan_min: 1
|
|
971
|
+
live_answer_view_min: 25
|
|
972
|
+
answer_snapshot_min: 8
|
|
973
|
+
|
|
974
|
+
consumer_contract:
|
|
975
|
+
facet_queries_min: 3
|
|
976
|
+
graph_traversals_min: 2
|
|
977
|
+
answer_artifacts_min: 25
|
|
978
|
+
|
|
979
|
+
business_capabilities:
|
|
980
|
+
active_min: 25
|
|
981
|
+
required_artifact_ids:
|
|
982
|
+
- live_answer_view__lots_sans_proprietaire
|
|
983
|
+
- live_answer_view__coda_a_rapprocher
|
|
984
|
+
- live_answer_view__interventions_en_retard
|
|
985
|
+
- live_answer_view__chantier_actif
|
|
986
|
+
|
|
987
|
+
starterkit_projection_audit:
|
|
988
|
+
quality_score_min: 70
|
|
989
|
+
required_facet_observation_gap_max: 0
|
|
990
|
+
required_schema_record_gap_max: 0
|
|
991
|
+
required_edge_type_gap_max: 0
|
|
992
|
+
planned_missing_count_max: 0
|
|
993
|
+
strict_only: true
|
|
994
|
+
|
|
995
|
+
bundle:
|
|
996
|
+
path: bundle/immeuble.bundle.json
|
|
997
|
+
documents_raw_min: 20
|
|
998
|
+
entities_raw_min: 400
|
|
999
|
+
relations_raw_min: 800
|
|
1000
|
+
answer_artifacts_min: 33
|
|
1001
|
+
|
|
1002
|
+
graph_search_min:
|
|
1003
|
+
query: appartement
|
|
1004
|
+
min_hits: 40
|
|
1005
|
+
|
|
1006
|
+
construction_lite:
|
|
1007
|
+
entity_types_min: 14
|
|
1008
|
+
graph_entities_min: 30
|
|
1009
|
+
live_answer_views_min: 6
|
|
1010
|
+
projection_results_min: 8
|
|
1011
|
+
delta_findings_min: 12
|
|
1012
|
+
gap_rules_min: 25
|
|
1013
|
+
|
|
1014
|
+
expected_findings:
|
|
1015
|
+
file: gap-rules/expected-findings.yaml
|
|
1016
|
+
min_count: 12
|
|
1017
|
+
`;
|
|
1018
|
+
writeFileSync(paths.acceptance, text, "utf8");
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function writeSuccessCriteria() {
|
|
1022
|
+
const text = `workspace_id: immeuble
|
|
1023
|
+
golden_workspace_id: immeuble
|
|
1024
|
+
parity_note: Single canonical workspace - bundle and structured-import share immeuble
|
|
1025
|
+
|
|
1026
|
+
entity_counts_min:
|
|
1027
|
+
buildings: 5
|
|
1028
|
+
blocks: 9
|
|
1029
|
+
units: 40
|
|
1030
|
+
households: 40
|
|
1031
|
+
cellars: 40
|
|
1032
|
+
lease_contracts: 10
|
|
1033
|
+
coda_entries: 35
|
|
1034
|
+
persons: 80
|
|
1035
|
+
maintenance_tickets: 10
|
|
1036
|
+
worksite_projects: 2
|
|
1037
|
+
|
|
1038
|
+
relation_edges:
|
|
1039
|
+
- contains
|
|
1040
|
+
- owns
|
|
1041
|
+
- occupies
|
|
1042
|
+
- leases
|
|
1043
|
+
- bills_to
|
|
1044
|
+
- matched_to
|
|
1045
|
+
- has_intervention
|
|
1046
|
+
- covers_asset
|
|
1047
|
+
- certified_asset
|
|
1048
|
+
- has_work_package
|
|
1049
|
+
|
|
1050
|
+
graph_search_min:
|
|
1051
|
+
appartement: 40
|
|
1052
|
+
|
|
1053
|
+
diagnostics:
|
|
1054
|
+
rules_pack: gap-rules/L3-full.json
|
|
1055
|
+
expected_findings: gap-rules/expected-findings.yaml
|
|
1056
|
+
missing_required_relations_min: 12
|
|
1057
|
+
`;
|
|
1058
|
+
writeFileSync(paths.successCriteria, text, "utf8");
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function writeSourceDocs() {
|
|
1062
|
+
mkdirSync(paths.docs, { recursive: true });
|
|
1063
|
+
const docs = {
|
|
1064
|
+
"procedures-operationnelles.md": "# Procedures operationnelles demo\n\nLa demo couvre proprietaires, occupants, baux, charges, CODA, relances, AG, maintenance, conformite, sinistres et chantiers.\n",
|
|
1065
|
+
"trous-pedagogiques.md": "# Trous pedagogiques attendus\n\nLes donnees contiennent volontairement des proprietaires manquants, occupants manquants, contrats incomplets, relances sans preuve, certificats expires et chantiers a risque.\n"
|
|
1066
|
+
};
|
|
1067
|
+
for (const [name, content] of Object.entries(docs)) {
|
|
1068
|
+
writeFileSync(join(paths.docs, name), content, "utf8");
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function main() {
|
|
1073
|
+
mkdirSync(paths.gapRules, { recursive: true });
|
|
1074
|
+
const bundle = JSON.parse(readFileSync(paths.bundle, "utf8"));
|
|
1075
|
+
|
|
1076
|
+
for (const key of ["entities_raw", "relations_raw", "documents_raw", "chunks_raw", "entity_aliases_raw", "entity_documents_raw", "entity_chunks_raw"]) {
|
|
1077
|
+
bundle[key] ??= [];
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
bundle.entities_raw = bundle.entities_raw.filter((row) => Number(row.entity_id) < GENERATED_MIN_ID && parseMeta(row.metadata_json).generated_by !== GENERATED_TAG);
|
|
1081
|
+
bundle.relations_raw = bundle.relations_raw.filter((row) => Number(row.relation_id) < GENERATED_REL_MIN_ID && parseMeta(row.metadata_json).generated_by !== GENERATED_TAG);
|
|
1082
|
+
bundle.documents_raw = bundle.documents_raw.filter((row) => parseMeta(row.metadata_json).generated_by !== GENERATED_TAG);
|
|
1083
|
+
bundle.chunks_raw = bundle.chunks_raw.filter((row) => parseMeta(row.metadata_json).generated_by !== GENERATED_TAG);
|
|
1084
|
+
bundle.entity_aliases_raw = bundle.entity_aliases_raw.filter((row) => Number(row.entity_id) < GENERATED_MIN_ID);
|
|
1085
|
+
bundle.entity_documents_raw = bundle.entity_documents_raw.filter((row) => Number(row.entity_id) < GENERATED_MIN_ID && Number(row.doc_id) < 100);
|
|
1086
|
+
bundle.entity_chunks_raw = bundle.entity_chunks_raw.filter((row) => Number(row.entity_id) < GENERATED_MIN_ID && Number(row.doc_id) < 100);
|
|
1087
|
+
|
|
1088
|
+
for (const type of [
|
|
1089
|
+
"ag_meeting", "agenda_item", "service_contract", "maintenance_ticket", "intervention",
|
|
1090
|
+
"insurance_policy", "claim", "meter", "meter_reading", "compliance_certificate"
|
|
1091
|
+
]) {
|
|
1092
|
+
addType(bundle, type, type, "operations");
|
|
1093
|
+
}
|
|
1094
|
+
for (const [edge, source, target] of [
|
|
1095
|
+
["covers_asset", "Entity", "Asset"],
|
|
1096
|
+
["provided_by", "Entity", "organization"],
|
|
1097
|
+
["concerns_asset", "Entity", "Asset"],
|
|
1098
|
+
["has_intervention", "maintenance_ticket", "intervention"],
|
|
1099
|
+
["covered_by", "claim", "insurance_policy"],
|
|
1100
|
+
["certifies_asset", "compliance_certificate", "Asset"],
|
|
1101
|
+
["measures_asset", "meter", "Asset"],
|
|
1102
|
+
["records_reading", "meter", "meter_reading"],
|
|
1103
|
+
["has_agenda_item", "ag_meeting", "agenda_item"],
|
|
1104
|
+
["flags_entity", "DeltaFinding", "Entity"]
|
|
1105
|
+
]) {
|
|
1106
|
+
addEdgeType(bundle, edge, source, target);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
buildPortfolioData(bundle);
|
|
1110
|
+
|
|
1111
|
+
const { entries, text } = buildProjectionCatalog();
|
|
1112
|
+
const artifacts = buildArtifacts(entries);
|
|
1113
|
+
bundle.mindbrain_answer_artifacts = artifacts;
|
|
1114
|
+
writeFileSync(paths.projectionCatalog, text, "utf8");
|
|
1115
|
+
writeAnswerSeeds(artifacts);
|
|
1116
|
+
writeBusinessCapabilities(entries);
|
|
1117
|
+
writeConsumerContract(entries);
|
|
1118
|
+
buildRules();
|
|
1119
|
+
writeExpectedFindings();
|
|
1120
|
+
writeAcceptance();
|
|
1121
|
+
writeSuccessCriteria();
|
|
1122
|
+
writeSourceDocs();
|
|
1123
|
+
|
|
1124
|
+
writeFileSync(paths.bundle, JSON.stringify(bundle, null, 2) + "\n", "utf8");
|
|
1125
|
+
console.log(JSON.stringify({
|
|
1126
|
+
ok: true,
|
|
1127
|
+
entities_raw: bundle.entities_raw.length,
|
|
1128
|
+
relations_raw: bundle.relations_raw.length,
|
|
1129
|
+
documents_raw: bundle.documents_raw.length,
|
|
1130
|
+
answer_artifacts: bundle.mindbrain_answer_artifacts.length,
|
|
1131
|
+
projection_catalog: entries.length
|
|
1132
|
+
}, null, 2));
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
main();
|