← Documentation Index

OpenCable — Electrical Analysis Reference

Complete reference for all automated electrical analysis functions — equations, triggers, thresholds, and problem codes. Current as of June 17, 2026 (v26.1).

Table of Contents

  1. 1. Analysis Overview
  2. 2. Voltage Drop Analysis
  3. 3. Ampacity Derating
  4. 4. Cable Aging Analysis
  5. 5. Raceway Fill Analysis
  6. 6. Tray Weight Analysis
  7. 7. Route Integrity Checks
  8. 8. SL / SG Mismatch Checks
  9. 9. Helper Functions
  10. 10. Trigger Summary
  11. 11. Cable Problem Code Reference
  12. 12. Constants & Defaults
  13. 13. Bus & Loadflow Analysis — Overview
  14. 14. Bus Power Calculations
  15. 15. Transformer Utilization
  16. 16. Power Factor Check
  17. 17. Phase Balance Check
  18. 18. Bus Problem Code Reference
  19. 19. Bus Ties
  20. 20. Transfer Switch (ATS) Modeling

1. Analysis Overview

SMECO runs two categories of analysis that write to the cable_problems and raceway_problems tables:

CategorySource FileWhen It RunsWhat It Checks
Electrical Analysis src/utils/cable-analysis.js User-initiated: Cable Analysis screen → Run button Voltage drop, ampacity derating, cable aging, missing load data
Route Integrity src/utils/cable-integrity.js Automatic: fires on every relevant save/delete operation Broken links, missing segments, SL/SG mismatches, raceway fill, tray weight
ℹ️ Problems persist until cleared. Electrical analysis problems (voltage drop, derating, aging) are cleared and re-evaluated each time a full analysis run is triggered. Route integrity problems are cleared and re-posted reactively — e.g. deleting a raceway immediately posts MISSING_SEGMENT on affected cables.

2. Voltage Drop Analysis

Function: calcVoltageDrop()

src/utils/cable-analysis.js  ·  Called by runCableAnalysis()

What it calculates

Resistive voltage drop along a cable conductor under load, using the standard NEC transmission-line approximation.

Equations

Single-Phase
Vdrop = 2 × I × (L / 1000) × (R·cosΦ + X·sinΦ)
Three-Phase
Vdrop = √3 × I × (L / 1000) × (R·cosΦ + X·sinΦ)
Percentage
Vdrop% = (Vdrop / Vsystem) × 100

Where:

SymbolDescriptionSource
ILoad current (A)resolveDesignAmps() cascade
LCable total length (ft)cable_register.total_length_ft
RAC resistance at 75°C (Ω/1000 ft)cable_types.resistance_ac
XReactance (Ω/1000 ft)cable_types.reactance (default 0)
cosΦPower factorcable_register.power_factor → load → default 0.85
sinΦ√(1 − cos²Φ)Computed
VsystemSystem operating voltage (V)resolveSystemVoltage() cascade

Acceptance Criteria

StatusConditionReferenceProblem Posted
OKVdrop% ≤ 3.0%NEC recommendationNone
WARN3.0% < Vdrop% ≤ 5.0%NEC 210.19(A) informational noteVOLTAGE_DROP_WARN
CRITICALVdrop% > 5.0%NEC 215.2(A) practical limitVOLTAGE_DROP_CRIT

Problem Detail Format

Voltage drop 4.2% (10.1V) exceeds 3% recommendation — 25A × 480ft @ 240V single-phase (source: load_linked)

Design Current Resolution Cascade

The function resolveDesignAmps() resolves load current in this priority order:

  1. cable_register.design_amps — explicit override on the cable record (highest priority)
  2. loads.design_amps — override on the linked load via loadflow linker
  3. Computed from load rating — using load.rating_val, rating_unit, power_factor, efficiency, and bus voltage:
    Three-phase: I = (kW × 1000) / (√3 × V × PF × eff)
    Single-phase: I = (kW × 1000) / (V × PF × eff)
  4. null — not resolvable → posts MISSING_LOAD_DATA

Unit Conversions (rating_unit)

UnitConversion to kW
kWDirect (no conversion)
HP× 0.7457
kVADirect (assumes unity PF in rating)
ANot convertible — skipped (use design_amps override)

System Voltage Resolution

  1. buses.voltage_volts via loadflow link → load → bus
  2. cable_types.voltage_rating as fallback
  3. null — voltage drop not calculable if absent

3. Ampacity Derating

Function: calcDeratedAmpacity()

src/utils/cable-analysis.js  ·  Called by runCableAnalysis()

What it calculates

The effective cable ampacity after applying three NEC derating factors. If the actual load current exceeds this derated value, the cable is overloaded.

Equation

Derated Ampacity
Iderated = Ibase × Ftemp × Fgroup × Faging
FactorSymbolSourceReference
Base ampacityIbasecable_types.ampacityManufacturer nameplate
Temperature correctionFtempcalcTempCorrectionFactor()NEC Table 310.16
Grouping/bundlingFgroupcalcGroupingFactor()NEC 310.15(C)(1)
Aging conditionFagingcable_types.aging_factorField assessment (0–1)

