hee_!J commited on
Commit
f606871
·
1 Parent(s): 10f82ba

feat: PHM 2016 CMP 통합 + 3개 알람 실데이터 동작

Browse files
.gitignore CHANGED
@@ -11,3 +11,4 @@ CLAUDE.md
11
  .claude/
12
  .mcp.json
13
  data/secom/raw/*.data
 
 
11
  .claude/
12
  .mcp.json
13
  data/secom/raw/*.data
14
+ data/phm2016/raw/
agents/cause.py CHANGED
@@ -35,7 +35,7 @@ TIER2_SCHEMA = {
35
  "additionalProperties": False,
36
  }
37
 
38
- SYSTEM_PROMPT = """당신은 반도체 Photo 공정 원인 분석 전문가입니다.
39
  주어진 이상 알람과 탐지 결과, 사내 지식 문서를 근거로 가장 가능성 높은 원인을
40
  2~3개 추정합니다. 각 원인은 기여도(pct, %)를 가지며 합이 100에 가깝도록 합니다.
41
  근거(evidence)는 제공된 문서 내용에 기반해 구체적으로 작성하고, citations에는
@@ -45,7 +45,8 @@ SYSTEM_PROMPT = """당신은 반도체 Photo 공정의 원인 분석 전문가
45
 
46
  def _build_query(alarm: dict, tier1: Tier1) -> str:
47
  feature = alarm.get("feature") or ""
48
- return f"{alarm['title']} {feature} 원인 CD 산포 렌즈 노광 진동 표면 결함"
 
49
 
50
 
51
  def run_cause(alarm: dict, tier1: Tier1) -> Tier2:
 
35
  "additionalProperties": False,
36
  }
37
 
38
+ SYSTEM_PROMPT = """당신은 반도체 공정 원인 분석 전문가입니다.
39
  주어진 이상 알람과 탐지 결과, 사내 지식 문서를 근거로 가장 가능성 높은 원인을
40
  2~3개 추정합니다. 각 원인은 기여도(pct, %)를 가지며 합이 100에 가깝도록 합니다.
41
  근거(evidence)는 제공된 문서 내용에 기반해 구체적으로 작성하고, citations에는
 
45
 
46
  def _build_query(alarm: dict, tier1: Tier1) -> str:
47
  feature = alarm.get("feature") or ""
48
+ sensors = " ".join(f["name"] for f in tier1["features"])
49
+ return f"{alarm['title']} {feature} {sensors} 원인 분석 공정 이상"
50
 
51
 
52
  def run_cause(alarm: dict, tier1: Tier1) -> Tier2:
agents/detection.py CHANGED
@@ -1,9 +1,12 @@
1
  """Tier 1 이상 탐지 에이전트
2
 
3
- 알람을 SECOM 웨 row에 매핑해 이상 점수와 기여 피처를 계산
4
- 모델: IsolationForest (experiments/tier1_detection 벤치마크에서 PR-AUC 가장 우수)
5
- 이상 점수는 학습 분포 대비 백분위로 0~1 정규화
6
- 기여 피처는 표준화 값의 절대크기 Top-N (정상 분포에서 가장 벗어난 센서)
 
 
 
7
  """
8
  from functools import lru_cache
9
 
@@ -11,22 +14,25 @@ import numpy as np
11
  from sklearn.ensemble import IsolationForest
12
 
13
  from core.schema import Tier1
 
14
  from data.secom.loader import load_secom
15
  from data.secom.preprocess import SecomPreprocessor
16
 
17
  RANDOM_STATE = 42
18
  TOP_N_FEATURES = 3
19
 
20
- # SECOM은 익명화 데이터 lot/step이 없음, MVP에선 알람을 특정 웨이퍼 row에 수동 매핑
21
- # secom_row: fail 라벨 row 인덱스, wafers: 데모 컨텍스트상 영향 웨이퍼 수
22
  ALARM_WAFER = {
23
- "A1": {"secom_row": 2, "wafers": 25},
 
 
24
  }
25
 
26
 
27
  @lru_cache(maxsize=1)
28
- def _fit_model():
29
- """SECOM 전체로 전처리기와 IsolationForest 학습, 첫 호출 시 1회"""
30
  X, _ = load_secom()
31
  pre = SecomPreprocessor().fit(X)
32
  Xz = pre.transform(X)
@@ -36,28 +42,74 @@ def _fit_model():
36
  return X, pre, model, train_scores
37
 
38
 
39
- def run_detection(alarm: dict) -> Tier1:
40
- mapping = ALARM_WAFER.get(alarm["id"])
41
- if mapping is None:
42
- raise ValueError(f"SECOM 매핑이 없는 알람: {alarm['id']}")
 
 
 
 
 
 
 
 
 
43
 
44
- X, pre, model, train_scores = _fit_model()
45
- Xz = pre.transform(X.iloc[[mapping["secom_row"]]])
 
46
 
47
  raw_score = float(-model.score_samples(Xz)[0])
48
- # 학습 분포 대비 백분위, 높을수록 이상
49
  score = float((train_scores < raw_score).mean())
50
 
51
- # 표준화 값의 절대크기 = 정상 분포에서 벗어난 정도, Top-N
52
  deviations = np.abs(Xz[0])
53
  top_idx = np.argsort(deviations)[::-1][:TOP_N_FEATURES]
54
  features = [
55
  {"name": pre.keep_cols[i], "value": round(float(deviations[i]), 2)}
56
  for i in top_idx
57
  ]
58
-
59
  return {
60
  "score": round(score, 2),
61
  "features": features,
62
  "lot": {"id": alarm["lot_id"], "wafers": mapping["wafers"]},
63
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """Tier 1 이상 탐지 에이전트
2
 
3
+ 알람을 터셋의 특정 wafer에 매핑해 이상 점수와 기여 피처를 계산
4
+ - A1, A2: SECOM 공개 데이터셋 (다른 fail row 매핑)
5
+ - A3: PHM 2016 CMP 공개 데이터셋 (CMP step 실측, 25개 명명 센서)
6
+
7
+ 모델: IsolationForest (벤치마크에서 PR-AUC 우수)
8
+ 이상 점수: 학습 분포 대비 백분위로 0~1 정규화
9
+ 기여 피처: 표준화 값의 절대크기 Top-N (정상 분포에서 가장 벗어난 센서)
10
  """
11
  from functools import lru_cache
12
 
 
14
  from sklearn.ensemble import IsolationForest
15
 
16
  from core.schema import Tier1
17
+ from data.phm2016.loader import load_phm_cmp
18
  from data.secom.loader import load_secom
19
  from data.secom.preprocess import SecomPreprocessor
20
 
21
  RANDOM_STATE = 42
22
  TOP_N_FEATURES = 3
23
 
24
+ # 알람 -> 데이터 소스 매핑
25
+ # wafers: 데모 컨텍스트상 영향 웨이퍼 수
26
  ALARM_WAFER = {
27
+ "A1": {"source": "secom", "row": 2, "wafers": 25},
28
+ "A2": {"source": "secom", "row": 10, "wafers": 18},
29
+ "A3": {"source": "phm_cmp", "wafer_id": 2058207580, "stage": "A", "wafers": 30},
30
  }
31
 
32
 
33
  @lru_cache(maxsize=1)
34
+ def _fit_secom():
35
+ """SECOM 전체로 전처리기와 IsolationForest 학습, 첫 호출 시 1회"""
36
  X, _ = load_secom()
37
  pre = SecomPreprocessor().fit(X)
38
  Xz = pre.transform(X)
 
42
  return X, pre, model, train_scores
43
 
44
 
45
+ @lru_cache(maxsize=1)
46
+ def _fit_phm_cmp():
47
+ """PHM CMP 전체로 표준화 + IsolationForest 학습, 첫 호출 시 1회"""
48
+ from sklearn.preprocessing import StandardScaler
49
+
50
+ features, _ = load_phm_cmp()
51
+ scaler = StandardScaler().fit(features.values)
52
+ Xz = scaler.transform(features.values)
53
+ model = IsolationForest(n_estimators=200, random_state=RANDOM_STATE)
54
+ model.fit(Xz)
55
+ train_scores = -model.score_samples(Xz)
56
+ return features, scaler, model, train_scores
57
+
58
 
59
+ def _detect_secom(alarm: dict, mapping: dict) -> Tier1:
60
+ X, pre, model, train_scores = _fit_secom()
61
+ Xz = pre.transform(X.iloc[[mapping["row"]]])
62
 
63
  raw_score = float(-model.score_samples(Xz)[0])
 
64
  score = float((train_scores < raw_score).mean())
65
 
 
66
  deviations = np.abs(Xz[0])
67
  top_idx = np.argsort(deviations)[::-1][:TOP_N_FEATURES]
68
  features = [
69
  {"name": pre.keep_cols[i], "value": round(float(deviations[i]), 2)}
70
  for i in top_idx
71
  ]
 
72
  return {
73
  "score": round(score, 2),
74
  "features": features,
75
  "lot": {"id": alarm["lot_id"], "wafers": mapping["wafers"]},
76
  }
77
+
78
+
79
+ def _detect_phm_cmp(alarm: dict, mapping: dict) -> Tier1:
80
+ features, scaler, model, train_scores = _fit_phm_cmp()
81
+ key = (mapping["wafer_id"], mapping["stage"])
82
+ if key not in features.index:
83
+ raise KeyError(f"PHM CMP에 없는 wafer/stage: {key}")
84
+
85
+ row = features.loc[[key]].values
86
+ Xz = scaler.transform(row)
87
+
88
+ raw_score = float(-model.score_samples(Xz)[0])
89
+ score = float((train_scores < raw_score).mean())
90
+
91
+ deviations = np.abs(Xz[0])
92
+ top_idx = np.argsort(deviations)[::-1][:TOP_N_FEATURES]
93
+ col_names = list(features.columns)
94
+ out_features = [
95
+ {"name": col_names[i], "value": round(float(deviations[i]), 2)}
96
+ for i in top_idx
97
+ ]
98
+ return {
99
+ "score": round(score, 2),
100
+ "features": out_features,
101
+ "lot": {"id": alarm["lot_id"], "wafers": mapping["wafers"]},
102
+ }
103
+
104
+
105
+ def run_detection(alarm: dict) -> Tier1:
106
+ mapping = ALARM_WAFER.get(alarm["id"])
107
+ if mapping is None:
108
+ raise ValueError(f"매핑 없는 알람: {alarm['id']}")
109
+
110
+ source = mapping["source"]
111
+ if source == "secom":
112
+ return _detect_secom(alarm, mapping)
113
+ if source == "phm_cmp":
114
+ return _detect_phm_cmp(alarm, mapping)
115
+ raise ValueError(f"알 수 없는 데이터 소스: {source}")
agents/rag/knowledge/FLOW-CMP-DOWN-001.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FLOW-CMP-DOWN-001 — CMP 하류 공정 의존성과 수율 영향
2
+
3
+ ## 공정 흐름
4
+
5
+ CMP (현재) → Diffusion → Implant → Metal Deposition
6
+
7
+ ## CMP 이상이 하류에 미치는 영향
8
+
9
+ CMP의 재료 제거율(MRR) 이상은 wafer 두께 편차로 직결되며 후공정 전반에 영향을
10
+ 미칩니다. 일반적으로:
11
+
12
+ - **Diffusion**: 두께 편차에 따라 도핑 깊이 균일도 저하, 약 10~15% 변동
13
+ - **Implant**: 표면 위치 오차로 도핑 농도 편차 발생, 약 5~10% 변동
14
+ - **Metal Deposition**: 표면 평탄도 손상 시 단차 피복성 저하
15
+
16
+ ## 수율 영향
17
+
18
+ CMP MRR 이탈이 1σ 이상 발생할 때 수율 손실은 통상 1.5~3.0 %p로 보고됩니다.
19
+ 특히 동일 chamber 처리 로트 전체에 영향이 미치므로 후공정 진입 보류 후 두께
20
+ 재측정이 우선 조치입니다.
21
+
22
+ ## 의존성 분류 기준
23
+
24
+ - current: 이상이 발생한 현재 공정
25
+ - impacted: 직접 영향을 받는 후공정 (delta 두 자리수 이상)
26
+ - minor: 영향이 경미한 후공정 (delta 한 자리수)
agents/rag/knowledge/FMEA-CMP-003.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FMEA-CMP-003 — CMP 공정 실패 모드 분석
2
+
3
+ ## 대상
4
+
5
+ CMP(Chemical Mechanical Planarization) 공정의 주요 실패 모드와 원인, 영향, 검출
6
+ 방법을 정리합니다.
7
+
8
+ ## 실패 모드
9
+
10
+ ### 1. 재료 제거율(MRR) 폭주 또는 부족
11
+
12
+ - 잠재 원인: 슬러리 유량 이상, 패드 마모, 압력 설정 오류
13
+ - 영향: 두께 편차 → 후공정 패턴 결함, 수율 손실
14
+ - 검출: AVG_REMOVAL_RATE 모니터링, SLURRY_FLOW_LINE 유량 SPC
15
+
16
+ ### 2. 표면 균일도 저하
17
+
18
+ - 잠재 원인: RETAINER_RING_PRESSURE 편차, MAIN_OUTER_AIR_BAG_PRESSURE 불균형
19
+ - 영향: 후공정 Photo 단계의 포커스 편차 유발
20
+ - 검출: 두께 매핑, 압력 센서 SPC
21
+
22
+ ### 3. 패드 컨디션 악화
23
+
24
+ - 잠재 원인: 드레서 사용량(USAGE_OF_DRESSER) 한계 초과, DRESSING_WATER_STATUS 비정상
25
+ - 영향: MRR 변동성 증가, 스크래치 발생
26
+ - 검출: 드레서 사용량 추적, 시각 검사
27
+
28
+ ## 우선순위
29
+
30
+ MRR 폭주는 즉시 후공정 영향이 크고 회수가 어려워 최우선 관리 대상입니다. 원인
31
+ 중 슬러리 라인 이상은 펌프 제어와 직접 연관되어 자동 인터록 적용이 효과적입니다.
agents/rag/knowledge/FMEA-ET-004.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FMEA-ET-004 — Etch 공정 실패 모드 분석
2
+
3
+ ## 대상
4
+
5
+ Etch(식각) 공정의 주요 실패 모드와 원인, 영향, 검출 방법을 정리합니다.
6
+
7
+ ## 실패 모드
8
+
9
+ ### 1. 트렌치 깊이 부족 또는 과식각
10
+
11
+ - 잠재 원인: 식각 가스 유량 이상, 플라즈마 밀도 저하, 전극 오염
12
+ - 영향: 후공정 CMP·Metal Deposition 단계의 단차 균일도 악화, 수율 손실
13
+ - 검출: 두께 SEM 계측, 가스 유량 SPC
14
+
15
+ ### 2. 측벽 프로파일 이상
16
+
17
+ - 잠재 원인: 챔버 압력 편차, 가스 비율 오류
18
+ - 영향: 패턴 정합 불량, CD 산포 증가
19
+ - 검출: 단면 SEM, 챔버 압력 SPC
20
+
21
+ ### 3. 표면 결함(미세 입자 부착)
22
+
23
+ - 잠재 원인: 챔버 클리닝 주기 초과, 전극 마모
24
+ - 영향: 후공정 결함 전파
25
+ - 검출: 결함 검사 장비, 챔버 클리닝 이력 추적
26
+
27
+ ## 우선순위
28
+
29
+ 트렌치 깊이 부족은 후공정 영향 범위가 가장 커 최우선 관리 대상입니다. 가스 유량
30
+ 이상은 자동 알람과 챔버 인터록을 통해 조기 차단이 가능합니다.
agents/rag/knowledge/INC-CMP-2025-0142.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # INC-CMP-2025-0142 — CMP 슬러리 유량 이상에 의한 재료 제거율 폭주
2
+
3
+ ## 분류
4
+
5
+ - 유형: 공정 인시던트
6
+ - 공정: CMP (Chemical Mechanical Planarization)
7
+ - 사업부: 메모리 1동
8
+ - 발생일: 2025-01-14
9
+
10
+ ## 증상
11
+
12
+ CMP 공정에서 평균 재료 제거율(AVG_REMOVAL_RATE)이 정상 범위(70~90 nm/min)를 크게
13
+ 벗어나 4,000 nm/min 이상으로 측정되었습니다. 동일 chamber에서 처리된 연속 로트
14
+ 대부분이 영향을 받았습니다.
15
+
16
+ ## 원인
17
+
18
+ 슬러리 공급 라인(SLURRY_FLOW_LINE_A/B/C) 유량이 정상치 대비 비정상적으로 높게
19
+ 유지되었음이 확인되었습니다. 정확히는 슬러리 공급 펌프 제어 신호 오류로 인해
20
+ 세 라인 모두 동시에 과공급된 사례입니다.
21
+
22
+ ## 조치
23
+
24
+ - 슬러리 공급 펌프 즉시 정지 및 교체
25
+ - 영향 로트 후공정 진입 보류 및 두께 계측으로 재작업 여부 판단
26
+ - 펌프 제어 로직 펌웨어 업데이트
27
+
28
+ ## 재발 방지
29
+
30
+ 슬러리 유량 임계값을 신설하고, 임계 초과 시 자동 알람 + 챔버 인터록이 작동하도록
31
+ 모니터링 체계를 강화하였습니다.
agents/rag/knowledge/INC-ET-2024-0301.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # INC-ET-2024-0301 — Etch 트렌치 깊이 부족에 의한 결함 발생
2
+
3
+ ## 분류
4
+
5
+ - 유형: 공정 인시던트
6
+ - 공정: Etch (식각)
7
+ - 사업부: 메모리 2동
8
+ - 발생일: 2024-08-12
9
+
10
+ ## 증상
11
+
12
+ Etch 공정에서 Trench Depth가 목표치 대비 약 12% 부족한 상태로 측정되었습니다.
13
+ 동일 chamber에서 처리된 로트에서 일관되게 관측되어 chamber 단위 이상으로
14
+ 판단되었습니다.
15
+
16
+ ## 원인
17
+
18
+ 식각 가스(SF6/CHF3) 유량 제어 밸브의 부분 고장으로 가스 공급량이 정상 대비 약
19
+ 8% 감소했음이 확인되었습니다. 추가로 전극 표면 부착물 누적으로 플라즈마 밀도가
20
+ 저하된 것이 복합 원인이었습니다.
21
+
22
+ ## 조치
23
+
24
+ - chamber 즉시 정지 및 가스 공급 밸브 교체
25
+ - 전극 표면 세정(약 4시간 소요)
26
+ - 영향 로트 후공정 진입 보류 후 두께 재측정으로 재작업 여부 판단
27
+
28
+ ## 재발 방지
29
+
30
+ 가스 유량 SPC를 강화하고, 전극 세정 주기를 30일에서 21일로 단축 적용하였습니다.
agents/rag/knowledge/SOP-CMP-SLURRY-001.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SOP-CMP-SLURRY-001 — CMP 슬러리 관리 표준 절차
2
+
3
+ ## 목적
4
+
5
+ CMP 공정의 슬러리 공급 시스템 점검과 이상 발생 시 대응 절차를 표준화합니다.
6
+
7
+ ## 적용 범위
8
+
9
+ 전 사업부 CMP 설비의 SLURRY_FLOW_LINE_A/B/C 공급 시스템.
10
+
11
+ ## 정기 점검 주기
12
+
13
+ - 슬러리 유량 캘리브레이션: 7일
14
+ - 펌프 동작 점검: 14일
15
+ - 슬러리 농도 측정: 매 로트
16
+
17
+ ## 이상 발생 시 대응 절차
18
+
19
+ 1. 챔버 정지 및 후공정 진입 차단
20
+ 2. SLURRY_FLOW_LINE_A/B/C 측정값 확인 후 정상 범위 이탈 여부 판단
21
+ 3. 펌프 제어 신호 점검 및 펌웨어 버전 확인
22
+ 4. 필요 시 펌프 교체 (예상 3시간)
23
+ 5. 시험 wafer로 MRR 정상 범위 재현 확인
24
+ 6. 챔버 재가동 및 로트 진입 재개
25
+
26
+ ## 기록
27
+
28
+ 이상 사례는 인시던트 DB에 기록하며, 슬러리 유량 추세는 일일 SPC 리포트에
29
+ 포함합니다.
core/pipeline.py CHANGED
@@ -10,7 +10,7 @@ from core.schema import TierData
10
  from data import demo
11
 
12
  # LLM 멀티에이전트로 처리할 알람
13
- REAL_AGENT_ALARMS = {"A1"}
14
 
15
 
16
  def get_tier_data(alarm_id: str) -> TierData | None:
 
10
  from data import demo
11
 
12
  # LLM 멀티에이전트로 처리할 알람
13
+ REAL_AGENT_ALARMS = {"A1", "A2", "A3"}
14
 
15
 
16
  def get_tier_data(alarm_id: str) -> TierData | None:
data/demo.py CHANGED
@@ -27,11 +27,12 @@ DEFAULT_ALARMS = [
27
  },
28
  {
29
  "id": "A3",
30
- "status": "done",
31
  "title": "CMP Step 이상",
32
  "lot_id": "L20240510-N-08",
33
- "feature": None,
34
- "time": "2시간 전",
 
35
  },
36
  ]
37
 
 
27
  },
28
  {
29
  "id": "A3",
30
+ "status": "warn",
31
  "title": "CMP Step 이상",
32
  "lot_id": "L20240510-N-08",
33
+ "feature": "재료 제거율(MRR)",
34
+ "feature_arrow": "",
35
+ "time": "1시간 전",
36
  },
37
  ]
38
 
data/phm2016/README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PHM 2016 CMP Dataset
2
+
3
+ CMP(Chemical Mechanical Planarization, 화학 기계적 연마) 공정 실측 데이터셋입니다.
4
+ A3 알람(CMP Step 이상)의 Tier 1 이상 탐지에서 사용합니다.
5
+
6
+ ## 개요
7
+
8
+ - 출처: PHM Society Data Challenge 2016
9
+ - 1,981 wafer × 2 stage(A/B), 25개 센서 실측값 + 평균 재료 제거율(MRR) 라벨
10
+ - 센서 이름이 모두 공개되어 있어 도메인 해석이 가능합니다 (SECOM의 익명성과 대조)
11
+
12
+ ## 주요 센서
13
+
14
+ - 압력: `PRESSURIZED_CHAMBER_PRESSURE`, `RETAINER_RING_PRESSURE`, `MAIN_OUTER_AIR_BAG_PRESSURE` 등
15
+ - 회전: `WAFER_ROTATION`, `STAGE_ROTATION`, `HEAD_ROTATION`
16
+ - 슬러리 유량: `SLURRY_FLOW_LINE_A/B/C`
17
+ - 사용량: `USAGE_OF_BACKING_FILM`, `USAGE_OF_DRESSER`, `USAGE_OF_POLISHING_TABLE` 등
18
+
19
+ ## 준비 방법
20
+
21
+ 원본 ZIP을 `data/phm2016/raw/`에 풀어두세요. 압축 해제 후 다음 구조가 되어야 합니다.
22
+
23
+ ```
24
+ data/phm2016/raw/
25
+ ├── CMP-training-removalrate.csv # 학습용 정답 라벨
26
+ ├── CMP-test-removalrate.csv
27
+ └── CMP-data/
28
+ ├── training/CMP-training-NNN.csv # trajectory 시계열 (185개)
29
+ └── test/CMP-test-NNN.csv
30
+ ```
31
+
32
+ raw 데이터는 약 160MB로 git에 포함되지 않습니다(`.gitignore`).
33
+
34
+ ## 로더 사용
35
+
36
+ ```python
37
+ from data.phm2016.loader import load_phm_cmp
38
+
39
+ features, labels = load_phm_cmp()
40
+ # features: (N, 19) — wafer-stage 단위 센서 평균
41
+ # labels: (N,) — AVG_REMOVAL_RATE
42
+ # index: MultiIndex (WAFER_ID, STAGE)
43
+ ```
44
+
45
+ trajectory 전체를 (WAFER_ID, STAGE)별로 평균 집계하여 wafer-stage 단위 feature
46
+ vector를 만듭니다. 첫 호출 시 약 10~30초가 소요되며 이후는 `lru_cache`로 즉시
47
+ 응답합니다.
48
+
49
+ ## 알람 매핑
50
+
51
+ A3(CMP Step 이상) 알람이 이 데이터의 특정 wafer에 매핑됩니다 (`agents/detection.py`의
52
+ `ALARM_WAFER`). MRR이 평균에서 크게 벗어난 wafer를 선택하여 이상 시나리오로
53
+ 사용합니다.
data/phm2016/__init__.py ADDED
File without changes
data/phm2016/loader.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PHM 2016 Data Challenge CMP 데이터셋 로더
2
+
3
+ CMP(Chemical Mechanical Planarization) 공정 센서 데이터, 25개 실 센서 이름 공개
4
+ - 1981 wafer × 2 stage(A/B)
5
+ - target: 평균 재료 제거율(AVG_REMOVAL_RATE)
6
+ 출처: https://phmsociety.org PHM Data Challenge 2016
7
+
8
+ raw/CMP-data/training/CMP-training-NNN.csv: trajectory 시계열 (wafer 다수)
9
+ raw/CMP-training-removalrate.csv: (WAFER_ID, STAGE, AVG_REMOVAL_RATE) 라벨
10
+
11
+ per-wafer feature vector를 만들기 위해 trajectory를 평균으로 집계
12
+ A3 알람(CMP step 이상)이 이 데이터로 Tier 1 이상 탐지 수행
13
+ """
14
+ from functools import lru_cache
15
+ from pathlib import Path
16
+
17
+ import pandas as pd
18
+
19
+ RAW_DIR = Path(__file__).parent / "raw"
20
+ TRAIN_TRAJ_DIR = RAW_DIR / "CMP-data" / "training"
21
+ TRAIN_LABEL = RAW_DIR / "CMP-training-removalrate.csv"
22
+
23
+ # 집계 대상 센서 컬럼, 진짜 의미 있는 이름들
24
+ SENSOR_COLS = [
25
+ "USAGE_OF_BACKING_FILM",
26
+ "USAGE_OF_DRESSER",
27
+ "USAGE_OF_POLISHING_TABLE",
28
+ "USAGE_OF_DRESSER_TABLE",
29
+ "PRESSURIZED_CHAMBER_PRESSURE",
30
+ "MAIN_OUTER_AIR_BAG_PRESSURE",
31
+ "CENTER_AIR_BAG_PRESSURE",
32
+ "RETAINER_RING_PRESSURE",
33
+ "RIPPLE_AIR_BAG_PRESSURE",
34
+ "USAGE_OF_MEMBRANE",
35
+ "USAGE_OF_PRESSURIZED_SHEET",
36
+ "SLURRY_FLOW_LINE_A",
37
+ "SLURRY_FLOW_LINE_B",
38
+ "SLURRY_FLOW_LINE_C",
39
+ "WAFER_ROTATION",
40
+ "STAGE_ROTATION",
41
+ "HEAD_ROTATION",
42
+ "DRESSING_WATER_STATUS",
43
+ "EDGE_AIR_BAG_PRESSURE",
44
+ ]
45
+
46
+
47
+ @lru_cache(maxsize=1)
48
+ def load_phm_cmp() -> tuple[pd.DataFrame, pd.Series]:
49
+ """trajectory 전체를 (WAFER_ID, STAGE)별로 평균 집계해 wafer-stage 단위 feature 반환
50
+
51
+ features: (N, 19) - 센서 평균값
52
+ labels: (N,) - AVG_REMOVAL_RATE
53
+ index: MultiIndex (WAFER_ID, STAGE)
54
+ """
55
+ if not TRAIN_TRAJ_DIR.exists() or not TRAIN_LABEL.exists():
56
+ raise FileNotFoundError(
57
+ f"PHM 2016 CMP 데이터가 없음, {RAW_DIR}에 데이터셋을 두세요 "
58
+ "(data/phm2016/README.md 참고)"
59
+ )
60
+
61
+ # trajectory 파일 전체 로드 후 (WAFER_ID, STAGE)별 평균
62
+ frames = []
63
+ for path in sorted(TRAIN_TRAJ_DIR.glob("CMP-training-*.csv")):
64
+ df = pd.read_csv(path, usecols=["WAFER_ID", "STAGE"] + SENSOR_COLS)
65
+ frames.append(df)
66
+ all_traj = pd.concat(frames, ignore_index=True)
67
+ features = all_traj.groupby(["WAFER_ID", "STAGE"])[SENSOR_COLS].mean()
68
+
69
+ labels_df = pd.read_csv(TRAIN_LABEL)
70
+ labels_df = labels_df.set_index(["WAFER_ID", "STAGE"])["AVG_REMOVAL_RATE"]
71
+
72
+ # feature와 label 인덱스 정합
73
+ common = features.index.intersection(labels_df.index)
74
+ return features.loc[common], labels_df.loc[common]
data/wip.py CHANGED
@@ -11,6 +11,14 @@ WIP_BY_ALARM: dict[str, list[ImpactLot]] = {
11
  {"label": "가공 중", "lots": 3, "wafers": 75},
12
  {"label": "대기 중", "lots": 5, "wafers": 125},
13
  ],
 
 
 
 
 
 
 
 
14
  }
15
 
16
 
 
11
  {"label": "가공 중", "lots": 3, "wafers": 75},
12
  {"label": "대기 중", "lots": 5, "wafers": 125},
13
  ],
14
+ "A2": [
15
+ {"label": "가공 중", "lots": 2, "wafers": 50},
16
+ {"label": "대기 중", "lots": 4, "wafers": 100},
17
+ ],
18
+ "A3": [
19
+ {"label": "가공 중", "lots": 4, "wafers": 100},
20
+ {"label": "대기 중", "lots": 6, "wafers": 150},
21
+ ],
22
  }
23
 
24