import { WBGTStatus, WBGTForecast, WBGTData, WBGTRealtimeData, WBGTRealtimeEntry, TokyoLocation } from './types.js';
import { LOCATION_DISPLAY_NAME_MAP, LOCATION_NAME_MAP, WBGT_THRESHOLDS } from './constants.js';
/**
* WBGT値から危険度レベルを判定
*/
export function getWBGTStatus(wbgt: number | null): WBGTStatus {
if (wbgt === null || isNaN(wbgt)) return "不明";
if (wbgt < WBGT_THRESHOLDS.SAFE) return "安全";
if (wbgt < WBGT_THRESHOLDS.CAUTION) return "注意";
if (wbgt < WBGT_THRESHOLDS.ALERT) return "警戒";
if (wbgt < WBGT_THRESHOLDS.HIGH_ALERT) return "厳重警戒";
return "危険";
}
/**
* タイムスタンプ文字列をフォーマット
* @param timeStr YYYYMMDDHH形式の文字列
* @returns YYYY/MM/DD HH:00形式の文字列
*/
export function formatDateTime(timeStr: string): string {
if (!timeStr || timeStr.length !== 10) return timeStr;
const year = timeStr.substring(0, 4);
const month = timeStr.substring(4, 6);
const day = timeStr.substring(6, 8);
const hour = timeStr.substring(8, 10);
return `${year}/${month}/${day} ${hour}:00`;
}
/**
* 地点英語名から日本語名を取得
*/
export function getLocationName(location: TokyoLocation): string {
return LOCATION_DISPLAY_NAME_MAP[location] || location;
}
/**
* 地点番号から日本語名を取得
*/
export function getLocationNameById(locationId: string): string {
return LOCATION_NAME_MAP[locationId] || `観測所ID: ${locationId}`;
}
/**
* WBGT予測CSVデータをパース
*/
export function parseWBGTForecastCSV(csvData: string, location: TokyoLocation): WBGTData {
const lines = csvData.split('\n').filter(line => line.trim());
if (lines.length < 2) {
throw new Error("Invalid CSV format");
}
// 1行目: 時刻データ
const timeHeaders = lines[0].split(',').slice(2);
// 2行目: 予測値データ
const dataLine = lines[1].split(',');
const locationId = dataLine[0];
const updateTime = dataLine[1];
const wbgtValues = dataLine.slice(2);
const forecasts: WBGTForecast[] = timeHeaders.map((timeStr, index) => {
const wbgtValue = wbgtValues[index];
const wbgt = wbgtValue && wbgtValue.trim() ? parseInt(wbgtValue.trim()) / 10 : null;
return {
datetime: formatDateTime(timeStr.trim()),
wbgt: wbgt,
status: getWBGTStatus(wbgt)
};
}).filter(item => item.wbgt !== null);
return {
locationId,
locationName: getLocationName(location),
prefecture: "東京都",
updateTime,
forecasts
};
}
/**
* 全東京地点CSVデータをパース
*/
export function parseAllTokyoCSV(csvData: string): WBGTData[] {
const lines = csvData.split('\n').filter(line => line.trim());
if (lines.length < 2) {
throw new Error("Invalid CSV format");
}
const timeHeaders = lines[0].split(',').slice(2);
const results: WBGTData[] = [];
for (let i = 1; i < lines.length; i++) {
const dataLine = lines[i].split(',');
if (dataLine.length < 3) continue;
const locationId = dataLine[0];
const updateTime = dataLine[1];
const wbgtValues = dataLine.slice(2);
const forecasts: WBGTForecast[] = timeHeaders.map((timeStr, index) => {
const wbgtValue = wbgtValues[index];
const wbgt = wbgtValue && wbgtValue.trim() ? parseInt(wbgtValue.trim()) / 10 : null;
return {
datetime: formatDateTime(timeStr.trim()),
wbgt: wbgt,
status: getWBGTStatus(wbgt)
};
}).filter(item => item.wbgt !== null);
results.push({
locationId,
locationName: getLocationNameById(locationId),
prefecture: "東京都",
updateTime,
forecasts
});
}
return results;
}
/**
* リアルタイムCSVデータをパース
*/
export function parseRealtimeCSV(csvData: string): WBGTRealtimeData {
const lines = csvData.split('\n').filter(line => line.trim());
if (lines.length < 2) {
throw new Error("Invalid CSV format");
}
const realtimeData: WBGTRealtimeEntry[] = [];
for (let i = 1; i < lines.length; i++) {
const values = lines[i].split(',');
if (values.length >= 3) {
const date = values[0];
const time = values[1];
const wbgt = parseFloat(values[2]);
realtimeData.push({
datetime: `${date} ${time}`,
wbgt: isNaN(wbgt) ? null : wbgt,
status: getWBGTStatus(wbgt)
});
}
}
return {
location: "東京(実測地点)",
prefecture: "東京都",
data: realtimeData.slice(-24) // 最新24時間のデータ
};
}