Temperature Correction Factor — NEC Table 310.16

Ambient temperature is the maximum of all room ambient temperatures along the cable route. Default: 40°C.

Ambient (°C)60°C Insulation75°C Insulation90°C Insulation
≤ 301.001.001.00
31–350.910.940.96
36–400.820.880.91
41–450.710.820.87
46–500.580.750.82
51–550.410.670.76
56–600.000.580.71
61–700.330.58
71–750.000.50
76–900.00

A factor of 0.00 means the cable cannot legally operate at that ambient — the cable is unsafe regardless of load.

Grouping / Bundling Factor — NEC 310.15(C)(1)

Applies when multiple current-carrying conductors share the same conduit or tray. The conductor count is summed across all ACTIVE cables in the same CONDUIT-type raceway.

Current-Carrying ConductorsDerating Factor
1–31.00 (no derating)
4–60.80
7–90.70
10–200.50
21–300.45
31–400.40
> 400.35

Aging Factor

Set manually on the cable type record from field inspection or megger testing. A value of 1.0 means no degradation; values below 0.75 independently trigger AGING_WARNING. Default is 1.0 (no derating) if not set.

Acceptance Criteria

StatusConditionProblem Posted
OKIload ≤ IderatedNone
OVERLOADIload > IderatedDERATING_EXCEEDED

Problem Detail Format

Load 45A exceeds derated ampacity 38.5A (base 50A × temp 0.88 × group 0.80 × aging 1.00) at 40°C ambient

4. Cable Aging Analysis

Function: calcAgingFlag()

src/utils/cable-analysis.js  ·  Called by runCableAnalysis()

What it calculates

Cable service life status relative to its rated design life. Two independent checks run in sequence — either can trigger a warning.

Check 1 — Aging Factor Condition

Condition Factor Check
If aging_factor < 0.75 → CRITICAL (regardless of age)

This check fires when field inspection or electrical testing (megger) indicates significant insulation degradation independent of calendar age.

Check 2 — Design Life Percentage

Age Calculation
ageyears = current_year − installation_year
pctlife = (ageyears / design_life_years) × 100

Acceptance Criteria

StatusConditionProblem Posted
OKpctlife < 80% AND aging_factor ≥ 0.75None
WARN80% ≤ pctlife < 100%AGING_WARNING
CRITICALpctlife ≥ 100% OR aging_factor < 0.75AGING_WARNING
⚠️ Data Requirements: Both cable_register.installation_year and cable_types.design_life_years must be populated for the age-based check to fire. If either is missing, only the aging_factor check applies.

Problem Detail Formats

// Aging factor triggered: Aging factor alert: aging_factor 0.62 below 0.75 threshold // Design life triggered (warning): Cable is 33 years old (82% of design life) — approaching design life of 40 years // Design life triggered (critical): Cable is 43 years old (107% of design life) — past design life of 40 years

Typical Design Life Values

Cable TypeTypical Design Life
XLPE / EPR (medium voltage)40 years
THWN / THHN (low voltage)30–40 years
MC Cable30 years
Instrumentation cable20–30 years
Fiber optic25–30 years

5. Raceway Fill Analysis

Function: computeFill()computeConduitFill() / computeTrayFill()

src/utils/fill-analysis.js  ·  Called by reanalyzeFillForRaceways()

Triggers

Fill is re-evaluated automatically on:

Cable segment inserted/removed Cable route replaced Cable type dimensions changed Raceway added/deleted

A. Conduit Fill — NEC Chapter 9, Table 1

Cable Area in Conduit
Acable = (π/4) × dconductor² × nconductors   (if conductor OD available)
Acable = (π/4) × dOD²   (if only cable OD available)
Fill Percentage
Aconduit = (π/4) × dinner²
Fill% = (Σ Acable / Aconduit) × 100

NEC Fill Limits

Cables in ConduitMax FillStatus = "over" ifProblem Posted
1 cable53%Fill% > 53%OVERFILL
2 cables31%Fill% > 31%OVERFILL
3 or more cables40%Fill% > 40%OVERFILL

B. Cable Tray Fill — NEC 392.22

Three fill methods are supported depending on the tray configuration:

Method 1: RANDOM (default)

For trays with mixed cable diameters, loaded randomly. NEC 392.22(A).

Effective Tray Depth (RANDOM)
deff = min(dactual, 6 in.)   (NEC 6-inch depth cap)
Fill Check — Stacked Cables
Amax = width × deff
Fillstacked% = (Σ Acable-OD / Amax) × 100
Fill Check — Single-Layer Required Cables
Fillsingle% = (Σ spacingOD / width) × 100

Status = "over" if Fillstacked% > 100% OR (single-layer cables present AND Fillsingle% > 100%)

