v7 단계 0 사전 등록 산출물 적대적 검토¶
종합 판정: CONDITIONAL REJECT¶
요약: 산출 수치 자체는 MLflow·JSON·스크립트 로직 수준에서 재현 가능하며, A1 G3/G4/G5의 블록 선택 결정화 규칙, A3 Apt88 argmax, A2 accepted 4 run 계산은 기록된 파이프라인대로 실행 시 동일하게 나온다. 그러나 이 산출물이 표방하는 "단계 1~4 모든 PAPE/HR 계산의 assertion 기준선, 코드 경로 drift 차단의 단일 진실"이라는 기능을 현 상태에서는 수행하지 못한다. 치명적 결함 2건 (§C1 정의 해시 알고리즘·payload 이중화, §C2 Gate 1 assertion 함수 불일치)은 단계 0.5 smoke 진입 전에 반드시 재작업해야 하며, 그 전까지는 "사전 등록 완료"라 주장할 수 없다. 추가로 A2 threshold는 실질적으로 track-e-tier0 3-4 run의 2-epoch prototype loss에만 근거한다는 심각한 대표성 결함이 있어, 단계 1~4 어떤 run도 이 threshold를 실제 발동시키지 못할 위험이 크다.
치명적 문제 (Critical Issues)¶
C1. 🔴 PAPE/HR 정의 해시가 Stage 0와 v7_runner 간에 알고리즘·payload 모두 불일치 (단일 코드 경로 원칙 즉시 파탄)¶
근거:
- experiments/federated/v7_0419_stage0_preregistration.py:91-94:
sha1(payload).hexdigest()[:12] = "1c4acef8a235"
payload = "PAPE_v7 := mean(...)|HR_v7 := ...|K=12|Q=90"
experiments/federated/v7_runner.py:113-126:
PAPE_DEFINITION_HASH = sha256(_PAPE_DEFINITION_SOURCE).hexdigest()[:16] = "88108ec3bd871e0f"
_PAPE_DEFINITION_SOURCE = "PAPE: target timestamps where y_true >= Q90(train+val). mean(...) over those timestamps."
왜 치명적인가:
- v7 design spec §2.4는 pape_definition_hash mismatch 시 fail-fast + "코드 경로 분기 차단"을 약속.
- 그 assertion이 통과하려면 stage 0의 정의 해시와 각 run에서 계산되는 해시가 같은 함수로 같은 문자열에서 나와야 한다. 지금은 비교 자체가 성립 불가능.
- 단계 1~4 run이 v7_runner 경유라면, MLflow에는 항상 "88108ec3bd871e0f"가 찍히고 stage 0의 "1c4acef8a235"와 영원히 다르다 — drift 탐지기가 상시 false alarm을 내거나, 아예 비교 자체가 skip될 것.
- 보고서 자체가 "정의 해시 1c4acef8a235 (PAPE/HR 정의 drift 탐지용)"으로 못 박고 있어 외부에서 운용 시 즉시 인지할 수 없는 silent-breakage 구조.
Resolve 요구사항:
1. 한 파일에만 정의한다. 가장 깨끗한 위치는 src/peak_analysis/metrics_v7.py 혹은 src/peak_analysis/v7/metrics.py에 compute_pape_v7, compute_hr_v7, PAPE_DEFINITION_SOURCE, HR_DEFINITION_SOURCE, definition_hash()를 모아 두고 stage 0 스크립트와 v7_runner 양쪽에서 동일 import로 사용.
2. payload / 해시 알고리즘 / 해시 길이를 한 곳에서만 고정. 재실행하여 stage_0_summary.json / MLflow param의 definition_hash를 덮어쓰고, 보고서 "정의 해시" 값을 그 단일 값으로 교체.
3. v7_runner의 현재 PAPE_DEFINITION_HASH / SCALER_SPACE_SIGNATURE 하드코딩은 import-and-equality로 치환되어야 한다.
C2. 🔴 Gate 1 "golden tensor 5쌍" assertion이 실제로는 다른 정의의 PAPE를 검사 (G3/G4/G5 실질 무효)¶
근거:
- v7_runner.py _run_golden_tensor_check (673~702): from peak_analysis.metrics import compute_pape, compute_hr를 호출.
- src/peak_analysis/metrics.py:213-248의 compute_pape는 window-max 기반 정의: mean(|max(y_true) − max(y_pred)| / max(y_true)) * 100 — window마다 argmax를 취해 peak 값을 비교.
- 반면 stage 0의 compute_pape_v7는 y_true >= Q90 마스크 시점만 집계하는 Q90-trigger 정의.
- 두 함수는 완전히 다른 값을 반환한다:
- G1 ([1,2,10,3] vs [1,2,8,3]): window-max는 |10-8|/10*100=20, Q90-trigger도 20 — 우연히 일치.
- G3~G5는 절대 같은 값을 내지 않는다. G3 (perfect) 0만 우연히 일치, G4·G5는 완전 불일치.
- stage 0 보고서의 G4 expected 67.34 / G5 61.48은 compute_pape_v7로만 재현되는 수치. v7_runner 쪽에서 이를 compute_pape로 돌리면 다른 수치가 나와 assertion FAIL이 상시 발생하거나, 보고서 값을 assertion target으로 쓰지 않아 Gate 1 자체가 무용.
왜 치명적인가:
- "단계 1~4 전 실험의 assertion 기준선(= override 금지)"이라는 표방이 코드 인터페이스 수준에서 이미 깨짐.
- Gate 1 통과가 쉬운 이유는 합격해서가 아니라 G3~G5 값이 _run_golden_tensor_check에서 아예 체크되지 않도록 코드가 짜여 있기 때문. v7_runner.py:690-699는 "G3~G5 TODO stage-0"만 남기고 G2 조차 손대지 않는다.
Resolve 요구사항:
1. v7_runner의 _run_golden_tensor_check를 stage 0 스크립트의 compute_pape_v7/compute_hr_v7 정의로 교체. C1 해소와 동일한 단일 모듈 import 경로 사용.
2. stage 0 보고서의 G1~G5 expected 값을 모두 코드 상수(GOLDEN_TENSORS)로 embed하되, y_true/y_pred 생성 코드까지 stage 0 스크립트와 동일 경로로 import. G5는 np.random.default_rng(42) 난수 blob을 ndarray로 freeze하여 artifact로 저장하고 v7_runner에서 load — 이래야 "random seed 의존성" 때문에 Python/numpy 버전 drift가 발생해도 동일 값 유지.
3. Gate 1이 G3~G5에 대해 atol=1e-6 수준으로 pass/fail을 판정하도록 assertion 실장. 현재 코드는 G1조차 atol=0.5(!!).
C3. 🔴 A2 threshold가 표본 편향 때문에 "silent divergence 탐지" 목적을 사실상 수행 못 함¶
근거:
- Accepted 4 run (outputs/v7_stage0/v6_loss_distribution.csv):
| run | final_train_loss | 비고 |
|---|---:|---|
| t0_kmeans_warmup2 | 1.127 | |
| t0_codebook_seed42 | 1.753 | epoch 2개뿐 (step 0, 1) — 2-epoch prototype |
| t0_yvq0_seed42 | 0.540 | |
| bench_R1_E5_b0_b1_fedrep_ditto_seed42 | 0.410 | |
- mlruns/455895458248398949/954ae57099eb472999cceb4fcb6646f6/metrics/train_loss 실물 확인: step 0, 1만 기록. 이 "max" 1.753은 사실상 수렴되기 전의 초기 loss.
- mlflow search_runs로 110 run 중 59건이 no_train_or_val_loss 탈락 — 실제로 FeDPM-Original-Phase2 run들(status: FINISHED, test_avg_pape 기록 완료, 분명히 정상 수렴)은 loss를 val_mse 로 저장하여 스크립트의 VAL_LOSS_COLS 화이트리스트에 안 걸림. 스크립트는 이들을 "정상 종료가 아님"으로 오분류.
왜 치명적인가:
- threshold 2.63 = 2-epoch prototype의 epoch-1 loss × 1.5. 정상 수렴한 v7 run은 이 값에 결코 도달하지 못한다 — loss divergence가 발생해도 threshold를 못 넘을 가능성이 매우 높다.
- "silent degradation 탐지"라는 목적의 정반대: threshold가 too permissive하고 문자 그대로 "v6 정상 run들은 이 값을 넘지 않았다"의 서술적 효력만 남아 있다.
- 보고서는 제한 사항을 투명하게 공개했다고 하나, "배수 1.5 덕분에 보수적"이라는 방어는 사실관계와 반대 — 실제로는 누락된 59 run들이 더 낮은 final loss 구간에 있을 가능성이 크고 (FedPM Phase2는 round 수십~수백을 돌았음), 포함되었으면 max가 더 작아지지 않고 같거나 커질 수도 있지만, 본질은 max의 대표성이 거의 없다는 것. 통계학적으로는 n=4의 max는 percentile 추정기로 사실상 정보 없음.
Resolve 요구사항:
1. A2를 재실행하되, VAL_LOSS_COLS / TRAIN_LOSS_COLS에 다음을 최소 추가: metrics.val_mse, metrics.val_mae, metrics.round_train_loss, metrics.round_val_loss, metrics.test_avg_mse 등 actually logged names. 각 experiment별 ls metrics/를 5 run씩 sample해 column universe를 열거한 뒤 whitelist 재정의.
2. "epoch ≥ N" 또는 "round ≥ N" 하한을 도입 — 2-epoch prototype run은 "정상 종료 수렴"이 아님을 명시적으로 배제. 적어도 v6 표준 config (e.g., track-e-tier0은 epoch 20, FedPM은 rounds=50)의 일정 비율 이상을 요구.
3. threshold의 대안 설계:
- (a) 수렴 기반: final_loss / initial_loss < 0.7 만족 run들의 final_loss 분포의 P95 기준 — silent divergence 탐지 본연의 목적에 부합.
- (b) run-by-run 기준: per-run epoch-1 loss × α (α=2.0 권장) — run 내부 기준 사용 시 표본 편향 회피.
- 설계 spec §2.4 문구도 재작성. "v6 historical max × 1.5"는 현 표본에선 의미가 없다.
4. 그 전까지는 threshold 2.63을 운영 사용하지 말 것. 보고서에 "이 threshold는 참고만, 실운용 안 함" 명시하거나, 단계 1 진입 전 재산출.
주요 문제 (Major Issues)¶
M1. 🟡 G5 expected PAPE 61.48이 n=2 샘플 기반 → "sanity 기준선"으로서 과도히 seed sensitive¶
근거: - Apt15 test[72:240] = 168시점 중 q90-hit 2개만 존재 (값 2.407, 1.800). 재현 확인 완료. - PAPE 61.48은 이 2 샘플 APE 평균: (41.82 + 81.13) / 2. - seed를 42 → 123/43/456/789/2024로 바꾸면 PAPE가 23.25, 50.60, 68.89, 75.97, 53.96로 2.6배 편차.
왜 major인가: - C1/C2 해결 후 G5가 진짜 "기준선"으로 쓰일 때, atol=1e-6 assertion은 동일 seed / numpy 버전에서만 통과. numpy RNG 알고리즘이 version update로 달라지면 silently 실패. - 동시에, peak detection sanity로서 n=2는 너무 얇다. random prediction이 "합리적으로 peak을 못 맞춘다"는 성질을 검증하는 데 n=2는 정보가 너무 적음.
Resolve 권고:
- G5 블록을 "q90-hit이 최소 K (K≥12 권장)개 있는 첫 블록"으로 규칙 변경. 현재 _extract_first_week는 "≥1 hit"이라 degenerate.
- 또는 G5의 y_pred를 ndarray로 freeze해 artifact로 저장하고 assertion은 ndarray equality로 수행 (seed/RNG 의존 제거).
- 문서에 "n=2 기반 수치, peak count<12이면 sanity로 약함" 명기.
M2. 🟡 A3 Apt_max_load: Apt88 vs Apt6 gap 1.6% — ranking이 metric 선택에 매우 민감¶
근거:
- stage0_summary.json: Apt88 daily_max_mean 3.8925 vs Apt6 3.8338 — 차이 0.059 kWh (1.53%).
- 다른 extremeness metric을 보면:
- daily_max_std Apt88 1.590, Apt51 1.690 → Apt51이 variability 관점 1위
- overall_max Apt88 9.17 vs Apt6 7.86 — Apt88이 1위지만 이는 single-hour outlier에 휘둘리기 쉬움
- daily_max_median Apt88 3.927 vs Apt6 3.824 — 0.103 차이로 Apt88 여유 있음
- daily_max_mean만 단일 기준으로 argmax를 취한 것은 정당화 부족. "extreme 가구"의 의미가 모호.
왜 major인가: - smoke test가 "Apt6(typical) vs Apt_max(extreme)"의 대비를 상정하는데, Apt88이 1.6% 차이인 가구라면 대비 효과가 약하다. - design spec §0.5가 "5가구 중 일평균 max 부하 가구"라는 일의적 규칙을 제시한 것은 맞지만, 0.5% 내 뒤집힘 민감도는 문서화 필수.
Resolve 권고:
- daily_max_mean 이외에도 daily_max_median, P95(daily_max), overall_max 각각에서 argmax를 확인해 consistency 문서화.
- 만약 "extreme"을 CV(coeff of variation) 또는 P95/median 등 peak variability로 재정의하면 Apt88이 아닐 수 있음. 대안 후보 명시 후 사용자 승인 절차 권장.
- 차선책: smoke 가구 pair를 Apt6(typical) + Apt88(extreme) + Apt51(high-variability) 3개로 확장 — 단계 0.5 12 runs 설계에 영향 없음 (cell×seed×household=3×3×2→3×3×3이 되지 않도록 분배 조정).
M3. 🟡 G2 "spec NaN → 구현 10.0" 결정이 spec 무단 수정에 가까움, 정당화 불충분¶
근거:
- design spec §2.2 표: | G2 | [5,5,5,5] | [4,5,6,5] | (Q90 trigger 없음, NaN) | (정의 외) |
- 구현: np.percentile([5,5,5,5], 90) = 5.0, y_true >= 5.0은 모두 True → compute_pape_v7는 10.0 반환.
- 보고서 결정: "단계 1~4 assertion은 10.000000을 expected로 체크".
왜 major인가: - spec 문구 "Q90 trigger 없음"은 degenerate 해석의 결과로, 개발자가 spec을 쓴 직접 의도는 "NaN이 나와야 한다 / 혹은 코드가 분기로 NaN을 내야 한다"일 가능성이 있다. 이 부분을 orchestrator 승인 없이 exp-expert가 "코드 정의 우위" 원칙으로 수정한 것. - 의사 결정이 design spec 업데이트 없이 보고서에만 기록되어, 2주 후 누가 읽어도 spec과 코드 사이 drift가 눈에 띄지 않는다. - 실용적으로 10.0이 잘못된 건 아니지만, peak 개념 없는 degenerate case를 "peak PAPE = 10.0"이라 기록하는 것이 논문/발표자료 해석에 misleading하다. constant-variance 시계열의 합성 테스트 등에서 degenerate 값을 실수로 expected라 썼다가 "sanity 통과"라 오판할 위험.
Resolve 요구사항:
1. 두 가지 중 하나 선택 후 design spec §2.2 update (orchestrator 승인 필요):
- (a) compute_pape_v7에 if y_true.std() < eps: return np.nan 추가 — spec NaN 채택.
- (b) "G2 degenerate → 10.0 expected"로 spec 수정.
2. stage 0 보고서 §구현 결정 사항 G2 문단에 "orchestrator approved via ADR-NNN" 출처 명시.
M4. 🟡 "아무 1주" 결정화 규칙이 v7_runner에 반영 안 된 상태 — "단일 구현에 박혀 있다"는 주장과 모순¶
근거:
- 보고서: "이 규칙은 단일 구현(_extract_first_week(...))에 박혀 있으며 단계 1~4의 smoke/full run 스크립트 모두 여기서 파생되어야 한다."
- 실제: _extract_first_week는 stage 0 스크립트 local 함수 (experiments/federated/v7_0419_stage0_preregistration.py:186). 다른 파일 어디에서도 import 되지 않는다 (Grep 확인).
- v7_runner.py에는 golden-tensor y_true/y_pred 생성 로직이 TODO(stage-0) placeholder뿐.
Resolve 요구사항:
- _extract_first_week, _load_household_split, compute_golden_tensors를 src/peak_analysis/v7/ 또는 experiments/federated/_v7_common.py 모듈로 빼내고, stage 0 스크립트와 v7_runner 양쪽이 공통 import.
- v7_runner의 GOLDEN_TENSORS를 공통 모듈의 build_golden_tensors() 호출로 대체. MLflow startup 시 한 번 호출 → 결과를 FS artifact에 freeze (C2 Resolve와 동일 패키지).
M5. 🟡 "정상 종료" 필터가 y_pred.npy 존재를 요구하지만, v6 대다수 run은 저장 형식·이름 규칙이 다름¶
근거:
- _has_ypred_artifact 휴리스틱 (line 403-426): 루트 + 1-depth 하위에서 *.npy 중 "y_pred"/"pred"를 이름에 포함한 파일.
- v6 FedPM 실험들은 predictions/Apt6_test_pred.npy, preds/y_pred_fold0.npy 등 다양한 패턴을 쓰며, 일부는 .pkl/.pt로 저장됨. false-negative 가능성 높음.
- 실제 A2 reject 분포: no_ypred_artifact=3만 탈락. 이게 "거의 없다"가 아니라 앞 단계(no_valid_pape=31, no_train_or_val_loss=59)에서 이미 탈락했기 때문 — y_pred.npy 조건이 실질 필터링을 하지 않음. 보고서 표현이 "y_pred 아티팩트 루트 또는 1-depth 하위에 존재"를 엄격한 필터 조건처럼 쓰지만 실제 효력은 거의 0.
Resolve 권고:
- no_ypred_artifact=3의 원인을 보고 — 실제 탈락된 3 run의 artifact list를 확인해 "y_pred가 정말 없다" vs "이름이 다르다"를 구별.
- 보고서 "정상 종료" 5조건 중 이 조건은 효력 약함을 명시. 또는 조건을 제거하고 3조건(FINISHED + PAPE + loss)로 단순화.
M6. 🟡 "재현 명령"이 실제로 실행해도 동일 값을 내는지 보고서가 증명하지 않음¶
근거:
- 보고서의 재현 스니펫 3개 모두 assert ... 형식이지만, 이 assertion들이 uv run pytest/uv run python으로 한 번 더 실행되어 통과했는지 보고서에 실행 증거가 없다.
- 재현 명령 uv run python experiments/federated/v7_0419_stage0_preregistration.py를 두 번째 실행 시 MLflow에 새로운 run_id가 생성될 텐데, "동일 정의 해시 / 동일 수치"가 나오는지 cross-check 로그가 없다.
Resolve 요구사항:
- (a) tests/test_v7_stage0_reproducibility.py 작성: compute_golden_tensors() 호출 후 G3_pape==0.0, G4_pape ≈ 67.3425, G5_pape ≈ 61.4756 assertion.
- (b) extract_v6_loss_distribution() 호출 후 summary["fail_fast_train_loss_threshold"] 정확 일치 assertion.
- (c) pytest 통과 로그를 보고서에 추가.
M7. 🟡 MLflow 로깅에서 G1/G2 metric이 누락 (design §2.5 item 4 완전성 위반)¶
근거:
- 스크립트 line 661-668: G1/G2는 metric 기록 대상에서 제외 (if r.gid in ("G3", "G4", "G5")). 근거는 "G1/G2는 spec 정의 고정값"이라는 inferred 의도.
- 하지만 G1 PAPE 20.0과 G2 PAPE 10.0은 실측 재현값이므로 MLflow metric으로 남겨두는 것이 drift 탐지에 유리. 특히 G2의 "10.0 결정"은 논쟁의 여지가 있어 MLflow history에 기록되어야 추후 audit 가능.
Resolve 권고:
- G1_pape, G2_pape 도 metric으로 로깅 (HR은 G1 argmax-sanity 의미로 G1_hr로 남겨도 됨).
- JSON에도 g1_pape_implementation = 20.0, g2_pape_implementation = 10.0을 명시적 필드로.
경미한 문제 (Minor Issues)¶
N1. 🟢 A2 scan scope 14 experiment에 DistilTS_KD, EC_Pair_KD*, Phase2-KD-Ablation, GWN_Teacher_Pretrain, Gate0A-A_adp-Validation 누락¶
근거: - 실제 존재하는 experiment (20+ 개) 중 KD/Teacher 관련을 완전 배제. - 명분: "v6 정상 종료 run 분포" → KD 실험은 teacher-student 구조라 loss 의미가 다름. 일리는 있으나 design spec §2.4의 "v6 GWN/FeDPM 정상 종료 run loss 분포" 문구와 정확히 align하진 않는다 — GWN 계열(EC_Pair_KD_GWN, Phase2-KD-Ablation)은 GWN training 산출물을 포함.
권고: 보고서 제한 사항에 "KD 계열은 loss scale이 student-only vs distillation-combined로 이종이라 배제" 문장 명시. 이미 일부 tried, 추가 설명 1줄.
N2. 🟢 np.random.seed 전역 호출과 default_rng 혼용¶
근거:
- stage 0 script line 633: np.random.seed(RANDOM_SEED) (global). line 313: np.random.default_rng(RANDOM_SEED) (G5 전용).
- 전역 np.random.seed는 G5 default_rng에 영향 없음. 혼란 소지 + "전역 seed 설정되었으니 재현 보장"이라는 오해.
권고: 전역 np.random.seed(RANDOM_SEED) 제거. Default RNG 사용 일관화.
N3. 🟢 HR K=12 고정이 "일 24시간 중 상위 50%"의 의미적 정합성 — 발표자료 해석에 주의 필요¶
근거: K=12는 24h의 절반이므로 "top-50% hit rate"다. Peak detection의 전통 정의("top-1 hit" 또는 "top-3 hit")와 다르다.
권고: 단계 1~4 figure caption에서 "HR@K=12 (top-50%)"를 명시. 초록에서 "HR (Hit Rate)" 서술이 K=1/K=3로 오해될 여지 — 본문 Method 섹션에 명기해야 논문·발표에서 주장 정당성 확보.
N4. 🟢 _row_final_train_loss가 MAX aggregation → "가장 큰 loss 가진 sub-component"를 final로 잡음¶
근거: line 447 return float(np.max(vals)). 한 run에 train_loss + b0_train_loss + fedrep_train_loss가 공존하면 가장 큰 값이 final이 됨. "보수적 threshold를 위한 max"라는 주석이 있지만, 이 선택은 per-run artifact를 하나의 스칼라로 압축하기 위한 ad-hoc choice라 run 간 비교성을 훼손.
권고: 한 run에 multiple loss columns이 공존하는 경우는 매우 적음 — 해당 run list를 logging해서 실제 몇 건이 영향받는지 확인. 대부분 1개일 가능성이 크면 그대로 두되, 근거 1줄 추가.
인정되는 강점 (Acknowledged Strengths)¶
- 규칙 결정화는 재현 가능: Apt6 / Apt15 블록 선택 규칙은 Python 버전 3.11 + numpy 고정 + seed=42 환경에서 재현 확인됨. 외부 평가 시에도 환경만 맞추면 동일 수치 재현 가능.
- A3 Apt88 argmax 재계산 가능: daily_max_mean 통계는 이중 재현 완료 (3.8925 / 3.8338).
- 투명성 공개 노력: Decision log에 "G2 10.0 vs spec NaN", "A2 표본 4건 제한", "'아무 1주' 결정화" 등을 자발적으로 기록한 점은 긍정적.
- MLflow artifact 3종 (JSON, CSV, self-script) 모두 run에 포함 — 재실행 경로 확보.
- rejection_reasons가 JSON에 구조화 저장되어 A2 재작업 시 필터 디버깅 가능.
exp-expert에게 전달하는 필수 수정 사항¶
P0 (단계 0.5 smoke 진입 전 반드시 해소)¶
- C1-fix:
src/peak_analysis/v7/metrics.py(또는 유사 모듈) 신설하고compute_pape_v7,compute_hr_v7,PAPE_DEFINITION_SOURCE,HR_DEFINITION_SOURCE,definition_hash()를 단일 소스로 배치. stage 0 스크립트와v7_runner.py양쪽이 동일 import. - C1-fix:
v7_runner.py의PAPE_DEFINITION_HASH/SCALER_SPACE_SIGNATURE하드코딩 제거 후 공통 모듈에서 import. 해시 알고리즘/길이/payload 한 곳 고정. - C1-fix: stage 0 재실행하여
definition_hash값을 단일 표준으로 교체.stage0_summary.json, MLflow params, 보고서 "정의 해시" 문자열 전부 update. - C2-fix:
v7_runner._run_golden_tensor_check가peak_analysis.metrics.compute_pape/compute_hr대신 공통 모듈의compute_pape_v7/compute_hr_v7를 호출하도록 수정. G3~G5 assertion 실제 활성화 (atol ≤ 1e-6). - C2-fix:
GOLDEN_TENSORS딕셔너리가compute_golden_tensors()호출 결과로 채워지도록 연결. G3y_truendarray를 stage 0 run의artifacts/golden/apt6_week.npy등으로 저장하고 v7_runner가 load. - C3-fix (A2 재작업):
VAL_LOSS_COLS/TRAIN_LOSS_COLS확장 (최소val_mse,round_train_loss,round_val_loss,test_avg_mse). 각 experiment별ls metrics/5 sample로 column universe를 먼저 확정. - C3-fix: "epoch/round ≥ N" 하한 도입 — 2-epoch prototype(t0_codebook_seed42)는 탈락시키는 필터.
- C3-fix: threshold 재산출 — "수렴 기반 P95" (final_loss/initial_loss < 0.7 통과 run의 P95) 또는 per-run 기준 (initial_loss × 2.0) 중 선택 후 design spec §2.4 update. 기존 2.63은 폐기 선언.
P1 (단계 0.5 smoke 중에라도 해소 권장)¶
- M1-fix (G5 보강):
_extract_first_week를 "q90-hit ≥ K_min (권장 12)개" 규칙으로 변경. 혹은 G5의y_pred를 artifact로 ndarray freeze. - M2-fix (Apt_max_load 민감도): 4개 metric (daily_max_mean, median, P95, overall_max) 각각의 argmax 가구를
stage0_summary.json에 추가 기록. 1개 이상이 Apt88이 아니면 보고서에 경고. - M3-fix (G2 정책): (a) compute_pape_v7에 degenerate-NaN 분기 추가 또는 (b) design spec §2.2를 "G2 expected = 10.0"으로 수정. 둘 중 하나를 ADR 또는 orchestrator 승인 절차로 확정.
- M4-fix:
_extract_first_week,_load_household_split,compute_golden_tensors를 공통 모듈로 이동 (C1/C2 fix 패키지). - M6-fix:
tests/test_v7_stage0_reproducibility.py작성 + pytest 통과 로그를 report에 append.
P2 (단계 1 진입 전 정리 권장)¶
- M5:
_has_ypred_artifact효력 검증 —no_ypred_artifact=3의 실제 exclude된 run id를 열어서 "진짜 없음" vs "이름 규칙 다름" 구분, 보고서에 1줄 기록. - M7: G1, G2 metric도 MLflow에 기록 (
G1_pape=20.0,G2_pape=10.0). - N1: A2 제외 experiment list에
DistilTS_KD,EC_Pair_KD*,Phase2-KD-Ablation,GWN_Teacher_Pretrain,Gate0A-A_adp-Validation명시 + 배제 이유 1줄. - N2: 전역
np.random.seed제거. - N3: 발표자료/본문 "HR" 표기 시
HR@K=12 (top-50%)명시.
재실험 권고 체크리스트 (stage 0 재실행)¶
- C1-fix (공통 정의 모듈) + C2-fix (v7_runner assertion 연결) 구현 완료
-
uv run python experiments/federated/v7_0419_stage0_preregistration.py재실행 → 새 MLflow run 생성 -
outputs/v7_stage0/stage0_summary.json덮어쓰기 + 새definition_hash값 확정 -
report/version7/exp-expert/v7_0419_stage0_preregistration.md수치/해시 update (이전 값과 diff 명시) -
tests/test_v7_stage0_reproducibility.py작성 + pytest 통과 - C3-fix (A2 threshold 재설계) 완료 후
fail_fast_train_loss_threshold새 값 반영 + design spec §2.4 update - stage 0 재실행 pass 후에만 단계 0.5 smoke 진입
판정 근거 요약¶
- Critical 3건 중 2건(C1, C2)이 단일 코드 경로 원칙의 구조적 위반이며, 이는 ADR-007의 핵심 process decision("PAPE/HR 정의 dual-track 위험 차단")과 직접 충돌.
- Critical C3은 산출된 threshold의 실효성을 문제 삼으며, 운영 안전망 §2.4 item 1의 기능을 무력화.
- Major 7건 중 4건(M1, M3, M4, M6)은 "사전 등록 완료" 요건을 이루는 재현성·spec 일관성 수준의 문제.
- 따라서 CONDITIONAL REJECT — 위 P0 8항목 완료 및 stage 0 재실행 후 재검토 시 PASS 가능.
- 수치·결정 log 자체는 재현 가능하므로, 재실행 workload는
<1h예상 (A2 재쿼리 제외하면 분 단위). A2 재쿼리 scope 확장 시 +30~60분.