Remove project device mirror fields

This commit is contained in:
2026-07-23 20:55:26 +02:00
parent 3fbf3ac622
commit 30d6f1a2e1
10 changed files with 1588 additions and 20 deletions
+3 -2
View File
@@ -16,8 +16,9 @@ and requires both `PRAGMA integrity_check` and `PRAGMA foreign_key_check` to pas
Database backups are operational recovery files and remain separate from the future
user-visible project version history.
2. Apply pending schema migrations (includes `0008_circuit_first_model` and the additive
`0009_project_device_circuit_fields` transition)
2. Apply pending schema migrations (includes `0008_circuit_first_model`, the additive
`0009_project_device_circuit_fields` transition and the post-cutover
`0011_project_device_canonical_fields` cleanup)
```bash
npm run db:migrate
@@ -344,6 +344,7 @@ Implemented foundation:
- circuit and device-row persistence value mapping is separated from the general repositories
- the legacy consumer UI and application read/write endpoints are removed after verified data cutover
- retained legacy rows are accessible only through explicit database upgrade tooling
- project devices no longer persist duplicate legacy power, phase, cosPhi or remark fields
## Phase 12: Project Revisions and Persistent Undo / Redo
+2 -2
View File
@@ -14,8 +14,8 @@
"build:api": "tsc -p tsconfig.json",
"build:web": "next build",
"start": "node dist/server/index.js",
"test": "tsx --test tests/project-device-schema.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
"test:watch": "tsx --watch --test tests/project-device-schema.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
"test": "tsx --test tests/project-device-schema.test.ts tests/project-device-schema-migration.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
"test:watch": "tsx --watch --test tests/project-device-schema.test.ts tests/project-device-schema-migration.test.ts tests/project-device-placement.service.test.ts tests/project-device-sync.service.test.ts tests/legacy-consumer-migration-planner.test.ts tests/legacy-consumer-migration.repository.test.ts tests/circuit-numbering.service.test.ts tests/circuit-write.rules.test.ts tests/circuit-power-calculation.test.ts tests/circuit-tree.controller.test.ts tests/circuit-grid-insertion.test.ts tests/circuit-grid-safety.test.ts tests/circuit-grid-model.test.ts tests/circuit-grid-projection.test.ts tests/distribution-board.repository.test.ts tests/circuit-device-row-transaction.repository.test.ts tests/project-device-row-sync.repository.test.ts tests/circuit-section-transaction.repository.test.ts tests/database-backup.test.ts",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:backup": "tsx scripts/db-backup.ts",
+18
View File
@@ -33,6 +33,16 @@ const projectDeviceColumns = new Set(
const missingProjectDeviceColumns = requiredProjectDeviceColumns.filter(
(name) => !projectDeviceColumns.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)
@@ -55,6 +65,14 @@ if (missingProjectDeviceColumns.length > 0) {
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);
@@ -0,0 +1,9 @@
ALTER TABLE `project_devices` DROP COLUMN `installed_power_per_unit_kw`;
--> statement-breakpoint
ALTER TABLE `project_devices` DROP COLUMN `demand_factor`;
--> statement-breakpoint
ALTER TABLE `project_devices` DROP COLUMN `phase_count`;
--> statement-breakpoint
ALTER TABLE `project_devices` DROP COLUMN `power_factor`;
--> statement-breakpoint
ALTER TABLE `project_devices` DROP COLUMN `note`;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -78,6 +78,13 @@
"when": 1784746800000,
"tag": "0010_circuit_control_requirement",
"breakpoints": true
},
{
"idx": 11,
"version": "6",
"when": 1784832541202,
"tag": "0011_project_device_canonical_fields",
"breakpoints": true
}
]
}
@@ -37,12 +37,7 @@ export class ProjectDeviceRepository {
simultaneityFactor: input.simultaneityFactor,
cosPhi: input.cosPhi ?? null,
remark: input.remark ?? null,
installedPowerPerUnitKw: input.powerPerUnit,
demandFactor: input.simultaneityFactor,
voltageV: input.voltageV ?? null,
phaseCount: input.phaseType === "three_phase" ? 3 : 1,
powerFactor: input.cosPhi ?? null,
note: input.remark ?? null,
});
const created = await this.findById(projectId, id);
if (!created) {
@@ -77,12 +72,7 @@ export class ProjectDeviceRepository {
simultaneityFactor: input.simultaneityFactor,
cosPhi: input.cosPhi ?? null,
remark: input.remark ?? null,
installedPowerPerUnitKw: input.powerPerUnit,
demandFactor: input.simultaneityFactor,
voltageV: input.voltageV ?? null,
phaseCount: input.phaseType === "three_phase" ? 3 : 1,
powerFactor: input.cosPhi ?? null,
note: input.remark ?? null,
})
.where(
and(eq(projectDevices.id, projectDeviceId), eq(projectDevices.projectId, projectId))
-6
View File
@@ -17,11 +17,5 @@ export const projectDevices = sqliteTable("project_devices", {
simultaneityFactor: real("simultaneity_factor").notNull().default(1),
cosPhi: real("cos_phi"),
remark: text("remark"),
// Transitional mirror columns retained until a dedicated schema cleanup migration removes them.
installedPowerPerUnitKw: real("installed_power_per_unit_kw").notNull(),
demandFactor: real("demand_factor").notNull(),
voltageV: real("voltage_v"),
phaseCount: integer("phase_count"),
powerFactor: real("power_factor"),
note: text("note"),
});
@@ -0,0 +1,98 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { describe, it } from "node:test";
import Database from "better-sqlite3";
describe("project device canonical-field migration", () => {
it("preserves canonical values while removing transitional mirror columns", () => {
const sqlite = new Database(":memory:");
try {
sqlite.exec(`
CREATE TABLE project_devices (
id text PRIMARY KEY NOT NULL,
power_per_unit real NOT NULL,
simultaneity_factor real NOT NULL,
phase_type text NOT NULL,
cos_phi real,
remark text,
voltage_v real,
installed_power_per_unit_kw real NOT NULL,
demand_factor real NOT NULL,
phase_count integer,
power_factor real,
note text
);
INSERT INTO project_devices VALUES (
'device-1',
0.25,
0.8,
'three_phase',
0.95,
'Canonical remark',
400,
0.25,
0.8,
3,
0.95,
'Canonical remark'
);
`);
const migrationSql = fs.readFileSync(
path.resolve(
"src",
"db",
"migrations",
"0011_project_device_canonical_fields.sql"
),
"utf8"
);
for (const statement of migrationSql.split("--> statement-breakpoint")) {
if (statement.trim()) {
sqlite.exec(statement);
}
}
const columns = (
sqlite.prepare("PRAGMA table_info(project_devices)").all() as Array<{
name: string;
}>
).map((column) => column.name);
assert.deepEqual(columns, [
"id",
"power_per_unit",
"simultaneity_factor",
"phase_type",
"cos_phi",
"remark",
"voltage_v",
]);
assert.deepEqual(
sqlite
.prepare(
`SELECT
power_per_unit AS powerPerUnit,
simultaneity_factor AS simultaneityFactor,
phase_type AS phaseType,
cos_phi AS cosPhi,
remark,
voltage_v AS voltageV
FROM project_devices
WHERE id = ?`
)
.get("device-1"),
{
powerPerUnit: 0.25,
simultaneityFactor: 0.8,
phaseType: "three_phase",
cosPhi: 0.95,
remark: "Canonical remark",
voltageV: 400,
}
);
} finally {
sqlite.close();
}
});
});