Method 2: DEPTH

Same as RANDOM but uses actual tray depth without the 6-inch cap.

Effective Tray Depth (DEPTH)
deff = dactual   (no cap)

Method 3: SINGLE_LAYER

For trays that must maintain a single layer of cables side-by-side.

Fill Check
Fill% = Σ spacingOD / width
Status = "over" if Fill% > 100%

Problem Detail Format

Raceway "TRAY201H" fill 87.3%

6. Tray Weight Analysis

Function: computeWeight()

src/utils/fill-analysis.js  ·  Called by reanalyzeFillForRaceways() for TRAY-type raceways only

What it calculates

Total distributed cable weight in a tray section compared to the tray catalog load capacity.

Equations

Weight per Unit Length
Wtotal = Σ weight_per_ft (lbs/ft) for all cables in tray
Total Weight in Section
Wsection = Wtotal × Ltray (lbs)
Loading Percentage
Load% = (Wtotal / capacity) × 100

Acceptance Criteria

StatusConditionProblem Posted
OKWtotal ≤ tray_types.load_capacity_lbsNone
OVERWtotal > tray_types.load_capacity_lbsOVERWEIGHT
NO CAPload_capacity_lbs not set on tray typeNone (skipped)

Problem Detail Format

Raceway "TRAY402H/A" weight 4.32 lbs/ft (limit 3.50 lbs/ft)

7. Route Integrity Checks

MISSING_SEGMENT Detection

src/utils/cable-integrity.js  ·  onRacewayDeleted() and analyzeCables()

What it detects

A cable's route contains a segment where the raceway has been deleted from the database. The segment row remains in cable_segment_routing with raceway_id = NULL and no label text.

Condition

seg.raceway_id IS NULL AND seg.label IS NULL -- not a deliberate label segment AND seg.seq NOT IN (1, 9999) -- not FROM/TO endpoints
Distinguishing missing segments from label segments: A row with raceway_id = NULL and a non-null label (e.g. "BY FIELD") is a valid free-text annotation — not a problem. Only rows with both NULL is a MISSING_SEGMENT.

Side Effects

Detail Format

Raceway "TRAY201H" (id 42) was deleted

BROKEN_LINK Detection

src/utils/cable-integrity.js  ·  onConnectionDeleted() and analyzeCables()

What it detects

Two consecutive ACTIVE real-raceway segments in a cable's route have no raceway_connections row between them — meaning there is no physical path between those raceways.

Check Logic

-- For each pair of consecutive real-raceway segments: SELECT 1 FROM raceway_connections WHERE (raceway_a_id = A AND raceway_b_id = B) OR (raceway_a_id = B AND raceway_b_id = A) -- If no row found → BROKEN_LINK

Label-only segments (raceway_id = NULL) are skipped — a label between two raceways does not break the connectivity requirement between them.

Triggers

Raceway connection deleted Full integrity scan (analyzeCables)

Detail Format

No connection between consecutive segments at seq 20 and seq 30

8. SL / SG Mismatch Checks

Service Level (SL) and Separation Group (SG) Mismatch

src/utils/cable-integrity.js  ·  onRacewayUpdated(), onCableUpdated(), analyzeCables()

What it detects

A cable's SL (voltage/service level) or SG (separation group/channel) attribute does not match the attribute declared on a raceway in its route. Mismatches indicate the cable is routed through a physically segregated raceway it should not share.

Check Conditions

CheckConditionProblem Posted
SL Mismatch cable.voltage IS NOT NULL
AND raceway.voltage IS NOT NULL
AND cable.voltage ≠ raceway.voltage
SL_MISMATCH
SG Mismatch cable.channel IS NOT NULL
AND raceway.channel IS NOT NULL
AND cable.channel ≠ raceway.channel
SG_MISMATCH
Null = unrestricted: A raceway with no SL or SG set accepts any cable. A mismatch is only raised when both the cable and the raceway have an explicit value that differs. Endpoint rows (seq 1 / seq 9999) and non-segment raceway types (containers, junction points) are excluded from this check.

Triggers

EventFunction CalledWhat It Checks
Raceway SL/SG changedonRacewayUpdated()All cables routed through that raceway
Cable SL/SG changedonCableUpdated()All raceways in that cable's route
Full integrity scananalyzeCables()All cables × all segments

Problem Detail Formats

-- SL Mismatch (raceway changed): Raceway SL "MV" does not match cable SL "LV" -- SG Mismatch (cable changed): Cable SG "GN" does not match raceway SG "RD" at seq 21

SL Values

CodeMeaningTypical Voltage Range
MVMedium Voltage≥ 1000 V
LVLow Voltage100–999 V
CPControl / Instrument Power< 100 V AC
DC125V DCDC circuits
FIBERFiber OpticN/A
COAXCoaxial / RFN/A

SG (Separation Group) Values

