Open-source query language for distribution networks

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.

Free & open source From the makers of NodeFabric
Millbrook — 06:40gridql
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
Who is without power right now, worked out from the switches as they stand — not from a list someone has to keep up to date.
What you can ask

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.

01  /  TOPOLOGY

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.

02  /  ENERGIZATION

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.

03  /  OFF-NORMAL

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.

04  /  PROTECTION

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.

05  /  ASSETS

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.

06  /  REUSE

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.

An outage, start to finish

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.

06:40  /  OUTAGE

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.

outage.gridql1 of 3
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
isolate.gridql2 of 3
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
THE PLAN  /  ISOLATE

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.

09:30  /  RESTORE

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.

restore.gridql3 of 3
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 data

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.

CSV  /  GIS exports

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.

Postgres

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.

CIM  /  OpenDSS

The formats models travel in

Import CIM RDF/XML and OpenDSS models; export a whole network or a single query’s answer as CIM.

Out

Answers you can use

Tables for people; JSON and CSV for scripts and spreadsheets; a Python API for everything else.

Licence

Free to use on your own grid.

Licence

AGPL-3.0-or-later. Source on GitHub.

Inside your utility

No obligations. Download it, script against it, modify it, run it on your own network data.

Your data

Not covered. Your network model, your query results, and the .gridql files you write are yours.

Coming Soon

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 NodeFabric
Get GridQL

Ask 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.

InstallPython 3.11+
pip install git+https://github.com/index-eng/gridql
gridql 'FIND reclosers'
Want help mapping your GIS export, or a commercial licence?
Write to info@index-labs.com.