Class map¶
How the pieces of fdia_graph connect: one module diagram and six class diagrams, all drawn from
the source by tools/class_diagrams.py (the AST gives each class's public methods, its bases and the
classes of other modules it names), so they cannot drift from the code. CI checks that the committed
sources match; python tools/class_diagrams.py rewrites them and tools/render_mermaid.py renders.
Read with ROADMAP.md (what each file is for) and
CONCEPTS_TO_CODE.md (which function implements which idea).
An open arrowhead is inheritance, a dashed arrow is a class using another (an argument type, a return type or a collaborator it builds), and a box marked with another package's name is a participant that lives there.
Modules¶
Which module imports which. Four foundations are read by nearly every module and are left out so the
real dependencies show: models/ (every returned record, every config model and the one validation
engine), formulas/ (the equations), schema.py (the file protocol) and errors.py (the named
errors). __init__.py, the fg.* public API, imports everything and is left out for the
same reason. streams.py is deprecated for generation and kept for reading the v0.7 stream files.

The dataset¶
FdiaGraph is the file. Five mixins over DatasetBase give it its faces: the static graph physics
(GraphMixin), the admittance matrices (AdmittanceMixin), records and batches (RecordsMixin), the
exports (ExportMixin) and the time-ordered windows and episodes (SequenceMixin). The methods that
produce records return models bundles (a record, a batch, an exported split, the episode table, the
summary); the graph properties are tensors or arrays, loader() is a DataLoader and windows() a tuple.

The engine¶
FdiaGenerator is the AC model of one system with its meter plan. GridBase declares what the mixins
provide: MeasurementMixin emits scans, PhysicsMixin re-solves the whole grid under new loads and
reads a scan's load and generation, and AttackMixin builds every attack (next section).
engine/records.py emits a benign scan and hands an attacked family to the mixin; timeline.py
decides when and where each episode runs.

The attacks¶
Every attack is built in engine/attacks/, one module per concern, composed into AttackMixin:
AreaMixin finds the attacker's area, FalseStateMixin solves and checks its false state and the
attack vector, StealthyMixin builds the ramp frames, MinimizeMixin finds the support that tampers
the fewest devices over an attack window [WU26, eq. 12], OverloadMixin builds the overload Am,
and EpisodeDesignMixin draws what an At episode attacks at its onset. AttackMixin.attack_frame
is the one entry for an attacked At scan; an Am frame comes from overload_step.

State estimation¶
SEBase holds the shared solve: the measurement function, the chord Jacobian, the meter weights and the
chord-Newton loop. Each estimator overrides one hook. GatedPrior takes a fitted localizer as its gate;
JacobianFeatures is the per-bus feature block the localizers can consume.

Localization and trusted meters¶
LocalizerBase calibrates any per-bus score to one false-alarm budget; the threshold arms score a stored
feature, ResidualLocalizer runs an estimator, the learned arms train a per-bus network.
TrustSelector chooses the meters to secure on the WLS Jacobian, by row reduction (TrustedMeters) or
by a deep Q-network (TrustedMetersDQN), and scores the residual test with them secured.

Federated training¶
FederatedLocalizer is a LearnedLocalizer whose fit runs FedAvg over a Partition of the buses:
each client trains its own copy with the shared LocalTrainer on its own buses, and FedBusMLP and
FedBusCNN take their encoder from BusMLP and BusCNN. RoundLog records every round.
RegionalPrior is a SubspacePrior whose basis is fitted per client of a Partition.

The records¶
Everything a call returns is a Bundle, a dict with attribute access and a fixed key order. The record
forms share seven field-group mixins, one per group of keys, so a record, a batch, an exported split and
a stream carry the same names. An exported split adds an eighth, export-only group,
PreviousFrameFields (the previous frame's readings on a timeline).

The scores¶
The result of every score() is a Bundle too: ErrorPair and EstimatorScores for the estimators,
OverallMetrics, BenignMetrics, FamilyMetrics and LocalizerScores for the localizers, TrustScores
for the trusted meters, JacobianOutputs for the feature block.

The named tuples of models/frames.py (Scan, Frame, OperatingLimits, FrameKnobs),
models/grid.py (the column indices, BranchModel, Admittances, MeterPlan, MeterBias,
Outage) and models/assets.py (AssetSpec) are plain
value types with no hierarchy, so they appear only where a class above uses them.