CodeLabelTypical Use
RDRedPrimary power / train A
GNGreenRedundant power / train B
YLYellowAlternate train / instrument
BLBlueFourth separation group

9. Helper Functions

FunctionFileWhat It DoesReturns
resolveWorstAmbient() cable-analysis.js MAX(ambient_temperature) from all rooms the cable passes through. Falls back to building.ambient_temp_c, then 40°C. Temperature in °C
resolvePhaseConfig() cable-analysis.js Determines single vs three-phase. Checks loadflow_link_cables for 3 distinct phase codes (A/B/C), then uses voltage > 240V heuristic, then defaults to single-phase. { isThreePhase, source }
calcTempCorrectionFactor() cable-analysis.js NEC Table 310.16 lookup. Returns multiplier based on insulation rating (60/75/90°C) and ambient temperature. Returns 0 if temperature exceeds rated insulation limit. Factor 0.0–1.0
calcGroupingFactor() cable-analysis.js NEC 310.15(C)(1) lookup. Returns derating multiplier based on total current-carrying conductors in a conduit. Factor 0.35–1.0
countConductorsInConduit() cable-analysis.js Sums cable_types.num_conductors (default 3) for all ACTIVE cables sharing the same CONDUIT raceway. Integer count
resolveSystemVoltage() cable-analysis.js Resolves operating voltage via bus link or cable type voltage rating. Volts or null
effectiveMethod() fill-analysis.js Returns fill method: "conduit" for CONDUIT type; otherwise ra.fill_method or "RANDOM". Method string
cableAreaForConduit() fill-analysis.js Cable conductor cross-section area: (π/4)×d²×n if conductor OD available, else (π/4)×OD². Area in in² or null
cableAreaForTray() fill-analysis.js Cable area for tray fill: always (π/4)×OD². Area in in² or null

10. Trigger Summary

User ActionFunctions CalledProblems Affected
Cable Analysis → Run (selected cables) runCableAnalysis() Clears and re-posts: VOLTAGE_DROP_WARN, VOLTAGE_DROP_CRIT, DERATING_EXCEEDED, AGING_WARNING, MISSING_LOAD_DATA
Raceway deleted onRacewayDeleted() Posts MISSING_SEGMENT on all affected cables; sets routing_status = ALTERED
Raceway connection deleted onConnectionDeleted() Posts BROKEN_LINK on cables whose consecutive route uses that connection
Raceway SL or SG changed onRacewayUpdated(), reanalyzeFillForRaceways() Updates SL_MISMATCH / SG_MISMATCH on affected cables; updates OVERFILL / OVERWEIGHT on that raceway
Cable SL or SG changed onCableUpdated() Clears and re-posts SL_MISMATCH / SG_MISMATCH for that cable
Cable type dimensions changed reanalyzeFillForCableType() Updates OVERFILL / OVERWEIGHT on all raceways containing cables of that type
Cable segment inserted / removed / replaced reanalyzeFillForRaceways() Updates OVERFILL / OVERWEIGHT on affected raceways
Route approved clearProblems() Clears ALL problems for that cable (full clean slate)

11. Problem Code Reference

Problem Type Severity Table Description Resolution
VOLTAGE_DROP_CRIT Critical cable_problems Calculated voltage drop exceeds 5%. Cable cannot reliably supply its load. Increase cable size, reduce length, raise system voltage, or reduce load.
VOLTAGE_DROP_WARN Warning cable_problems Calculated voltage drop between 3–5%. Exceeds NEC recommendation. Increase cable size or reduce length. Acceptable for non-critical loads.
DERATING_EXCEEDED Critical cable_problems Load current exceeds derated ampacity after applying temperature, grouping, and aging factors. Increase cable size, reduce conduit fill, improve ambient conditions, or reduce load.
AGING_WARNING Action Required cable_problems Cable has reached ≥80% of design life, or aging_factor < 0.75 from field inspection. Schedule cable testing (megger), plan replacement, or update aging_factor after inspection.
MISSING_LOAD_DATA Info cable_problems Voltage drop and derating checks could not run — no load current resolvable from cable, load link, or load rating. Set cable.design_amps override, or link cable to a load via the Loadflow Linker.
MISSING_SEGMENT Critical cable_problems A raceway in the cable's route was deleted. The segment row has raceway_id = NULL. Edit route in Cable Routing (Links) — remove the broken segment and reroute through a valid raceway.
BROKEN_LINK Critical cable_problems Consecutive raceways in the route have no raceway_connections row — no physical path exists. Re-establish the connection in Raceway Links, or reroute the cable through connected raceways.
SL_MISMATCH Warning cable_problems Cable's service level (voltage class) differs from an explicit SL restriction on a segment raceway. Reroute through appropriate SL-rated raceways, or clear the raceway SL restriction if incorrect.
SG_MISMATCH Warning cable_problems Cable's separation group (channel) differs from an explicit SG restriction on a segment raceway. Reroute through appropriate SG-designated raceways, or correct the cable/raceway SG assignment.
OVERFILL Warning raceway_problems Cable fill in a conduit or tray exceeds NEC maximum (53%/31%/40% conduit, 100% tray). Add parallel raceway, increase raceway size, or reroute cables.
OVERWEIGHT Warning raceway_problems Total cable weight per foot in a tray exceeds the tray catalog's load_capacity_lbs rating. Reroute heavier cables, add tray supports, or upgrade tray to higher load rating.

