Infrastructure Cascade Simulator · AfifAI
Case study · Critical infrastructure · 2024–2025

Simulating how one failure cascades across power and gas, with water next

Emergency planners at an Israeli critical-infrastructure resilience company can now load a national asset catalog, drop a failure scenario on it, and watch the effects spread through the network in 15-minute steps. They see which substations, pipelines and customers lose supply, for how long, and why.

.NET 8C#SQL Server spatialEF CoreTalend ETLReact + TypeScriptMapbox GLECharts
Client
Israeli critical-infrastructure resilience company (confidential)
My role
Team lead & architect (hands-on)
Timeline
Nov 2024 – Aug 2025 · catalog, map, then simulation
Status
Delivered
At a glance
900×faster than real time: a 15-minute step plays in 1 s, so a 24-hour scenario runs in ≈1.6 min
1 clickfor a full data refresh: 40 ETL jobs, 20 sync procedures, geometry and connections
2database round trips per KMZ map import, not ≈2 per asset
20×fewer status calls on the live run page (a 20 s poll plus a local clock)
32validation routines in the ETL. Bad rows are kept and flagged.
3 · 18sectors and asset types in one catalog
149REST endpoints across 9 controllers
432operating constraints on 159 assets and 169 links in the national seed dataset
The problem

The data existed, but nothing connected it

The client models how a national emergency, such as an earthquake or an attack on a facility, would spread through critical infrastructure. Their inputs were spreadsheets, one per asset type and sector, each with its own columns. Coordinates came in three notations, map geometry came as KMZ files, and there was no model of which asset feeds which.

Answering “if this transformer loses 40% of its capacity at 08:15, which customers go dark, and when do they come back?” meant reasoning by hand over disconnected files.

What I owned

From an asset catalog to a running simulation

  • Designed the backend: an 18-project .NET 8 solution with three databases for ETL staging, the main catalog and simulation results.
  • Built the catalog API: create, read, update and delete, plus Excel import and export, for every electric and natural-gas asset type.
  • Made the ETL a single API action: run the Talend jobs, sync them to the main database, compute geometry, then load the asset connections.
  • Built the spatial layer: SQL geometry, GeoJSON stored per asset, KMZ import and map queries limited to the visible area.
  • Designed and wrote the simulation engine, from v1 in March 2025 to v2 in April, and the simulation screens on top of it.
  • Led the frontend and ETL developers, reviewed PRs, and ran four environments (dev, QA, stage, production).
Architecture

Validate in staging, sync in one transaction, simulate in memory

The diagram is wider than the screen: swipe sideways to see all of it →

ETL output lands in a separate staging database first. Nothing reaches the catalog the simulation reads until it has passed validation and the transactional sync. The simulation loads the asset graph into memory once, then writes results in one batch per step.

How a cascade plays out

One event, four steps

This is a small synthetic network that follows the engine's rules. None of the names, values or topology come from the client. Pick a step to see what the engine changes and which follow-up events it schedules.

operationaldegraded / on batteryno supply

