In LPH 2/3 there are no circuits yet, but the office needs a power balance by DIN 276 cost group, as in the Excel template "Leistungsbilanz - KG400": each trade enters consumers by piece or by area (W/m²) with GLF and cos phi; the balance sums AV and SV per cost group, adds a reserve and derives the maximum current. The module is separate from the circuit structure: own tables (migration 0010), own folder src/estimate-balance and an import-free shared calculation used by API and UI. Writes go through project commands (estimate-balance.insert/.update/.delete with complete documents), so revision checks and Undo/Redo apply; it is not part of project snapshots or transfer yet. A comparison endpoint sums the device rows of all circuit lists by cost group (longest prefix) against the estimate. The UI adds a sidebar entry, a list of Stände (template KG 400, empty or copy) and a spreadsheet-like editor with autosave, AMEV picker for the GLF, totals, reserve, maximum current, comparison table and print layout. The tests reproduce the template example exactly (143.24 kW, 148.48 kVA, 157.56 kW / 163.33 kVA with 10 % reserve, 235.75 A). See docs/estimate-balance.md and issue #7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
259 lines
7.7 KiB
JavaScript
259 lines
7.7 KiB
JavaScript
const Database = require("better-sqlite3");
|
|
const path = require("node:path");
|
|
|
|
const dbPath = path.resolve("data", "leistungsbilanz.db");
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
const requiredTables = [
|
|
"buildings",
|
|
"estimate_balances",
|
|
"estimate_groups",
|
|
"estimate_rows",
|
|
"circuit_sections",
|
|
"circuits",
|
|
"circuit_device_rows",
|
|
"circuit_list_equipment_identifiers",
|
|
"circuit_protection_devices",
|
|
"distribution_board_components",
|
|
"distribution_board_component_protection_devices",
|
|
"document_branding_assets",
|
|
"document_branding_settings",
|
|
];
|
|
|
|
const tablePlaceholders = requiredTables.map(() => "?").join(", ");
|
|
const rows = db
|
|
.prepare(
|
|
`SELECT name
|
|
FROM sqlite_master
|
|
WHERE type = 'table' AND name IN (${tablePlaceholders})`
|
|
)
|
|
.all(...requiredTables);
|
|
|
|
const existing = new Set(rows.map((row) => row.name));
|
|
const missing = requiredTables.filter((name) => !existing.has(name));
|
|
const requiredProjectDeviceColumns = [
|
|
"phase_type",
|
|
"connection_kind",
|
|
"cost_group",
|
|
"power_per_unit",
|
|
"simultaneity_factor",
|
|
"cos_phi",
|
|
"remark",
|
|
];
|
|
const projectDeviceColumns = new Set(
|
|
db.prepare("PRAGMA table_info(project_devices)").all().map((column) => column.name)
|
|
);
|
|
const missingProjectDeviceColumns = requiredProjectDeviceColumns.filter(
|
|
(name) => !projectDeviceColumns.has(name)
|
|
);
|
|
const requiredCircuitDeviceRowColumns = ["manual_quantity"];
|
|
const circuitDeviceRowColumns = new Set(
|
|
db.prepare("PRAGMA table_info(circuit_device_rows)").all().map((column) => column.name)
|
|
);
|
|
const missingCircuitDeviceRowColumns = requiredCircuitDeviceRowColumns.filter(
|
|
(name) => !circuitDeviceRowColumns.has(name)
|
|
);
|
|
const removedProjectDeviceColumns = [
|
|
"installed_power_per_unit_kw",
|
|
"demand_factor",
|
|
"phase_count",
|
|
"power_factor",
|
|
"note",
|
|
];
|
|
const remainingRemovedProjectDeviceColumns = removedProjectDeviceColumns.filter(
|
|
(name) => projectDeviceColumns.has(name)
|
|
);
|
|
const requiredCircuitColumns = ["control_requirement"];
|
|
const circuitColumns = new Set(
|
|
db.prepare("PRAGMA table_info(circuits)").all().map((column) => column.name)
|
|
);
|
|
const missingCircuitColumns = requiredCircuitColumns.filter((name) => !circuitColumns.has(name));
|
|
const removedCircuitColumns = [
|
|
"protection_type",
|
|
"protection_rated_current",
|
|
"protection_characteristic",
|
|
];
|
|
const remainingRemovedCircuitColumns = removedCircuitColumns.filter((name) =>
|
|
circuitColumns.has(name)
|
|
);
|
|
const requiredCircuitSectionColumns = ["category", "group_number"];
|
|
const circuitSectionColumns = new Set(
|
|
db
|
|
.prepare("PRAGMA table_info(circuit_sections)")
|
|
.all()
|
|
.map((column) => column.name)
|
|
);
|
|
const missingCircuitSectionColumns = requiredCircuitSectionColumns.filter(
|
|
(name) => !circuitSectionColumns.has(name)
|
|
);
|
|
const requiredProjectColumns = [
|
|
"internal_project_number",
|
|
"external_project_number",
|
|
"building_owner",
|
|
"description",
|
|
"is_public_building",
|
|
"enabled_distribution_board_supply_types",
|
|
"site_address",
|
|
"work_stage",
|
|
"project_lead",
|
|
"project_editor",
|
|
];
|
|
const projectColumns = new Set(
|
|
db.prepare("PRAGMA table_info(projects)").all().map((column) => column.name)
|
|
);
|
|
const missingProjectColumns = requiredProjectColumns.filter(
|
|
(name) => !projectColumns.has(name)
|
|
);
|
|
const requiredFloorColumns = ["building_id"];
|
|
const floorColumns = new Set(
|
|
db.prepare("PRAGMA table_info(floors)").all().map((column) => column.name)
|
|
);
|
|
const missingFloorColumns = requiredFloorColumns.filter(
|
|
(name) => !floorColumns.has(name)
|
|
);
|
|
const requiredDistributionBoardColumns = [
|
|
"floor_id",
|
|
"supply_type",
|
|
"simultaneity_factor",
|
|
"board_number",
|
|
"location",
|
|
"fed_from",
|
|
"remark",
|
|
];
|
|
const distributionBoardColumns = new Set(
|
|
db
|
|
.prepare("PRAGMA table_info(distribution_boards)")
|
|
.all()
|
|
.map((column) => column.name)
|
|
);
|
|
const missingDistributionBoardColumns =
|
|
requiredDistributionBoardColumns.filter(
|
|
(name) => !distributionBoardColumns.has(name)
|
|
);
|
|
const requiredEquipmentIdentifierTriggers = [
|
|
"circuits_equipment_identifier_insert",
|
|
"circuits_equipment_identifier_update",
|
|
"circuits_equipment_identifier_delete",
|
|
"distribution_board_components_equipment_identifier_insert",
|
|
"distribution_board_components_equipment_identifier_update",
|
|
"distribution_board_components_equipment_identifier_delete",
|
|
];
|
|
const equipmentIdentifierTriggers = new Set(
|
|
db
|
|
.prepare(
|
|
`SELECT name
|
|
FROM sqlite_master
|
|
WHERE type = 'trigger'`
|
|
)
|
|
.all()
|
|
.map((trigger) => trigger.name)
|
|
);
|
|
const missingEquipmentIdentifierTriggers =
|
|
requiredEquipmentIdentifierTriggers.filter(
|
|
(name) => !equipmentIdentifierTriggers.has(name)
|
|
);
|
|
const integrityCheck = db.pragma("integrity_check", { simple: true });
|
|
const foreignKeyViolations = db.pragma("foreign_key_check");
|
|
|
|
console.log("Database:", dbPath);
|
|
console.log("Required tables:", requiredTables.join(", "));
|
|
console.log("Existing tables:", [...existing].join(", ") || "(none)");
|
|
console.log("Required project-device columns:", requiredProjectDeviceColumns.join(", "));
|
|
console.log("Required circuit-device-row columns:", requiredCircuitDeviceRowColumns.join(", "));
|
|
console.log("Required circuit columns:", requiredCircuitColumns.join(", "));
|
|
console.log(
|
|
"Required circuit-section columns:",
|
|
requiredCircuitSectionColumns.join(", ")
|
|
);
|
|
console.log("Required project columns:", requiredProjectColumns.join(", "));
|
|
console.log("Required floor columns:", requiredFloorColumns.join(", "));
|
|
console.log(
|
|
"Required distribution-board columns:",
|
|
requiredDistributionBoardColumns.join(", ")
|
|
);
|
|
console.log(
|
|
"Required BMK synchronization triggers:",
|
|
requiredEquipmentIdentifierTriggers.join(", ")
|
|
);
|
|
console.log("Integrity:", integrityCheck);
|
|
console.log("Foreign-key violations:", foreignKeyViolations.length);
|
|
|
|
if (missing.length > 0) {
|
|
console.error("Missing tables:", missing.join(", "));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingFloorColumns.length > 0) {
|
|
console.error("Missing floor columns:", missingFloorColumns.join(", "));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingProjectDeviceColumns.length > 0) {
|
|
console.error("Missing project-device columns:", missingProjectDeviceColumns.join(", "));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (remainingRemovedProjectDeviceColumns.length > 0) {
|
|
console.error(
|
|
"Transitional project-device columns still present:",
|
|
remainingRemovedProjectDeviceColumns.join(", ")
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingCircuitColumns.length > 0) {
|
|
console.error("Missing circuit columns:", missingCircuitColumns.join(", "));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingCircuitDeviceRowColumns.length > 0) {
|
|
console.error(
|
|
"Missing circuit-device-row columns:",
|
|
missingCircuitDeviceRowColumns.join(", ")
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (remainingRemovedCircuitColumns.length > 0) {
|
|
console.error(
|
|
"Transitional circuit protection columns still present:",
|
|
remainingRemovedCircuitColumns.join(", ")
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingCircuitSectionColumns.length > 0) {
|
|
console.error(
|
|
"Missing circuit-section columns:",
|
|
missingCircuitSectionColumns.join(", ")
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingProjectColumns.length > 0) {
|
|
console.error("Missing project columns:", missingProjectColumns.join(", "));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingDistributionBoardColumns.length > 0) {
|
|
console.error(
|
|
"Missing distribution-board columns:",
|
|
missingDistributionBoardColumns.join(", ")
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (missingEquipmentIdentifierTriggers.length > 0) {
|
|
console.error(
|
|
"Missing BMK synchronization triggers:",
|
|
missingEquipmentIdentifierTriggers.join(", ")
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (integrityCheck !== "ok" || foreignKeyViolations.length > 0) {
|
|
console.error("Database integrity or foreign-key verification failed.");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("Circuit-first and distribution-component schema verification passed.");
|