ua-outages 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -2
  3. package/dist/certs.d.ts +14 -0
  4. package/dist/certs.d.ts.map +1 -0
  5. package/dist/certs.js +51 -0
  6. package/dist/certs.js.map +1 -0
  7. package/dist/client.d.ts +107 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +283 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/errors.d.ts +32 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +12 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/fuzzy.d.ts +28 -0
  16. package/dist/fuzzy.d.ts.map +1 -0
  17. package/dist/fuzzy.js +173 -0
  18. package/dist/fuzzy.js.map +1 -0
  19. package/dist/http.d.ts +53 -0
  20. package/dist/http.d.ts.map +1 -0
  21. package/dist/http.js +121 -0
  22. package/dist/http.js.map +1 -0
  23. package/dist/https-fetch.d.ts +7 -0
  24. package/dist/https-fetch.d.ts.map +1 -0
  25. package/dist/https-fetch.js +44 -0
  26. package/dist/https-fetch.js.map +1 -0
  27. package/dist/image/cards.d.ts +45 -0
  28. package/dist/image/cards.d.ts.map +1 -0
  29. package/dist/image/cards.js +229 -0
  30. package/dist/image/cards.js.map +1 -0
  31. package/dist/image/digit-model.d.ts +2 -0
  32. package/dist/image/digit-model.d.ts.map +1 -0
  33. package/dist/image/digit-model.js +48 -0
  34. package/dist/image/digit-model.js.map +1 -0
  35. package/dist/image/glyphs.d.ts +59 -0
  36. package/dist/image/glyphs.d.ts.map +1 -0
  37. package/dist/image/glyphs.js +234 -0
  38. package/dist/image/glyphs.js.map +1 -0
  39. package/dist/image/grid.d.ts +46 -0
  40. package/dist/image/grid.d.ts.map +1 -0
  41. package/dist/image/grid.js +137 -0
  42. package/dist/image/grid.js.map +1 -0
  43. package/dist/image/jpeg.d.ts +9 -0
  44. package/dist/image/jpeg.d.ts.map +1 -0
  45. package/dist/image/jpeg.js +309 -0
  46. package/dist/image/jpeg.js.map +1 -0
  47. package/dist/image/png.d.ts +16 -0
  48. package/dist/image/png.d.ts.map +1 -0
  49. package/dist/image/png.js +149 -0
  50. package/dist/image/png.js.map +1 -0
  51. package/dist/image/table.d.ts +27 -0
  52. package/dist/image/table.d.ts.map +1 -0
  53. package/dist/image/table.js +47 -0
  54. package/dist/image/table.js.map +1 -0
  55. package/dist/image/template-codec.d.ts +33 -0
  56. package/dist/image/template-codec.d.ts.map +1 -0
  57. package/dist/image/template-codec.js +49 -0
  58. package/dist/image/template-codec.js.map +1 -0
  59. package/dist/index.d.ts +14 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +7 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/providers/cherkasy.d.ts +32 -0
  64. package/dist/providers/cherkasy.d.ts.map +1 -0
  65. package/dist/providers/cherkasy.js +111 -0
  66. package/dist/providers/cherkasy.js.map +1 -0
  67. package/dist/providers/chernihiv.d.ts +8 -0
  68. package/dist/providers/chernihiv.d.ts.map +1 -0
  69. package/dist/providers/chernihiv.js +81 -0
  70. package/dist/providers/chernihiv.js.map +1 -0
  71. package/dist/providers/chernivtsi.d.ts +35 -0
  72. package/dist/providers/chernivtsi.d.ts.map +1 -0
  73. package/dist/providers/chernivtsi.js +288 -0
  74. package/dist/providers/chernivtsi.js.map +1 -0
  75. package/dist/providers/dtek.d.ts +12 -0
  76. package/dist/providers/dtek.d.ts.map +1 -0
  77. package/dist/providers/dtek.js +132 -0
  78. package/dist/providers/dtek.js.map +1 -0
  79. package/dist/providers/gpv.d.ts +24 -0
  80. package/dist/providers/gpv.d.ts.map +1 -0
  81. package/dist/providers/gpv.js +81 -0
  82. package/dist/providers/gpv.js.map +1 -0
  83. package/dist/providers/image-schedule.d.ts +19 -0
  84. package/dist/providers/image-schedule.d.ts.map +1 -0
  85. package/dist/providers/image-schedule.js +51 -0
  86. package/dist/providers/image-schedule.js.map +1 -0
  87. package/dist/providers/index.d.ts +4 -0
  88. package/dist/providers/index.d.ts.map +1 -0
  89. package/dist/providers/index.js +107 -0
  90. package/dist/providers/index.js.map +1 -0
  91. package/dist/providers/ivano-frankivsk.d.ts +31 -0
  92. package/dist/providers/ivano-frankivsk.d.ts.map +1 -0
  93. package/dist/providers/ivano-frankivsk.js +184 -0
  94. package/dist/providers/ivano-frankivsk.js.map +1 -0
  95. package/dist/providers/kharkiv.d.ts +18 -0
  96. package/dist/providers/kharkiv.d.ts.map +1 -0
  97. package/dist/providers/kharkiv.js +47 -0
  98. package/dist/providers/kharkiv.js.map +1 -0
  99. package/dist/providers/khmelnytskyi.d.ts +32 -0
  100. package/dist/providers/khmelnytskyi.d.ts.map +1 -0
  101. package/dist/providers/khmelnytskyi.js +194 -0
  102. package/dist/providers/khmelnytskyi.js.map +1 -0
  103. package/dist/providers/kirovohrad.d.ts +20 -0
  104. package/dist/providers/kirovohrad.d.ts.map +1 -0
  105. package/dist/providers/kirovohrad.js +264 -0
  106. package/dist/providers/kirovohrad.js.map +1 -0
  107. package/dist/providers/lviv.d.ts +18 -0
  108. package/dist/providers/lviv.d.ts.map +1 -0
  109. package/dist/providers/lviv.js +146 -0
  110. package/dist/providers/lviv.js.map +1 -0
  111. package/dist/providers/mykolaiv.d.ts +24 -0
  112. package/dist/providers/mykolaiv.d.ts.map +1 -0
  113. package/dist/providers/mykolaiv.js +132 -0
  114. package/dist/providers/mykolaiv.js.map +1 -0
  115. package/dist/providers/poltava.d.ts +11 -0
  116. package/dist/providers/poltava.d.ts.map +1 -0
  117. package/dist/providers/poltava.js +171 -0
  118. package/dist/providers/poltava.js.map +1 -0
  119. package/dist/providers/posts.d.ts +18 -0
  120. package/dist/providers/posts.d.ts.map +1 -0
  121. package/dist/providers/posts.js +50 -0
  122. package/dist/providers/posts.js.map +1 -0
  123. package/dist/providers/poweron.d.ts +66 -0
  124. package/dist/providers/poweron.d.ts.map +1 -0
  125. package/dist/providers/poweron.js +218 -0
  126. package/dist/providers/poweron.js.map +1 -0
  127. package/dist/providers/provider.d.ts +42 -0
  128. package/dist/providers/provider.d.ts.map +1 -0
  129. package/dist/providers/provider.js +2 -0
  130. package/dist/providers/provider.js.map +1 -0
  131. package/dist/providers/rivne.d.ts +6 -0
  132. package/dist/providers/rivne.d.ts.map +1 -0
  133. package/dist/providers/rivne.js +83 -0
  134. package/dist/providers/rivne.js.map +1 -0
  135. package/dist/providers/sumy.d.ts +30 -0
  136. package/dist/providers/sumy.d.ts.map +1 -0
  137. package/dist/providers/sumy.js +203 -0
  138. package/dist/providers/sumy.js.map +1 -0
  139. package/dist/providers/ternopil.d.ts +37 -0
  140. package/dist/providers/ternopil.d.ts.map +1 -0
  141. package/dist/providers/ternopil.js +217 -0
  142. package/dist/providers/ternopil.js.map +1 -0
  143. package/dist/providers/ua-text.d.ts +33 -0
  144. package/dist/providers/ua-text.d.ts.map +1 -0
  145. package/dist/providers/ua-text.js +144 -0
  146. package/dist/providers/ua-text.js.map +1 -0
  147. package/dist/providers/uz.d.ts +17 -0
  148. package/dist/providers/uz.d.ts.map +1 -0
  149. package/dist/providers/uz.js +192 -0
  150. package/dist/providers/uz.js.map +1 -0
  151. package/dist/providers/volyn.d.ts +29 -0
  152. package/dist/providers/volyn.d.ts.map +1 -0
  153. package/dist/providers/volyn.js +159 -0
  154. package/dist/providers/volyn.js.map +1 -0
  155. package/dist/providers/yasno.d.ts +29 -0
  156. package/dist/providers/yasno.d.ts.map +1 -0
  157. package/dist/providers/yasno.js +121 -0
  158. package/dist/providers/yasno.js.map +1 -0
  159. package/dist/providers/zakarpattia.d.ts +19 -0
  160. package/dist/providers/zakarpattia.d.ts.map +1 -0
  161. package/dist/providers/zakarpattia.js +98 -0
  162. package/dist/providers/zakarpattia.js.map +1 -0
  163. package/dist/providers/zaporizhzhia.d.ts +11 -0
  164. package/dist/providers/zaporizhzhia.d.ts.map +1 -0
  165. package/dist/providers/zaporizhzhia.js +47 -0
  166. package/dist/providers/zaporizhzhia.js.map +1 -0
  167. package/dist/providers/zhytomyr.d.ts +21 -0
  168. package/dist/providers/zhytomyr.d.ts.map +1 -0
  169. package/dist/providers/zhytomyr.js +231 -0
  170. package/dist/providers/zhytomyr.js.map +1 -0
  171. package/dist/schedule.d.ts +41 -0
  172. package/dist/schedule.d.ts.map +1 -0
  173. package/dist/schedule.js +127 -0
  174. package/dist/schedule.js.map +1 -0
  175. package/dist/text.d.ts +24 -0
  176. package/dist/text.d.ts.map +1 -0
  177. package/dist/text.js +156 -0
  178. package/dist/text.js.map +1 -0
  179. package/dist/time.d.ts +23 -0
  180. package/dist/time.d.ts.map +1 -0
  181. package/dist/time.js +83 -0
  182. package/dist/time.js.map +1 -0
  183. package/dist/types.d.ts +133 -0
  184. package/dist/types.d.ts.map +1 -0
  185. package/dist/types.js +2 -0
  186. package/dist/types.js.map +1 -0
  187. package/dist/util.d.ts +5 -0
  188. package/dist/util.d.ts.map +1 -0
  189. package/dist/util.js +19 -0
  190. package/dist/util.js.map +1 -0
  191. package/package.json +63 -4
  192. package/src/certs.ts +51 -0
  193. package/src/client.ts +413 -0
  194. package/src/errors.ts +29 -0
  195. package/src/fuzzy.ts +183 -0
  196. package/src/http.ts +165 -0
  197. package/src/https-fetch.ts +50 -0
  198. package/src/image/cards.ts +276 -0
  199. package/src/image/digit-model.ts +48 -0
  200. package/src/image/glyphs.ts +262 -0
  201. package/src/image/grid.ts +164 -0
  202. package/src/image/jpeg.ts +351 -0
  203. package/src/image/png.ts +158 -0
  204. package/src/image/table.ts +66 -0
  205. package/src/image/template-codec.ts +78 -0
  206. package/src/index.ts +13 -0
  207. package/src/providers/cherkasy.ts +138 -0
  208. package/src/providers/chernihiv.ts +94 -0
  209. package/src/providers/chernivtsi.ts +344 -0
  210. package/src/providers/dtek.ts +173 -0
  211. package/src/providers/gpv.ts +96 -0
  212. package/src/providers/image-schedule.ts +69 -0
  213. package/src/providers/index.ts +108 -0
  214. package/src/providers/ivano-frankivsk.ts +235 -0
  215. package/src/providers/kharkiv.ts +59 -0
  216. package/src/providers/khmelnytskyi.ts +230 -0
  217. package/src/providers/kirovohrad.ts +333 -0
  218. package/src/providers/lviv.ts +173 -0
  219. package/src/providers/mykolaiv.ts +168 -0
  220. package/src/providers/poltava.ts +198 -0
  221. package/src/providers/posts.ts +61 -0
  222. package/src/providers/poweron.ts +261 -0
  223. package/src/providers/provider.ts +42 -0
  224. package/src/providers/rivne.ts +101 -0
  225. package/src/providers/sumy.ts +249 -0
  226. package/src/providers/ternopil.ts +275 -0
  227. package/src/providers/ua-text.ts +140 -0
  228. package/src/providers/uz.ts +219 -0
  229. package/src/providers/volyn.ts +215 -0
  230. package/src/providers/yasno.ts +167 -0
  231. package/src/providers/zakarpattia.ts +117 -0
  232. package/src/providers/zaporizhzhia.ts +52 -0
  233. package/src/providers/zhytomyr.ts +283 -0
  234. package/src/schedule.ts +140 -0
  235. package/src/text.ts +172 -0
  236. package/src/time.ts +98 -0
  237. package/src/types.ts +193 -0
  238. package/src/util.ts +23 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pavlo Huk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,229 @@
