From 4dcd42281520cb46269e04776a170689b33a19e2 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 20 Mar 2026 10:41:35 +0900 Subject: [PATCH] =?UTF-8?q?[st]=20rename:=20entities=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/.gitignore | 5 +- .../verification/HbStatisticsVerification.tsx | 24 +- .../verification/ImrVerificationHistory.tsx | 520 ++++++++++++++++++ .../VerificationArticleManagement.tsx | 4 +- .../detail/HbVerificationDetail.tsx | 2 +- .../verification/detail/RegionalGrid.tsx | 6 +- .../verification/detail/ScatterChart.tsx | 6 +- .../verification/detail/VerificationList.tsx | 9 +- .../entities/statistics/api/analysis-api.ts | 53 +- .../statistics/api/analysis-history-api.ts | 2 +- .../st/src/entities/statistics/api/imr-api.ts | 2 +- .../api/statistics-analysis-result-api.ts | 6 +- apps/st/src/entities/statistics/index.ts | 59 ++ .../{ => analysis}/analysis-interface.ts | 0 .../statistics/model/imr-interface.ts | 63 --- .../model/imr/imr-detail-grid-column-info.ts | 84 +++ .../model/imr/imr-detail-grid-columns.tsx | 78 +++ .../statistics/model/imr/imr-interface.ts | 119 ++++ .../imr/imr-verification-history-columns.tsx | 119 ++++ .../statistics-analysis-detail-interface.ts | 0 .../statistics-analysis-master-interface.ts} | 2 +- .../statistics-analysis-result-interface.ts | 4 +- .../verification-history-columns.tsx | 2 +- .../verification-list-columns.tsx | 3 +- .../model/verification-article-mock.ts | 150 ----- .../verification-article-column.ts | 0 .../verification-article-interface.ts | 0 27 files changed, 1041 insertions(+), 281 deletions(-) create mode 100644 apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx create mode 100644 apps/st/src/entities/statistics/index.ts rename apps/st/src/entities/statistics/model/{ => analysis}/analysis-interface.ts (100%) delete mode 100644 apps/st/src/entities/statistics/model/imr-interface.ts create mode 100644 apps/st/src/entities/statistics/model/imr/imr-detail-grid-column-info.ts create mode 100644 apps/st/src/entities/statistics/model/imr/imr-detail-grid-columns.tsx create mode 100644 apps/st/src/entities/statistics/model/imr/imr-interface.ts create mode 100644 apps/st/src/entities/statistics/model/imr/imr-verification-history-columns.tsx rename apps/st/src/entities/statistics/model/{ => statistics-analysis}/statistics-analysis-detail-interface.ts (100%) rename apps/st/src/entities/statistics/model/{analysis-history-interface.ts => statistics-analysis/statistics-analysis-master-interface.ts} (94%) rename apps/st/src/entities/statistics/model/{ => statistics-analysis}/statistics-analysis-result-interface.ts (91%) rename apps/st/src/entities/statistics/model/{ => statistics-analysis}/verification-history-columns.tsx (95%) rename apps/st/src/entities/statistics/model/{ => statistics-analysis}/verification-list-columns.tsx (96%) delete mode 100644 apps/st/src/entities/statistics/model/verification-article-mock.ts rename apps/st/src/entities/statistics/model/{ => verification-article}/verification-article-column.ts (100%) rename apps/st/src/entities/statistics/model/{ => verification-article}/verification-article-interface.ts (100%) diff --git a/apps/st/.gitignore b/apps/st/.gitignore index f06d9d40b..3f74d9ee3 100644 --- a/apps/st/.gitignore +++ b/apps/st/.gitignore @@ -39,4 +39,7 @@ next-env.d.ts .env* !.env.example -*.md \ No newline at end of file +*.md + +# next-agents-md +.next-docs/ diff --git a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx index 07c2663c9..3bb9d22af 100644 --- a/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx +++ b/apps/st/src/components/statistics/verification/HbStatisticsVerification.tsx @@ -15,19 +15,17 @@ import { ENDPOINT } from "@/shared/config"; import { useAlertStore } from "@/shared/lib"; import type { ApiErrorResponse } from "@/shared/model"; import { CodeSelect } from "@/entities/code"; -import { analysisApi } from "@/entities/statistics/api/analysis-api"; -import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api"; -import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; -import type { - StAnlsMstResponse, - StAnlsMstSearchParams, - StAnlsOutlierListResponse, -} from "@/entities/statistics/model/analysis-history-interface"; import type { AnalysisRequestPayload, AnalysisResponseDto, -} from "@/entities/statistics/model/analysis-interface"; -import { verificationHistoryColumns } from "@/entities/statistics/model/verification-history-columns"; + StAnlsMstResponse, + StAnlsMstSearchParams, + StAnlsOutlierListResponse, +} from "@/entities/statistics"; +import { verificationHistoryColumns } from "@/entities/statistics"; +import { analysisApi } from "@/entities/statistics/api/analysis-api"; +import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api"; +import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; import type { VerificationTargetCodeResponse } from "@/entities/statistics/model/verification-target-management-interface"; import { Button, @@ -228,11 +226,7 @@ const HbStatisticsVerification = () => { state: { pagination: historyPagination, }, - onPaginationChange: (updater) => { - setHistoryPagination((prev) => - typeof updater === "function" ? updater(prev) : updater, - ); - }, + onPaginationChange: setHistoryPagination, meta: { onOutlierCellClick: (row: StAnlsMstResponse) => { setOutlierDialog({ open: true, anlsSn: row.anlsSn }); diff --git a/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx b/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx new file mode 100644 index 000000000..f6f76077b --- /dev/null +++ b/apps/st/src/components/statistics/verification/ImrVerificationHistory.tsx @@ -0,0 +1,520 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { Line } from "react-chartjs-2"; + +import { useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table"; +import type { ChartOptions } from "chart.js"; +import { + CategoryScale, + Chart as ChartJS, + Legend, + LinearScale, + LineElement, + PointElement, + Title, + Tooltip as ChartTooltip, +} from "chart.js"; +import dayjs from "dayjs"; + +import { ENDPOINT } from "@/shared/config"; +import { CodeApi, CodeSelect } from "@/entities/code"; +import type { + StAnlsMstResponse, + StAnlsMstSearchParams, + StAnlsRsltResponse, +} from "@/entities/statistics"; +import { + imrDetailGridColumnInfo, + imrVerificationHistoryColumns, +} from "@/entities/statistics"; +import { analysisHistoryApi } from "@/entities/statistics/api/analysis-history-api"; +import { imrApi } from "@/entities/statistics/api/imr-api"; +import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api"; +import { + Button, + DataTable, + DateRangePicker, + Label, + LoadingWithAnimation, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components"; + +import DataGrid from "@/components/module/DataGrid"; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + ChartTooltip, + Legend, +); + +type DateRangeValue = { + from: Date; + to?: Date; +}; + +const EMPTY_DATA: never[] = []; + +const ImrVerificationHistory = () => { + // Area A Search States + const [searchSido, setSearchSido] = useState(""); + const [searchSigungu, setSearchSigungu] = useState(""); + const [searchStatsCd, setSearchStatsCd] = useState(""); + const [dateRange, setDateRange] = useState({ + from: dayjs().subtract(1, "month").toDate(), + to: dayjs().toDate(), + }); + const [appliedSearchParams, setAppliedSearchParams] = + useState({ + page: 0, + size: 10, + vrfcMthdCd: "IMR", + sort: "anlsSn,desc", + }); + + // Selected States + const [selectedHistory, setSelectedHistory] = + useState(null); + const [selectedDetail, setSelectedDetail] = + useState(null); + + // Area B Grid State + const [gridRowData, setGridRowData] = useState< + Record[] + >([]); + + // Area A Pagination + const [historyPagination, setHistoryPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + // Codes for Filter + const { data: lwpoCodes } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "LAESECD"], + queryFn: () => CodeApi.getCode("LAESECD"), + }); + + const sidoOptions = useMemo(() => { + if (!lwpoCodes) return []; + return lwpoCodes.filter( + (c) => c.lwpoCd.startsWith("G3") && c.lwpoCd.length === 4, + ); + }, [lwpoCodes]); + + const sigunguOptions = useMemo(() => { + if (!lwpoCodes || searchSido === "") return []; + return lwpoCodes.filter( + (c) => c.lwpoCd.startsWith(searchSido) && c.lwpoCd.length === 7, + ); + }, [lwpoCodes, searchSido]); + + // Area A Data Fetching + const { data: historyData } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, + appliedSearchParams, + historyPagination, + ], + queryFn: () => + analysisHistoryApi.getAnalysisHistoryList({ + ...appliedSearchParams, + page: historyPagination.pageIndex, + size: historyPagination.pageSize, + }), + }); + + const historyTable = useReactTable({ + data: historyData?.content ?? EMPTY_DATA, + columns: imrVerificationHistoryColumns, + getRowId: (row) => String(row.anlsSn), + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + manualPagination: true, + pageCount: historyData?.totalPages ?? 0, + state: { + pagination: historyPagination, + rowSelection: selectedHistory + ? { [selectedHistory.anlsSn]: true } + : {}, + }, + onPaginationChange: setHistoryPagination, + }); + + // Area B Data Fetching + const { data: detailData, isLoading: isDetailLoading } = useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT, + selectedHistory?.anlsSn, + ], + queryFn: () => + stAnlsRsltApi.getImrResultList(selectedHistory!.anlsSn, { + page: 0, + size: 100, + sort: "rsltSn,asc", + }), + enabled: !!selectedHistory, + }); + + useEffect(() => { + if (!detailData || !detailData.content) { + setGridRowData([]); + return; + } + const mapped = detailData.content.map((item) => ({ + ...item, + statsNm: selectedHistory?.vrfcTrgtNm ?? "-", + })); + setGridRowData(mapped as unknown as Record[]); + }, [detailData, selectedHistory]); + + // Area C Data Fetching + const { data: chartData, isLoading: isChartLoading } = useQuery({ + queryKey: [ + "imr-log", + selectedHistory?.anlsSn, + selectedDetail?.vrfcTrgtSn, + selectedDetail?.admdstCd, + ], + queryFn: () => + imrApi.getOutliersLog({ + anlsSn: selectedHistory!.anlsSn, + vrfcTrgtSn: selectedDetail!.vrfcTrgtSn, + admdstCd: selectedDetail!.admdstCd, + }), + enabled: !!selectedHistory && !!selectedDetail, + }); + + const handleSearch = () => { + const params: StAnlsMstSearchParams = { + vrfcMthdCd: "IMR", + sort: "anlsSn,desc", + }; + if (searchSido !== "") + params.sewerBzmnCd = + searchSigungu !== "" ? searchSigungu : searchSido; + if (searchStatsCd) params.statsCd = searchStatsCd; + if (dateRange?.from) + params.verifyDtStart = dayjs(dateRange.from).format("YYYY-MM-DD"); + if (dateRange?.to) + params.verifyDtEnd = dayjs(dateRange.to).format("YYYY-MM-DD"); + + setAppliedSearchParams(params); + setHistoryPagination((prev) => ({ ...prev, pageIndex: 0 })); + setSelectedHistory(null); + setSelectedDetail(null); + }; + + const handleReset = () => { + setSearchSido(""); + setSearchSigungu(""); + setSearchStatsCd(""); + const defaultRange = { + from: dayjs().subtract(1, "month").toDate(), + to: dayjs().toDate(), + }; + setDateRange(defaultRange); + }; + + const chartOptions: ChartOptions<"line"> = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: "top" as const, + labels: { + font: { + size: 12, + }, + }, + }, + title: { + display: true, + text: selectedDetail + ? `${selectedDetail.fcltNm} - I-MR 분석 차트` + : "I-MR 분석 차트", + font: { + size: 16, + weight: "bold", + }, + }, + }, + scales: { + y: { + beginAtZero: false, + title: { + display: true, + text: "수치", + }, + }, + x: { + title: { + display: true, + text: "연도", + }, + }, + }, + }), + [selectedDetail], + ); + + const lineChartData = useMemo(() => { + if (!chartData || !chartData.history) return null; + + const labels = chartData.history.map((h) => String(h.year)); + const actualData = chartData.history.map((h) => h.x); + const uclData = chartData.history.map((h) => h.ucl); + const lclData = chartData.history.map((h) => h.lcl); + const predictedData = chartData.history.map((h) => h.predicted); + + return { + labels, + datasets: [ + { + label: "실측치 (X)", + data: actualData, + borderColor: "rgb(75, 192, 192)", + backgroundColor: "rgba(75, 192, 192, 0.5)", + tension: 0.1, + pointRadius: 4, + pointHoverRadius: 6, + }, + { + label: "상한선 (UCL)", + data: uclData, + borderColor: "rgb(255, 99, 132)", + backgroundColor: "rgba(255, 99, 132, 0.2)", + borderDash: [5, 5], + pointStyle: "false", + fill: false, + }, + { + label: "하한선 (LCL)", + data: lclData, + borderColor: "rgb(54, 162, 235)", + backgroundColor: "rgba(54, 162, 235, 0.2)", + borderDash: [5, 5], + pointStyle: "false", + fill: false, + }, + { + label: "예측치 (Predicted)", + data: predictedData, + borderColor: "rgb(153, 102, 255)", + backgroundColor: "rgba(153, 102, 255, 0.5)", + borderDash: [2, 2], + pointStyle: "false", + fill: false, + }, + ], + }; + }, [chartData]); + + return ( +
+ {/* 영역 A: 검증 이력 그리드 + 검색 필터 */} +
+
+
+ + +
+ +
+ + +
+ +
+ + + setDateRange( + val + ? { from: val.from!, to: val.to } + : undefined, + ) + } + className="w-[20rem]" + /> +
+ +
+ + +
+ +
+ + +
+
+ +
+ { + setSelectedHistory(row); + setSelectedDetail(null); + }} + viewPagination={true} + className="[&_td]:!p-0" + /> +
+
+ +
+ {/* 영역 B: 상세 데이터 그리드 */} +
+
+