12. Constants & Defaults

ConstantValueReferenceUsed In
Voltage drop warning threshold3.0%NEC 210.19(A) informational noteVOLTAGE_DROP_WARN
Voltage drop critical threshold5.0%NEC 215.2(A) practical limitVOLTAGE_DROP_CRIT
Default power factor0.85NEC industrial defaultVoltage drop, ampacity
Default ambient temperature40°CNEC design standardTemperature correction
Default insulation temperature rating75°CNEC Table 310.16 default columnTemperature correction
Default conductor count (per cable)3Industry standard (3-phase)Grouping factor
Aging factor critical threshold0.75Engineering judgmentAGING_WARNING (critical)
Design life warning threshold80% of design lifeCondition assessment practiceAGING_WARNING (warn)
Design life critical threshold100% of design lifeEnd-of-life assessmentAGING_WARNING (critical)
Tray depth cap (RANDOM method)6 in.NEC 392.22(A)Tray fill (RANDOM)
Conduit fill — 1 cable53%NEC Ch.9 Table 1Conduit fill
Conduit fill — 2 cables31%NEC Ch.9 Table 1Conduit fill
Conduit fill — 3+ cables40%NEC Ch.9 Table 1Conduit fill
HP to kW conversion× 0.7457IEEE standardLoad rating conversion
Three-phase voltage threshold> 240 VNEC industrial heuristicPhase configuration
NEC References: All analysis thresholds reference the National Electrical Code (NEC / NFPA 70). The application implements the 2023 edition. Specific articles referenced: 210.19(A), 215.2(A), 310.15(C)(1), Table 310.16, 392.22, Chapter 9 Table 1.

13. Bus & Loadflow Analysis — Overview

Purpose

Bus analysis computes the electrical demand on every bus in the loadflow model and checks for capacity, power quality, and configuration problems. The same core math runs in two contexts:

FileContextPurpose
src/modules/electrical/utils/loadflow-math.js Browser (client-side) Real-time interactive display on the CRD and SLD screens. Reacts immediately to scenario selection without a server round-trip.
src/utils/bus-analysis.js Server (Node.js) Triggered by POST /api/bus-analysis/run. Runs the same calculations against the live database and writes results to the bus_problems table for persistent reporting.

Data Model

TableKey ColumnsNotes
buses id, name, voltage_volts, xformer_kva, parent_bus_id, transfer_mode, ts_normal_source_bus_id, ts_emergency_source_bus_id Self-referencing hierarchy — parent_bus_id = NULL for root (source) buses and ATS buses. xformer_kva is the step-down transformer nameplate rating. Transfer switch columns are NULL for normal buses; see §20.
generators id, bus_id, kw_rating, kv_rating, power_factor, status status: Active | Offline. Generators subtract from bus net demand (they supply power).
loads id, bus_id, load_type, rating_val, rating_unit, efficiency, power_factor, design_amps, status load_type: MOTOR | STATIC | HEATER | VALVE | LIGHTING.
status: RUNNING | STANDBY | STARTING | MAINTENANCE | RETIRED.
loadflow_cable_links id, link_type, bus_id, load_id, to_bus_id, generator_id link_type: BUS_LOAD | BUS_BUS | GEN_BUS. Bridges the abstract loadflow model to physical cables in the cable register.
loadflow_link_cables id, link_id, cable_id, phase (A/B/C/N), sort_order One row per physical phase conductor. The phase column is used for phase balance analysis.
scenarios id, name, description, status, priority Named what-if study configurations. Each scenario selects a subset of loads and generators to treat as active.
scenario_items scenario_id, item_type (LOAD|GENERATOR|BUS|BUS_TIE), item_id, status, delay_sec One row per item included in the scenario. The status field overrides the item's database status for that scenario. delay_sec supports sequenced startup studies. BUS_TIE items use status CLOSED | OPEN to override the tie breaker state.
bus_problems bus_id, problem_type, detail, created_at Written by runBusAnalysis(). Unique on (bus_id, problem_type) — re-running always overwrites with fresh results.
bus_ties id, name, bus_a_id, bus_b_id, tie_state Bus tie breakers connecting two buses. tie_state: OPEN | CLOSED. UNIQUE on (bus_a_id, bus_b_id). Used by analysis to detect TIE_COMBINED_OVERLOAD and by the SLD for overlay rendering. Added June 2026.

Transfer Switch Columns on buses Table

Added June 2026 via migration add_transfer_switch_to_buses.sql. Null on normal buses; populated only on Automatic Transfer Switch (ATS) buses.

