212 lines
7.5 KiB
TypeScript
212 lines
7.5 KiB
TypeScript
/**
|
|
* 📄 Excel to JSON Utility
|
|
* 엑셀 데이터 JSON 변환 유틸리티
|
|
*
|
|
* - 엑셀 파일 데이터 파싱 및 JSON 변환 기능
|
|
* - 날짜 및 시간 포맷팅 및 유효성 검사
|
|
* - Zod 스키마를 통한 데이터 타입 검증 및 안전성 보장
|
|
* - 수용가-맨홀 매칭결과, 시간패턴, 유량자료 등 데이터 변환
|
|
* - 강우자료 및 수용가 물사용량 데이터 처리
|
|
* - 다양한 날짜 형식(M/D/YYYY, YYYY-MM-DD 등) 지원
|
|
*/
|
|
export const TEST_UUID = '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e'
|
|
import { z } from "zod";
|
|
|
|
// "YYYY-MM-DDTHH:MM:SS" 형식으로 포맷팅 하는 함수
|
|
function parseAndFormatDateTimeToISOCompatibleString(
|
|
dateInput: string,
|
|
timeInputForMDY: string | null,
|
|
inputType: 'YYYY-MM-DD_H:MM' | 'M/D/YYYY_H:MM' | 'M/D/YY_H:MM'
|
|
): string | null {
|
|
let year: number, month: number, day: number, hours: number, minutes: number;
|
|
let datePartStr: string;
|
|
let timePartStr: string;
|
|
|
|
if (inputType === 'YYYY-MM-DD_H:MM') {
|
|
const parts = String(dateInput).split(' ');
|
|
if (parts.length !== 2) {
|
|
console.warn(`Invalid dateTime format for ${inputType} (split space):`, dateInput);
|
|
return null;
|
|
}
|
|
datePartStr = parts[0] ?? ""; // "YYYY-MM-DD"
|
|
timePartStr = parts[1] ?? ""; // "H:MM"
|
|
|
|
const dateParts = datePartStr.split('-');
|
|
if (dateParts.length !== 3) {
|
|
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
|
|
return null;
|
|
}
|
|
year = parseInt(dateParts[0] ?? '', 10);
|
|
month = parseInt(dateParts[1] ?? '', 10);
|
|
day = parseInt(dateParts[2] ?? '', 10);
|
|
} else if (inputType === 'M/D/YYYY_H:MM') {
|
|
datePartStr = String(dateInput); // "M/D/YYYY"
|
|
if (!timeInputForMDY) {
|
|
console.warn(`Time input is missing for ${inputType}:`, dateInput);
|
|
return null;
|
|
}
|
|
timePartStr = String(timeInputForMDY); // "H:MM"
|
|
|
|
const dateParts = datePartStr.split('/');
|
|
if (dateParts.length !== 3) {
|
|
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
|
|
return null;
|
|
}
|
|
month = parseInt(dateParts[0] ?? '', 10);
|
|
day = parseInt(dateParts[1] ?? '', 10);
|
|
year = parseInt(dateParts[2] ?? '', 10);
|
|
} else if (inputType === 'M/D/YY_H:MM') { // "M/D/YY H:MM" 형식 처리
|
|
const parts = String(dateInput).split(' ');
|
|
if (parts.length !== 2) {
|
|
console.warn(`Invalid dateTime format for ${inputType} (split space):`, dateInput);
|
|
return null;
|
|
}
|
|
datePartStr = parts[0] ?? ""; // "M/D/YY"
|
|
timePartStr = parts[1] ?? ""; // "H:MM"
|
|
|
|
const dateParts = datePartStr.split('/');
|
|
if (dateParts.length !== 3) {
|
|
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
|
|
return null;
|
|
}
|
|
month = parseInt(dateParts[0] ?? '', 10);
|
|
day = parseInt(dateParts[1] ?? '', 10);
|
|
year = parseInt(dateParts[2] ?? '', 10);
|
|
|
|
// YY to YYYY conversion (assuming 2000s)
|
|
if (year < 100) {
|
|
year += 2000;
|
|
}
|
|
} else {
|
|
console.warn("Invalid inputType for date parsing.");
|
|
return null;
|
|
}
|
|
|
|
// 시간 파싱 (H:MM) - 공통 로직
|
|
const timeParts = timePartStr.split(':');
|
|
if (timeParts.length !== 2) {
|
|
console.warn(`Invalid time format (timePart):`, timePartStr);
|
|
return null;
|
|
}
|
|
hours = parseInt(timeParts[0] ?? '', 10);
|
|
minutes = parseInt(timeParts[1] ?? '', 10);
|
|
|
|
if (isNaN(year) || isNaN(month) || isNaN(day) || isNaN(hours) || isNaN(minutes)) {
|
|
console.warn("Invalid parsed date or time numeric values:", { year, month, day, hours, minutes });
|
|
return null;
|
|
}
|
|
|
|
// "YYYY-MM-DD HH:MM:SS" 형식으로 포맷팅 - 공통 로직
|
|
const formattedYear = String(year).padStart(4, '0'); // 연도는 4자리로 보장 (M/D/YYYY의 경우)
|
|
const formattedMonth = String(month).padStart(2, '0');
|
|
const formattedDay = String(day).padStart(2, '0');
|
|
const formattedHours = String(hours).padStart(2, '0');
|
|
const formattedMinutes = String(minutes).padStart(2, '0');
|
|
return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHours}:${formattedMinutes}:00`;
|
|
}
|
|
|
|
|
|
// 수용가-맨홀 매칭결과 (수정 필요)
|
|
export const MatchingResultZod = z.object({
|
|
anlsSn: z.number(), // 분석 일련번호
|
|
mhlMngNo: z.string(), // 맨홀 번호 (임시값이라 추후 수정 필요)
|
|
rcptrNo: z.string(), // 수용가 번호
|
|
});
|
|
export type MatchingResultZodType = z.infer<typeof MatchingResultZod>;
|
|
export const excelToMatchingResultData = (excelData: any) => {
|
|
const MatchingResultData: MatchingResultZodType[] = excelData.map((item: any) => {
|
|
|
|
return {
|
|
anlsSn: Number(item["NO"]),
|
|
mhlMngNo: String(item["FTR_IDN"]), // (임시값이라 추후 수정 필요)
|
|
rcptrNo: String(item["수용가"]),
|
|
};
|
|
}).filter((el: any) => el !== undefined);
|
|
return MatchingResultData;
|
|
};
|
|
|
|
|
|
// 시간패턴
|
|
export const timePatternZod = z.object({
|
|
hrId: z.number(), // 시간
|
|
rt: z.number().nullish(), // 비율
|
|
});
|
|
export type timePatternZodType = z.infer<typeof timePatternZod>;
|
|
export const excelToTimePatternData = (excelData: any) => {
|
|
const timePatternData: timePatternZodType[] = excelData.map((item: any) => {
|
|
|
|
return {
|
|
hrId: Number(item["시간"]),
|
|
rt: Number(item["비율"]),
|
|
};
|
|
}).filter((el: any) => el !== undefined);
|
|
return timePatternData;
|
|
};
|
|
|
|
|
|
// 유량자료
|
|
export const flowRateZod = z.object({
|
|
pplMngNo: z.string(), // 관로번호
|
|
mtrngDt: z.string(), // 시간
|
|
oilq: z.number().nullish(), // 유량(㎥/h)
|
|
});
|
|
export type flowRateZodType = z.infer<typeof flowRateZod>;
|
|
export const excelToFlowRateData = (excelData: any) => {
|
|
const flowRateData: flowRateZodType[] = excelData.map((item: any) => {
|
|
const dateTimeStr = String(item["시간"]); // "M/D/YY H:MM" 형식 (예: "2/23/25 0:10")
|
|
|
|
const formattedDateTime = parseAndFormatDateTimeToISOCompatibleString(dateTimeStr, null, 'M/D/YY_H:MM');
|
|
|
|
if (!formattedDateTime) return undefined;
|
|
|
|
return {
|
|
pplMngNo: String(item["관로번호"]),
|
|
mtrngDt: formattedDateTime,
|
|
oilq: Number(item["유량(㎥/h)"]),
|
|
};
|
|
}).filter((el: any) => el !== undefined);
|
|
return flowRateData;
|
|
};
|
|
|
|
// 수용가 물사용량
|
|
export const rcptrInfoZod = z.object({
|
|
rcptrNo: z.string(), // 수용가번호
|
|
useYm: z.string(), // 사용연월
|
|
usqty: z.number().nullish(), // 사용량(㎥)
|
|
});
|
|
export type rcptrInfoZodType = z.infer<typeof rcptrInfoZod>;
|
|
export const excelToRcptrInfoData = (excelData: any) => {
|
|
const flowRateData: rcptrInfoZodType[] = excelData.map((item: any) => {
|
|
|
|
return {
|
|
rcptrNo: String(item["수용가번호"]),
|
|
useYm: String(item["사용연월"]),
|
|
usqty: Number(item["사용량(㎥)"]),
|
|
};
|
|
}).filter((el: any) => el !== undefined);
|
|
return flowRateData;
|
|
};
|
|
|
|
|
|
// 강우자료 (수정 필요)
|
|
export const rainZod = z.object({
|
|
mtrngDt: z.string(), // 날짜 + 시간
|
|
amrf: z.number().nullish(), // 강우량(mm)
|
|
});
|
|
export type rainZodType = z.infer<typeof rainZod>;
|
|
export const excelToRainData = (excelData: any) => {
|
|
const rainData: rainZodType[] = excelData.map((item: any) => {
|
|
const datePartStr = String(item["날짜"]); // "M/D/YYYY" 형식 (예: "5/9/2021")
|
|
const timePartStr = String(item["시간"]); // "H:MM" 형식 (예: "8:00")
|
|
|
|
const formattedForRainZod = parseAndFormatDateTimeToISOCompatibleString(datePartStr, timePartStr, 'M/D/YYYY_H:MM');
|
|
|
|
if (!formattedForRainZod) return undefined;
|
|
|
|
return {
|
|
mtrngDt: formattedForRainZod,
|
|
amrf: Number(item["강우량(mm)"]),
|
|
};
|
|
}).filter((el: any) => el !== undefined);
|
|
return rainData;
|
|
}; |