1
2
Real-time web application for tracking power outages in Ukraine
3
across DTEK regions.
4
5
Features visual traffic light status indicators, multi-address
6
tracking with custom labels, and hourly schedules for planned
7
outages. Emergency alerts are distinguished with pulsing
8
indicators.
9
10
11
12
13
┌─┐ ┌─ ┌─┐ ┌─┐ 00:00 – 04:00 ██ power
14
│ │ ├─┐ ▪ │ │ │ │ ▶ 04:00 – 07:30 ░░ outage [██████░░░░]
15
└─┘ └─┘ ▪ └─┘ └─┘ 07:30 – 08:00 ▒▒ maybe
16
08:00 – 13:00 ██ power
17
today 13:00 – 16:30 ░░ outage
18
GPV1.2 16:30 – 17:00 ▒▒ maybe
19
17:00 – 21:00 ██ power
20
○ outage 21:00 – 23:30 ░░ outage
21
next 07:30 maybe 23:30 – 00:00 ██ power
22
in 1h30m
23
25
26
export function transformBuildingStatus(raw: DtekBuildingStatus): BuildingStatus {
27
const result: BuildingStatus = {};
28
29
// Extract schedule group (e.g., "GPV1.2")
30
const group = extractScheduleGroup(raw.sub_type_reason);
31
if (group) result.group = group;
32
33
// Set outage if API reports active blackout with dates
34
if (raw.type && raw.start_date && raw.end_date) {
35
result.outage = {
36
type: getOutageType(raw.sub_type),
37
from: raw.start_date,
38
to: raw.end_date,
39
};
40
}
41
42
return result;
43
}
44
45
46
47
48
49
export function transformBuildingStatus(raw: DtekBuildingStatus): BuildingStatus {
const result: BuildingStatus = {};
// Extract schedule group (e.g., "GPV1.2")
const group = extractScheduleGroup(raw.sub_type_reason);
if (group) result.group = group;
// Set outage if API reports active blackout with dates
if (raw.type && raw.start_date && raw.end_date) {
result.outage = {
type: getOutageType(raw.sub_type),
from: raw.start_date,
to: raw.end_date,
};
}
return result;
}
/**
* Extract schedule group ID from sub_type_reason array
* Looks for pattern like "GPV1.2", "GPV2.1", etc.
*/
export function extractScheduleGroup(subTypeReason: string[] | null): string | undefined {
if (!subTypeReason?.length) return undefined;
return subTypeReason.find((r) => /^GPV\d+\.\d+$/.test(r));
}
/**
* Determine outage type from DTEK sub_type field
*
* Known sub_type values:
* - "Аварійні ремонтні роботи" → emergency (infrastructure failure)
* - "Стабілізаційне відключення (Згідно графіку погодинних відключень)" → stabilization
* - "Планові ремонтні роботи" → planned
* - null or unknown → planned (safe default, no pulsing)
*/
export function getOutageType(subType: string | null): OutageType {
if (!subType) return 'planned';
if (subType.includes('Аварійн')) return 'emergency';
if (subType.includes('Стабілізаційн')) return 'stabilization';
return 'planned';
}
/**
* Helper to extract Set-Cookie headers from Response
* Node 20+ has headers.getSetCookie(), fallback for older versions
*/
function getSetCookieHeaders(headers: Headers): string[] {
// Node 20+ has getSetCookie() method on Headers
const headersWithGetSetCookie = headers as Headers & {
getSetCookie?: () => string[];
};
if (typeof headersWithGetSetCookie.getSetCookie === 'function') {
return headersWithGetSetCookie.getSetCookie();
}
const sc = headers.get('set-cookie');
return sc ? [sc] : [];
}
/**
* Service registry for per-region instances
* Lazily creates service instances on first access
*/
const serviceRegistry = new Map<RegionCode, DtekService>();
/**
* Get a DtekService instance for a specific region
* Creates a new instance if one doesn't exist for that region
*
* @param region - Region code (e.g., 'kem', 'oem', 'dnem', 'dem')
* @returns DtekService instance for the specified region
*/
export function getDtekService(region: RegionCode): DtekService {
let service = serviceRegistry.get(region);
if (!service) {
service = createDtekService(region);
serviceRegistry.set(region, service);
}
return service;
}