Skip to content

Commit bfe0db5

Browse files
committed
storage fix
1 parent 5ab42af commit bfe0db5

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

src/monitor.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { chromium } from "playwright"
22
import path from "node:path"
3+
import fs from "node:fs"
34
import {
45
CITY_KYIV, STREET_KYIV, HOUSE_KYIV,
56
CITY_ODESA, STREET_ODESA, HOUSE_ODESA,
@@ -488,10 +489,92 @@ function transformYasnoFormat(yasnoRaw) {
488489
return { schedule: scheduleMap, emergency: isEmergency };
489490
}
490491

492+
// --- ЕКСПОРТ ---
493+
function transformToExportFormat(schedule, regionId) {
494+
const exportData = {
495+
regionId: regionId,
496+
lastUpdated: new Date().toISOString(),
497+
fact: {
498+
data: {}
499+
}
500+
};
501+
502+
const dates = new Set();
503+
504+
for (const [groupId, dateMap] of Object.entries(schedule)) {
505+
for (const [dateStr, dailyMap] of Object.entries(dateMap)) {
506+
// Parse date as Kyiv Midnight
507+
// dateStr is YYYY-MM-DD
508+
// Winter Time (Jan) is UTC+2.
509+
// Simple workaround: Create UTC date and substract 2 hours (7200s) if we assume strict winter time,
510+
// OR better: use date string constructs that Date() accepts.
511+
// "2026-01-30T00:00:00+02:00"
512+
const ts = Math.floor(new Date(`${dateStr}T00:00:00+02:00`).getTime() / 1000);
513+
const timestampKey = ts.toString();
514+
515+
if (!exportData.fact.data[timestampKey]) {
516+
exportData.fact.data[timestampKey] = {};
517+
}
518+
519+
const gpvKey = `GPV${groupId}`;
520+
const hoursData = {};
521+
522+
for (let h = 1; h <= 24; h++) {
523+
const hh = String(h - 1).padStart(2, '0');
524+
const val00 = dailyMap[`${hh}:00`];
525+
const val30 = dailyMap[`${hh}:30`];
526+
527+
let status = 'yes'; // Default ON
528+
if (val00 === 1 && val30 === 1) status = 'yes';
529+
else if (val00 === 2 && val30 === 2) status = 'no';
530+
else if (val00 === 2 && val30 === 1) status = 'first'; // OFF first half = first half is 'shutdown' -> wait.
531+
// User mappings:
532+
// "first": 00-30 OFF (2), 30-00 ON (1) -> "first" status usually means "first half off" or "first half something".
533+
// Let's check user example:
534+
// "GPV2.1" "1": "first" => Val00=?, Val30=?
535+
// In example GPV2.1 hour 1 is 'first'.
536+
// Let's see transformToSvitloFormat logic (reverse):
537+
// case "first": val00 = 2; val30 = 1; (OFF, ON)
538+
// So 'first' maps to [2, 1]
539+
// case "second": val00 = 1; val30 = 2; (ON, OFF)
540+
541+
if (val00 === 2 && val30 === 1) status = 'first';
542+
else if (val00 === 1 && val30 === 2) status = 'second';
543+
544+
hoursData[h.toString()] = status;
545+
}
546+
547+
exportData.fact.data[timestampKey][gpvKey] = hoursData;
548+
}
549+
}
550+
551+
// Fill summary
552+
const keys = Object.keys(exportData.fact.data).sort();
553+
if (keys.length > 0) {
554+
exportData.fact.today = parseInt(keys[0]);
555+
556+
const now = new Date();
557+
now.setHours(now.getHours() + 2); // Quick hack for Kyiv approx if env is UTC, or just use local
558+
// Actually user wanted "30.01.2026 00:02" format.
559+
// Let's just formatting current time.
560+
const d = new Date();
561+
const pad = (n) => String(n).padStart(2, '0');
562+
exportData.fact.update = `${pad(d.getDate())}.${pad(d.getMonth() + 1)}.${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
563+
}
564+
565+
return exportData;
566+
}
567+
491568
// 4. ГОЛОВНИЙ ЗАПУСК
492569
async function run() {
493570
console.log("🚀 Starting Multi-Region Scraper (Robust Mode with Odesa Fix)...");
494571

572+
// Ensure artifacts directory exists
573+
const artifactsDir = path.resolve("artifacts");
574+
if (!fs.existsSync(artifactsDir)) {
575+
fs.mkdirSync(artifactsDir);
576+
}
577+
495578
const browser = await chromium.launch({ headless: true });
496579
const processedRegions = [];
497580
const globalDates = { today: null, tomorrow: null };
@@ -715,6 +798,15 @@ async function run() {
715798
schedule: chernivtsiSchedule,
716799
emergency: false
717800
});
801+
802+
// Save to JSON for repo
803+
try {
804+
const exportData = transformToExportFormat(chernivtsiSchedule, "Chernivtsi");
805+
fs.writeFileSync(path.join(artifactsDir, "chernivtsi.json"), JSON.stringify(exportData, null, 2));
806+
console.log("💾 Saved artifacts/chernivtsi.json");
807+
} catch (e) {
808+
console.error("❌ Failed to save artifacts/chernivtsi.json:", e);
809+
}
718810
}
719811

720812
await browser.close();
@@ -737,6 +829,14 @@ async function run() {
737829
timestamp: Date.now()
738830
};
739831

832+
// Save last-message.json for repo
833+
try {
834+
fs.writeFileSync(path.join(artifactsDir, "last-message.json"), JSON.stringify(JSON.parse(finalOutput.body), null, 2));
835+
console.log("💾 Saved artifacts/last-message.json");
836+
} catch (e) {
837+
console.error("❌ Failed to save artifacts/last-message.json:", e);
838+
}
839+
740840
if (!CF_WORKER_URL || !CF_WORKER_TOKEN) {
741841
console.error("❌ Missing Cloudflare secrets!");
742842
process.exit(1);

0 commit comments

Comments
 (0)