Events the engine scheduled
    Design decisions

    Keep the physics pluggable and the data path boring

    A staging database, then a MERGE sync

    Talend writes to its own staging database. A single stored procedure runs 20 per-type MERGE syncs inside one transaction. A failed refresh leaves the catalog exactly as it was. The trade-off is a second copy of the 57 catalog tables, which has to be kept in step with the main schema.

    One calculation strategy per asset type

    Generators, transformers, lines, customer loads and gas assets each have their own strategy class behind one interface, with a default for types not modelled yet. Adding the gas sector meant writing a new strategy, not changing the engine. The trade-off is that cross-sector coupling is modelled through dependency links, not a combined physics solver.

    Two passes per step instead of a power-flow solver

    Each step sorts the asset graph topologically. A reverse pass pushes demand upstream, then a forward pass pushes supply downstream. It's fast and easy to explain to planners. The trade-off is that it approximates the electrical behavior rather than solving full load flow.

    The engine schedules its own follow-up events

    A failure with a repair time schedules its own recovery. A generator start schedules “online” after its startup time, and a load on battery schedules its depletion. Planners write only the first event. The engine produces the timeline and saves every event it generates.

    Store geometry once, query by viewport

    Point and line geometry, and their GeoJSON, are computed when data is synced or edited and stored on the asset. A map request is a spatial intersect with the visible rectangle, followed by string concatenation of the stored GeoJSON. The trade-off is that every edit path has to recompute geometry, so that lives in one service method.

    Engineering log

    Problems worth remembering

    Nov 2024

    18 asset types, 18 different spreadsheets

    Each asset type had its own columns, units and lookup values. Writing a separate import and API for each one would not have scaled.

    Built a generic catalog layer: base controller and storage, one Excel import and export utility, and one mapping profile. A new asset type needed an entity and its mappings, not new plumbing.

    Dec 2024

    Starting ETL jobs from a web button

    The Talend jobs are standalone exported scripts. A web request had to start them, wait for them, and not leave orphan processes behind when someone cancelled.

    The API starts each job as a child process with an OS-specific launcher, waits asynchronously, and kills the whole process tree on cancel. A failure in one sector is logged and the other sectors keep running.

    Dec 2024

    Coordinates in three notations

    Source sheets mixed degrees-minutes-seconds, degrees with decimal minutes, and decimal degrees.

    ETL routines convert all three to decimal degrees and validate them. From March 2025, rows that fail validation are written to an invalid-rows table with the reason, so the job no longer stops or silently drops them.

    Jan 2025

    The map needed lines, not only points

    The first map read a static GeoJSON file. Real voltage lines and pipelines are polylines with many vertices, and the map file's line styling had to survive the import.

    Converted points and polylines to SQL Server geometry, stored GeoJSON on each asset, and served the map through a viewport-intersect query. KMZ import matches assets by external ID with one batched lookup and one bulk update.

    Feb 2025

    Read queries paying for change tracking

    Large read queries with several includes were loaded into the EF change tracker even though nothing would be saved.

    Switched those read paths to no-tracking with identity resolution. Related entities stay de-duplicated, without the tracking overhead.

    Apr 2025

    The order of calculation changed the answer

    A transformer's required input depends on what its downstream loads need, but its output depends on what its upstream supply delivers. Calculating assets in arbitrary order gave results that depended on that order.

    Engine v2 topologically sorts the graph and runs two passes on every step: reverse for demand, forward for supply. Events are grouped per asset and processed before recalculation.

    Jun 2025

    A run that never reached 100%

    The run list showed a current step that never matched the total, so finished runs looked stuck.

    Aligned the step-count formula between the service and the engine. Each step now also records CPU, memory and elapsed time, so slow runs can be explained.

    Jul 2025

    Scenario and constraints in two places

    Planners kept failure events and asset constraints in separate files, which made runs easy to set up inconsistently.

    One combined workbook import for scenario and constraints, a results-by-step API, and chain views (tree, Sankey and force graph) showing how a failure travels.

    Outcome

    Before and after

    AreaBeforeAfter
    Data refreshPer-type spreadsheets, processed job by jobOne action runs 40 ETL jobs, a transactional sync, geometry and connections
    Bad source rowsRows rejected without a recordRow kept in an invalid-rows table with the reason
    MapStatic, pre-parsed GeoJSON fileLive viewport query on SQL geometry, always current with the catalog
    KMZ importA per-asset approach: ≈2 database calls per asset2 calls per file: one batched lookup, one bulk update
    What-if analysisReasoning by hand over disconnected files15-minute steps, 24 h in ≈1.6 min, per-asset results, dependency chains and Excel export
    Live run pagePolling every second would mean 60 status calls a minute3 calls a minute (a 20 s poll, stale requests aborted) plus a local 1 s clock
    • Electric and natural-gas sectors simulate end to end on a national seed dataset of 159 assets, 169 links and 432 constraints.
    • The water sector has its schema and ETL jobs in place. Its calculation strategy is the next step.
    • Each run is stored with its steps, per-asset results, dependency results, generated events and resource metrics, so a run can be replayed and audited.
    • About 10 months from the first catalog endpoint to running multi-sector simulations, across four environments.
    Stack

    Technology stack

    Backend
    .NET 8, ASP.NET Core Web API, EF Core, AutoMapper, JWT, NLog
    Data
    SQL Server with spatial types (NetTopologySuite), stored procedures, MERGE sync
    ETL
    Talend jobs and joblets, custom Java validation routines
    Simulation
    In-process C# engine: event queue, strategy pattern, topological passes
    Front end
    React 18, TypeScript, Vite, Redux Toolkit, Ant Design, Mapbox GL, ECharts, i18next (3 languages)
    Delivery
    Docker Compose, publish scripts, dev, QA, stage and production branches

    Free scoping call · 30 minutes · No commitment

    Bring one process. Leave with a plan and a number to beat.

    Write a few lines: the process or system, what hurts today (time, errors, cost or an AI pilot that stalled) and who uses it. After a short call you get a written plan and a fixed quote within ≈3 days.

    Get a free scoping callafif.mansour@gmail.com

    Opens a pre-filled email · reply within one business day

    The model reads. The code counts.

    All projects