Ask the grid in its own words.
GridQL lets engineers, planners, and operators question their network the way they already talk about it — feeders, reclosers, phases, protection zones, and who is energized right now — without knowing how the GIS or asset database behind it is laid out.
gridql 'FIND loads WHERE NOT energized SELECT name, feeder, customer_count' name feeder customer_count --------------------- ------- -------------- Mill Pond Rd 2-20 FDR-701 6 Mill Pond Rd 22-30 FDR-701 4 Millbrook High School FDR-701 1 Orchard Ln 1-13 FDR-701 7 Maplewood Care Home FDR-701 1 Willow Way 2-18 FDR-702 6 Willow Way 20-30 FDR-702 5 7 rows
Questions about the wiring, answered from the wiring.
SQL is the right tool for billing and work orders. But “which customers are beyond this recloser?” is not a question about rows — it depends on connectivity, feeder boundaries, and where the normally open points are. In SQL it is a recursive query written against one utility’s schema. In GridQL it is one line, and it means the same thing on every utility’s data.
What is beyond this device?
FIND loads DOWNSTREAM OF "REC-1201-01" SELECT name, kw
Follows the circuit as it is built, stops at the feeder boundary, and never walks through a normally open tie.
Who is out right now?
FIND loads WHERE NOT energized SELECT SUM(customer_count)
Traced from every source through the switches as they currently stand — including across a tie that has been closed.
What is not where it should be?
FIND switches WHERE state != normal_state
Locked-out reclosers, blown fuses, and ties left closed after a restoration, in one list.
How much load sits behind each device?
FIND loads SELECT protected_by, COUNT(*), SUM(kw) GROUP BY protected_by ORDER BY SUM(kw) DESC
Every device knows its nearest protective device, so exposure per fuse or recloser is a grouping, not a study.
Filters that read like the question
FIND transformers FED BY "FDR-1201" WHERE kva >= 0.5MVA
Units behave: 0.5MVA and 500 kVA are the same number, and a comparison that makes no sense is refused.
Saved, reviewed, run again
gridql run feeder_report --feeder FDR-1202
Queries live in .gridql files with parameters, so a report written once runs on any feeder and can be reviewed like code.
GridQL refuses rather than guesses: a misspelled attribute or a device that does not exist is an error, not an empty answer that looks like a real one.
Storm morning in Millbrook.
A small town, two feeders, one normally open tie between them — and a storm overnight. The example project in the GridQL repository follows the morning in three saved queries. Every table below is real output; the lines between them are the comments from the query files.
Thirty customers out. Where do you look?
Two devices are off normal: a recloser on the north feeder has locked out, and a fuse on the south feeder has blown. Everything below them is dark.
The next question matters just as much: which fuses are dead but not blown? They are intact, only dark because the recloser above them is open — a crew sent to re-fuse them would find nothing to do. What a device is doing and whether power reaches it are separate facts, and GridQL keeps them separate.
gridql run outage -- Anything not in its normal position mRID name feeder state normal_state ---------- ------------------ ------- ----- ------------ FU-702-03 Willow Way tap FDR-702 OPEN CLOSED REC-701-01 Mill Pond Recloser FDR-701 OPEN CLOSED -- Fuses that are dead but not blown mRID name state --------- -------------------- ------ FU-701-02 Mill Pond Rd tap CLOSED FU-701-03 Millbrook High riser CLOSED FU-701-04 Orchard Ln tap CLOSED FU-701-05 Maplewood Care riser CLOSED
gridql run isolate -- The switches at the ends of the faulted span mRID name type state ---------- ------------------ -------- ------ FU-701-02 Mill Pond Rd tap fuse CLOSED REC-701-01 Mill Pond Recloser recloser OPEN SW-701-02 Pine St Switch switch CLOSED -- What a tie could pick up once it is opened COUNT(*) SUM(customer_count) SUM(kw) -------- ------------------- ------- 3 9 363
Cut off the fault. Pick up the rest.
The line patrol finds a tree across one span. GridQL names the switches at each end of it: the recloser upstream, already open, and the Pine St switch downstream.
Opening that switch leaves the school, the care home, and Orchard Ln healthy but dark — 363 kW that the neighbouring feeder can carry through the tie at the far end. The same file serves the next storm: the device, the span, and the switch are parameters.
Twenty back on. Ten waiting on the tree crew.
With the switch open and the tie closed, only the customers on the faulted span are still out. The ones picked up through the tie are energized from the south feeder — but they still belong to the north feeder, so nothing about the circuit’s design is quietly rewritten by a temporary switching state.
The last list is the one to close the day with: every switch that has to go back to normal.
gridql run restore --csv data/restored -- Still out mRID name feeder customer_count kw ------- ------------------ ------- -------------- -- SP-7022 Mill Pond Rd 2-20 FDR-701 6 22 SP-7024 Mill Pond Rd 22-30 FDR-701 4 14 -- To return to normal mRID name feeder state normal_state ----------- ---------------------- ------- ------ ------------ REC-701-01 Mill Pond Recloser FDR-701 OPEN CLOSED SW-701-02 Pine St Switch FDR-701 OPEN CLOSED TIE-701-702 Tie to Millbrook South FDR-701 CLOSED OPEN
Millbrook is an illustrative network shipped with GridQL as examples/storm-morning — run it yourself.
Your export, your column names.
Every source is read into the same network model, so a saved query does not care where the data came from. A mapping file says what your GIS export’s tables and columns mean — nobody has to rename anything — and rows that will not load are reported by line number instead of silently dropped.
Whatever your GIS writes
A file per equipment type, voltages in volts, switch positions as O and C, connectivity as nodes — a mapping reads it as it is.
Straight from the database
Read-only, through the same kind of mapping. Query it live, or keep a snapshot and refresh it when you choose.
The formats models travel in
Import CIM RDF/XML and OpenDSS models; export a whole network or a single query’s answer as CIM.
Answers you can use
Tables for people; JSON and CSV for scripts and spreadsheets; a Python API for everything else.
Free to use on your own grid.
AGPL-3.0-or-later. Source on GitHub.
No obligations. Download it, script against it, modify it, run it on your own network data.
Not covered. Your network model, your query results, and the .gridql files you write are yours.
GridQL, inside NodeFabric.
Today GridQL is a standalone tool. We are working toward NodeFabric supporting it natively, so the questions on this page can be asked of your live one-line — the same model your operators switch against and your crews read in the field.
About NodeFabricAsk your own feeders.
GridQL runs on Python 3.11 or later and ships with sample feeders, so the first query works before you have exported anything. When you are ready, point it at your own export.
pip install git+https://github.com/index-eng/gridql gridql 'FIND reclosers'
Write to info@index-labs.com.