@@ -48,24 +48,41 @@ def decision_summary(res: Dict, metric_name: str, business_threshold: float = 0.
4848 V_A = res ["V_A" ]
4949 V_B = res ["V_B" ]
5050 D = res ["Delta" ]
51- alpha = float (res .get ("alpha" , 0.05 ))
51+ alpha = float (res .get ("alpha" , res . get ( "inference_alpha" , 0.05 ) ))
5252 ci_level = int (round ((1.0 - alpha ) * 100 ))
53- A_lo , A_hi = res [ "V_A_CI" ]
54- B_lo , B_hi = res [ "V_B_CI" ]
55- D_lo , D_hi = res [ "Delta_CI" ]
53+ a_ci = res . get ( "V_A_CI" )
54+ b_ci = res . get ( "V_B_CI" )
55+ d_ci = res . get ( "Delta_CI" )
5656
5757 lines = []
5858 lines .append (f"Метрика: { metric_name } " )
59- lines .append (f"V(A) = { V_A :.6f} ({ ci_level } % CI: { A_lo :.6f} .. { A_hi :.6f} )" )
60- lines .append (f"V(B) = { V_B :.6f} ({ ci_level } % CI: { B_lo :.6f} .. { B_hi :.6f} )" )
61- lines .append (f"Delta (B−A) = { D :.6f} ({ ci_level } % CI: { D_lo :.6f} .. { D_hi :.6f} )" )
62-
63- if D_lo > business_threshold :
64- lines .append (f"Решение: модель B лучше A, поскольку нижняя граница CI превышает порог { business_threshold } ." )
65- elif D_hi < - business_threshold :
66- lines .append (f"Решение: модель A лучше B, поскольку верхняя граница CI ниже -{ business_threshold } ." )
59+ if a_ci is not None :
60+ A_lo , A_hi = a_ci
61+ lines .append (f"V(A) = { V_A :.6f} ({ ci_level } % CI: { A_lo :.6f} .. { A_hi :.6f} )" )
6762 else :
68- lines .append ("Решение: статистически значимого отличия не обнаружено или эффект слишком мал." )
63+ lines .append (f"V(A) = { V_A :.6f} (CI недоступен)" )
64+ if b_ci is not None :
65+ B_lo , B_hi = b_ci
66+ lines .append (f"V(B) = { V_B :.6f} ({ ci_level } % CI: { B_lo :.6f} .. { B_hi :.6f} )" )
67+ else :
68+ lines .append (f"V(B) = { V_B :.6f} (CI недоступен)" )
69+ if d_ci is not None :
70+ D_lo , D_hi = d_ci
71+ lines .append (f"Delta (B−A) = { D :.6f} ({ ci_level } % CI: { D_lo :.6f} .. { D_hi :.6f} )" )
72+ else :
73+ lines .append (f"Delta (B−A) = { D :.6f} (CI недоступен)" )
74+
75+ if d_ci is not None :
76+ if D_lo > business_threshold :
77+ lines .append (f"Решение: модель B лучше A, поскольку нижняя граница CI превышает порог { business_threshold } ." )
78+ elif D_hi < - business_threshold :
79+ lines .append (f"Решение: модель A лучше B, поскольку верхняя граница CI ниже -{ business_threshold } ." )
80+ else :
81+ lines .append ("Решение: статистически значимого отличия не обнаружено или эффект слишком мал." )
82+ elif res .get ("is_significant" ) is True :
83+ lines .append ("Решение: обнаружено статистически значимое отличие, но без CI интерпретация менее устойчива." )
84+ else :
85+ lines .append ("Решение: CI не передан; итог следует трактовать как предварительный." )
6986 recommendation = res .get ("recommendation" )
7087 trust_level = res .get ("trust_level" )
7188 if trust_level is not None :
@@ -94,7 +111,10 @@ def analyze_logs(
94111) -> str :
95112 """Проверяет наличие ключевых столбцов в логах и формирует краткий отчёт."""
96113
97- lines = ["Проверка входных данных для off-policy оценки:" ]
114+ lines = [
115+ "Проверка входных данных для off-policy оценки:" ,
116+ "- Рекомендуемый high-level путь: compare_policies(...) или OPEEvaluator(...).evaluate_summary(...)." ,
117+ ]
98118
99119 # базовые колонки
100120 missing_basic = [c for c in (user_id_col , action_a_col ) if c not in df .columns ]
@@ -114,28 +134,27 @@ def analyze_logs(
114134 try :
115135 a_B = policyB .action_argmax (df )
116136 share = float (np .mean (a_B == df .get (action_a_col , - 1 )))
117- lines .append (
118- f"- Replay: политика B совпадает с A в { share * 100 :.1f} % случаев."
119- )
137+ lines .append (f"- Replay overlap A/B: { share * 100 :.1f} %." )
120138 if share < 0.1 :
121- lines [- 1 ] += " Требуется больше пересечений для надёжной оценки."
139+ lines .append ("- Replay: низкий overlap, оценка может быть шумной и зависимой от support логирующей политики." )
140+ else :
141+ lines .append ("- Replay: интерпретируйте как диагностический baseline, а не как универсально несмещённую оценку." )
122142 except Exception :
123- lines .append (
124- "- Replay: не удалось вычислить пересечение действий A и B."
125- )
143+ lines .append ("- Replay: не удалось вычислить пересечение действий A и B." )
126144 else :
127- lines .append ("- Replay: политика B не передана." )
145+ lines .append ("- Replay: политика B не передана; overlap-диагностика недоступна ." )
128146
129- # IPS / SNIPS
147+ # Propensity source modes
130148 if propensity_col in df .columns :
131- lines .append (f"- IPS/SNIPS: колонка { propensity_col } найдена." )
149+ lines .append (f"- Propensity: колонка { propensity_col } найдена; режим auto сможет использовать logged propensity path." )
150+ lines .append ("- Propensity: estimated path также доступен через propensity_source='estimated'." )
132151 else :
133152 lines .append (
134- "- IPS/SNIPS: propensities не найдены."
135- " Необходимо добавить колонку с π_A(a|x) или обучить модель пропенсити."
153+ f"- Propensity: колонка { propensity_col } не найдена; режим auto перейдёт в estimated propensity path."
136154 )
155+ lines .append ("- Propensity: strict logged path требует валидную propensity_col." )
137156
138- # DM
157+ # Feature availability for DM/DR family
139158 if feature_cols is None :
140159 feature_candidates = ["age" , "income" , "risk" , "loyal" ]
141160 feats = [c for c in feature_candidates if c in df .columns ]
@@ -144,19 +163,11 @@ def analyze_logs(
144163 if feats :
145164 lines .append (f"- DM: доступны признаки { feats } , ок." )
146165 else :
147- lines .append ("- DM: признаки не найдены." )
148-
149- # DR
150- if propensity_col not in df .columns :
151- if feats :
152- lines .append (
153- "- DR: пропенсити отсутствуют, но можно применить DM;"
154- " метод DR будет смещён, если модель неточна."
155- )
156- else :
157- lines .append ("- DR: нет пропенсити и признаков, метод неприменим." )
158- else :
159- lines .append ("- DR: можно применить, пропенсити присутствуют." )
166+ lines .append ("- DM/DR-family: признаки не найдены; модели nuisance могут быть нестабильны." )
167+
168+ if feats :
169+ lines .append ("- DR/SNDR/Switch-DR: применимы через официальный comparison API; проверяйте CI/p-value и diagnostics вместе." )
170+ lines .append ("- Cross-fit: опционально рекомендуется для дополнительного bias-hardening в DR-family режимах." )
160171
161172 for line in lines :
162173 logging .info (line )
0 commit comments