

scripts와 ml 코드가 실제로 무엇을 하는지 단계별로 해설합니다.NDCG 결과를 읽는 기준과 단순화 체크리스트를 정리합니다.eta_mean은 평균 이동시간이고 eta_range는 참여자 간 이동시간 격차입니다.
purpose_match는 모임 목적과 장소 카테고리 일치 여부를 나타냅니다.target_score를 라벨로 사용합니다.
그리고 ETA 통계량과 의도 정보를 피처로 사용합니다.eta_mean: 참여자 평균 이동시간입니다.eta_std: 참여자 이동시간의 퍼짐 정도입니다.eta_range: 최소와 최대 ETA 차이입니다.eta_min: 최소 이동시간입니다.eta_max: 최대 이동시간입니다.eta_p90: 상위 지연 구간을 반영한 보수적 지표입니다.purpose_match: purpose와 category의 의미 일치 여부입니다.hour_bucket: 시간대를 아침 오후 저녁 같은 버킷으로 묶은 값입니다.group_size: 참여자 수로, 편차 확대 가능성을 반영합니다.
eta_range = float(np.max(etas) - np.min(etas))
eta_mean = float(np.mean(etas))
bonus = _purpose_category_bonus(purpose, str(place.get("category", "")))
score = 1.2 * eta_range + 0.3 * eta_mean - 8.0 * bonuseta_range와 eta_mean으로 이동 부담과 형평성을 함께 반영합니다.click_score = weights.click if int(action.get("is_clicked", 0)) == 1 else 0.0
vote_score = weights.vote if int(action.get("is_voted", 0)) == 1 else 0.0
final_score = weights.final if int(action.get("is_final_selected", 0)) == 1 else 0.0
rating = float(action.get("rating", 0) or 0)
rating = max(0.0, min(5.0, rating))
feedback_score = (rating / 5.0) * weights.rating_max_bonus
return click_score + vote_score + final_score + feedback_scoreetas_np = np.array(etas, dtype=float)
eta_mean = float(np.mean(etas_np))
eta_std = float(np.std(etas_np))
eta_max = float(np.max(etas_np))
eta_min = float(np.min(etas_np))
eta_range = float(eta_max - eta_min)
eta_p90 = float(np.percentile(etas_np, 90))def ndcg_at_k(y_true, y_score, group_ids, k):
ndcgs = []
for gid in np.unique(group_ids):
m = group_ids == gid
t = y_true[m]
s = y_score[m]
order = np.argsort(-s)
ideal = np.argsort(-t)
dcg = _dcg(t[order], k=k)
idcg = _dcg(t[ideal], k=k)
ndcgs.append(0.0 if idcg == 0.0 else dcg / idcg)
return float(np.mean(ndcgs)) if ndcgs else 0.0room_id 그룹 안에서 상위 순위 품질을 평가합니다._mac_linux.sh로 끝나는 스크립트, Windows는 _windows.ps1로 끝나는 스크립트를 사용합니다.scripts/install_libomp_mac.sh를 먼저 실행합니다.#!/usr/bin/env bash
set -euo pipefail
python3 -m venv .venv-ml
source .venv-ml/bin/activate
python -m pip install --upgrade pip
pip install -r requirements-ml.txt
echo "Environment ready."set -euo pipefail은 실패를 즉시 노출해 조용한 실패를 줄입니다.#!/usr/bin/env bash
set -euo pipefail
source .venv-ml/bin/activate
python ml/synthetic_generator_part3.py --stations ml/out/stations.csv --places ml/out/places.csv --n-rooms 80 --seed 42 --out-rooms ml/rooms_part3.jsonl --out-actions ml/synthetic_actions_part3.jsonl
python ml/label_builder.py --input ml/synthetic_actions_part3.jsonl --output ml/synthetic_actions_part3_labeled.jsonlseed를 사용해 재현성을 확보합니다.target_score 라벨로 변환해 학습 가능한 형태로 만듭니다.#!/usr/bin/env bash
set -euo pipefail
source .venv-ml/bin/activate
python ml/feature_builder_part3.py --stations ml/out/stations.csv --places ml/out/places.csv --edges ml/out/edges.csv --rooms ml/rooms_part3.jsonl --labeled-actions ml/synthetic_actions_part3_labeled.jsonl --out-dir ml/out_part3train_ltr.csv를 생성합니다.feature_report.json에는 분할 정보와 ETA 소스 사용량이 기록됩니다.#!/usr/bin/env bash
set -euo pipefail
source .venv-ml/bin/activate
python ml/evaluate_part3.py --data ml/out_part3/train_ltr.csv --feature-report ml/out_part3/feature_report.json --out ml/out_part3/eval_report.json --k 4no_eta, eta_mean_only, full_fairness를 동일 조건에서 비교합니다.eval_report.json이 해석의 기준이 됩니다.{
"models": {
"no_eta": {
"ndcg_at_k": 0.553259
},
"eta_mean_only": {
"ndcg_at_k": 0.541776
},
"full_fairness": {
"ndcg_at_k": 0.669059
}
}
}eta_mean_only는 평균만 반영하므로 불공정 신호를 충분히 반영하지 못합니다.full_fairness는 분포 피처를 함께 사용해 순위 품질이 개선됩니다.다음 4편에서는 이 피처 테이블을 기반으로 XGBoost 학습 파이프라인과 실패 시 fallback 설계를 다루겠습니다.