This community repository automatically tracks power outages in the AES Ohio (formerly DP&L) service area. It polls the utility's public map data to build a historical, incident-level dataset that is otherwise unavailable to the public.
Please visit AES Ohio's official dashboard for auto-refreshing current outage information.
This is the main branch, which hosts the code for collecting and making sense of the data. The data branch is for data. See docs/local-dev.md to learn how to set up and run a local development environment.
Upcoming:
- For a moment in time, a distribution chart of incident size.
- Tie this to the mouse position on another chart? Or use a different control mechanism?
- Might want a time-picker component that can be reused, that uses the known time divisions. Then that can be used with the chart or it can key off another chart or control, like a scrub-bar.
- Incident map with time-travel scrub-bar
- Need underlying map tiles
- Need county boundaries
- Need AES area boundary
- Scan forwards/backwards to see data for a specific event, such as changes to estimated completion time, find the completion time, or see if customers impacted changed.
- In what ways can a specific event be accessed in the UI?
- Detect and list recent outage periods, since we assume that most major outages are detectable by a minimum, rapidly created size. This might or might not be a precomputed property. Sqlite can probably handle this.
- An auto-playing loop of an outage period, with highlighting of incidents as they are about to be resolved.
- Combine visualization settings
- Apply regional and time filters to charts and queries
- Perhaps allow building a custom dashboard? Or at least differentiate different views.
- Visually distinguish the different charts better
Low Priority:
- Consider normalizing times to absolute time (e.g. UTC), when possible. Only impacts one hour per year. UTC access time information is already in the commit messages generated by the Action.
- A report component (or set of components) as a sort of health dashboard that checks for missed updates in the dataset. Maybe a calendar? Show a color for each day depending on how many updates were missed. Then allowing clicking on a day to see a list of missing updates.
- Calendar view with peak affected customers per day determining size/brightness of color behind the day?
- Other datasette features, such as SpatiaLite for SQLite and geospatial plugins
- Understand how an event might move as it is updated. Might clarify the precision. See incident
1805707as an example with 35 updates that adjusted its location. - (Likely pre-empted.) Improve db updates. Currently: only on code push or GH Actions daily cron job.
- Rebuild more often, e.g. hourly, gated by recency of last build and level of current outages, rather than a flat daily cadence.
- Keep some older versions of the db around for anyone who loaded recently and hasn't refreshed?
- Show a warning if the old db is missing? Maybe suggest refresh in a red banner and disable new queries until then?
- Have the GH Action for data ingest retry on failure?
- Change the sqlite pattern to be inverted? Provide a base sqlite image, updated on some cadence for new visitors, then have the browser replay events up to the current one and keep it in OPFS. manifest.json tracks the latest file available. Use CSV, JSON, or sql .patch to hold new data. This would preserve offline querying, but require using up some visitor disk space. It's the browser's job to decide caching strategy, so that isn't really an issue.
- Using a gh-pages branch for this makes the update much smoother for GitHub, because it can use git diff to natively do cache invalidation and CDN updates, instead of needing a full tarball of the site. An update to site code only touches the site, and an update to built data only touches the data. In fact, we can just use a patch commit via the API instead of checking out the whole branch to make the commit. We also don't need an action cache, keeping the actions as simple as possible.
- Rely on webapp polling for updated data availability.
- This also lets the full heartbeat file exist only in the built gh-pages branch, avoiding steady bloat of the data branch.
- Every so often, we can reset git history on that branch when the main sqlite file changes, using an orphan branch to preserve only the current state, though it should be fully regenerable. The main issue is just not disrupting the site by deleting it while rebuilding.
- After some years, we'd need to stop assuming a visitor wants all data and make that slightly more opt-in. Which is its own challenge for managing what is or isn't in the dataset, and occurs separately from how the data is delivered and kept up to date.
- Note that this shift would improve many things: cleaner data branch, reduced build complexity (cleanly separate app and data updates without maintaining a pair of Actions caches), lower load on Actions (especially if GitHub modernizes the runner to make it lighter weight), respect visitor bandwidth, remove the need for jsdelivr (or even stop the separate usage of heartbeat.json and the db), reduce latency for data updates, and enable offline app usage. The main motivations for allowing latency were a) politeness to the build resources and b) avoiding competition with the official dashboard. This will provide an improvement on (a). And (b) is still philosophically the case, but introducing an arbitrary delay isn't necessary; we should still keep the current outage map off of the front page and any outage map should be focused on things the official dashboard doesn't do, like show the evolution of events over time.
- This can be split between 1) the simplified gh-pages build, data branch, and cache-invalidation and 2) in-browser patching. The latter introduces new browser compatibility and cache invalidation issues. Probably still worth it. May want to add some kind of telemetry for errors, if feasible.
Out of Scope:
- Real-time parity with the official dashboard. Implementing this feature could count as creating a substitute for the official dashboard. This project is focused on understanding historical facts. While data refresh is handy, any such feature should be focused on ease of use and historical context, not attempting to provide the most up-to-date data possible.
Commercial outage aggregators typically roll up data to the county, city, or zip-code level. By tracking the raw incident data over time, this dataset enables:
- Temporal Accessibility: A time-lapse view. The simple feeling that you can see what is happening. The ability to not just see a number go down, but to see the dots disappear as incidents resolve. The ability to see how big those numbers were before, so you know how bad it got and that progress has been steadily made, without needing to remember or physically record the numbers for yourself.
- Restoration Dynamics: Have incidents near me been resolved recently? Have incidents of the size affecting me been resolved recently? Are larger outages typically resolved faster than smaller ones? Provide an unofficial estimate for how long recovery will take. What were the cummulative outage hours? Were outages reported earlier resolved any sooner than those reported later, once those later ones were reported?
- Anomaly Detection: Identifying long-lived outages that persist after the main recovery period.
- Incident Dynamics: Correlating weather events to the physical size and spread of failures. Where did the storm hit first?
This project doesn't aim to implement all of those features, but it might try to answer many of them. And where the project doesn't provide an analysis, other members of the community can do their own.
The data is sourced directly from the public, unauthenticated XML endpoint used by the official AES Ohio outage map (DPLOMSDATA.xml).
This project operates under a strict "polite polling" policy:
- Matched Frequency: The script polls the endpoint every 15 minutes, matching the refresh rate of the utility's own dashboard.
- Negligible Footprint: The request fetches a small XML file, avoiding the additional bandwidth overhead for a web browser to load the full dashboard.
This project uses Git Scraping, a pattern pioneered by the civic tech community to archive shifting public data.
- A GitHub Action, invoked via webhook call by cron-job.org, runs a lightweight Python script every 15 minutes.
- The script fetches the XML, parses it into sorted JSON to ensure clean diffs, and logs the current total to a heartbeat file.
- If the incident data has changed (new outages appeared, or existing ones dropped off), the changes are committed to the
databranch. - Git's version history acts as a time-series database.
For more background on the methodology powering this repository, see Simon Willison's 5-minute lightning talk on Git scraping from 2021, which demonstrates using this pattern to track PG&E outages.
If you represent AES Ohio or GitHub and disagree with this understanding, please contact a project maintainer or submit an Issue on this GitHub project.
- Civic Purpose: This project operates on the principle that civic data relevant to public safety and utility reliability should be accessible for non-commercial analysis.
- Public Access: The data is public and unauthenticated. This project uses the same endpoint as AES Ohio's official dashboard. Automated access of public web data does not inherently constitute unauthorized access. (Van Buren established a gates-up/gates-down distinction, which the Ninth Circuit later applied to public web data in hiQ Labs v. LinkedIn, establishing persuasive federal precedent).
- Facts aren't Copyrightable: The outage figures are facts, and facts themselves are not copyrightable. We convert this data into our own schema: we use json and csv instead of xml, use field names of our choice, normalize field data, and split entity types (incident, county, total) across files.
- This project does currently store the upstream XML verbatim so that script behavior can be audited against the original; we believe any copyright interest by AES would rest in the selection and arrangement of that file rather than the underlying data. Since there are limited ways to format the essential core of outage incident data and the selection is based on whether or not the incident is known to exist, the presentation and selection in the file might not be copyrightable. Retention for non-commercial research, with no substitution for AES Ohio's own service, is a fair use posture, though not a determination anyone but a court can make. If requested, the project can drop retention of the raw XML.
- Absence of Damages: The system generates no meaningful load and causes no service degradation. Its operation is equivalent to someone leaving the dashboard open on a desktop computer.
- Data Granularity: AES already appears to apply fuzzing to incident locations, saying that "outage locations are approximate." Therefore, the risk of identifying specific AES Customers in a way that negatively impacts them is considered low.
- A maintainer couldn't tell which exact incident affected them, since the reporting subsystem acknowledged the outage existed for the address, but none were shown particularly close to them.
- The dataset underlying this web app allows finding when an incident was actually resolved, with at most 15min precision. This is considered negligibly different from the system already reporting: when an incident occurred, at one-minute precision; when an incident is expected to be resolved, at half-hour precision; or a user simply sitting there and checking every 15min. The main difference between the dashboard and this app is just the simultaneous and later-retrievable existence of incident data from multiple points in time.
- No Applicable Terms: The dashboard domain, myprofile.aes-ohio.com, does not have a robots.txt, nor does it have Terms of Service that could be located on 2026-09-06.
- For comparison, Google's ToS were easily locatable on the dashboard's map. A search in the Chrome Developer Tools did not show any other instance of the word "terms" in the page's HTML elements.
- The related subdomain, www.aes-ohio.com does have a robots.txt and a ToS, but neither suggests that they apply to the dashboard's subdomain. Browsewrap ToS are generally considered weak absent conspicuous notice or active consent, but that's barely relevant here.
- The robots.txt permits all user-agents to access many public parts of that site, with exclusions appearing to mainly be for site configuration, administration, user accounts, and content with dynamic paths. It has no default rule to allow or deny unenumerated paths and patterns.
- The ToS file, dated 2023-01-01 when observed on 2026-09-06, specifies that it is for
aes.com, the website ("Site"), does not mention Ohio, aes-ohio.com, or any subdomains of aes-ohio.com, and it only uses the plural "sites" when discussing links from the Site to third party websites. So it's not clear if it even applies to www.aes-ohio.com.
- GitHub Acceptable Use: GitHub hosts numerous Git Scraping projects for civic data. GitHub Next, an R&D team at GitHub, released Flat Data (not currently used by this project), a project which explicitly referenced the Git Scraping phenomenon as its basis. That doesn't provide absolute proof that this is within GitHub's ToS, but it does show clear institutional support for the pattern. Our compute burden remains negligible (running for ~10 seconds every 15 minutes) and the data contributes to an open-source research project.