1
- # Temporary Holding Version
1
+ # ua-outages
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript SDK for Ukrainian power outage schedules (графіки погодинних відключень).
4
+
5
+ - **Schedules straight from the grid operators.** No aggregators or Telegram channels in between: each region reads its oblenergo's own API or page.
6
+ - **Find a group by address.** Settlement, street and house in; the outage group ("черга") out.
7
+ - **One shape for every region.** Intervals in Kyiv time with explicit offsets, DST-safe.
8
+ - **Health check.** See in one call which sources still parse, so you notice when an operator changes its site.
9
+ - No runtime dependencies. Node 20+.
10
+
11
+ **See it running: [hukpo.github.io/ua-outages](https://hukpo.github.io/ua-outages/).** A live map of Ukraine where every light is a house in one of the groups and goes dark when its group is scheduled off, plus a countdown for your group, calendar feeds, shared "power everywhere" windows for several addresses, and an archive. It is a static page rebuilt by GitHub Actions every 15 minutes from this SDK (`site/`, `.github/workflows/site.yml`).
12
+
13
+ ```sh
14
+ npm install ua-outages
15
+ ```
16
+
17
+ ```ts
18
+ import { OutageClient, stateAt, nextOutage } from 'ua-outages'
19
+
20
+ const outages = new OutageClient()
21
+
22
+ const { group } = await outages.findGroup('kyiv', { street: 'вул. Хрещатик', house: '6' })
23
+ const schedule = await outages.getSchedule('kyiv', group)
24
+
25
+ stateAt(schedule.days) // 'on' | 'off' | 'maybe' | 'unknown'
26
+ nextOutage(schedule.days) // { from: '2026-11-05T19:30:00+02:00', to: '2026-11-06T00:00:00+02:00', kind: 'off' }
27
+ ```
28
+
29
+ ## Regions
30
+
31
+ <!-- regions:start -->
32
+
33
+ | id | region | operator | SDK reads | schedule | address | notes | schedule | address lookup |
34
+ | ----------------- | ----------------------- | --------------------------------------- | -------------------------------------------------------------- | ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
35
+ | `kyiv` | Київ | ДТЕК Київські електромережі (YASNO) | app.yasno.ua API | ✅ | ✅ | | [static.yasno.ua](https://static.yasno.ua/kyiv/outages) | [static.yasno.ua](https://static.yasno.ua/kyiv/outages) |
36
+ | `dnipro` | Дніпропетровська (ДТЕК) | ДТЕК Дніпровські електромережі (YASNO) | app.yasno.ua API | ✅ | ✅ | | [static.yasno.ua](https://static.yasno.ua/dnipro/outages) | [static.yasno.ua](https://static.yasno.ua/dnipro/outages) |
37
+ | `dnipro-cek` | Дніпропетровська (ЦЕК) | ЦЕК (YASNO) | app.yasno.ua API | ✅ | ✅ | | [static.yasno.ua](https://static.yasno.ua/dnipro/outages) | [static.yasno.ua](https://static.yasno.ua/dnipro/outages) |
38
+ | `odesa` | Одеська | ДТЕК Одеські електромережі | schedule page + /ua/ajax | ✅ | ✅ | | [dtek-oem.com.ua](https://www.dtek-oem.com.ua/ua/shutdowns) | [dtek-oem.com.ua](https://www.dtek-oem.com.ua/ua/shutdowns) |
39
+ | `kyiv-region` | Київська | ДТЕК Київські регіональні електромережі | schedule page + /ua/ajax (same provider as Odesa) | ⚠️ code ready | ⚠️ code ready | the site answers automated clients with a bot check, so requests come back `blocked`; untested against live data | [dtek-krem.com.ua](https://www.dtek-krem.com.ua/ua/shutdowns) | [dtek-krem.com.ua](https://www.dtek-krem.com.ua/ua/shutdowns) |
40
+ | `donetsk` | Донецька | ДТЕК Донецькі електромережі | schedule page + /ua/ajax (same provider as Odesa) | ⚠️ code ready | ⚠️ code ready | the site answers automated clients with a bot check, so requests come back `blocked`; untested against live data | [dtek-dem.com.ua](https://www.dtek-dem.com.ua/ua/shutdowns) | [dtek-dem.com.ua](https://www.dtek-dem.com.ua/ua/shutdowns) |
41
+ | `lviv` | Львівська | Львівобленерго | api.loe.lviv.ua, power-api.loe.lviv.ua | ✅ | ✅ | | [poweron.loe.lviv.ua](https://poweron.loe.lviv.ua/) | [poweron.loe.lviv.ua](https://poweron.loe.lviv.ua/) |
42
+ | `ternopil` | Тернопільська | Тернопільобленерго | api-poweron.toe.com.ua | ✅ | ✅ | schedule read per group via a reference address | [poweron.toe.com.ua](https://poweron.toe.com.ua/) | [poweron.toe.com.ua](https://poweron.toe.com.ua/) |
43
+ | `zakarpattia` | Закарпатська | Закарпаттяобленерго | api-outage-zakarpat-energy.inneti.net | ✅ / image | ✅ | no outages while no picture is published; a published picture is reported as `unsupported` | [zakarpat.energy](https://zakarpat.energy/customers/break-in-electricity-supply/schedule/) | [outage.zakarpat.energy](https://outage.zakarpat.energy/schedule_queues) |
44
+ | `ivano-frankivsk` | Івано-Франківська | Прикарпаттяобленерго | be-svitlo.oe.if.ua | ✅ | ✅ | full address only, no autocomplete | [svitlo.oe.if.ua](https://svitlo.oe.if.ua/) | [svitlo.oe.if.ua](https://svitlo.oe.if.ua/) |
45
+ | `chernivtsi` | Чернівецька | Чернівціобленерго | schedule page + district directories | ✅ | ✅ | groups numbered 1–12 | [oblenergo.cv.ua](https://oblenergo.cv.ua/shutdowns/) | [oblenergo.cv.ua](https://oblenergo.cv.ua/shutdowns-search/) |
46
+ | `rivne` | Рівненська | Рівнеобленерго | schedule page | ✅ | — | addresses only in PDF | [roe.vsei.ua](https://www.roe.vsei.ua/disconnections) | — |
47
+ | `volyn` | Волинська | Волиньобленерго | inneti archive API + table image (read by colour), lookup form | ✅ image | ✅ | full address only, no autocomplete | [energy.volyn.ua](https://energy.volyn.ua/spozhyvacham/perervy-u-elektropostachanni/hrafik-vidkliuchen/) | [energy.volyn.ua](https://energy.volyn.ua/spozhyvacham/perervy-u-elektropostachanni/cherga/) |
48
+ | `khmelnytskyi` | Хмельницька | Хмельницькобленерго | page text, else archive table image (read by colour) | ✅ text/image | ✅ | | [hoe.com.ua](https://hoe.com.ua/page/pogodinni-vidkljuchennja) | [hoe.com.ua](https://hoe.com.ua/shutdown/queue) |
49
+ | `chernihiv` | Чернігівська | Чернігівобленерго | schedule poster (times read from the picture) | ✅ image | — | the schedule API and the lookup need a captcha; the same schedule is published as a poster | [chernihivoblenergo.com.ua](https://chernihivoblenergo.com.ua/files/other/schedule_groups.jpg) | [interruptions.energy.cn.ua](https://interruptions.energy.cn.ua/interruptions) |
50
+ | `zhytomyr` | Житомирська | Житомиробленерго | schedule page + lookup form | ✅ | ✅ | | [ztoe.com.ua](https://www.ztoe.com.ua/unhooking-search.php) | [ztoe.com.ua](https://www.ztoe.com.ua/unhooking-search.php) |
51
+ | `kirovohrad` | Кіровоградська | Кіровоградобленерго | kiroe.com.ua JSON | ✅ today | ✅ | | [kiroe.com.ua](https://kiroe.com.ua/electricity-blackout) | [kiroe.com.ua](https://kiroe.com.ua/electricity-blackout) |
52
+ | `cherkasy` | Черкаська | Черкасиобленерго | news API | ✅ | — | lookup server has a broken TLS chain | [cherkasyoblenergo.com](https://www.cherkasyoblenergo.com/off) | [cabinet.cherkasyoblenergo.com](https://cabinet.cherkasyoblenergo.com/) |
53
+ | `sumy` | Сумська | Сумиобленерго | soe.com.ua/next-api | ✅ today | ✅ | | [soe.com.ua](https://www.soe.com.ua/consumers/shutdowns) | [soe.com.ua](https://www.soe.com.ua/consumers/shutdowns) |
54
+ | `poltava` | Полтавська | Полтаваобленерго | poe.pl.ua/customs | ✅ | ✅ | buildings with several connections have no single group | [poe.pl.ua](https://www.poe.pl.ua/disconnection/power-outages/) | [poe.pl.ua](https://www.poe.pl.ua/) |
55
+ | `kharkiv` | Харківська | Харківобленерго | Drupal JSON:API (news) | ✅ | — | lookup needs a captcha | [oblenergo.kharkiv.ua](https://oblenergo.kharkiv.ua/uk) | [oblenergo.kharkiv.ua](https://oblenergo.kharkiv.ua/uk/gaoinfo) |
56
+ | `zaporizhzhia` | Запорізька | Запоріжжяобленерго | news page | ✅ | — | no web lookup | [zoe.com.ua](https://www.zoe.com.ua/outage/) | [Telegram bot](https://t.me/esozoebot) |
57
+ | `mykolaiv` | Миколаївська | Миколаївобленерго | off.energy.mk.ua API | ✅ | ✅ | | [off.energy.mk.ua](https://off.energy.mk.ua/) | [off.energy.mk.ua](https://off.energy.mk.ua/) |
58
+
59
+ **АТ «Укрзалізниця»** is a distribution operator too: homes near stations and along the lines are wired to the railway's grid and get its own schedule, which differs from the oblenergo's. It has one region per oblast page on [uz.gov.ua](https://www.uz.gov.ua/about/activity/electropostachannia/electro_consumers/temporary_shutdown/grafikiobmezhen/): `uz-kyiv`, `uz-kyiv-region`, `uz-dnipro`, … (`uz-<region id>`), and two branches each in Vinnytsia (`uz-vinnytsia`, `uz-vinnytsia-odesa`) and Sumy (`uz-sumy`, `uz-sumy-south`). Today's schedule only, read from the HTML table; a page with no table means no outages. The page also lists each group's addresses (no lookup yet).
60
+
61
+ **Not covered**
62
+
63
+ | region | operator | why | schedule | address lookup |
64
+ | ---------- | ---------------- | ------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------- |
65
+ | Вінницька | Вінницяобленерго | schedule and lookup behind Cloudflare | [voe.com.ua](https://www.voe.com.ua/disconnection/detailed) | [disconnection.voe.com.ua](https://disconnection.voe.com.ua/) |
66
+ | Херсонська | Херсонобленерго | no public schedule | [ksoe.com.ua](https://ksoe.com.ua/) | — |
67
+
68
+ Луганська область and Crimea are not covered. "today" means the operator publishes only the current day. "⚠️ code ready" means the provider is implemented but the operator currently blocks automated requests. "image" means the schedule is read from the table picture the operator publishes.
69
+ <!-- regions:end -->
70
+
71
+ "Address" means the SDK can turn an address into a group. Zakarpattia publishes its schedule only as a picture: while none is published `getSchedules` returns no groups (no outages announced); once one is, it throws `unsupported` with the picture's address. Its address lookup works either way.
72
+
73
+ ### Why some regions are missing
74
+
75
+ Kyiv region and Donetsk (DTEK sites) answer automated clients with a bot check, so these providers report `blocked`. Vinnytsia keeps its data behind a bot check, and Kherson publishes no schedule, so they have no provider. This SDK does not try to get around access controls.
76
+
77
+ If an operator grants you access (an allowlisted server, an API key, a data-sharing agreement), pass a `fetch` that applies it. Providers make every request through it:
78
+
79
+ ```ts
80
+ const outages = new OutageClient({
81
+ fetch: (url, init) => {
82
+ // e.g. route DTEK requests through the server the operator allowlisted for you
83
+ if (url.startsWith('https://www.dtek-krem.com.ua/'))
84
+ return fetch(url.replace('https://www.dtek-krem.com.ua', ALLOWLISTED_PROXY), init)
85
+ return fetch(url, init)
86
+ },
87
+ })
88
+ ```
89
+
90
+ In interactive tools (a CLI, a desktop app, a script you run yourself), `fetch` can also be backed by a visible browser window: the person using the tool passes the site's check themselves, and the requests then run in that same browser session. This SDK doesn't ship such a fetch and never solves checks on anyone's behalf. Expect the session to expire and the check to come back. It doesn't fit servers or cron jobs, where nobody is there to pass it.
91
+
92
+ The same option works for proxies, caching, logging and tests. When an operator opens its data publicly, as YASNO did for Kyiv and Dnipro, support follows; issues and contacts at operators are welcome.
93
+
94
+ ## API
95
+
96
+ ### `new OutageClient(options?)`
97
+
98
+ | option | default | |
99
+ | ------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
100
+ | `cacheTtlMs` | `300000` | Reuse a region's schedule for this long. `0` turns caching off. Concurrent requests for one region share a single fetch. |
101
+ | `timeoutMs` | `15000` | Per request. |
102
+ | `retries` | `1` | Extra attempts after a network error or a 5xx. |
103
+ | `fetch` | `globalThis.fetch` | Bring your own (proxy, logging, tests). |
104
+ | `userAgent` | `ua-outages (+repo url)` | |
105
+ | `providers` | built-in | Replace or extend the regions. |
106
+
107
+ ### Schedules
108
+
109
+ ```ts
110
+ outages.regions() // RegionInfo[]: id, name, operator, capabilities
111
+ await outages.getSchedules('lviv') // every group: { groups, schedules: { '1.1': DaySchedule[] }, updatedAt }
112
+ await outages.getSchedule('lviv', '3.1') // one group; accepts "GPV3.1", "черга 3.1", …
113
+ ```
114
+
115
+ A `DaySchedule` is `{ date: 'YYYY-MM-DD', status, intervals }`:
116
+
117
+ - `status` is `scheduled`, `no-outages`, `emergency` (the plan doesn't apply) or `unknown` (not published yet).
118
+ - `intervals` are sorted and merged: `{ from, to, kind }`, where `kind` is `off` or `maybe` ("можливе відключення", switching time).
119
+
120
+ `updatedAt` is when the operator last changed the schedule, when the source says so.
121
+
122
+ ### Schedules published as pictures
123
+
124
+ Volyn publishes the schedule only as a table image, and Khmelnytskyi does when it doesn't post it as text on its schedule page (the text wins for its date). The SDK reads those itself: it decodes the PNG, finds the grid and classifies each cell by colour, with no dependencies. When an operator posts a text announcement as an image under the same date, it is skipped. A colour the SDK doesn't know is reported as a `parse` error instead of being guessed.
125
+
126
+ Chernihiv publishes a poster of twelve cards, one per group, with the off times written out ("00:00 - 02:30"). The SDK reads those too, without OCR: it decodes the JPEG, finds the orange cards, cuts each time line into glyphs and matches the digits against a model of the poster font, trained on hand-checked posters (`scripts/train-digits.ts`). The date comes from the poster header. A digit it isn't sure about, a time line it can't read or a date that isn't current gives a `parse` error, never a guess. On 30 older posters the model hadn't seen (Telegram copies at 40% of the original size), it read 24 exactly and refused the other 6. The original on the site is full size.
127
+
128
+ ### Address → group
129
+
130
+ ```ts
131
+ await outages.findGroup('dnipro', { city: 'Днепр', street: 'проспект Яворницкого', house: '41' })
132
+ // { region: 'dnipro', group: '5.1', address: { city: 'м. Дніпро', street: 'просп. Дмитра Яворницького', house: '41' } }
133
+ ```
134
+
135
+ People rarely know a street's official name, so matching is forgiving:
136
+
137
+ | they type | finds |
138
+ | ----------------------------------- | -------------------------------------------------------------- |
139
+ | `Хрищатик`, `крещатик` | вул. Хрещатик (typos) |
140
+ | `Адмиральская`, `Днепр`, `Киевская` | вул. Адміральська, м. Дніпро, вул. Київська (Russian spelling) |
141
+ | `Тараса Шевченка` | вул. Шевченка (the directory omits the first name) |
142
+ | `Богдана Хмельницького` | Хмельницького Б. (an initial) |
143
+ | `Центральний проспект` | пр. Центральний (type in any position) |
144
+ | `Шевч` | вул. Шевченка (still typing) |
145
+
146
+ `findGroup` picks a name on its own only when one match is clearly the best. Otherwise it throws `not-found` with suggestions, for example when "Соборна" is both a вулиця and a площа, or two villages share a name (pass `district` then). Some buildings belong to more than one group; you get those in `otherGroups`.
147
+
148
+ #### Autocomplete
149
+
150
+ ```ts
151
+ await outages.prefetch('lviv') // optional: load the settlement list before the user types
152
+
153
+ const [city] = await outages.searchCities('lviv', 'Львов')
154
+ const streets = await outages.searchStreets('lviv', 'Городоцк', { city, limit: 10 })
155
+ // [{ id, name: 'Городоцька', score: 0.92 }, { name: 'Суха-Городоцька', score: 0.74 }, …]
156
+ const houses = await outages.searchHouses('lviv', streets[0])
157
+ const match = await outages.groupOf('lviv', houses[0], streets[0], city)
158
+ ```
159
+
160
+ Results come back ranked, best first, each with a `score` from 0 to 1. Most operators publish a whole directory: the SDK downloads it once and searches it locally, so later keystrokes cost nothing. For operators whose server does the searching (YASNO streets, Sumy, Khmelnytskyi), a misspelled query is also sent as word fragments, so a typo still finds the street. `capabilities.streetSearch` tells you whether a region supports autocomplete. Ivano-Frankivsk and Volyn only check a complete address, so there `findGroup` works but autocomplete doesn't.
161
+
162
+ ### Helpers
163
+
164
+ ```ts
165
+ stateAt(days, at?) // power state at an instant
166
+ intervalAt(days, at?) // the interval covering an instant
167
+ nextOutage(days, from?, { kinds?, includeCurrent? }) // joins outages across midnight
168
+ minutesOf(day, kind?) // total scheduled minutes
169
+ normalizeGroup('Черга 3.1') // '3.1'
170
+ fuzzyScore('Хрищатик', 'вул. Хрещатик') // 0..1, the matcher behind the search
171
+ ```
172
+
173
+ ### Health check
174
+
175
+ ```ts
176
+ const report = await outages.healthCheck({ address: true })
177
+ // { ok, checkedAt, regions: [{ region, status: 'ok' | 'stale' | 'empty' | 'error', latencyMs, groups, updatedAt, message, address }] }
178
+ ```
179
+
180
+ - `empty`: the source works but publishes nothing right now. Usually it means there are no outages.
181
+ - `stale`: the schedule hasn't changed for `staleAfterHours` (default 72).
182
+ - `error`: a network failure, a bot check (`blocked`), or a format change (`parse`).
183
+
184
+ From the terminal: `npm run health` (`-- --address`, `-- --json`, `-- kyiv lviv`). The `Source health` workflow runs it daily.
185
+
186
+ ### Errors
187
+
188
+ Everything throws `OutageError` with a `code`:
189
+
190
+ | code | meaning |
191
+ | ------------- | --------------------------------------------------------------- |
192
+ | `network` | DNS, timeout, connection reset |
193
+ | `http` | non-2xx from the operator |
194
+ | `blocked` | a firewall or bot check answered instead of the operator |
195
+ | `parse` | the response arrived, but its format changed |
196
+ | `unsupported` | unknown region, or the operator doesn't offer this |
197
+ | `not-found` | unknown group, or the address couldn't be matched unambiguously |
198
+
199
+ ## How it works and its limits
200
+
201
+ Each region is a provider in `src/providers/` that reads the operator's own source: a JSON API where one exists (YASNO, Mykolaiv, Sumy, …), otherwise the schedule page or the operator's news posts. Nothing goes around captchas or bot protection. When an operator puts one in front of its data, the SDK reports `blocked` instead.
202
+
203
+ Notes on every source (endpoints, formats, quirks) are in [`docs/sources`](docs/sources).
204
+
205
+ Operators change their sites without warning. Every provider is tested against real saved responses (`test/fixtures`), and `healthCheck` catches changes in production. If a region breaks, please open an issue with the `npm run health` output.
206
+
207
+ ## Development
208
+
209
+ ```sh
210
+ npm install
211
+ npm test # offline, on fixtures
212
+ npm run test:live # hits real servers
213
+ npm run health -- --address
214
+ ```
215
+
216
+ Adding a region: implement `Provider` (`src/providers/provider.ts`) with `getSchedules` and, if the operator has a lookup, the address methods. Save real responses as fixtures, write tests next to the others, and register the provider in `src/providers/index.ts`.
217
+
218
+ ## Releasing
219
+
220
+ Bump the version and push the tag; the Release workflow (`.github/workflows/release.yml`) tests, builds and publishes it to npm with provenance:
221
+
222
+ ```sh
223
+ npm version patch # or minor / major
224
+ git push --follow-tags
225
+ ```
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public intermediate certificates for operators whose servers send only their own
3
+ * certificate. Browsers and curl fetch the missing intermediate themselves; Node doesn't, so
4
+ * requests fail with UNABLE_TO_VERIFY_LEAF_SIGNATURE. Adding the intermediate completes the
5
+ * chain up to a root Node already trusts; verification is not relaxed.
6
+ */
7
+ /**
8
+ * Sectigo Public Server Authentication CA DV R36 (issued by Root R46, valid to 2036-03-21),
9
+ * from http://crt.sectigo.com/SectigoPublicServerAuthenticationCADVR36.crt
10
+ * SHA-256 8C:54:C3:34:B6:6B:A4:E4:26:77:2A:F4:A3:F9:13:6C:19:A1:AE:C7:29:FD:B2:8C:53:5C:07:A5:A4:EF:22:E0
11
+ * Needed by chernihivoblenergo.com.ua.
12
+ */
13
+ export declare const SECTIGO_DV_R36 = "\n-----BEGIN CERTIFICATE-----\nMIIGTDCCBDSgAwIBAgIQOXpmzCdWNi4NqofKbqvjsTANBgkqhkiG9w0BAQwFADBf\nMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD\nEy1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw\nHhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBgMQswCQYDVQQGEwJHQjEY\nMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFB1Ymxp\nYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gQ0EgRFYgUjM2MIIBojANBgkqhkiG9w0B\nAQEFAAOCAY8AMIIBigKCAYEAljZf2HIz7+SPUPQCQObZYcrxLTHYdf1ZtMRe7Yeq\nRPSwygz16qJ9cAWtWNTcuICc++p8Dct7zNGxCpqmEtqifO7NvuB5dEVexXn9RFFH\n12Hm+NtPRQgXIFjx6MSJcNWuVO3XGE57L1mHlcQYj+g4hny90aFh2SCZCDEVkAja\nEMMfYPKuCjHuuF+bzHFb/9gV8P9+ekcHENF2nR1efGWSKwnfG5RawlkaQDpRtZTm\nM64TIsv/r7cyFO4nSjs1jLdXYdz5q3a4L0NoabZfbdxVb+CUEHfB0bpulZQtH1Rv\n38e/lIdP7OTTIlZh6OYL6NhxP8So0/sht/4J9mqIGxRFc0/pC8suja+wcIUna0HB\npXKfXTKpzgis+zmXDL06ASJf5E4A2/m+Hp6b84sfPAwQ766rI65mh50S0Di9E3Pn\n2WcaJc+PILsBmYpgtmgWTR9eV9otfKRUBfzHUHcVgarub/XluEpRlTtZudU5xbFN\nxx/DgMrXLUAPaI60fZ6wA+PTAgMBAAGjggGBMIIBfTAfBgNVHSMEGDAWgBRWc1hk\nlfmSGrASKgRieaFAFYghSTAdBgNVHQ4EFgQUaMASFhgOr872h6YyV6NGUV3LBycw\nDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYI\nKwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgEw\nVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdv\nUHVibGljU2VydmVyQXV0aGVudGljYXRpb25Sb290UjQ2LmNybDCBhAYIKwYBBQUH\nAQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3Rp\nZ29QdWJsaWNTZXJ2ZXJBdXRoZW50aWNhdGlvblJvb3RSNDYucDdjMCMGCCsGAQUF\nBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEA\nYtOC9Fy+TqECFw40IospI92kLGgoSZGPOSQXMBqmsGWZUQ7rux7cj1du6d9rD6C8\nze1B2eQjkrGkIL/OF1s7vSmgYVafsRoZd/IHUrkoQvX8FZwUsmPu7amgBfaY3g+d\nq1x0jNGKb6I6Bzdl6LgMD9qxp+3i7GQOnd9J8LFSietY6Z4jUBzVoOoz8iAU84OF\nh2HhAuiPw1ai0VnY38RTI+8kepGWVfGxfBWzwH9uIjeooIeaosVFvE8cmYUB4TSH\n5dUyD0jHct2+8ceKEtIoFU/FfHq/mDaVnvcDCZXtIgitdMFQdMZaVehmObyhRdDD\n4NQCs0gaI9AAgFj4L9QtkARzhQLNyRf87Kln+YU0lgCGr9HLg3rGO8q+Y4ppLsOd\nunQZ6ZxPNGIfOApbPVf5hCe58EZwiWdHIMn9lPP6+F404y8NNugbQixBber+x536\nWrZhFZLjEkhp7fFXf9r32rNPfb74X/U90Bdy4lzp3+X1ukh1BuMxA/EEhDoTOS3l\n7ABvc7BYSQubQ2490OcdkIzUh3ZwDrakMVrbaTxUM2p24N6dB+ns2zptWCva6jzW\nr8IWKIMxzxLPv5Kt3ePKcUdvkBU/smqujSczTzzSjIoR5QqQA6lN1ZRSnuHIWCvh\nJEltkYnTAH41QJ6SAWO66GrrUESwN/cgZzL4JLEqz1Y=\n-----END CERTIFICATE-----\n";
14
+ //# sourceMappingURL=certs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,+uEAqC1B,CAAA"}
package/dist/certs.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Public intermediate certificates for operators whose servers send only their own
3
+ * certificate. Browsers and curl fetch the missing intermediate themselves; Node doesn't, so
4
+ * requests fail with UNABLE_TO_VERIFY_LEAF_SIGNATURE. Adding the intermediate completes the
5
+ * chain up to a root Node already trusts; verification is not relaxed.
6
+ */
7
+ /**
8
+ * Sectigo Public Server Authentication CA DV R36 (issued by Root R46, valid to 2036-03-21),
9
+ * from http://crt.sectigo.com/SectigoPublicServerAuthenticationCADVR36.crt
10
+ * SHA-256 8C:54:C3:34:B6:6B:A4:E4:26:77:2A:F4:A3:F9:13:6C:19:A1:AE:C7:29:FD:B2:8C:53:5C:07:A5:A4:EF:22:E0
11
+ * Needed by chernihivoblenergo.com.ua.
12
+ */
13
+ export const SECTIGO_DV_R36 = `
14
+ -----BEGIN CERTIFICATE-----
15
+ MIIGTDCCBDSgAwIBAgIQOXpmzCdWNi4NqofKbqvjsTANBgkqhkiG9w0BAQwFADBf
16
+ MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
17
+ Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
18
+ HhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBgMQswCQYDVQQGEwJHQjEY
19
+ MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFB1Ymxp
20
+ YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gQ0EgRFYgUjM2MIIBojANBgkqhkiG9w0B
21
+ AQEFAAOCAY8AMIIBigKCAYEAljZf2HIz7+SPUPQCQObZYcrxLTHYdf1ZtMRe7Yeq
22
+ RPSwygz16qJ9cAWtWNTcuICc++p8Dct7zNGxCpqmEtqifO7NvuB5dEVexXn9RFFH
23
+ 12Hm+NtPRQgXIFjx6MSJcNWuVO3XGE57L1mHlcQYj+g4hny90aFh2SCZCDEVkAja
24
+ EMMfYPKuCjHuuF+bzHFb/9gV8P9+ekcHENF2nR1efGWSKwnfG5RawlkaQDpRtZTm
25
+ M64TIsv/r7cyFO4nSjs1jLdXYdz5q3a4L0NoabZfbdxVb+CUEHfB0bpulZQtH1Rv
26
+ 38e/lIdP7OTTIlZh6OYL6NhxP8So0/sht/4J9mqIGxRFc0/pC8suja+wcIUna0HB
27
+ pXKfXTKpzgis+zmXDL06ASJf5E4A2/m+Hp6b84sfPAwQ766rI65mh50S0Di9E3Pn
28
+ 2WcaJc+PILsBmYpgtmgWTR9eV9otfKRUBfzHUHcVgarub/XluEpRlTtZudU5xbFN
29
+ xx/DgMrXLUAPaI60fZ6wA+PTAgMBAAGjggGBMIIBfTAfBgNVHSMEGDAWgBRWc1hk
30
+ lfmSGrASKgRieaFAFYghSTAdBgNVHQ4EFgQUaMASFhgOr872h6YyV6NGUV3LBycw
31
+ DgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYI
32
+ KwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgEw
33
+ VAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdv
34
+ UHVibGljU2VydmVyQXV0aGVudGljYXRpb25Sb290UjQ2LmNybDCBhAYIKwYBBQUH
35
+ AQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3Rp
36
+ Z29QdWJsaWNTZXJ2ZXJBdXRoZW50aWNhdGlvblJvb3RSNDYucDdjMCMGCCsGAQUF
37
+ BzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEA
38
+ YtOC9Fy+TqECFw40IospI92kLGgoSZGPOSQXMBqmsGWZUQ7rux7cj1du6d9rD6C8
39
+ ze1B2eQjkrGkIL/OF1s7vSmgYVafsRoZd/IHUrkoQvX8FZwUsmPu7amgBfaY3g+d
40
+ q1x0jNGKb6I6Bzdl6LgMD9qxp+3i7GQOnd9J8LFSietY6Z4jUBzVoOoz8iAU84OF
41
+ h2HhAuiPw1ai0VnY38RTI+8kepGWVfGxfBWzwH9uIjeooIeaosVFvE8cmYUB4TSH
42
+ 5dUyD0jHct2+8ceKEtIoFU/FfHq/mDaVnvcDCZXtIgitdMFQdMZaVehmObyhRdDD
43
+ 4NQCs0gaI9AAgFj4L9QtkARzhQLNyRf87Kln+YU0lgCGr9HLg3rGO8q+Y4ppLsOd
44
+ unQZ6ZxPNGIfOApbPVf5hCe58EZwiWdHIMn9lPP6+F404y8NNugbQixBber+x536
45
+ WrZhFZLjEkhp7fFXf9r32rNPfb74X/U90Bdy4lzp3+X1ukh1BuMxA/EEhDoTOS3l
46
+ 7ABvc7BYSQubQ2490OcdkIzUh3ZwDrakMVrbaTxUM2p24N6dB+ns2zptWCva6jzW
47
+ r8IWKIMxzxLPv5Kt3ePKcUdvkBU/smqujSczTzzSjIoR5QqQA6lN1ZRSnuHIWCvh
48
+ JEltkYnTAH41QJ6SAWO66GrrUESwN/cgZzL4JLEqz1Y=
49
+ -----END CERTIFICATE-----
50
+ `;
51
+ //# sourceMappingURL=certs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"certs.js","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqC7B,CAAA"}
@@ -0,0 +1,107 @@
1
+ import { type HttpOptions } from './http.js';
2
+ import type { Provider } from './providers/provider.js';
3
+ import type { AddressQuery, City, GroupMatch, GroupSchedule, House, RegionHealth, RegionId, RegionInfo, RegionSchedule, Street } from './types.js';
4
+ export interface OutageClientOptions extends HttpOptions {
5
+ /** How long fetched schedules are reused, default 5 min. 0 disables the cache. */
6
+ cacheTtlMs?: number;
7
+ /** Clock override, mostly for tests */
8
+ now?: () => Date;
9
+ /** Replace the built-in providers (tests, or your own sources) */
10
+ providers?: readonly Provider[];
11
+ }
12
+ export interface HealthCheckOptions {
13
+ /** Only these regions; default all */
14
+ regions?: RegionId[];
15
+ /** Also run the address lookup with each region's sample address */
16
+ address?: boolean;
17
+ /** A schedule not changed for longer than this is `stale`, default 72 h */
18
+ staleAfterHours?: number;
19
+ /** Parallel requests, default 6 */
20
+ concurrency?: number;
21
+ }
22
+ export interface HealthReport {
23
+ checkedAt: string;
24
+ ok: boolean;
25
+ regions: RegionHealth[];
26
+ }
27
+ /**
28
+ * Entry point. One instance can be shared across the app; it caches schedules briefly and
29
+ * deduplicates concurrent requests for the same region.
30
+ *
31
+ * ```ts
32
+ * const outages = new OutageClient()
33
+ * const { group } = await outages.findGroup('kyiv', { street: 'Хрещатик', house: '1' })
34
+ * const schedule = await outages.getSchedule('kyiv', group)
35
+ * ```
36
+ */
37
+ export declare class OutageClient {
38
+ private readonly ctx;
39
+ private readonly providers;
40
+ private readonly cacheTtlMs;
41
+ private readonly cache;
42
+ private readonly searchCache;
43
+ constructor(opts?: OutageClientOptions);
44
+ /** Every supported region with what it can do. */
45
+ regions(): RegionInfo[];
46
+ region(id: RegionId): RegionInfo;
47
+ private describe;
48
+ /** All groups of a region. */
49
+ getSchedules(region: RegionId, opts?: {
50
+ fresh?: boolean;
51
+ }): Promise<RegionSchedule>;
52
+ /** One group. Accepts loose spellings: "3.1", "GPV3.1", "черга 3.1". */
53
+ getSchedule(region: RegionId, group: string, opts?: {
54
+ fresh?: boolean;
55
+ }): Promise<GroupSchedule>;
56
+ /**
57
+ * Settlements matching what the user typed, best first. Forgiving: typos, missing "м."/"с.",
58
+ * Russian-style spelling. Empty for single-city regions (e.g. Kyiv).
59
+ */
60
+ searchCities(region: RegionId, query: string, opts?: SearchOptions & {
61
+ district?: string;
62
+ }): Promise<City[]>;
63
+ /**
64
+ * Streets matching what the user typed, best first, for autocomplete. Forgiving: typos
65
+ * ("Хрищатик"), no street type, any word order, a skipped first name ("Шевченка" for
66
+ * "Тараса Шевченка"), "и" for "і", a word typed halfway. Each result carries a `score` (0..1).
67
+ */
68
+ searchStreets(region: RegionId, query: string, opts?: SearchOptions & {
69
+ city?: City;
70
+ }): Promise<Street[]>;
71
+ searchHouses(region: RegionId, street: Street, query?: string): Promise<House[]>;
72
+ /** Group of an exact house picked from search results. */
73
+ groupOf(region: RegionId, house: House, street: Street, city?: City): Promise<GroupMatch>;
74
+ /**
75
+ * Loads a region's settlement list (and a city's streets) ahead of time, so the first
76
+ * keystrokes of an autocomplete don't wait for the operator. Optional.
77
+ */
78
+ prefetch(region: RegionId, opts?: {
79
+ city?: City;
80
+ }): Promise<void>;
81
+ /**
82
+ * Finds the outage group of an address in one call. Names may be misspelled; when the street
83
+ * or settlement can't be picked with confidence, throws `not-found` listing the suggestions.
84
+ */
85
+ findGroup(region: RegionId, address: AddressQuery): Promise<GroupMatch>;
86
+ /**
87
+ * Ranks a provider's candidates. Providers with a directory return it whole (cached here);
88
+ * providers whose operator searches server-side get the query, plus word fragments when the
89
+ * query alone finds little, so a typo in one part of the name still lands.
90
+ */
91
+ private rankedSearch;
92
+ /** Candidate lists, cached briefly: whole directories per scope, server results per term. */
93
+ private candidates;
94
+ /**
95
+ * Fetches every region (bypassing the cache) and reports whether parsing still works.
96
+ * Use it in CI or a cron job to notice when an operator changes its site.
97
+ */
98
+ healthCheck(opts?: HealthCheckOptions): Promise<HealthReport>;
99
+ private checkRegion;
100
+ private provider;
101
+ private addressProvider;
102
+ }
103
+ export interface SearchOptions {
104
+ /** Max results, default 10 */
105
+ limit?: number;
106
+ }
107
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}