ColumnTypeDescription
transfer_modeVARCHAR(10)NULL = normal bus. NORMAL = connected to normal/utility source. EMERGENCY = connected to emergency/generator source. OFF = switch open, bus isolated.
ts_normal_source_bus_idINT FKThe normal (utility) source bus that feeds this ATS bus when transfer_mode = NORMAL.
ts_emergency_source_bus_idINT FKThe emergency (generator) source bus that feeds this ATS bus when transfer_mode = EMERGENCY.

Scenarios and Their Effect on Calculations

Scenarios allow engineers to model what-if conditions — emergency configurations, maintenance outages, worst-case startup sequences — without altering the live database. Three modes are available in the CRD/SLD screens via the scenario dropdown:

ModeHow Load Status Is ResolvedHow Generator Status Is ResolvedBus Status
Live Database Use loads.status exactly as stored in the database. Use generators.status exactly as stored. All buses ENERGIZED unless manually set otherwise.
Worst Case (All ON) Every load is forced to RUNNING regardless of its stored status. This represents the maximum possible demand. Every generator is forced Online. Combined with all loads running, this tests maximum simultaneous demand against maximum available generation. All buses ENERGIZED.
Named Scenario Only loads listed in scenario_items for that scenario contribute to demand, using the status field from the scenario item row. Loads not listed default to OFF (zero demand). Only generators listed in scenario_items supply power, using the scenario item's status. Unlisted generators default to Offline. Buses listed in scenario_items with status Online are ENERGIZED; Offline sets the bus to DE-ENERGIZED, zeroing its entire subtree. Bus ties listed with status CLOSED or OPEN override the tie breaker's nameplate state.
Server-side scenario support (partial). runBusAnalysis(pool, busIds, scenarioItems) now accepts an optional scenarioItems array. When provided, BUS_TIE items in the array override tie breaker states for the closed-tie combined overload check. Load and generator status overrides are not yet applied server-side — those still use live loads.status / generators.status from the database.
Scenario item delay_sec: The delay field is captured for future use in sequenced startup studies (e.g. motor starting stagger to limit inrush). It is not currently used in any calculation — all items in a scenario are treated as simultaneously active.

14. Bus Power Calculations

Function: calcLoadPower(load)

src/utils/bus-analysis.js  ·  src/modules/electrical/utils/loadflow-math.js

Converts a load's nameplate rating to real power (P), reactive power (Q), and apparent power (S). The calculation depends on the rating_unit and the load's current operating status.

Step 1 — Convert rating to kW

rating_unitEquationNotes
kWP = rating_val / efficiencyDirect — most common
HPP = (rating_val × 0.7457) / efficiencyMotor nameplate horsepower
kVAP = (rating_val × PF) / efficiencyExtracts real component from apparent power
AP = 0Cannot convert without bus voltage — set design_amps override instead
STATIC / HEATER typeP = rating_valResistive loads — efficiency is already unity, no HP conversion

Step 2 — Compute P / Q / S by operating status

Normal Running (RUNNING / STANDBY)
S = P / PF    Q = √(S² − P²)
Motor Starting — Inrush (status = STARTING)
Sstart = (P / 0.30) × 6.0    Qstart = √(Sstart² − P²)
PFstart = 0.30 typical  ·  Inrush multiplier = 6.0 × (NEC 430.52 locked-rotor convention)

Load Status Summary

StatusContributes to Demand?Notes
RUNNINGYes — full P/Q/SNormal operating condition
STANDBYYes — full P/Q/SStandby loads remain energised and draw power
STARTINGYes — 6× inrush S at PF 0.30Used in worst-case scenario or motor start studies
MAINTENANCENo — P/Q/S = 0Isolated; breaker open
RETIREDNo — P/Q/S = 0Permanently decommissioned
OFF (scenario default)No — P/Q/S = 0Load not listed in the active scenario

Function: calcGeneratorPower(gen)

Generators supply power — their P/Q/S is subtracted from bus demand.

Generator Output
P = kw_rating    S = P / PF    Q = √(S² − P²)
Offline generators contribute P = Q = S = 0

Function: calcBusDemand(bus, allBuses)

Recursively aggregates net demand from a bus and all its child buses in the hierarchy. A visited set prevents infinite recursion if the hierarchy contains a cycle. If the bus has transfer_mode = 'OFF', the function returns zero immediately — the bus is isolated and contributes no demand.

Local Net Demand
Plocal = ΣPloads − ΣPgenerators
Qlocal = ΣQloads − ΣQgenerators
Slocal = √(Plocal² + Qlocal²)
Total Demand Including Child Buses
Ptotal = Plocal + ΣPchild buses
Qtotal = Qlocal + ΣQchild buses
Stotal = √(Ptotal² + Qtotal²)
Bus Feeder Current (Three-Phase)
Ibus = (Stotal × 1000) / (√3 × Vbus)   [Amps]