+ 이상치 데이터 +

+ +
+
+ {isDetailLoading ? ( +
+
+ +
+
+ ) : !selectedHistory || gridRowData.length === 0 ? ( +
+ {!selectedHistory + ? "항목을 선택해주세요." + : "표시할 항목이 없습니다."} +
+ ) : ( + { + if (!e.rowId) return; + const row = detailData?.content?.find( + (item) => + String(item.rsltSn) === + String(e.rowId), + ); + if (row) setSelectedDetail(row); + }} + /> + )} +
+
+ + {/* 영역 C: Line Chart */} +
+

+ 검증 결과 차트 +

+
+ {isChartLoading ? ( +
+ +
+ ) : lineChartData ? ( + + ) : ( +
+ 상세 데이터를 선택하면 차트가 표시됩니다. +
+ )} +
+
+
+
+ ); +}; + +export default ImrVerificationHistory; diff --git a/apps/st/src/components/statistics/verification/VerificationArticleManagement.tsx b/apps/st/src/components/statistics/verification/VerificationArticleManagement.tsx index d919a6823..ec4f2b082 100644 --- a/apps/st/src/components/statistics/verification/VerificationArticleManagement.tsx +++ b/apps/st/src/components/statistics/verification/VerificationArticleManagement.tsx @@ -7,12 +7,12 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ENDPOINT } from "@/shared/config"; import { CodeSelect } from "@/entities/code/ui/code-select"; -import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; import { hbArticleColumnInfo, imrArticleColumnInfo, validationArticleColumnInfo, -} from "@/entities/statistics/model/verification-article-column"; +} from "@/entities/statistics"; +import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service"; import type { VerificationTargetManagementResponse, VerificationTargetManagementSearchParams, diff --git a/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx b/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx index 7df3c0d95..4ec8b3a39 100644 --- a/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx +++ b/apps/st/src/components/statistics/verification/detail/HbVerificationDetail.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface"; +import type { StAnlsDtlResponse } from "@/entities/statistics"; import { Button } from "@common/components"; import Indicators from "./Indicators"; diff --git a/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx b/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx index c783ab64f..a932cfb3e 100644 --- a/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx +++ b/apps/st/src/components/statistics/verification/detail/RegionalGrid.tsx @@ -4,10 +4,10 @@ import { useQuery } from "@tanstack/react-query"; import { ENDPOINT } from "@/shared/config"; import { CodeApi } from "@/entities/code/api/code-service"; +import type { StAnlsRsltResponse } from "@/entities/statistics"; import { useStAnlsRsltList } from "@/entities/statistics/api/queries"; import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api"; import { regionalGridColumnInfo as columnInfo } from "@/entities/statistics/model/regional-grid-columns"; -import type { StAnlsRsltResponse } from "@/entities/statistics/model/statistics-analysis-result-interface"; import { Button, LoadingWithAnimation, @@ -112,7 +112,7 @@ const RegionalGrid: React.FC = ({ if (appliedFilter.status) { data = data.filter( (item) => - item.otlrYn.toLowerCase() === appliedFilter.status, + item.otlrYnCd.toLowerCase() === appliedFilter.status, ); } } @@ -132,7 +132,7 @@ const RegionalGrid: React.FC = ({ const mappedData = data.map((item) => ({ ...item, - otlrYn: mapOtlrToLabel(item.otlrYn), + otlrYn: mapOtlrToLabel(item.otlrYnCd), })); setGridRowData( diff --git a/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx b/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx index 3f020ab93..57eba1445 100644 --- a/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx +++ b/apps/st/src/components/statistics/verification/detail/ScatterChart.tsx @@ -19,11 +19,11 @@ import zoomPlugin from "chartjs-plugin-zoom"; import { ENDPOINT } from "@/shared/config"; import { CodeApi } from "@/entities/code/api/code-service"; +import type { StAnlsRsltResponse } from "@/entities/statistics"; import { useStAnlsChartData, useStAnlsRsltList, } from "@/entities/statistics/api/queries"; -import type { StAnlsRsltResponse } from "@/entities/statistics/model/statistics-analysis-result-interface"; import { LoadingWithAnimation } from "@common/components"; import type { AppliedFilter } from "./HbVerificationDetail"; @@ -124,7 +124,7 @@ const ScatterChart: React.FC = ({ if ( appliedFilter.status && - item.otlrYn.toLowerCase() !== appliedFilter.status + item.otlrYnCd.toLowerCase() !== appliedFilter.status ) { return; } @@ -135,7 +135,7 @@ const ScatterChart: React.FC = ({ item, }; - if (item.otlrYn === "0") { + if (item.otlrYnCd === "0") { normalPoints.push(pt); } else { outlierPoints.push(pt); diff --git a/apps/st/src/components/statistics/verification/detail/VerificationList.tsx b/apps/st/src/components/statistics/verification/detail/VerificationList.tsx index 84f3b17ba..3966b3131 100644 --- a/apps/st/src/components/statistics/verification/detail/VerificationList.tsx +++ b/apps/st/src/components/statistics/verification/detail/VerificationList.tsx @@ -1,15 +1,12 @@ import React, { useEffect, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; -import { - getCoreRowModel, - useReactTable, -} from "@tanstack/react-table"; +import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { ENDPOINT } from "@/shared/config"; +import type { StAnlsDtlResponse } from "@/entities/statistics"; +import { getVerificationListColumns } from "@/entities/statistics"; import { stAnlsDtlApi } from "@/entities/statistics/api/statistics-analysis-detail-api"; -import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface"; -import { getVerificationListColumns } from "@/entities/statistics/model/verification-list-columns"; import { DataTable } from "@common/components"; interface VerificationListProps { diff --git a/apps/st/src/entities/statistics/api/analysis-api.ts b/apps/st/src/entities/statistics/api/analysis-api.ts index 0b607b299..b6d6c91f1 100644 --- a/apps/st/src/entities/statistics/api/analysis-api.ts +++ b/apps/st/src/entities/statistics/api/analysis-api.ts @@ -2,40 +2,39 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; import type { - AnalysisRequestPayload, - AnalysisResponseDto, - JobStatusDto, - AnalysisResultDto, -} from "../model/analysis-interface"; + AnalysisRequestPayload, + AnalysisResponseDto, + JobStatusDto, + AnalysisResultDto, +} from "../model/analysis/analysis-interface"; export class AnalysisApiService { - public async analyze( - payload: AnalysisRequestPayload, - ): Promise { - const response = await client.post( - `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/analyze`, - payload, - ); + public async analyze( + payload: AnalysisRequestPayload, + ): Promise { + const response = await client.post( + `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/analyze`, + payload, + ); - return response.data; - } + return response.data; + } - public async getJobStatus(jobId: string): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/status/${jobId}`, - ); + public async getJobStatus(jobId: string): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/status/${jobId}`, + ); - return response.data; - } + return response.data; + } - public async getResult(jobId: string): Promise { - const response = await client.get( - `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/result/${jobId}`, - ); + public async getResult(jobId: string): Promise { + const response = await client.get( + `${ENDPOINT.STATVERIFY_SERVICE.ANALYSIS}/result/${jobId}`, + ); - return response.data; - } + return response.data; + } } export const analysisApi = new AnalysisApiService(); - diff --git a/apps/st/src/entities/statistics/api/analysis-history-api.ts b/apps/st/src/entities/statistics/api/analysis-history-api.ts index 5d6be6a4d..316684f99 100644 --- a/apps/st/src/entities/statistics/api/analysis-history-api.ts +++ b/apps/st/src/entities/statistics/api/analysis-history-api.ts @@ -5,7 +5,7 @@ import type { StAnlsMstListApiResponse, StAnlsMstSearchParams, StAnlsOutlierListResponse, -} from "../model/analysis-history-interface"; +} from "../model/statistics-analysis/statistics-analysis-master-interface"; export class AnalysisHistoryApiService { public async getAnalysisHistoryList( diff --git a/apps/st/src/entities/statistics/api/imr-api.ts b/apps/st/src/entities/statistics/api/imr-api.ts index 18bc8db39..b9f913bd5 100644 --- a/apps/st/src/entities/statistics/api/imr-api.ts +++ b/apps/st/src/entities/statistics/api/imr-api.ts @@ -10,7 +10,7 @@ import type { ImrListResponse, ImrLogRequest, ImrLogResponse, -} from "../model/imr-interface"; +} from "../model/imr/imr-interface"; export class ImrApiService { /** diff --git a/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts b/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts index 189877e42..fa8983233 100644 --- a/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts +++ b/apps/st/src/entities/statistics/api/statistics-analysis-result-api.ts @@ -1,11 +1,12 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; +import type { PageableParams } from "@/shared/model"; import type { ChartRetDto, StAnlsRsltListApiResponse, StAnlsRsltSearchParams, -} from "../model/statistics-analysis-result-interface"; +} from "../model/statistics-analysis/statistics-analysis-result-interface"; export class StAnlsRsltApiService { public async getResultList( @@ -23,7 +24,7 @@ export class StAnlsRsltApiService { public async getImrResultList( anlsSn: number, - params: { page?: number; size?: number }, + params: PageableParams, ): Promise { const response = await client.get( `${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_RSLT}/imr/rsltlist/${anlsSn}`, @@ -62,4 +63,3 @@ export class StAnlsRsltApiService { } export const stAnlsRsltApi = new StAnlsRsltApiService(); - diff --git a/apps/st/src/entities/statistics/index.ts b/apps/st/src/entities/statistics/index.ts new file mode 100644 index 000000000..82cc2d995 --- /dev/null +++ b/apps/st/src/entities/statistics/index.ts @@ -0,0 +1,59 @@ +export { + type AnalysisHistoryListApiResponse, + type AnalysisRequestPayload, + type AnalysisResponseDto, + type AnalysisResultDto, + type AnalysisSummaryDto, + type JobStatusDto, + type OutlierRecordDto, +} from "./model/analysis/analysis-interface"; +export { imrDetailGridColumnInfo } from "./model/imr/imr-detail-grid-column-info"; +export { imrDetailGridColumns } from "./model/imr/imr-detail-grid-columns"; +export { + type ImrDetailRequest, + type ImrDetailResponse, + type ImrDetailResponseItem, + type ImrDetectRequest, + type ImrDetectResponse, + type ImrListRequest, + type ImrListResponse, + type ImrLogDataParam, + type ImrLogRequest, + type ImrLogResponse, +} from "./model/imr/imr-interface"; +export { imrVerificationHistoryColumns } from "./model/imr/imr-verification-history-columns"; +export { + type StAnlsDtlListApiResponse, + type StAnlsDtlResponse, + type StAnlsDtlSearchParams, +} from "./model/statistics-analysis/statistics-analysis-detail-interface"; +export { + type StAnlsMstListApiResponse, + type StAnlsMstResponse, + type StAnlsMstResponseDto, + type StAnlsMstSearchDto, + type StAnlsMstSearchParams, + type StAnlsOutlierDataResponse, + type StAnlsOutlierDataResponseDto, + type StAnlsOutlierListResponse, + type StAnlsOutlierListResponseDto, +} from "./model/statistics-analysis/statistics-analysis-master-interface"; +export { + type ChartBoundaryPointDto, + type ChartRetDto, + type StAnlsRsltListApiResponse, + type StAnlsRsltResponse, + type StAnlsRsltSearchParams, +} from "./model/statistics-analysis/statistics-analysis-result-interface"; +export { verificationHistoryColumns } from "./model/statistics-analysis/verification-history-columns"; +export { getVerificationListColumns } from "./model/statistics-analysis/verification-list-columns"; +export { + hbArticleColumnInfo, + imrArticleColumnInfo, + validationArticleColumnInfo, +} from "./model/verification-article/verification-article-column"; +export { + type HBArticleItem, + type IMRArticleItem, + type ValidationArticleItem, +} from "./model/verification-article/verification-article-interface"; diff --git a/apps/st/src/entities/statistics/model/analysis-interface.ts b/apps/st/src/entities/statistics/model/analysis/analysis-interface.ts similarity index 100% rename from apps/st/src/entities/statistics/model/analysis-interface.ts rename to apps/st/src/entities/statistics/model/analysis/analysis-interface.ts diff --git a/apps/st/src/entities/statistics/model/imr-interface.ts b/apps/st/src/entities/statistics/model/imr-interface.ts deleted file mode 100644 index 148b60644..000000000 --- a/apps/st/src/entities/statistics/model/imr-interface.ts +++ /dev/null @@ -1,63 +0,0 @@ -export interface ImrDetectRequest { - anlsSn: number; - vrfcTrgtSn: number; - statsYr: string; - userId?: string; -} - -export interface ImrDetectResponse { - success: boolean; - message: string; - jobId?: string; -} - -export interface ImrListRequest { - anlsSn?: number; - vrfcTrgtSn?: number; - statsYr?: string; -} - -export interface ImrListResponse { - anlsSn: number; - vrfcTrgtSn: number; - statsYr: string; - admdstCd: string; - admdstNm: string; - modelNm: string; - anlsDt: string; -} - -export interface ImrDetailRequest { - anlsSn: number; - vrfcTrgtSn: number; - admdstCd?: string; -} - -export interface ImrDetailResponse { - anlsSn: number; - vrfcTrgtSn: number; - details: Array<{ - admdstCd: string; - admdstNm: string; - val: number; - isOutlier: boolean; - score: number; - }>; -} - -export interface ImrLogRequest { - anlsSn: number; - vrfcTrgtSn: number; - admdstCd: string; -} - -export interface ImrLogResponse { - anlsSn: number; - vrfcTrgtSn: number; - admdstCd: string; - logs: Array<{ - timestamp: string; - level: string; - message: string; - }>; -} diff --git a/apps/st/src/entities/statistics/model/imr/imr-detail-grid-column-info.ts b/apps/st/src/entities/statistics/model/imr/imr-detail-grid-column-info.ts new file mode 100644 index 000000000..45fd10aef --- /dev/null +++ b/apps/st/src/entities/statistics/model/imr/imr-detail-grid-column-info.ts @@ -0,0 +1,84 @@ +import { ValueType } from "realgrid"; + +import type { ColumnInfo } from "@/components/type/DataGrid.type"; + +export const imrDetailGridColumnInfo: ColumnInfo = { + columns: [ + { + name: "sido", + fieldName: "sido", + header: { text: "시도" }, + width: 80, + }, + { + name: "gugun", + fieldName: "gugun", + header: { text: "구군" }, + width: 80, + }, + { + name: "admdstNm", + fieldName: "admdstNm", + header: { text: "행정구역" }, + width: 100, + }, + { + name: "admdstCd", + fieldName: "admdstCd", + header: { text: "시설코드" }, + width: 100, + }, + { + name: "fcltNm", + fieldName: "fcltNm", + header: { text: "시설명" }, + width: 150, + }, + { + name: "addr", + fieldName: "addr", + header: { text: "주소" }, + width: 200, + styleName: "left-column", + }, + { + name: "pipeSeCd", + fieldName: "pipeSeCd", + header: { text: "관로구분" }, + width: 80, + }, + { + name: "vrfcTrgtSn", + fieldName: "vrfcTrgtSn", + header: { text: "항목번호" }, + width: 80, + }, + { + name: "statsNm", + fieldName: "statsNm", + header: { text: "항목명" }, + width: 150, + }, + { + name: "cryrVl", + fieldName: "cryrVl", + header: { text: "당해년도" }, + width: 100, + valueType: ValueType.NUMBER, + dataFormat: "#,##0", + styleName: "right-column", + }, + ], + columnLayout: [ + "sido", + "gugun", + "admdstNm", + "admdstCd", + "fcltNm", + "addr", + "pipeSeCd", + "vrfcTrgtSn", + "statsNm", + "cryrVl", + ], +}; diff --git a/apps/st/src/entities/statistics/model/imr/imr-detail-grid-columns.tsx b/apps/st/src/entities/statistics/model/imr/imr-detail-grid-columns.tsx new file mode 100644 index 000000000..80aef8bef --- /dev/null +++ b/apps/st/src/entities/statistics/model/imr/imr-detail-grid-columns.tsx @@ -0,0 +1,78 @@ +import { createColumnHelper } from "@tanstack/react-table"; + +import type { StAnlsRsltResponse } from "../statistics-analysis/statistics-analysis-result-interface"; + +const columnHelper = createColumnHelper(); + +export const imrDetailGridColumns = [ + columnHelper.accessor("sido", { + header: "시도", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("gugun", { + header: "구군", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("admdstNm", { + header: "행정구역", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("admdstCd", { + header: "시설코드", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("fcltNm", { + header: "시설명", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("addr", { + header: "주소", + cell: (info) => ( +
+ {info.getValue() ?? "-"} +
+ ), + }), + columnHelper.accessor("pipeSeCd", { + header: "관로구분", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("vrfcTrgtSn", { + header: "항목번호", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.display({ + id: "statsNm", + header: "항목명", + cell: (info) => { + const statsNm = (info.table.options.meta as { statsNm?: string }) + ?.statsNm; + return
{statsNm ?? "-"}
; + }, + }), + columnHelper.accessor("cryrVl", { + header: "당해년도", + cell: (info) => ( +
+ {info.getValue()?.toLocaleString() ?? "-"} +
+ ), + }), +]; diff --git a/apps/st/src/entities/statistics/model/imr/imr-interface.ts b/apps/st/src/entities/statistics/model/imr/imr-interface.ts new file mode 100644 index 000000000..ce134eb18 --- /dev/null +++ b/apps/st/src/entities/statistics/model/imr/imr-interface.ts @@ -0,0 +1,119 @@ +/** + * 이상치(IMR) 탐지 실행 요청 DTO + */ +export interface ImrDetectRequest { + userId?: string; // 사용자 ID + sewerBzmnCd: string; // 하수도 사업자 코드 + statsCd: string; // 통계 탭/항목 코드 + statsYr: string; // 분석 기준 통계 연도 +} + +/** + * 이상치(IMR) 탐지 실행 응답 DTO + */ +export interface ImrDetectResponse { + anlsSn: number; // 분석 테이블 마스터 일련번호 + jobId: string; // Celery/비동기 처리를 위한 고유 작업 ID + totalOtlrCnt: number; // 탐지된 총 이상치 건수 + statusCode: string; // 처리 상태 코드 (COMPLETED, FAIL, ERROR 등) + errorMsg?: string; // 에러 발생 시 상세 메시지 + processingTimeMs: number; // 총 처리 소요 시간 (밀리초) +} + +/** + * 이상치 탐지 목록(마스터) 조회 요청 DTO + */ +export interface ImrListRequest { + sewerBzmnCd?: string; // 하수도 사업자 코드 + statsCd?: string; // 통계 항목 코드 + statsYr?: string; // 통계 연도 +} + +/** + * 이상치 탐지 목록(마스터) 조회 응답 DTO + */ +export interface ImrListResponse { + anlsSn: number; // 분석 마스터 일련번호 + anlsSttsCd: string; // 분석 상태 코드 (COMPLETED, FAIL, ERROR) + nmlDataNocs: number; // 정상 판정 데이터 건수 + exclDataNocs: number; // 분석 제외 데이터 건수 + otlrDataNocs: number; // 이상치 판정 데이터 건수 +} + +/** + * 이상치 탐지 상세 조회 요청 DTO + */ +export interface ImrDetailRequest { + anlsSn: number; // 분석 일련번호 + vrfcTrgtSn: number; // 검증 대상 지표 일련번호 +} + +/** + * 이상치 탐지 상세 조회 응답 아이템 + */ +export interface ImrDetailResponseItem { + anlsSn: number; // 분석 일련번호 + vrfcTrgtSn: number; // 검증 대상 지표 일련번호 + admdstCd: string; // 행정동(행정구역) 코드 + cryrVl: number; // 당해년도 원천 데이터 값 + otlrTrsfRt: number; // 이상치 판단 기준 (변이 척도) + otlrYn: string; // 이상치 여부 플래그 (Y/N) +} + +/** + * 이상치 탐지 상세 조회 응답 DTO + */ +export interface ImrDetailResponse { + items: ImrDetailResponseItem[]; +} + +/** + * 이상치 산출 로그 요청 DTO + */ +export interface ImrLogRequest { + jobUuid?: string; // 작업 고유 식별자 + anlsSn: number; // 분석 일련번호 + vrfcTrgtSn: number; // 검증 대상 지표 일련번호 + admdstCd: string; // 행정동(행정구역) 코드 +} + +/** + * 이상치 로그 기록용 연도/시퀀스별 파라미터 DTO + */ +export interface ImrLogDataParam { + year: number; // 대상 연도 + t: number; // 시간 시퀀스 + tDev: number; // 시점 간의 편차 + x: number; // 원천 데이터 값 + xDev: number; // 원천 데이터 값 편차 + prod: number; // t편차와 x편차의 곱 + tDevSq: number; // t편차의 제곱 + predicted: number; // 예측 정상치 + residuals: number; // 잔차 + mr: number; // 이동 범위(MR) + ucl: number; // 관리 상한선 + lcl: number; // 관리 하한선 + judgment: string; // 최종 판정 +} + +/** + * 이상치 산출 상세 로그 응답 DTO + */ +export interface ImrLogResponse { + sewerBzmnCd: string; // 하수도 사업자 코드 + colNm: string; // 영문 컬럼명 + vrfcTrgtNm: string; // 분석 대상 지표명 + admdstCd: string; // 행정동 코드 + regressionA: number; // 회귀식 기울기(a) + regressionB: number; // 회귀식 절편(b) + tMean: number; // 시퀀스 평균값 + xMean: number; // 원데이터 평균값 + prodMean: number; // 편차 곱의 평균 + tDevSqMean: number; // t편차 제곱의 평균 + mrMean: number; // 잔차 이동 범위 평균(MR) + uc: number; // 상한 허용 폭(UCL) + lc: number; // 하한 허용 폭(LCL) + controlRange: number; // 관리 전체 폭(CL) + confidenceLevel: string; // 신뢰 구간 표기 + history: ImrLogDataParam[]; // 연도별 상세 추적 기록 +} diff --git a/apps/st/src/entities/statistics/model/imr/imr-verification-history-columns.tsx b/apps/st/src/entities/statistics/model/imr/imr-verification-history-columns.tsx new file mode 100644 index 000000000..57e052189 --- /dev/null +++ b/apps/st/src/entities/statistics/model/imr/imr-verification-history-columns.tsx @@ -0,0 +1,119 @@ +import { createColumnHelper } from "@tanstack/react-table"; + +import { + Progress, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@common/components"; + +import type { StAnlsMstResponse } from "../statistics-analysis/statistics-analysis-master-interface"; + +const columnHelper = createColumnHelper(); + +export const imrVerificationHistoryColumns = [ + columnHelper.accessor("anlsBgngDt", { + header: "검증 실행 일시", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor( + (row) => ({ + anlsSttsCd: row.anlsSttsCd, + anlsSttsNm: row.anlsSttsNm, + anlsPrgrsRt: row.anlsPrgrsRt, + errMsgCn: row.errMsgCn, + }), + { + id: "anlsSttsNm", + header: "진행 상태", + cell: (info) => { + const { anlsSttsCd, anlsSttsNm, anlsPrgrsRt, errMsgCn } = + info.getValue(); + if (anlsSttsCd === "PROCESSING") { + return ( +
+ + + {anlsPrgrsRt ?? 0}% + +
+ ); + } + if (anlsSttsCd === "FAILED") { + return ( +
+ {anlsSttsNm} + + + + + +

+ {errMsgCn} +

+
+
+
+ ); + } + return anlsSttsNm; + }, + }, + ), + columnHelper.accessor("anlsEndDt", { + header: "종료 일시", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("statsYr", { + header: "검증년도", + cell: (info) => ( +
+ {info.getValue() ?? "-"} +
+ ), + }), + columnHelper.accessor("sewerBzmnCd", { + header: "하수도사업자", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("vrfcTrgtNm", { + header: "검증대상", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("rgtrNm", { + header: "검증자", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("nmlDataNocs", { + header: "정상건수", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("exclDataNocs", { + header: "제외건수", + cell: (info) => ( +
{info.getValue() ?? "-"}
+ ), + }), + columnHelper.accessor("otlrDataNocs", { + header: "이상건수", + cell: (info) => { + const value = info.getValue() ?? 0; + return
{value}
; + }, + }), +]; diff --git a/apps/st/src/entities/statistics/model/statistics-analysis-detail-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-detail-interface.ts similarity index 100% rename from apps/st/src/entities/statistics/model/statistics-analysis-detail-interface.ts rename to apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-detail-interface.ts diff --git a/apps/st/src/entities/statistics/model/analysis-history-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts similarity index 94% rename from apps/st/src/entities/statistics/model/analysis-history-interface.ts rename to apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts index a680dcc77..f8a539146 100644 --- a/apps/st/src/entities/statistics/model/analysis-history-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-master-interface.ts @@ -47,7 +47,7 @@ export interface StAnlsMstResponseDto { /** * 분석 현황 마스터 검색 DTO */ -export interface StAnlsMstSearchDto extends PageableParams { +export interface StAnlsMstSearchDto extends PageableParams { /** 분석상태코드 (COMPLETED, FAILED, QUEUED, PROCESSING) */ anlsSttsCd?: string; /** 작업UUID */ diff --git a/apps/st/src/entities/statistics/model/statistics-analysis-result-interface.ts b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-result-interface.ts similarity index 91% rename from apps/st/src/entities/statistics/model/statistics-analysis-result-interface.ts rename to apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-result-interface.ts index 8e91b7d02..5f7422826 100644 --- a/apps/st/src/entities/statistics/model/statistics-analysis-result-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-analysis/statistics-analysis-result-interface.ts @@ -20,7 +20,7 @@ export interface StAnlsRsltResponse { chgRt: number | null; // 변경율 (r_i) trsfRt: number | null; // 변환 비율 (s_i) otlrTrsfRt: number | null; // 이상치 변환 비율 (E_i) - otlrYn: string; // 이상치 여부 (0: 정상, 1: 이상치, 2: 제외) + otlrYnCd: string; // 이상치 여부 (0: 정상, 1: 이상치, 2: 제외) } export interface StAnlsRsltSearchParams extends PageableParams { @@ -29,7 +29,7 @@ export interface StAnlsRsltSearchParams extends PageableParams { admdstCd?: string; // 행정구역 코드 admdstNm?: string; // 행정구역 명칭 fcltNm?: string; // 시설명 - otlrYn?: string; // 이상치 여부 (y/n) + otlrYnCd?: string; // 이상치 여부 (0: 정상, 1: 이상치, 2: 제외) } export type StAnlsRsltListApiResponse = PageableList; diff --git a/apps/st/src/entities/statistics/model/verification-history-columns.tsx b/apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx similarity index 95% rename from apps/st/src/entities/statistics/model/verification-history-columns.tsx rename to apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx index f5fc29b38..edd85652e 100644 --- a/apps/st/src/entities/statistics/model/verification-history-columns.tsx +++ b/apps/st/src/entities/statistics/model/statistics-analysis/verification-history-columns.tsx @@ -7,7 +7,7 @@ import { TooltipTrigger, } from "@common/components"; -import type { StAnlsMstResponse } from "./analysis-history-interface"; +import type { StAnlsMstResponse } from "./statistics-analysis-master-interface"; const columnHelper = createColumnHelper(); diff --git a/apps/st/src/entities/statistics/model/verification-list-columns.tsx b/apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx similarity index 96% rename from apps/st/src/entities/statistics/model/verification-list-columns.tsx rename to apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx index d2c503add..05d0ddae5 100644 --- a/apps/st/src/entities/statistics/model/verification-list-columns.tsx +++ b/apps/st/src/entities/statistics/model/statistics-analysis/verification-list-columns.tsx @@ -2,9 +2,10 @@ import React from "react"; import type { ColumnDef } from "@tanstack/react-table"; -import type { StAnlsDtlResponse } from "@/entities/statistics/model/statistics-analysis-detail-interface"; import { Checkbox } from "@common/components"; +import type { StAnlsDtlResponse } from "./statistics-analysis-detail-interface"; + interface GetVerificationListColumnsParams { rowData: StAnlsDtlResponse[]; checkedSns: string[]; diff --git a/apps/st/src/entities/statistics/model/verification-article-mock.ts b/apps/st/src/entities/statistics/model/verification-article-mock.ts deleted file mode 100644 index f4986656c..000000000 --- a/apps/st/src/entities/statistics/model/verification-article-mock.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { fakerKO as faker } from "@faker-js/faker"; -import type { - HBArticleItem, - IMRArticleItem, - ValidationArticleItem, -} from "./verification-article-interface"; - -const HB_STATS_ITEMS = [ - "하수도 보급률", - "하수관로", - "하수처리시설 및 방류량보고", - "펌프장", - "공공하수처리시설", - "분뇨처리시설", - "개인하수처리시설", - "하수도재정", - "물 재이용", -]; - -const HB_VERIFICATION_ITEMS = [ - "행정구역 보급률", - "유역환경청별 보급률", - "관로연장", - "처리용량", - "방류량", - "펌프용량", -]; - -const IMR_STATS_ITEMS = [ - "하수도 보급률", - "하수관로", - "하수처리시설 및 방류량보고", - "펌프장", - "공공하수처리시설", - "분뇨처리시설", - "개인하수처리시설", - "하수도재정", - "물 재이용", -]; - -const VALIDATION_STATS_ITEMS = [ - "하수관로 유지관리", - "하수저류시설 및 빗물펌프장", - "공공하수처리시설", - "분뇨처리시설", - "개인하수처리시설", - "하수도재정", -]; - -const VERIFICATION_NAMES = [ - "제정별 사업비", - "관로길이", - "공란검증", - "범위검증", - "논리검증", - "유지보수", -]; - -const TABLE_NAMES = [ - "tb_stats_drng_sys", - "tb_stats_ppline", - "tb_stats_fclt", - "tb_stats_pump_stt", - "tb_stats_sewg_trtm", -]; - -const COLUMN_NAMES = [ - "drng_sys_rate", - "ppline_len", - "fclt_cpct", - "pump_cpct", - "trtm_qty", - "popl_cnt", -]; - -const BASIC_RULES = [ - "값 > 0", - "값 >= 0", - "값 != NULL", - "범위: 0-100", - "A > B", -]; - -function formatDateTime(date: Date): string { - const y = date.getFullYear(); - const m = String(date.getMonth() + 1).padStart(2, "0"); - const d = String(date.getDate()).padStart(2, "0"); - const h = String(date.getHours()).padStart(2, "0"); - const min = String(date.getMinutes()).padStart(2, "0"); - return `${y}-${m}-${d} ${h}:${min}`; -} - -/** H-B 탭 목데이터 생성 */ -export function createHBArticleMock(count = 10): HBArticleItem[] { - faker.seed(100); - return Array.from({ length: count }, (_, _index) => ({ - statsItmNm: faker.helpers.arrayElement(HB_STATS_ITEMS), - vrfcItmNm: faker.helpers.arrayElement(HB_VERIFICATION_ITEMS), - tblNm: faker.helpers.arrayElement(TABLE_NAMES), - colNm: faker.helpers.arrayElement(COLUMN_NAMES), - percentileVal: faker.number.float({ - min: 0, - max: 100, - fractionDigits: 2, - }), - aVal: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }), - cVal: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }), - regNm: faker.person.fullName(), - regDt: formatDateTime(faker.date.recent({ days: 90 })), - })); -} - -/** I-MR 탭 목데이터 생성 */ -export function createIMRArticleMock(count = 10): IMRArticleItem[] { - faker.seed(200); - return Array.from({ length: count }, (_, _index) => ({ - statsItmNm: faker.helpers.arrayElement(IMR_STATS_ITEMS), - vrfcNm: faker.helpers.arrayElement(VERIFICATION_NAMES), - tblNm: faker.helpers.arrayElement(TABLE_NAMES), - colNm: faker.helpers.arrayElement(COLUMN_NAMES), - regNm: faker.person.fullName(), - regDt: formatDateTime(faker.date.recent({ days: 90 })), - })); -} - -/** Validation 탭 목데이터 생성 */ -export function createValidationArticleMock( - count = 6, -): ValidationArticleItem[] { - faker.seed(300); - return Array.from({ length: count }, (_, index) => ({ - no: `${index + 1}`, - statsItmNm: faker.helpers.arrayElement(VALIDATION_STATS_ITEMS), - vrfcNm: faker.helpers.arrayElement(VERIFICATION_NAMES), - tblNm: faker.helpers.arrayElement(TABLE_NAMES), - colNm: faker.helpers.arrayElement(COLUMN_NAMES), - cmpTblNm: faker.helpers.arrayElement(TABLE_NAMES), - cmpColNm: faker.helpers.arrayElement(COLUMN_NAMES), - bscRul: faker.helpers.arrayElement(BASIC_RULES), - })); -} - -/** H-B 탭 기본 목데이터 */ -export const hbArticleMockData = createHBArticleMock(10); - -/** I-MR 탭 기본 목데이터 */ -export const imrArticleMockData = createIMRArticleMock(10); - -/** Validation 탭 기본 목데이터 */ -export const validationArticleMockData = createValidationArticleMock(6); diff --git a/apps/st/src/entities/statistics/model/verification-article-column.ts b/apps/st/src/entities/statistics/model/verification-article/verification-article-column.ts similarity index 100% rename from apps/st/src/entities/statistics/model/verification-article-column.ts rename to apps/st/src/entities/statistics/model/verification-article/verification-article-column.ts diff --git a/apps/st/src/entities/statistics/model/verification-article-interface.ts b/apps/st/src/entities/statistics/model/verification-article/verification-article-interface.ts similarity index 100% rename from apps/st/src/entities/statistics/model/verification-article-interface.ts rename to apps/st/src/entities/statistics/model/verification-article/verification-article-interface.ts