PowerWatch
Community-reported electricity status for Nigerian neighbourhoods: an Expo mobile app, a Fastify API and a landing page. A neighbourhood's status follows the majority of distinct people who reported in the last 30 minutes.
- 74
- API endpoints
- 21
- Prisma models
- 26
- Mobile screens
- 6
- Geographic levels
- Role
- Author
- Year
- 2026
- Status
- Open source
- Type
- Power-outage reporting platform
01Overview
PowerWatch lets people report in one tap whether power is on or off where they live, and see the status of their neighbourhood before they get home. Everyone following that neighbourhood gets a push notification when the status changes.
It began as a team project for the Orange internship programme 2026, where I built the backend. The repository now holds three parts: the API, a mobile app for Android and iPhone, and a landing page. The API is Fastify 5 and Prisma 7 over MySQL, with 74 endpoints across eight route groups.
The interesting part is the geography. "Is the power out?" is a question about a neighbourhood, but outages are caused at substation and feeder level, which does not map onto any address a person would type.
02The problem
Nigerian grid supply is unreliable in ways that are locally obvious and nationally invisible. There is no shared record of who is off, for how long, or how often, so nobody can tell a bad week from a bad transformer.
Crowd-sourcing that record has two hard parts. The first is location: a report is only useful if it lands in the right area, and Nigerian addressing does not reliably resolve below a local government area. The second is trust: one wrong or repeated report must not flip the status of a whole neighbourhood.
It also has to work on a phone, on mobile data, during a blackout. That rules out anything heavy.
03Architecture
- 01
Six-level geographic hierarchy
Country → State → LGA → City → Town → Neighborhood, modelled as first-class entities and seeded with Nigeria's 37 states and their LGAs and towns. A report attaches at the finest level the user can identify, and every summary rolls up through the chain, so the same data answers both "is my street off" and "how did Ondo State do this month".
- 02
Consensus decides the status
Each person is counted once, by their most recent report in the window. The neighbourhood's status is the majority of those reporters over the last 30 minutes, and a tie keeps the current status. The server also refuses a second report from the same person for the same neighbourhood within 5 minutes.
- 03
Reports, outages and the join between them
Report is what a person submitted. Outage is the event. OutageReport joins them. When the consensus flips to OFF an outage starts at the earliest OFF report that was counted, and when it flips back the outage closes with its duration recorded.
- 04
Pre-aggregated summaries
DailyReportSummary, WeeklyOutageSummary and MonthlyStatistic hold report counts, outage totals and the longest outage per neighbourhood, so weekly and monthly figures are not recomputed from the report table on every request.
- 05
Layered Fastify backend
routes → controllers → services → repositories, with services split into commands and queries across ten areas, and Swagger generated from the route schemas. There are 21 Prisma models, and Prisma 7 talks to MySQL through the MariaDB adapter.
- 06
Auth and delivery
JWT access and refresh tokens with device-bound sessions, a 6-digit email code for sign-up and password reset, and rate limiting on the public routes. Push notifications go through the Expo Push Service, and outage alerts, restoration alerts and community updates each have their own switch. Every notification is stored in NotificationLog.
- 07
Mobile app and landing page
The app is Expo SDK 57 with Expo Router, 26 screens in light and dark mode, and a MapLibre map of nearby neighbourhoods coloured by status. The session token lives in SecureStore. The landing page is React 19 on Vite with Tailwind CSS and explains the app from 320 px phones up to desktop.
- 08
Audit trail
AuditLog and NotificationLog record who changed what and what was actually delivered. On a civic dataset that may be cited, being able to reconstruct the record matters more than saving the rows.
04Key decisions
Count people, not reports
A status that follows the latest report can be flipped by one person. Counting each person once inside a time window means a wrong report is outvoted, and reporting ten times counts as reporting once.
Geocode, then fall back offline
GPS coordinates are resolved through OSM Nominatim's boundary data, falling back to a local LGA coordinate dataset when that fails, and mapped onto the seeded six-level hierarchy. A first version used LGA centre points, which put reports near a boundary in the wrong area.
Materialise the summaries
The map and history read from pre-aggregated tables. Live aggregation over reports would have been simpler until the first thousand rows, and then permanently slower.
05Stack
- TypeScript
- Fastify 5
- Prisma 7
- MySQL
- Expo
- React Native
- MapLibre
- React 19
- Vite
- Tailwind CSS
- Expo Push
- Swagger
- Zod
- Nodemailer