A bus with isExporting = true (total P < −0.1 kW) has more local generation than load — power flows up to the parent bus rather than down from it.

15. Transformer Utilization

Function: calcXformerUtilization(totalS_kva, xformer_kva)

src/utils/bus-analysis.js

Checks the loading on the step-down transformer feeding a bus against its nameplate kVA rating (buses.xformer_kva). The check is skipped if xformer_kva is not set.

Transformer Loading
Loading% = (Stotal / kVAnameplate) × 100
StatusConditionReferenceProblem Posted
OKLoading% < 80%Normal operating marginNone
WARN80% ≤ Loading% < 100%ANSI C57.12 loading guidelinesXFORMER_WARN
OVERLOADLoading% ≥ 100%Nameplate limitXFORMER_OVERLOAD
Transformer 94.2% loaded — 941.8 kVA of 1000 kVA nameplate

16. Power Factor Check

Function: calcBusPowerFactor(p, s)

src/utils/bus-analysis.js

Computes the composite displacement power factor across all loads on the bus and its children. A low PF means excessive reactive current is circulating — it occupies transformer and conductor capacity without doing useful work and increases I²R losses.

Composite Bus Power Factor
PFbus = |Ptotal| / Stotal
StatusConditionReferenceProblem Posted
OKPF ≥ 0.85IEEE 141 / utility tariff standardNone
WARNPF < 0.85IEEE 141 minimum recommendationLOW_POWER_FACTOR
Composite power factor 0.762 is below 0.85 — P=381.0 kW, Q=312.4 kVAR, S=500.2 kVA

Capacitor Bank Sizing

kVAR Required to Correct to Target PF
Qcap = P × (tanΦcurrent − tanΦtarget)
where tanΦ = √(1/PF² − 1)

17. Phase Balance Check

Function: calcPhaseBalance(phaseCurrents)

src/utils/bus-analysis.js

Measures current imbalance across the three phases. Phase assignment comes from loadflow_link_cables.phase — each cable linked to a bus carries current on its assigned phase. Phase balance data is only meaningful after cables have been assigned phases in the Loadflow Linker.

NEMA MG-1 Imbalance Method
Iavg = (IA + IB + IC) / 3
Imbalance% = (max|Iphase − Iavg| / Iavg) × 100
StatusConditionReferenceProblem Posted
OKImbalance% ≤ 10%NEMA MG-1 / NEC 220.61None
WARNImbalance% > 10%NEMA MG-1 §14.35PHASE_IMBALANCE
Motor impact: NEMA MG-1 §14.35 requires motor output derating above 1% voltage imbalance. At 5% imbalance, winding current imbalance can reach 25–40%, significantly increasing heating and reducing motor life.
Phase B deviates 14.3% from average — exceeds 10% limit

18. Bus Problem Code Reference

Bus problems are written to the bus_problems table by runBusAnalysis(). Each row is unique on (bus_id, problem_type) — re-running always replaces results with the latest values. Problems are cleared per-bus at the start of each run.

Problem TypeSeverityDescriptionResolution
XFORMER_OVERLOAD Critical Bus transformer demand ≥ 100% of nameplate kVA. Risk of thermal damage and failure under sustained load. Shed non-critical loads, transfer loads to an alternate bus, or upgrade the transformer.
XFORMER_WARN Warning Transformer 80–100% loaded. No headroom for load growth, motor starting inrush, or contingency. Review load growth projections. Consider adding a parallel transformer or shedding lower-priority loads.
BUS_OVERCURRENT Critical Computed bus feeder current exceeds the linked cable's ampacity rating. Increase feeder cable size, reduce load, or add a parallel feeder.
LOW_POWER_FACTOR Warning Composite bus power factor below 0.85. Excessive reactive current reduces effective transformer capacity and increases conductor losses. Install a power factor correction capacitor bank. Required kVAR = P × (tanΦcurrent − tanΦtarget).
PHASE_IMBALANCE Warning Largest phase deviation exceeds 10% of the three-phase average. Causes motor winding overheating and reduced insulation life. Redistribute single-phase loads across phases. Reassign cable phases in the Loadflow Linker.
EXPORTING_BUS Info Bus shows net generation surplus (Ptotal < −0.1 kW) but no generators are assigned in the model. Likely a model configuration error. Verify generator assignments in the Loadflow Linker. Check parent bus configuration.
TS_OPEN_WITH_LOADS Warning Transfer switch is set to OFF (open) but loads are assigned to the bus. Those loads are currently de-energized — no power flows to them. Transfer the switch to NORMAL or EMERGENCY, or remove loads that should not remain on this bus during an open-switch condition.
TS_NO_SOURCE Critical Transfer switch is in an active position (NORMAL or EMERGENCY) but the corresponding source bus FK is not assigned. Analysis cannot route demand correctly. Set the missing source bus on the ATS record in Bus Maintenance — assign ts_normal_source_bus_id or ts_emergency_source_bus_id as appropriate.
TIE_COMBINED_OVERLOAD Critical A closed bus tie breaker joins two buses whose combined demand exceeds at least one transformer's nameplate kVA. Each tied bus is checked independently against the combined load. Open the tie breaker, shed load from one of the tied buses, or upgrade the transformer on the overloaded bus before closing the tie.

