328 lines
14 KiB
Markdown
328 lines
14 KiB
Markdown
# 일일/월간/연간 생산 레포트 구현 계획
|
||
|
||
## 현황 파악
|
||
|
||
| 항목 | 내용 |
|
||
|------|------|
|
||
| DB | PostgreSQL + TimescaleDB, `history_table` |
|
||
| 데이터 주기 | 1분 스냅샷 |
|
||
| 현재 태그 수 | 1,807개 |
|
||
| 이용 가능 태그 종류 | FI, FICQ, FIC, LI, LICA, TI, TICA, PI, PIC 등 |
|
||
|
||
---
|
||
|
||
## ⚠️ 구현 전 반드시 확인해야 할 사항 (사용자 결정 필요)
|
||
|
||
### 1. 생산량 정의
|
||
|
||
어떤 태그가 "생산량"을 나타내는지 사용자가 지정해야 한다.
|
||
|
||
| 질문 | 예시 |
|
||
|------|------|
|
||
| 주요 생산 제품은? | 예: 에틸렌, LPG, 나프타 등 |
|
||
| 각 제품의 생산량 태그는? | 예: `ficq-6101.pv` = 6호기 에틸렌 출하 유량 |
|
||
| 유량의 공학 단위는? | 예: ton/hr, Nm³/hr, kg/hr |
|
||
| 적산 방법은? | 순간유량(PV) × 1분 → 시간적산, 또는 별도 적산 태그 존재 여부 |
|
||
| 조업시간 판정 기준 태그는? | 예: 특정 태그값 > 0 이면 운전 중 |
|
||
|
||
### 2. 레포트 항목 정의
|
||
|
||
사용자가 원하는 레포트 항목을 구체화해야 한다. 아래는 일반적인 공정 플랜트 기준 초안.
|
||
|
||
---
|
||
|
||
## 레포트 구조 설계 (초안)
|
||
|
||
### 일일 레포트 (1일치)
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────┐
|
||
│ ○○플랜트 일일 생산 레포트 2026-05-01 (목) │
|
||
├──────────────┬─────────────────────────────────────────┤
|
||
│ 1. 생산 실적 │ 제품별 일일 생산량 (ton 또는 Nm³) │
|
||
│ │ - 각 FICQ 태그 적산값 │
|
||
│ │ - 전일 대비 증감 │
|
||
│ │ - 월 누계 │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 2. 조업 현황 │ 운전시간 (분), 가동률 (%) │
|
||
│ │ 트러블/정지 횟수 │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 3. 주요 공정 │ 핵심 온도/압력/레벨 태그 평균·최대·최소 │
|
||
│ 조건 │ (TI, PI, LI 대표 태그 선정) │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 4. 품질 지표 │ 해당 태그 존재 시 추가 │
|
||
└──────────────┴─────────────────────────────────────────┘
|
||
```
|
||
|
||
### 월간 레포트 (1개월치 — 일별 집계)
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────┐
|
||
│ ○○플랜트 월간 생산 레포트 2026년 5월 │
|
||
├──────────────┬─────────────────────────────────────────┤
|
||
│ 1. 월간 생산 │ 제품별 월 총 생산량 │
|
||
│ 실적 │ 일별 생산량 추이 (테이블 + 차트) │
|
||
│ │ 최대/최소 생산일 │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 2. 가동률 │ 월간 가동 시간 / 목표 시간 │
|
||
│ │ 일별 가동률 테이블 │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 3. 공정 조건 │ 월 평균·최대·최소 (온도, 압력, 레벨) │
|
||
│ 통계 │ 일별 평균 추이 테이블 │
|
||
└──────────────┴─────────────────────────────────────────┘
|
||
```
|
||
|
||
### 연간 레포트 (1년치 — 월별 집계)
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────┐
|
||
│ ○○플랜트 연간 생산 레포트 2026년 │
|
||
├──────────────┬─────────────────────────────────────────┤
|
||
│ 1. 월별 생산 │ 제품별 월별 생산량 (1~12월 테이블) │
|
||
│ 실적 │ 연 총 생산량 │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 2. 월별 가동률│ 월별 가동시간 / 가동률 │
|
||
├──────────────┼─────────────────────────────────────────┤
|
||
│ 3. 공정 조건 │ 월별 평균 공정 조건 추이 │
|
||
│ 월별 통계 │ │
|
||
└──────────────┴─────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 핵심 SQL 설계
|
||
|
||
### 일일 유량 적산 (1분 데이터 → 일일 합계)
|
||
|
||
```sql
|
||
-- FICQ 태그 일일 적산값 (단위: 공학단위/분 × 1440분)
|
||
-- 순간유량이 ton/hr 단위일 경우 × (1/60) = ton/분
|
||
SELECT
|
||
recorded_at::date AS day,
|
||
tagname,
|
||
SUM(value::double precision / 60.0) AS daily_total, -- ton/hr → ton
|
||
COUNT(*) AS data_points,
|
||
ROUND(COUNT(*) / 1440.0 * 100, 1) AS data_rate_pct -- 데이터 충족률
|
||
FROM history_table
|
||
WHERE tagname IN ('ficq-6101.pv', 'ficq-6113.pv', ...) -- 생산 태그 목록
|
||
AND recorded_at >= '2026-05-01+09'
|
||
AND recorded_at < '2026-06-01+09'
|
||
GROUP BY day, tagname
|
||
ORDER BY day, tagname;
|
||
```
|
||
|
||
### 공정 조건 일별 통계
|
||
|
||
```sql
|
||
SELECT
|
||
recorded_at::date AS day,
|
||
tagname,
|
||
AVG(value::double precision) AS avg_val,
|
||
MAX(value::double precision) AS max_val,
|
||
MIN(value::double precision) AS min_val,
|
||
STDDEV(value::double precision) AS stddev_val
|
||
FROM history_table
|
||
WHERE tagname IN ('ti-6111a.pv', 'ti-6211a.pv', ...) -- 대표 온도/압력 태그
|
||
AND recorded_at >= '2026-05-01+09'
|
||
AND recorded_at < '2026-06-01+09'
|
||
GROUP BY day, tagname
|
||
ORDER BY day, tagname;
|
||
```
|
||
|
||
### 조업시간 계산 (일별 가동분 집계)
|
||
|
||
```sql
|
||
-- 특정 태그(기준 태그)의 값이 0 초과인 분의 수 = 가동 분
|
||
SELECT
|
||
recorded_at::date AS day,
|
||
COUNT(*) FILTER (
|
||
WHERE value::double precision > 0
|
||
) AS running_minutes,
|
||
COUNT(*) AS total_minutes,
|
||
ROUND(
|
||
COUNT(*) FILTER (WHERE value::double precision > 0)
|
||
/ COUNT(*)::numeric * 100, 1
|
||
) AS availability_pct
|
||
FROM history_table
|
||
WHERE tagname = 'ficq-6101.pv' -- 가동 판정 기준 태그
|
||
AND recorded_at >= '2026-05-01+09'
|
||
AND recorded_at < '2026-06-01+09'
|
||
GROUP BY day
|
||
ORDER BY day;
|
||
```
|
||
|
||
### 월별 집계 (연간 레포트용)
|
||
|
||
```sql
|
||
SELECT
|
||
DATE_TRUNC('month', recorded_at AT TIME ZONE 'Asia/Seoul') AS month,
|
||
tagname,
|
||
SUM(value::double precision / 60.0) AS monthly_total,
|
||
AVG(value::double precision) AS avg_val
|
||
FROM history_table
|
||
WHERE tagname IN ('ficq-6101.pv', ...)
|
||
AND recorded_at >= '2026-01-01+09'
|
||
AND recorded_at < '2027-01-01+09'
|
||
GROUP BY month, tagname
|
||
ORDER BY month, tagname;
|
||
```
|
||
|
||
---
|
||
|
||
## 구현 구성요소
|
||
|
||
### Backend (C# ASP.NET Core)
|
||
|
||
#### 새 인터페이스 추가 (`IExperionServices.cs`)
|
||
|
||
```csharp
|
||
// 레포트 서비스 인터페이스
|
||
public interface IProductionReportService
|
||
{
|
||
Task<DailyReportResult> GetDailyReportAsync(DateOnly date, ReportConfig config);
|
||
Task<MonthlyReportResult> GetMonthlyReportAsync(int year, int month, ReportConfig config);
|
||
Task<AnnualReportResult> GetAnnualReportAsync(int year, ReportConfig config);
|
||
}
|
||
|
||
// 레포트 설정 (사용자 정의 태그 목록)
|
||
public record ReportConfig(
|
||
List<string> ProductionTags, // 생산량 적산 태그
|
||
List<string> ConditionTags, // 공정 조건 태그 (온도/압력/레벨)
|
||
string AvailabilityTag, // 가동 판정 기준 태그
|
||
string FlowUnit, // 유량 단위 (ton/hr, Nm³/hr 등)
|
||
double FlowConvFactor // 단위 환산 계수 (예: 1/60 for ton/hr → ton/min)
|
||
);
|
||
|
||
// 결과 레코드
|
||
public record DailyProductionRow(string TagName, double DailyTotal, int DataPoints, double DataRatePct);
|
||
public record ConditionStatRow(string TagName, double Avg, double Max, double Min, double Stddev);
|
||
public record DailyReportResult(
|
||
DateOnly Date,
|
||
List<DailyProductionRow> Production,
|
||
List<ConditionStatRow> Conditions,
|
||
int RunningMinutes, int TotalMinutes, double AvailabilityPct
|
||
);
|
||
// Monthly/Annual 유사 구조
|
||
```
|
||
|
||
#### 새 컨트롤러 (`ExperionControllers.cs`)
|
||
|
||
```
|
||
GET /api/report/daily?date=2026-05-01
|
||
GET /api/report/monthly?year=2026&month=5
|
||
GET /api/report/annual?year=2026
|
||
GET /api/report/config ← 현재 레포트 설정 조회
|
||
POST /api/report/config ← 레포트 설정 저장 (태그 목록 등)
|
||
```
|
||
|
||
#### 레포트 설정 저장
|
||
|
||
`report_config.json` 파일로 서버 디렉토리에 저장 (appsettings 또는 별도 JSON).
|
||
|
||
---
|
||
|
||
### Frontend (HTML/JS)
|
||
|
||
#### 새 탭 추가 (`index.html`)
|
||
|
||
사이드바에 `09 생산레포트` 탭 추가, `#pane-report` 섹션.
|
||
|
||
#### UI 구성
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ [일일] [월간] [연간] ← 레포트 유형 선택 │
|
||
│ 날짜/월/년 선택기 │
|
||
│ [조회] [Excel 다운로드] [PDF 인쇄] │
|
||
├─────────────────────────────────────────────┤
|
||
│ │
|
||
│ 레포트 본문 렌더링 영역 │
|
||
│ (HTML 테이블 기반, 인쇄 스타일 적용) │
|
||
│ │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### JS 함수 목록
|
||
|
||
```javascript
|
||
rptLoadDaily(date) // 일일 레포트 로드
|
||
rptLoadMonthly(y, m) // 월간 레포트 로드
|
||
rptLoadAnnual(y) // 연간 레포트 로드
|
||
rptRender(data, type) // 레포트 HTML 렌더링
|
||
rptExportExcel() // SheetJS로 Excel 출력 (기존 t2sExportExcel 패턴 재사용)
|
||
rptPrint() // window.print() — 인쇄/PDF 저장
|
||
rptSaveConfig(config) // 태그 설정 저장
|
||
```
|
||
|
||
---
|
||
|
||
### 레포트 설정 화면 (태그 관리)
|
||
|
||
레포트 생성 전에 **어떤 태그가 무슨 의미인지** 등록해야 한다.
|
||
|
||
```
|
||
생산량 태그 등록:
|
||
태그명 설명 단위 환산계수
|
||
ficq-6101.pv 6호기 원료 유량 ton/hr 0.01667
|
||
ficq-6113.pv 6호기 제품 유량 ton/hr 0.01667
|
||
...
|
||
|
||
공정 조건 태그 등록:
|
||
태그명 설명 단위
|
||
ti-6111a.pv 6호기 반응온도 ℃
|
||
pi-6101.pv 6호기 반응압력 kPa
|
||
li-6111.pv 6호기 레벨 %
|
||
...
|
||
|
||
가동 판정 기준 태그: ficq-6101.pv (값 > [0] 이면 운전)
|
||
```
|
||
|
||
이 설정을 `report_config.json`으로 저장하고 API에서 읽어 SQL을 동적 생성.
|
||
|
||
---
|
||
|
||
## 수정/추가 파일 목록
|
||
|
||
| 파일 | 작업 |
|
||
|------|------|
|
||
| `src/Core/Application/Interfaces/IExperionServices.cs` | `IProductionReportService`, `ReportConfig`, 결과 record 추가 |
|
||
| `src/Infrastructure/Database/ExperionDbContext.cs` | `ProductionReportService` 구현 (일별/월별/연간 SQL) |
|
||
| `src/Web/Controllers/ExperionControllers.cs` | `ProductionReportController` 추가 (5개 엔드포인트) |
|
||
| `src/Web/Program.cs` | `ProductionReportService` DI 등록 |
|
||
| `src/Web/wwwroot/index.html` | `09 생산레포트` 탭 + `#pane-report` 섹션 추가 |
|
||
| `src/Web/wwwroot/js/app.js` | `rptLoad*`, `rptRender`, `rptExportExcel`, `rptPrint` 구현 |
|
||
| `src/Web/wwwroot/css/style.css` | 레포트 전용 스타일 + `@media print` 인쇄 스타일 |
|
||
| `src/Web/report_config.json` | 생산 태그 설정 파일 (신규) |
|
||
|
||
서버 코드(Python MCP) 변경 없음.
|
||
|
||
---
|
||
|
||
## 구현 순서 권장
|
||
|
||
```
|
||
1단계 (필수 선행): 사용자가 생산 태그 목록 및 단위 확정
|
||
↓
|
||
2단계: report_config.json 스키마 설계 + ReportConfig record
|
||
↓
|
||
3단계: DB 쿼리 레이어 (ProductionReportService) — 일별 집계부터
|
||
↓
|
||
4단계: API 엔드포인트 (컨트롤러)
|
||
↓
|
||
5단계: 프론트엔드 UI + 테이블 렌더링
|
||
↓
|
||
6단계: Excel export + 인쇄 스타일
|
||
↓
|
||
7단계: 월간 → 연간으로 확장
|
||
```
|
||
|
||
---
|
||
|
||
## 주의 사항
|
||
|
||
- **KST 기준 집계**: `recorded_at`은 UTC 저장이므로 일별 집계 시 `AT TIME ZONE 'Asia/Seoul'` 또는 `+ INTERVAL '9 hours'` 적용 필수. 그렇지 않으면 UTC 기준으로 집계되어 날짜가 달라짐.
|
||
- **유량 적산 단위**: `ficq-*.pv` 태그가 `ton/hr` 단위이면 1분 적산 시 `× (1/60)`. 단위 확인 필수.
|
||
- **결측 데이터 처리**: 데이터 충족률(`data_rate_pct`)이 낮은 날은 레포트에 `*` 표시 권장.
|
||
- **과거 데이터 부재**: 현재 history_table은 2026-04-22부터만 존재. 5월 데이터는 5월 이후 수집 시 자동 반영됨.
|
||
- **TimescaleDB 활용**: `time_bucket()` 또는 `DATE_TRUNC()`로 일별/월별 집계 — `time_bucket_gapfill()`로 결측 구간 0 채움 가능.
|