Electrical Schema Refactor Plan
Overview
Complete refactor of the electrical/technical database schema to support:
- Motor control devices (VFDs, soft starters, contactors, motor starters)
- Protection device settings (trip units, relays, fuses, overloads)
- Protection coordination curves (IEEE, IEC, manufacturer)
- Arc flash calculations
- Scenario/contingency analysis
Design Decisions
Protection Device Topology (Single Source of Truth)
Decision: Protection device electrical location is determined from component_connections graph traversal, NOT stored as explicit columns on protection_devices.
Rationale:
component_connectionsalready encodes topology (source → target edges)- Explicit columns (
protected_bus_id,upstream_connection_id) would duplicate data and risk desync - PandaPower's
network_builder.pyalready traverses connections to resolve electrical endpoints
How it works:
When user draws: BUS-001 → CB-101 → TX-001
component_connections:
| source_component | target_component |
| BUS-001 (bus) | CB-101 (prot) |
| CB-101 (prot) | TX-001 (xfmr) |At runtime:
- Power flow:
_resolve_electrical_connections()bypasses CB-101, connects BUS-001 → TX-001 directly - Protection analysis: Build PandaPower network with CB-101 as switch, use
pp.topology.create_graph()to determine:
- Line-side bus (upstream): BUS-001
- Load-side equipment (protected): TX-001
Queries requiring topology (TCC coordination, arc flash upstream device) compute from graph rather than querying explicit columns.
Bus-Centric Topology (Runtime Computation)
Decision: No branches table. Bus-centric topology (from_bus/to_bus relationships) is computed by network_builder.py at runtime, not stored in schema.
Context: Traditional power systems tools (ETAP, SKM, EasyPower) use bus-centric topology where everything connects to/between buses. The concern is that component_connections allows arbitrary connections (cable→generator, floating fuses).
Rationale:
network_builder.pyalready transformscomponent_connectionsinto bus-centric PandaPower model- Creates virtual/implicit buses for components not on explicit buses
- A
branchestable would duplicate data and risk desync with canvas connections - Consistent with protection device topology decision (single source of truth)
How it works:
component_connections (canvas) → PandaPower network (bus-centric)
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ UTIL-001 → CB-001 → TX-001 │ → │ ext_grid[bus=0] │
│ TX-001 → BUS-001 │ │ trafo[hv_bus=0, lv_bus=1] │
│ BUS-001 → LOAD-001 │ │ load[bus=1] │
└─────────────────────────────┘ └──────────────────────────────┘network_builder.py handles:
- Pass 1-2: Direct bus connections, transformer HV/LV sides
- Pass 3: Component-to-component junctions (bypassing protection devices)
- Pass 4: Virtual buses for isolated components
Validation: Invalid connections (if needed) enforced at UI or server action level, not schema.
VFD/Soft Starter Settings (Composition over Dedicated Table)
Decision: No drive_settings table. VFDs/soft starters use motor_control_devices + child device_components for built-in protection.
Rationale:
- Built-in overload reuses
overload_relay_settingstable (no duplication) - Drive-specific settings (ramp times, bypass mode) go in
device_components.settingsJSONB - Matches real-world structure (VFD contains multiple protection functions)
- Enables TCC curves per protection element
Model:
motor_control_devices (type="vfd")
├── device_components (role="drive_unit") → JSONB: ramp times, bypass, control mode
└── device_components (role="built_in_overload") → overload_relay_settings tableFinal Schema
-- ============================================
-- PROJECT SETTINGS
-- ============================================
CREATE TABLE project_settings (
id UUID PRIMARY KEY,
project_id UUID NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE,
base_mva DECIMAL DEFAULT 100, -- power base for pu calcs
nominal_frequency INTEGER DEFAULT 60, -- 50 or 60 Hz
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- SCENARIOS (for what-if analysis)
-- ============================================
CREATE TABLE scenarios (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "Baseline", "Generator Offline", "Future Load"
description TEXT,
parent_scenario_id UUID REFERENCES scenarios(id), -- for branching
is_baseline BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now(),
UNIQUE(project_id, name)
);
-- ============================================
-- CONTINGENCY CASES
-- ============================================
CREATE TABLE contingency_cases (
id UUID PRIMARY KEY,
scenario_id UUID NOT NULL REFERENCES scenarios(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "Loss of TX-001"
contingency_type TEXT, -- "N-1", "N-2", "custom"
description TEXT,
outaged_component_id UUID,
outaged_component_type TEXT, -- "transformer", "generator", "utility_feed"
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- SCENARIO COMPONENT OVERRIDES (per-scenario value changes)
-- ============================================
CREATE TABLE scenario_component_overrides (
id UUID PRIMARY KEY,
scenario_id UUID NOT NULL REFERENCES scenarios(id) ON DELETE CASCADE,
component_id UUID NOT NULL,
component_type TEXT NOT NULL, -- "transformer", "load", "generator", etc.
overrides JSONB NOT NULL, -- sparse diff: only changed fields
/*
Examples:
transformer: {"tap_position": 2.5}
load: {"active_power_kw": 150, "power_factor": 0.9}
generator: {"in_service": false}
*/
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now(),
UNIQUE(scenario_id, component_id, component_type)
);
CREATE INDEX idx_scenario_overrides_scenario ON scenario_component_overrides(scenario_id);
CREATE INDEX idx_scenario_overrides_component ON scenario_component_overrides(component_id, component_type);
-- ============================================
-- DEVICE CATALOG (reference specs, reusable)
-- ============================================
CREATE TABLE device_catalog (
id UUID PRIMARY KEY,
organization_id UUID, -- null = system default
name TEXT NOT NULL, -- "ABB ACS580", "SEL-751", "Bussmann LPJ"
device_category TEXT NOT NULL, -- "vfd", "soft_starter", "contactor", "breaker_lv",
-- "breaker_hv", "fuse", "overload_relay",
-- "protective_relay", "trip_unit"
manufacturer TEXT, -- "ABB", "Siemens", "SEL"
model_number TEXT, -- "ACS580-01-073A"
standard TEXT, -- "IEC_60255", "IEEE_C37_112"
configuration JSONB NOT NULL, -- flexible specs per category
/*
breaker_lv: {frame_sizes: [100,250,400], sensor_ratings: [...], functions: [...]}
breaker_hv: {voltage_class: 15, interrupting_ka: 40, operating_mechanism: "spring"}
fuse: {class: "J", voltage: 600, ratings: [30,60,100], i2t_data: {...}}
contactor: {coil_voltages: [24,120,240], ac3_rating: 25, ac4_rating: 9}
overload: {class: "10", current_ranges: [[0.1,0.16], [18,25]]}
vfd: {hp_range: [1,500], voltage: 480, control_modes: ["v/hz","vector"]}
soft_starter:{hp_range: [5,400], starting_methods: ["voltage_ramp","current_limit"]}
relay: {elements: {50:{}, 51:{}, 49:{}}, ct_ratios: [...]}
trip_unit: {frame_ratings: [250,400], functions: {lt:{}, st:{}, inst:{}, gf:{}}}
*/
is_system_default BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
description TEXT,
created_by TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_device_catalog_category ON device_catalog(device_category);
CREATE INDEX idx_device_catalog_org ON device_catalog(organization_id);
CREATE INDEX gin_device_catalog_config ON device_catalog USING GIN(configuration);
-- ============================================
-- STANDARD CURVE EQUATIONS (IEEE, IEC)
-- ============================================
CREATE TABLE standard_curve_equations (
id UUID PRIMARY KEY,
curve_standard TEXT NOT NULL, -- "IEEE_C37_112", "IEC_60255"
curve_type TEXT NOT NULL, -- "moderately_inverse", "very_inverse", etc.
curve_label TEXT, -- "U1", "U2", "SI", "VI", "EI"
-- IEEE: t = (A / (M^P - 1) + B) * TD
coefficient_a DECIMAL, -- IEEE A
coefficient_b DECIMAL, -- IEEE B
coefficient_p DECIMAL, -- IEEE P exponent
-- IEC: t = (K / (M^α - 1)) * TD
coefficient_k DECIMAL, -- IEC K
coefficient_alpha DECIMAL, -- IEC α
min_multiple DECIMAL, -- min current multiple
max_multiple DECIMAL, -- max current multiple
reset_coefficient DECIMAL, -- for reset time calc
UNIQUE(curve_standard, curve_type)
);
-- ============================================
-- CATALOG CURVE DATA (manufacturer TCC points)
-- ============================================
CREATE TABLE catalog_curve_data (
id UUID PRIMARY KEY,
catalog_id UUID NOT NULL REFERENCES device_catalog(id) ON DELETE CASCADE,
curve_type TEXT NOT NULL, -- "minimum_melt", "total_clear", "long_time",
-- "short_time", "instantaneous", "damage"
curve_name TEXT, -- "Band A", "Setting 0.5"
applies_to_rating DECIMAL, -- fuse amp rating this curve applies to
applies_to_setting TEXT, -- trip unit band/setting
curve_points JSONB NOT NULL, -- [[current_mult, time_sec], ...]
/*
[[1.5, 1000], [2, 100], [3, 20], [6, 4], [10, 1], [20, 0.1]]
*/
current_basis TEXT, -- "ampere_rating", "pickup_setting", "absolute_amps"
min_current_mult DECIMAL,
max_current_mult DECIMAL,
min_time_sec DECIMAL,
max_time_sec DECIMAL,
interpolation TEXT DEFAULT 'log_log', -- "log_log", "log_linear", "linear"
source TEXT, -- "manufacturer_published", "digitized", "tested"
source_document TEXT,
digitized_date DATE,
digitized_by TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_catalog_curve_catalog ON catalog_curve_data(catalog_id);
CREATE INDEX idx_catalog_curve_type ON catalog_curve_data(curve_type);
CREATE INDEX gin_catalog_curve_points ON catalog_curve_data USING GIN(curve_points);
-- ============================================
-- TRIP UNIT CURVE SEGMENTS (LV breaker curve shapes)
-- ============================================
CREATE TABLE trip_unit_curve_segments (
id UUID PRIMARY KEY,
catalog_id UUID NOT NULL REFERENCES device_catalog(id) ON DELETE CASCADE,
segment_type TEXT NOT NULL, -- "long_time", "short_time", "instantaneous", "ground_fault"
applies_to_band TEXT, -- "A", "B", "C", "0.5", "1", etc.
curve_shape TEXT, -- "i2t", "i4t", "definite_time", "vertical", "points"
time_at_1_5x DECIMAL, -- anchor: time at 1.5x pickup
time_at_6x DECIMAL, -- anchor: time at 6x pickup
time_at_transition DECIMAL, -- time at ST/INST transition
operating_time_sec DECIMAL, -- for instantaneous
min_current_mult DECIMAL,
max_current_mult DECIMAL,
tolerance_type TEXT, -- "band", "percent", "fixed"
tolerance_minus_pct DECIMAL,
tolerance_plus_pct DECIMAL,
curve_points JSONB, -- fallback if shape = "points"
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_trip_segment_catalog ON trip_unit_curve_segments(catalog_id);
CREATE INDEX idx_trip_segment_type ON trip_unit_curve_segments(segment_type);
-- ============================================
-- CABLE CATALOG
-- ============================================
CREATE TABLE cable_series (
id UUID PRIMARY KEY,
organization_id UUID, -- null = system default
mfg_name TEXT, -- "Southwire", "General Cable"
series_name TEXT NOT NULL, -- "THHN", "XHHW-2", "MV-105"
conductor_material TEXT, -- "copper", "aluminum"
insulation_type TEXT, -- "THHN", "XHHW", "EPR", "XLPE"
rated_voltage_kv DECIMAL, -- 0.6, 5, 15, 35
temperature_rating_c INTEGER, -- 75, 90, 105
is_system_default BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
description TEXT,
created_by TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE TABLE cable_variants (
id UUID PRIMARY KEY,
cable_series_id UUID NOT NULL REFERENCES cable_series(id) ON DELETE CASCADE,
conductor_size TEXT NOT NULL, -- "12 AWG", "500 kcmil", "4/0"
resistance_per_km DECIMAL, -- ohms/km
reactance_per_km DECIMAL, -- ohms/km
capacitance_per_km DECIMAL, -- μF/km
rated_amperage DECIMAL, -- base ampacity
weight_per_km DECIMAL, -- kg/km
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- BUSES
-- ============================================
CREATE TABLE buses (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "BUS-001", "MCC-1", "SWGR-A"
base_kv DECIMAL NOT NULL, -- 0.48, 4.16, 13.8
rated_amperage DECIMAL, -- 1200, 2000, 3000
bus_type TEXT, -- "swgr", "mcc", "panel", "junction"
is_slack_bus BOOLEAN DEFAULT false,
in_service BOOLEAN DEFAULT true,
-- Bus bracing (for arc flash)
bracing_rating_ka DECIMAL, -- short-time withstand
bracing_duration_cycles INTEGER DEFAULT 30,
-- MCC-specific ratings
main_bus_rating_amps DECIMAL,
vertical_bus_rating_amps DECIMAL,
horizontal_bus_rating_amps DECIMAL,
-- Power flow results
voltage_pu DECIMAL,
voltage_angle_deg DECIMAL,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- CABLES
-- ============================================
CREATE TABLE cables (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "C-001", "F-MCC1-PNL3"
cable_variant_id UUID REFERENCES cable_variants(id),
length_km DECIMAL,
parallel_sets INTEGER DEFAULT 1,
installation_type TEXT, -- "conduit", "tray", "direct_buried", "aerial"
conduit_type TEXT, -- "pvc", "rigid_steel", "emt"
ambient_temp_c DECIMAL,
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- TRANSFORMERS
-- ============================================
CREATE TABLE transformers (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "TX-001", "XFMR-MAIN"
transformer_type TEXT, -- "two_winding", "three_winding", "auto"
rated_kva DECIMAL NOT NULL,
primary_voltage DECIMAL NOT NULL, -- kV
secondary_voltage DECIMAL NOT NULL, -- kV
tertiary_voltage DECIMAL, -- kV (for 3-winding)
impedance_percent DECIMAL, -- 5.75, 6.0
x_over_r_ratio DECIMAL,
vector_group TEXT, -- "Dyn11", "Yy0"
primary_connection TEXT, -- "delta", "wye", "wye_grounded"
secondary_connection TEXT,
tap_position DECIMAL,
tap_min DECIMAL,
tap_max DECIMAL,
tap_step DECIMAL,
cooling_type TEXT, -- "ONAN", "ONAF"
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- GENERATORS
-- ============================================
CREATE TABLE generators (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "GEN-001", "EMERG-GEN"
generator_type TEXT, -- "synchronous", "induction", "inverter"
rated_mva DECIMAL NOT NULL,
rated_voltage DECIMAL NOT NULL, -- kV
power_factor DECIMAL,
-- Impedances
xd DECIMAL, -- synchronous reactance pu
xd_prime DECIMAL, -- transient reactance pu
xd_double_prime DECIMAL, -- subtransient reactance pu
x0 DECIMAL, -- zero sequence
x2 DECIMAL, -- negative sequence
grounding_type TEXT, -- "solid", "resistance", "ungrounded"
grounding_resistance_ohms DECIMAL,
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- UTILITY FEEDS
-- ============================================
CREATE TABLE utility_feeds (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "UTIL-001", "POCO"
voltage_kv DECIMAL NOT NULL,
-- 3-phase fault
input_method TEXT, -- "mva", "impedance", "fault_current"
short_circuit_mva DECIMAL,
xr_ratio DECIMAL,
-- Asymmetric faults
short_circuit_mva_lg DECIMAL, -- line-to-ground
xr_ratio_lg DECIMAL,
short_circuit_mva_ll DECIMAL, -- line-to-line
xr_ratio_ll DECIMAL,
-- Direct impedance input
r1_ohms DECIMAL,
x1_ohms DECIMAL,
r0_ohms DECIMAL,
x0_ohms DECIMAL,
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- LOADS
-- ============================================
CREATE TABLE loads (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "L-001", "HVAC-RTU1", "M-001"
load_type TEXT, -- "static", "motor", "lighting", "receptacle"
-- Power
active_power_kw DECIMAL,
reactive_power_kvar DECIMAL,
power_factor DECIMAL,
rated_voltage DECIMAL, -- kV
-- Motor-specific
motor_hp DECIMAL,
motor_efficiency DECIMAL,
motor_type TEXT, -- "induction", "synchronous", "wound_rotor"
motor_poles INTEGER,
motor_rpm INTEGER,
locked_rotor_current_pct DECIMAL, -- % of FLA
starting_pf DECIMAL,
-- Motor fault contribution
motor_xd_double_prime DECIMAL, -- subtransient pu
motor_x_over_r DECIMAL,
motor_mf DECIMAL, -- multiplying factor per IEEE 551
-- Load factors
demand_factor DECIMAL DEFAULT 1.0,
diversity_factor DECIMAL DEFAULT 1.0,
load_factor DECIMAL DEFAULT 1.0,
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- PROTECTION DEVICES
-- ============================================
CREATE TABLE protection_devices (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "CB-101", "FU-001", "52-M1"
device_type TEXT NOT NULL, -- "circuit_breaker_hv", "circuit_breaker_lv",
-- "fuse", "overload_relay", "protective_relay",
-- "disconnect_switch", "surge_arrester"
rated_voltage DECIMAL, -- kV
rated_current DECIMAL, -- A
interrupting_capacity DECIMAL, -- kA
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- MOTOR CONTROL DEVICES
-- ============================================
CREATE TABLE motor_control_devices (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "VFD-001", "SS-001", "MS-001", "CONT-001"
device_type TEXT NOT NULL, -- "vfd", "soft_starter", "contactor", "motor_starter"
rated_voltage DECIMAL, -- kV
rated_current DECIMAL, -- A (FLA)
rated_hp DECIMAL,
rated_kw DECIMAL,
in_service BOOLEAN DEFAULT true,
position_x DECIMAL NOT NULL,
position_y DECIMAL NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- DEVICE COMPONENTS (links devices to catalog + settings)
-- ============================================
CREATE TABLE device_components (
id UUID PRIMARY KEY,
-- Link to ONE parent device
protection_device_id UUID REFERENCES protection_devices(id) ON DELETE CASCADE,
motor_control_device_id UUID REFERENCES motor_control_devices(id) ON DELETE CASCADE,
CHECK (
(protection_device_id IS NOT NULL AND motor_control_device_id IS NULL) OR
(protection_device_id IS NULL AND motor_control_device_id IS NOT NULL)
),
catalog_id UUID REFERENCES device_catalog(id),
ct_id UUID REFERENCES current_transformers(id), -- shared CT (null for built-in sensing)
component_role TEXT NOT NULL, -- "trip_unit", "breaker", "contactor", "overload",
-- "drive_unit", "built_in_overload", "relay", "fuse"
-- Overflow settings not in dedicated tables
settings JSONB,
setting_type TEXT DEFAULT 'current', -- "current", "as_found", "as_recommended"
notes TEXT,
created_by TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_device_comp_prot ON device_components(protection_device_id);
CREATE INDEX idx_device_comp_motor ON device_components(motor_control_device_id);
CREATE INDEX gin_device_comp_settings ON device_components USING GIN(settings);
-- ============================================
-- TRIP UNIT SETTINGS (LV breaker electronic trip)
-- ============================================
CREATE TABLE trip_unit_settings (
id UUID PRIMARY KEY,
device_component_id UUID NOT NULL UNIQUE REFERENCES device_components(id) ON DELETE CASCADE,
-- Frame & sensor
frame_amps DECIMAL, -- 250, 400, 800
sensor_amps DECIMAL, -- 200, 400
-- Long time
lt_pickup_amps DECIMAL,
lt_pickup_pct DECIMAL, -- % of sensor
lt_delay_band TEXT, -- "A", "B", "C", "1", "2"
lt_delay_seconds DECIMAL,
lt_i2t_enabled BOOLEAN DEFAULT true,
-- Short time
st_pickup_mult DECIMAL, -- multiple of LT pickup
st_pickup_amps DECIMAL,
st_delay_band TEXT,
st_delay_seconds DECIMAL,
st_i2t_enabled BOOLEAN DEFAULT false,
-- Instantaneous
inst_enabled BOOLEAN DEFAULT true,
inst_pickup_mult DECIMAL,
inst_pickup_amps DECIMAL,
inst_override_ka DECIMAL, -- high fault override
-- Ground fault
gf_enabled BOOLEAN DEFAULT false,
gf_pickup_amps DECIMAL,
gf_pickup_pct DECIMAL,
gf_delay_band TEXT,
gf_delay_seconds DECIMAL,
gf_i2t_enabled BOOLEAN DEFAULT false,
-- ZSI
zsi_enabled BOOLEAN DEFAULT false,
zsi_mode TEXT, -- "blocking", "permissive"
-- Misc
maintenance_mode_enabled BOOLEAN DEFAULT false,
curve_family TEXT, -- manufacturer curve ID
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now(),
CHECK (lt_pickup_amps IS NOT NULL OR lt_pickup_pct IS NOT NULL)
);
-- ============================================
-- HV BREAKER SETTINGS
-- ============================================
CREATE TABLE hv_breaker_settings (
id UUID PRIMARY KEY,
device_component_id UUID NOT NULL UNIQUE REFERENCES device_components(id) ON DELETE CASCADE,
breaker_type TEXT, -- "vacuum", "sf6", "oil", "air_magnetic"
rated_voltage_kv DECIMAL,
rated_current_amps DECIMAL,
interrupting_rating_ka DECIMAL,
close_and_latch_ka DECIMAL,
-- Operating times
opening_time_cycles DECIMAL,
arcing_time_cycles DECIMAL,
total_clearing_time_cycles DECIMAL,
relay_trip_time_cycles DECIMAL,
-- Reclosing
reclose_enabled BOOLEAN DEFAULT false,
reclose_time_cycles DECIMAL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- FUSE SETTINGS
-- ============================================
CREATE TABLE fuse_settings (
id UUID PRIMARY KEY,
device_component_id UUID NOT NULL UNIQUE REFERENCES device_components(id) ON DELETE CASCADE,
fuse_class TEXT, -- "J", "RK1", "RK5", "L", "T", "CC"
fuse_type TEXT, -- "dual_element", "single_element", "current_limiting"
voltage_rating DECIMAL,
ampere_rating DECIMAL NOT NULL,
interrupting_rating_ka DECIMAL,
-- Let-through data
let_through_i2t DECIMAL,
peak_let_through_ip DECIMAL,
-- Manufacturer reference
manufacturer TEXT,
catalog_series TEXT,
-- Quick coordination check points
time_at_10x_min_melt DECIMAL,
time_at_10x_total_clear DECIMAL,
time_at_100x_min_melt DECIMAL,
time_at_100x_total_clear DECIMAL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- RELAY OVERCURRENT SETTINGS
-- ============================================
CREATE TABLE relay_oc_settings (
id UUID PRIMARY KEY,
device_component_id UUID NOT NULL UNIQUE REFERENCES device_components(id) ON DELETE CASCADE,
-- CT configuration
ct_primary DECIMAL, -- 400
ct_secondary DECIMAL DEFAULT 5, -- 5
ct_ratio_text TEXT, -- "400:5"
ct_accuracy_class TEXT, -- "C200", "C400", "C800"
ct_saturation_voltage DECIMAL,
-- Phase 51 (time overcurrent)
phase_51_enabled BOOLEAN DEFAULT true,
phase_51_pickup_amps_sec DECIMAL, -- secondary amps
phase_51_pickup_amps_pri DECIMAL, -- primary amps (calculated)
phase_51_curve_standard TEXT, -- "IEEE_C37_112", "IEC_60255"
phase_51_curve_type TEXT, -- "very_inverse", "extremely_inverse"
phase_51_time_dial DECIMAL,
phase_51_time_adder DECIMAL,
phase_51_reset_type TEXT, -- "instantaneous", "timed"
-- Phase 50 (instantaneous)
phase_50_enabled BOOLEAN DEFAULT true,
phase_50_pickup_amps_sec DECIMAL,
phase_50_pickup_amps_pri DECIMAL,
phase_50_delay_cycles DECIMAL,
-- Ground 51G
ground_51_enabled BOOLEAN DEFAULT false,
ground_51_pickup_amps_sec DECIMAL,
ground_51_pickup_amps_pri DECIMAL,
ground_51_curve_standard TEXT,
ground_51_curve_type TEXT,
ground_51_time_dial DECIMAL,
-- Ground 50G
ground_50_enabled BOOLEAN DEFAULT false,
ground_50_pickup_amps_sec DECIMAL,
ground_50_pickup_amps_pri DECIMAL,
ground_50_delay_cycles DECIMAL,
-- Negative sequence 46
neg_seq_enabled BOOLEAN DEFAULT false,
neg_seq_pickup_pct DECIMAL,
neg_seq_time_dial DECIMAL,
-- Directional
directional_enabled BOOLEAN DEFAULT false,
directional_mta DECIMAL, -- max torque angle
-- Reclosing
reclosing_enabled BOOLEAN DEFAULT false,
reclosing_shots INTEGER,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- OVERLOAD RELAY SETTINGS
-- ============================================
CREATE TABLE overload_relay_settings (
id UUID PRIMARY KEY,
device_component_id UUID NOT NULL UNIQUE REFERENCES device_components(id) ON DELETE CASCADE,
overload_type TEXT, -- "bimetal", "eutectic", "electronic", "solid_state"
fla_range_min DECIMAL,
fla_range_max DECIMAL,
current_setting DECIMAL, -- actual dial setting
trip_class TEXT, -- "10", "20", "30", "10A", "20A"
trip_time_at_6x DECIMAL, -- seconds at 6x FLA
-- Electronic overload features
phase_loss_enabled BOOLEAN,
phase_unbalance_enabled BOOLEAN,
unbalance_trip_pct DECIMAL,
ground_fault_enabled BOOLEAN,
gf_pickup_amps DECIMAL,
jam_protection_enabled BOOLEAN,
jam_current_pct DECIMAL,
jam_time_seconds DECIMAL,
-- Thermal memory
thermal_memory_enabled BOOLEAN DEFAULT true,
thermal_memory_tc DECIMAL, -- time constant seconds
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- CURRENT TRANSFORMERS (shared across devices)
-- ============================================
CREATE TABLE current_transformers (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "CT-001", "MAIN-CT"
ct_ratio_primary DECIMAL NOT NULL, -- 400
ct_ratio_secondary DECIMAL DEFAULT 5, -- 5
accuracy_class TEXT, -- "C200", "C400", "C800"
burden_va DECIMAL,
saturation_voltage DECIMAL,
ct_type TEXT, -- "window", "bushing", "bar"
polarity_marking TEXT,
ct_application TEXT, -- "protection", "metering", "dual"
knee_point_voltage DECIMAL,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_ct_project ON current_transformers(project_id);
-- ============================================
-- DEVICE CURVE OVERRIDES (instance-level curve mods)
-- ============================================
CREATE TABLE device_curve_overrides (
id UUID PRIMARY KEY,
device_component_id UUID NOT NULL REFERENCES device_components(id) ON DELETE CASCADE,
base_catalog_curve_id UUID REFERENCES catalog_curve_data(id),
curve_type TEXT,
curve_name TEXT,
override_type TEXT, -- "replace", "shift_time", "shift_current"
curve_points JSONB, -- replacement points
time_multiplier DECIMAL, -- shift factor
current_multiplier DECIMAL, -- shift factor
reason TEXT, -- "field_tested", "manufacturer_correction"
notes TEXT,
created_by TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_curve_override_comp ON device_curve_overrides(device_component_id);
CREATE INDEX gin_curve_override_points ON device_curve_overrides USING GIN(curve_points);
-- ============================================
-- ARC FLASH PARAMETERS
-- ============================================
CREATE TABLE arc_flash_parameters (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
component_id UUID NOT NULL,
component_type TEXT NOT NULL, -- "bus", "mcc", "panel"
working_distance_mm DECIMAL, -- 455, 610, 910
electrode_config TEXT, -- "VCB", "VCBB", "HCB", "HOA", "VOA"
enclosure_height_mm DECIMAL,
enclosure_width_mm DECIMAL,
enclosure_depth_mm DECIMAL,
gap_mm DECIMAL,
calc_method TEXT, -- "IEEE_1584_2018", "IEEE_1584_2002", "NFPA_70E_table"
-- Override upstream device
upstream_device_id UUID,
upstream_device_type TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_arc_flash_project ON arc_flash_parameters(project_id);
CREATE INDEX idx_arc_flash_component ON arc_flash_parameters(component_id, component_type);
-- ============================================
-- CONNECTIONS (edges between devices)
-- ============================================
CREATE TABLE component_connections (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
source_component_id UUID NOT NULL,
source_component_type TEXT NOT NULL, -- "bus", "cable", "transformer", etc.
source_handle_side TEXT,
source_handle_index INTEGER DEFAULT 0,
target_component_id UUID NOT NULL,
target_component_type TEXT NOT NULL,
target_handle_side TEXT,
target_handle_index INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- CANVAS STATE
-- ============================================
CREATE TABLE canvas_state (
id UUID PRIMARY KEY,
project_id UUID NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE,
viewport_x DECIMAL DEFAULT 0,
viewport_y DECIMAL DEFAULT 0,
zoom_level DECIMAL DEFAULT 1,
grid_enabled BOOLEAN DEFAULT true,
snap_to_grid BOOLEAN DEFAULT true,
grid_size INTEGER DEFAULT 50,
auto_connect_enabled BOOLEAN DEFAULT true,
updated_at TIMESTAMP DEFAULT now()
);
-- ============================================
-- STUDY RUNS (calculation input parameters)
-- ============================================
CREATE TABLE study_runs (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
scenario_id UUID REFERENCES scenarios(id) ON DELETE CASCADE,
study_type TEXT NOT NULL, -- "power_flow", "short_circuit", "arc_flash"
parameters JSONB NOT NULL,
/*
power_flow: {
algorithm: "newton_raphson",
max_iterations: 100,
tolerance: 0.0001,
enforce_q_limits: true
}
short_circuit: {
standard: "IEEE_551",
calculation_type: "momentary",
include_motor_contribution: true,
prefault_voltage_pu: 1.0
}
arc_flash: {
standard: "IEEE_1584_2018",
working_distance_mm: 455,
electrode_configuration: "VCB"
}
*/
status TEXT DEFAULT 'pending', -- "pending", "running", "completed", "failed"
error_message TEXT,
run_at TIMESTAMP DEFAULT now(),
completed_at TIMESTAMP,
run_by TEXT,
created_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_study_runs_project ON study_runs(project_id);
CREATE INDEX idx_study_runs_scenario ON study_runs(scenario_id);
CREATE INDEX idx_study_runs_type ON study_runs(study_type);
-- ============================================
-- CALCULATION RESULTS
-- ============================================
CREATE TABLE calculation_results (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
scenario_id UUID REFERENCES scenarios(id) ON DELETE CASCADE,
study_run_id UUID REFERENCES study_runs(id) ON DELETE CASCADE,
component_id UUID NOT NULL,
component_type TEXT NOT NULL,
calculation_type TEXT NOT NULL, -- "power_flow", "short_circuit", "arc_flash"
results JSONB NOT NULL,
/*
power_flow: {voltage_pu: 0.98, loading_pct: 75, losses_kw: 2.5}
short_circuit: {i_3ph_ka: 25.4, i_lg_ka: 18.2, x_r_ratio: 12}
arc_flash: {incident_energy: 4.2, arc_duration: 0.35, ppe_level: 2}
*/
calculated_at TIMESTAMP DEFAULT now(),
calculated_by TEXT
);
CREATE INDEX idx_calc_results_project ON calculation_results(project_id);
CREATE INDEX idx_calc_results_scenario ON calculation_results(scenario_id);
CREATE INDEX idx_calc_results_study_run ON calculation_results(study_run_id);
CREATE INDEX gin_calc_results ON calculation_results USING GIN(results);Task List
Phase 0: Preparation
- [ ] 0.1 Backup current database
- [ ] 0.2 Document current schema state
- [ ] 0.3 Create feature branch for schema refactor
- [ ] 0.4 Review existing data to plan migrations
Phase 1: Core Schema Foundation
1.1 Project-Level Settings
- [ ] 1.1.1 Create project_settings table
- [ ] 1.1.2 Add FK to projects with ON DELETE CASCADE
- [ ] 1.1.3 Add unique constraint on project_id
- [ ] 1.1.4 Create Drizzle schema file
- [ ] 1.1.5 Generate migration
1.2 Scenario Management
- [ ] 1.2.1 Create scenarios table
- [ ] 1.2.2 Add parent_scenario_id self-referential FK
- [ ] 1.2.3 Add is_baseline boolean
- [ ] 1.2.4 Add unique constraint on (project_id, name)
- [ ] 1.2.5 Create Drizzle schema file
- [ ] 1.2.6 Generate migration
1.3 Scenario Component Overrides
- [ ] 1.3.1 Create scenario_component_overrides table
- [ ] 1.3.2 Add scenario_id FK with ON DELETE CASCADE
- [ ] 1.3.3 Add unique constraint on (scenario_id, component_id, component_type)
- [ ] 1.3.4 Create indexes (scenario_id, component_id/type)
- [ ] 1.3.5 Create Drizzle schema file
- [ ] 1.3.6 Generate migration
1.4 Study Runs
- [ ] 1.4.1 Create study_runs table
- [ ] 1.4.2 Add project_id, scenario_id FKs
- [ ] 1.4.3 Add parameters JSONB with example structures
- [ ] 1.4.4 Add status, error_message columns
- [ ] 1.4.5 Create indexes (project, scenario, type)
- [ ] 1.4.6 Add study_run_id FK to calculation_results
- [ ] 1.4.7 Create Drizzle schema file
- [ ] 1.4.8 Generate migration
1.5 Equipment In-Service Status
- [ ] 1.3.1 Add in_service to buses
- [ ] 1.3.2 Add in_service to cables
- [ ] 1.3.3 Add in_service to transformers
- [ ] 1.3.4 Add in_service to generators
- [ ] 1.3.5 Add in_service to utility_feeds
- [ ] 1.3.6 Add in_service to loads
- [ ] 1.3.7 Add in_service to protection_devices
- [ ] 1.3.8 Add in_service to motor_control_devices
- [ ] 1.3.9 Generate migration
Phase 2: Cable Catalog Enhancements
2.1 Organization Scoping
- [ ] 2.1.1 Add organization_id to cable_series (nullable)
- [ ] 2.1.2 Add is_system_default boolean
- [ ] 2.1.3 Add is_active boolean
- [ ] 2.1.4 Add created_by column
- [ ] 2.1.5 Add created_at/updated_at columns
2.2 Cable Variant Enhancements
- [ ] 2.2.1 Add is_active boolean
- [ ] 2.2.2 Add created_at/updated_at columns
- [ ] 2.2.3 Generate migration
Phase 3: Bus Enhancements
- [ ] 3.1 Add bracing_rating_ka
- [ ] 3.2 Add bracing_duration_cycles
- [ ] 3.3 Add main_bus_rating_amps
- [ ] 3.4 Add vertical_bus_rating_amps
- [ ] 3.5 Add horizontal_bus_rating_amps
- [ ] 3.6 Generate migration
Phase 4: Motor Fault Contribution
4.1 Motor Impedance Data
- [ ] 4.1.1 Add motor_xd_double_prime to loads
- [ ] 4.1.2 Add motor_x_over_r
- [ ] 4.1.3 Add motor_mf (IEEE 551 multiplying factor)
- [ ] 4.1.4 Add motor_poles
- [ ] 4.1.5 Add motor_rpm
- [ ] 4.1.6 Add motor_type enum
4.2 Load Diversity
- [ ] 4.2.1 Add demand_factor
- [ ] 4.2.2 Add diversity_factor
- [ ] 4.2.3 Add load_factor
- [ ] 4.2.4 Generate migration
Phase 5: Utility Feed Enhancements
- [ ] 5.1 Add short_circuit_mva_lg
- [ ] 5.2 Add xr_ratio_lg
- [ ] 5.3 Add short_circuit_mva_ll
- [ ] 5.4 Add xr_ratio_ll
- [ ] 5.5 Generate migration
Phase 6: Standard Curve Equations
- [ ] 6.1.1 Create standard_curve_equations table
- [ ] 6.1.2 Add IEEE coefficients (A, B, P)
- [ ] 6.1.3 Add IEC coefficients (K, α)
- [ ] 6.1.4 Add min/max multiple columns
- [ ] 6.1.5 Add unique constraint
- [ ] 6.2.1 Seed IEEE moderately_inverse (A=0.0515, B=0.114, P=0.02)
- [ ] 6.2.2 Seed IEEE very_inverse (A=19.61, B=0.491, P=2.0)
- [ ] 6.2.3 Seed IEEE extremely_inverse (A=28.2, B=0.1217, P=2.0)
- [ ] 6.2.4 Seed IEEE short_time_inverse
- [ ] 6.2.5 Seed IEEE long_time_inverse
- [ ] 6.3.1 Seed IEC standard_inverse (K=0.14, α=0.02)
- [ ] 6.3.2 Seed IEC very_inverse (K=13.5, α=1.0)
- [ ] 6.3.3 Seed IEC extremely_inverse (K=80.0, α=2.0)
- [ ] 6.3.4 Seed IEC long_time_inverse (K=120.0, α=1.0)
- [ ] 6.4 Generate migration and seed script
Phase 7: Catalog Curve Data
- [ ] 7.1 Create catalog_curve_data table
- [ ] 7.2 Add FK to device_catalog
- [ ] 7.3 Add curve_type, curve_name, applies_to columns
- [ ] 7.4 Add curve_points JSONB
- [ ] 7.5 Add interpolation, source columns
- [ ] 7.6 Create indexes
- [ ] 7.7 Create GIN index on curve_points
- [ ] 7.8 Generate migration
Phase 8: Trip Unit Curve Segments
- [ ] 8.1 Create trip_unit_curve_segments table
- [ ] 8.2 Add segment_type, applies_to_band
- [ ] 8.3 Add curve_shape, anchor time columns
- [ ] 8.4 Add tolerance columns
- [ ] 8.5 Create indexes
- [ ] 8.6 Generate migration
Phase 9: Device Curve Overrides
- [ ] 9.1 Create device_curve_overrides table
- [ ] 9.2 Add FK to device_components
- [ ] 9.3 Add override_type, multipliers
- [ ] 9.4 Add reason, notes
- [ ] 9.5 Create indexes
- [ ] 9.6 Generate migration
Phase 10: Trip Unit Settings Table
- [ ] 10.1 Create trip_unit_settings table
- [ ] 10.2 Add device_component_id FK + unique constraint
- [ ] 10.3 Add frame/sensor columns
- [ ] 10.4 Add long_time columns (pickup, delay, i2t)
- [ ] 10.5 Add short_time columns
- [ ] 10.6 Add instantaneous columns
- [ ] 10.7 Add ground_fault columns
- [ ] 10.8 Add ZSI columns
- [ ] 10.9 Add maintenance_mode, curve_family
- [ ] 10.10 Add CHECK constraint for pickup
- [ ] 10.11 Generate migration
Phase 11: Fuse Settings Table
- [ ] 11.1 Create fuse_settings table
- [ ] 11.2 Add device_component_id FK + unique constraint
- [ ] 11.3 Add classification columns (class, type)
- [ ] 11.4 Add rating columns
- [ ] 11.5 Add let-through columns
- [ ] 11.6 Add quick coordination check columns
- [ ] 11.7 Generate migration
Phase 12: Relay Overcurrent Settings Table
- [ ] 12.1 Create relay_oc_settings table
- [ ] 12.2 Add device_component_id FK + unique constraint
- [ ] 12.3 Add CT configuration columns
- [ ] 12.4 Add phase 51 columns
- [ ] 12.5 Add phase 50 columns
- [ ] 12.6 Add ground 51G columns
- [ ] 12.7 Add ground 50G columns
- [ ] 12.8 Add negative sequence 46 columns
- [ ] 12.9 Add directional columns
- [ ] 12.10 Add reclosing columns
- [ ] 12.11 Generate migration
Phase 13: Overload Relay Settings Table
- [ ] 13.1 Create overload_relay_settings table
- [ ] 13.2 Add device_component_id FK + unique constraint
- [ ] 13.3 Add overload_type, FLA range
- [ ] 13.4 Add trip_class, trip_time_at_6x
- [ ] 13.5 Add electronic overload feature columns
- [ ] 13.6 Add thermal memory columns
- [ ] 13.7 Generate migration
Phase 14: HV Breaker Settings Table
- [ ] 14.1 Create hv_breaker_settings table
- [ ] 14.2 Add device_component_id FK + unique constraint
- [ ] 14.3 Add breaker hardware columns
- [ ] 14.4 Add operating time columns
- [ ] 14.5 Add reclosing columns
- [ ] 14.6 Generate migration
Phase 15: Arc Flash Parameters Table
- [ ] 15.1 Create arc_flash_parameters table
- [ ] 15.2 Add project FK
- [ ] 15.3 Add component_id/type columns
- [ ] 15.4 Add working distance, electrode config
- [ ] 15.5 Add enclosure dimensions
- [ ] 15.6 Add calc_method
- [ ] 15.7 Add upstream device override columns
- [ ] 15.8 Create indexes
- [ ] 15.9 Generate migration
Phase 16: CT Data Table (Project-Scoped, Shared)
- [ ] 16.1 Create current_transformers table with project_id FK
- [ ] 16.2 Add name column for identification
- [ ] 16.3 Add rating columns (ratio, accuracy_class, burden)
- [ ] 16.4 Add physical columns (type, polarity)
- [ ] 16.5 Add application columns (protection/metering/dual, knee_point)
- [ ] 16.6 Add ct_id FK to device_components (nullable, for shared CT)
- [ ] 16.7 Create index on project_id
- [ ] 16.8 Generate migration
Phase 17: Contingency Cases Table
- [ ] 17.1 Create contingency_cases table
- [ ] 17.2 Add scenario FK
- [ ] 17.3 Add outage columns
- [ ] 17.4 Generate migration
Phase 18: Motor Control Devices Table (NEW)
- [ ] 18.1 Create motor_control_devices table
- [ ] 18.2 Add device_type enum (vfd, soft_starter, contactor, motor_starter)
- [ ] 18.3 Add rating columns
- [ ] 18.4 Add in_service, position columns
- [ ] 18.5 Generate migration
Phase 19: Device Components Table Update
- [ ] 19.1 Add motor_control_device_id FK (nullable)
- [ ] 19.2 Add CHECK constraint for exactly one FK
- [ ] 19.3 Create indexes
- [ ] 19.4 Generate migration
Phase 20: Rename protection_catalog to device_catalog
- [ ] 20.1 Rename table
- [ ] 20.2 Add new device_category values (vfd, soft_starter, contactor, etc.)
- [ ] 20.3 Update all FKs
- [ ] 20.4 Generate migration
Phase 21: GIN Indexes for JSONB
- [ ] 21.1 Create GIN index on device_catalog.configuration
- [ ] 21.2 Create GIN index on device_components.settings
- [ ] 21.3 Create GIN index on calculation_results.results
- [ ] 21.4 Create GIN index on catalog_curve_data.curve_points
- [ ] 21.5 Create GIN index on device_curve_overrides.curve_points
- [ ] 21.6 Generate migration
Phase 22: Foreign Key Cleanup
- [ ] 22.1 Add FK from buses.project_id to projects
- [ ] 22.2 Add FK from cables.project_id to projects
- [ ] 22.3 Add FK from transformers.project_id to projects
- [ ] 22.4 Add FK from generators.project_id to projects
- [ ] 22.5 Add FK from utility_feeds.project_id to projects
- [ ] 22.6 Add FK from loads.project_id to projects
- [ ] 22.7 Add FK from protection_devices.project_id to projects
- [ ] 22.8 Add FK from motor_control_devices.project_id to projects
- [ ] 22.9 Add FK from component_connections.project_id to projects
- [ ] 22.10 Add FK from calculation_results.project_id to projects
- [ ] 22.11 Add FK from arc_flash_parameters.project_id to projects
- [ ] 22.12 Review and set ON DELETE CASCADE where appropriate
- [ ] 22.13 Generate migration
Phase 23: Documentation
- [ ] 23.1 Update src/db/README.md with new tables
Summary
Scope: Backend schema only (Drizzle schema files, migrations, seed scripts). Frontend components, server actions, and tests deferred to application layer refactor.
New tables: 17
- project_settings, scenarios, contingency_cases, scenario_component_overrides
- study_runs, standard_curve_equations, catalog_curve_data, trip_unit_curve_segments
- motor_control_devices, trip_unit_settings, hv_breaker_settings, fuse_settings
- relay_oc_settings, overload_relay_settings, current_transformers
- device_curve_overrides, arc_flash_parameters
Modified tables: 9
- buses, cables, transformers, generators, utility_feeds, loads
- protection_devices, calculation_results, device_components
Renamed tables: 1
- protection_catalog → device_catalog
Design note: VFD/soft starter settings use device_components with child components for built-in protection (role="built_in_overload" → overload_relay_settings). Drive-specific settings (ramp times, bypass, control mode) stored in device_components.settings JSONB.
Phases: 23 (0-22 + documentation) Total tasks: ~130
Implementation Progress (AI Handoff Notes)
Last updated: 2026-01-24
Completed
- Phase 1: Core Schema Files ✅
- Created:
project-settings-schema.ts,scenarios-schema.ts,contingency-cases-schema.ts,scenario-component-overrides-schema.ts,study-runs-schema.ts - Added to
drizzle.config.tsandsrc/db/schema/index.ts
- Phase 2: in_service Columns ✅
- Added
in_serviceboolean to: buses, cables, transformers, generators, utility_feeds, loads - Also added related columns per plan (bracing for buses, tap settings for transformers, grounding for generators, asymmetric fault data for utility_feeds, motor fault contribution for loads)
- Phase 3: Curve and Settings Tables ✅
- Created:
standard-curve-equations-schema.ts,catalog-curve-data-schema.ts,trip-unit-curve-segments-schema.ts - Created:
device-components-schema.ts(NEW table replacing protection_settings) - Created:
trip-unit-settings-schema.ts,fuse-settings-schema.ts,relay-oc-settings-schema.ts,overload-relay-settings-schema.ts,hv-breaker-settings-schema.ts - Created:
device-curve-overrides-schema.ts
- Phase 4: Protection Devices Refactor ✅
- Simplified
protection-devices-schema.ts- removed 15+ deprecated columns (manufacturer, settings, coordination, maintenance, etc.) - Added
in_serviceandoverload_relayto device type enum - Created:
motor-control-devices-schema.ts,current-transformers-schema.ts
- Phase 5: Device Components ✅
- Deleted
protection-settings-schema.ts(table was empty) device-components-schema.tsnow has proper FKs to motor_control_devices, current_transformers, protection_devices, device_catalog- Created:
arc-flash-parameters-schema.ts - Updated
calculation-results-schema.tswith scenario_id, study_run_id, and JSONB results
- Phase 6: Rename protection_catalog → device_catalog ✅
- Created
device-catalog-schema.tswith: - Renamed table from "protection_catalog" to "device_catalog"
- New exports (deviceCatalogTable, InsertDeviceCatalog, SelectDeviceCatalog, etc.)
- Backward-compatible re-exports for protectionCatalogTable
- New device_category values for motor control: vfd, soft_starter, contactor, motor_starter
- TypeScript interfaces for all config types (VFDConfig, SoftStarterConfig, etc.)
- Updated RLS policies in
rls-policies.ts: - Renamed to canViewDeviceCatalog, canCreateDeviceCatalog, canModifyDeviceCatalog
- Added backward compatibility aliases
- Updated importing files:
device-components-schema.tscatalog-curve-data-schema.tstrip-unit-curve-segments-schema.tsdb.ts- Created
device-catalog-actions.ts(renamed from protection-catalog-actions.ts) - Deleted old files:
protection-catalog-schema.ts,protection-catalog-actions.ts - Updated
drizzle.config.tsandindex.ts
- Phase 7: Server Actions ✅
- Deleted
protection-settings-actions.ts(table was removed) - Updated
use-protection-settings.tshook: - Removed broken useProtectionSettings hook
- Renamed catalog hooks to useDeviceCatalog with backward-compat alias
- Updated
protection-settings-section.tsxto show migration notice - Created new action files:
motor-control-devices-actions.ts(uses factory pattern)scenarios-actions.ts(custom, handles baseline flag and unique names)study-runs-actions.ts(custom, handles status updates)current-transformers-actions.ts(custom CRUD)arc-flash-parameters-actions.ts(custom, includes upsert)device-components-actions.ts(custom, handles dual parent FK pattern)- Updated
component-connections-actions.tsto include motor_control_devices in: - Import statement
getComponentTypefunctioncleanupOrphanConnectionsActionfunction
- Phase 8: Generate Migration ✅
- Migration generated:
drizzle/0010_marvelous_yellowjacket.sql(38KB, 723 lines) - Interactive prompts answered:
device_catalog: selected "rename from protection_catalog"project_settings: selected "create table" (new table, NOT rename from protection_settings)- Migration reviewed and verified correct
- Phase 9: Apply Migration to Database ✅
- Migration applied successfully:
drizzle/0010_marvelous_yellowjacket.sql - Fixed migration file: Added
USING results::jsonbclause to handle text→jsonb conversion - PostgreSQL notices about truncated identifier names are benign
- Phase 10: Fix TypeScript Errors ✅
- Fixed
calculation-results-actions.ts: updatedAt→calculatedAt(column renamed in schema)- Removed
createdByfrom insert (now has default) - Fixed
power-flow-actions.tsandshort-circuit-actions.ts: - Changed
results: JSON.stringify({...})→results: {...}(JSONB column takes objects directly) - Fixed
action-factory.ts: calculationResultsTable.updatedAt→calculatedAt- Fixed
device-components-actions.ts: - Changed type assertions to
as unknown as SelectDeviceComponent[]for relations queries - Fixed
debug-copy-button.tsx: - Updated
CalculationDebugInfointerface:updatedAt→calculatedAt - Fixed
utility-node.tsx: - Added all new utility_feeds columns with null fallbacks
- Added
inServicefield with default true
Verification Status
- ✅
pnpm lintpasses (only 2 pre-existing warnings) - ✅
pnpm tsc --noEmitpasses (0 errors) - ✅ Migration applied successfully
- ✅ All new schema files follow existing patterns
Schema Refactor Complete
The electrical schema refactor is now COMPLETE. All Drizzle schema files have been created/updated, the migration has been applied, and all TypeScript errors have been resolved.
Important Notes
- Circular Reference Fix:
scenarios-schema.tshad a circular type reference inparentScenarioId. Fixed by removing the typed return from the FK reference. The self-reference still works via Drizzle relations.
- RLS Strategy: Many new tables (contingency_cases, settings tables, etc.) intentionally have NO RLS policies - they rely on FK cascade from parent tables that DO have RLS.
- Backward Compatibility: Deprecated re-exports exist throughout for gradual migration:
protectionCatalogTable→deviceCatalogTableuseProtectionCatalog→useDeviceCatalog- RLS policy aliases in
rls-policies.ts
- JSONB Results Column:
calculation_results.resultsis now typed asCalculationResultData(JSONB), not string. Calculation actions now pass objects directly instead of JSON.stringify.
- Migration Applied: The migration has been applied to the dev database. If deploying to production, the migration will DROP
protection_settingstable and remove 17+ columns fromprotection_devices.