Proposed Future Checks

Problem TypeDescriptionEquation
STARTING_TRANSIENT Motor starting kVA exceeds available transformer headroom (nameplate − running load) Sstart = (P / 0.30) × 6.0   vs   kVAnameplate − Srunning
HARMONIC_RISK High proportion of non-linear loads (STATIC, VALVE) relative to total load Non-linear kVA / total kVA > 30%
BUS_VOLTAGE_DROP Estimated bus voltage below nominal −5% based on feeder impedance (requires cable Z data) Vbus = Vsource − Ibus × Zfeeder
SCENARIO_OVERLOAD Transformer OK under live conditions but overloaded under a specific named scenario Run runBusAnalysis() with scenario-overridden load statuses applied

19. Bus Ties

Bus tie breakers connect two buses at the same voltage level, allowing load to be transferred between buses when one source is lost or overloaded. Added June 2026.

Data Model

ColumnTypeDescription
idINT PKAuto-increment
nameVARCHAR(100)Human-readable label, e.g. TIE-R11A-R11B
bus_a_idINT FK → busesFirst bus (order is arbitrary). Unique constraint on (bus_a_id, bus_b_id).
bus_b_idINT FK → busesSecond bus. Both FKs cascade-delete if the referenced bus is deleted.
tie_stateVARCHAR(6)OPEN | CLOSED. Default: OPEN.

Analysis Behavior

Bus ties affect analysis in two places:

  1. Combined overload check (server-side)runBusAnalysis() uses Union-Find to group all buses connected through closed ties. The combined demand of the merged group is checked against each member's individual transformer rating. A combined overload posts TIE_COMBINED_OVERLOAD on the affected bus.
  2. SLD overlay (client-side) — The SLD screen fetches bus ties via GET /api/bus-ties and renders a dashed visual connection between tied bus cards. Closed ties are shown with a solid colored overlay; open ties are shown dashed.

API Routes

MethodEndpointDescription
GET/api/bus-tiesAll ties with bus_a_name, bus_b_name resolved
POST/api/bus-tiesCreate tie (name, bus_a_id, bus_b_id, tie_state)
PUT/api/bus-ties/:idUpdate name or tie_state
DELETE/api/bus-ties/:idDelete tie

Scenario Override

A scenario_items row with item_type = 'BUS_TIE' and status = 'CLOSED' or 'OPEN' overrides the tie's nameplate tie_state for that scenario. The Scenario Maintenance screen exposes a BUS TIE tab with Closed/Open toggle buttons that default to the tie's current nameplate state.

20. Transfer Switch (ATS) Modeling

An Automatic Transfer Switch (ATS) bus is modeled by setting transfer_mode on a buses row. A normal bus has transfer_mode = NULL. An ATS bus has two possible source buses — normal (utility) and emergency (generator) — and a current position. Added June 2026.

Transfer Mode Values

transfer_modeEffective ParentSLD BehaviorAnalysis Behavior
NULLparent_bus_id (static)Standard bus cardNormal demand rollup
NORMALts_normal_source_bus_idATS badge (green) + standby emergency card shown dimmedDemand rolls up through normal source
EMERGENCYts_emergency_source_bus_idATS badge (red) + standby normal card shown dimmedDemand rolls up through emergency source
OFFnull (isolated)ATS badge (grey)calcBusDemand returns zero immediately; posts TS_OPEN_WITH_LOADS if loads exist

SLD Rendering (loadflow-sld.js)

When a bus has transfer_mode set, the SLD renders two side-by-side cards at that level of the tree:

The standby bus is marked visited so it is not re-rendered as an independent root node.

Bus Maintenance Screen

The Bus Maintenance screen exposes a Transfer Switch section on the add/edit modal with three fields: Transfer Mode (dropdown: — / NORMAL / EMERGENCY / OFF), Normal Source Bus, and Emergency Source Bus. The source bus dropdowns exclude the current bus.

Analysis Cascade for ATS Buses

// In runBusAnalysis() — before calcBusDemand is called: let effectiveParent = b.parent_bus_id if (b.transfer_mode === "NORMAL") effectiveParent = b.ts_normal_source_bus_id if (b.transfer_mode === "EMERGENCY") effectiveParent = b.ts_emergency_source_bus_id if (b.transfer_mode === "OFF") effectiveParent = null
ATS vs. parent_bus_id: An ATS bus should have parent_bus_id = NULL in the database. Its effective parent is determined dynamically from transfer_mode and the appropriate source FK. Setting a static parent_bus_id on an ATS bus would result in double-counting demand in both the tree and the ATS rollup.