@@ -3218,119 +3218,178 @@ def closure_forecast_reset_reentry_rebuild_persistence_for_target(
32183218 )
32193219
32203220
3221- def closure_forecast_reset_reentry_rebuild_churn_for_target (
3221+ class _ChurnTierSpec (NamedTuple ):
3222+ """Tier-specific literals for the reset-reentry churn builder.
3223+
3224+ The rebuild / rererestore churn families share one algorithm and differ only in
3225+ the per-event freshness/reset keys they read, their reason prose, and their
3226+ output keys.
3227+ """
3228+
3229+ freshness_key : str
3230+ reset_key : str
3231+ reasons : dict [str , str ]
3232+ score_key : str
3233+ status_out_key : str
3234+ reason_key : str
3235+ path_key : str
3236+
3237+
3238+ def _churn_for_target_base (
32223239 target : dict [str , Any ],
32233240 closure_forecast_events : list [dict [str , Any ]],
32243241 transition_history_meta : dict [str , Any ],
32253242 * ,
3243+ spec : _ChurnTierSpec ,
32263244 ordered_reset_reentry_events_for_target : Callable [
32273245 [dict [str , Any ], list [dict [str , Any ]]], list [dict [str , Any ]]
32283246 ],
3229- closure_forecast_reset_reentry_rebuild_side_from_event : Callable [[dict [str , Any ]], str ],
3247+ side_from_event : Callable [[dict [str , Any ]], str ],
32303248 class_direction_flip_count : Callable [[list [str ]], int ],
3231- target_specific_normalization_noise : Callable [[dict [str , Any ], dict [str , Any ]], bool ],
3249+ target_specific_normalization_noise : Callable [
3250+ [dict [str , Any ], dict [str , Any ]], bool
3251+ ],
32323252 clamp_round : Callable [..., float ],
3233- closure_forecast_reset_reentry_rebuild_path_label : Callable [[dict [str , Any ]], str ],
3234- class_reset_reentry_rebuild_persistence_window_runs : int ,
3253+ path_label : Callable [[dict [str , Any ]], str ],
3254+ window_runs : int ,
32353255) -> dict [str , Any ]:
32363256 matching_events = ordered_reset_reentry_events_for_target (
32373257 target ,
32383258 closure_forecast_events ,
3239- )[:class_reset_reentry_rebuild_persistence_window_runs ]
3259+ )[:window_runs ]
32403260 relevant_events = [
3241- event
3242- for event in matching_events
3243- if closure_forecast_reset_reentry_rebuild_side_from_event (event ) != "none"
3244- ]
3245- side_path = [
3246- closure_forecast_reset_reentry_rebuild_side_from_event (event ) for event in relevant_events
3261+ event for event in matching_events if side_from_event (event ) != "none"
32473262 ]
3263+ side_path = [side_from_event (event ) for event in relevant_events ]
32483264 current_side = side_path [0 ] if side_path else "none"
32493265 local_noise = target_specific_normalization_noise (target , transition_history_meta )
32503266 if current_side == "none" :
32513267 churn_score = 0.0
32523268 churn_status = "none"
3253- churn_reason = ""
32543269 else :
32553270 flip_count = class_direction_flip_count (
32563271 [
3257- "supporting-confirmation" if side == "confirmation" else "supporting-clearance"
3272+ "supporting-confirmation"
3273+ if side == "confirmation"
3274+ else "supporting-clearance"
32583275 for side in side_path
32593276 ]
32603277 )
32613278 churn_score = float (flip_count ) * 0.20
3262- stability_status = target .get ("closure_forecast_stability_status" , "watch" )
3263- momentum_status = target .get ("closure_forecast_momentum_status" , "insufficient-data" )
3279+ stability_status = str (target .get ("closure_forecast_stability_status" , "watch" ))
3280+ momentum_status = str (
3281+ target .get ("closure_forecast_momentum_status" , "insufficient-data" )
3282+ )
32643283 if stability_status == "oscillating" :
32653284 churn_score += 0.15
32663285 if momentum_status == "reversing" :
32673286 churn_score += 0.10
32683287 if momentum_status == "unstable" :
32693288 churn_score += 0.10
32703289 freshness_path = [
3271- event .get (
3272- "closure_forecast_reset_reentry_freshness_status" ,
3273- "insufficient-data" ,
3274- )
3290+ str (event .get (spec .freshness_key , "insufficient-data" ))
32753291 for event in relevant_events
32763292 ]
32773293 if any (
3278- previous == "fresh" and current in {"mixed-age" , "stale" , "insufficient-data" }
3294+ previous == "fresh"
3295+ and current in {"mixed-age" , "stale" , "insufficient-data" }
32793296 for previous , current in zip (freshness_path , freshness_path [1 :])
32803297 ):
32813298 churn_score += 0.10
32823299 if any (
3283- event .get ("closure_forecast_reset_reentry_reset_status" , "none" ) != "none"
3284- for event in relevant_events
3300+ event .get (spec .reset_key , "none" ) != "none" for event in relevant_events
32853301 ):
32863302 churn_score += 0.10
32873303 if (
32883304 len (relevant_events ) >= 2
32893305 and side_path [0 ] == side_path [1 ]
3290- and relevant_events [0 ].get (
3291- "closure_forecast_reset_reentry_freshness_status" ,
3292- "insufficient-data" ,
3293- )
3306+ and relevant_events [0 ].get (spec .freshness_key , "insufficient-data" )
32943307 == "fresh"
3295- and relevant_events [1 ].get (
3296- "closure_forecast_reset_reentry_freshness_status" ,
3297- "insufficient-data" ,
3298- )
3308+ and relevant_events [1 ].get (spec .freshness_key , "insufficient-data" )
32993309 == "fresh"
33003310 ):
33013311 churn_score -= 0.10
33023312 churn_score = clamp_round (churn_score , lower = 0.0 , upper = 0.95 )
33033313 if local_noise and current_side == "confirmation" :
33043314 churn_status = "blocked"
3305- churn_reason = (
3306- "Local target instability is preventing positive confirmation-side rebuild persistence."
3307- )
33083315 elif churn_score >= 0.45 or flip_count >= 2 :
33093316 churn_status = "churn"
3310- churn_reason = (
3311- "Rebuilt reset re-entry is flipping enough that restored posture should be softened quickly."
3312- )
33133317 elif churn_score >= 0.20 :
33143318 churn_status = "watch"
3315- churn_reason = (
3316- "Rebuilt reset re-entry is wobbling and may lose its restored strength soon."
3317- )
33183319 else :
33193320 churn_status = "none"
3320- churn_reason = ""
33213321
3322+ churn_reason = spec .reasons .get (churn_status , "" )
33223323 return {
3323- "closure_forecast_reset_reentry_rebuild_churn_score" : churn_score ,
3324- "closure_forecast_reset_reentry_rebuild_churn_status" : churn_status ,
3325- "closure_forecast_reset_reentry_rebuild_churn_reason" : churn_reason ,
3326- "recent_reset_reentry_rebuild_churn_path" : " -> " .join (
3327- closure_forecast_reset_reentry_rebuild_path_label (event )
3328- for event in matching_events
3329- if event
3324+ spec .score_key : churn_score ,
3325+ spec .status_out_key : churn_status ,
3326+ spec .reason_key : churn_reason ,
3327+ spec .path_key : " -> " .join (
3328+ path_label (event ) for event in matching_events if event
33303329 ),
33313330 }
33323331
33333332
3333+ _REBUILD_CHURN_SPEC = _ChurnTierSpec (
3334+ freshness_key = "closure_forecast_reset_reentry_freshness_status" ,
3335+ reset_key = "closure_forecast_reset_reentry_reset_status" ,
3336+ reasons = {
3337+ "blocked" : "Local target instability is preventing positive confirmation-side rebuild persistence." ,
3338+ "churn" : "Rebuilt reset re-entry is flipping enough that restored posture should be softened quickly." ,
3339+ "watch" : "Rebuilt reset re-entry is wobbling and may lose its restored strength soon." ,
3340+ },
3341+ score_key = "closure_forecast_reset_reentry_rebuild_churn_score" ,
3342+ status_out_key = "closure_forecast_reset_reentry_rebuild_churn_status" ,
3343+ reason_key = "closure_forecast_reset_reentry_rebuild_churn_reason" ,
3344+ path_key = "recent_reset_reentry_rebuild_churn_path" ,
3345+ )
3346+
3347+
3348+ _RERERESTORE_CHURN_SPEC = _ChurnTierSpec (
3349+ freshness_key = "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_freshness_status" ,
3350+ reset_key = "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_reset_status" ,
3351+ reasons = {
3352+ "blocked" : "Local target instability is preventing positive confirmation-side re-re-restored hold." ,
3353+ "churn" : "Re-re-restored rebuilt re-entry is flipping enough that stronger posture should be softened quickly." ,
3354+ "watch" : "Re-re-restored rebuilt re-entry is wobbling and may lose its stronger posture soon." ,
3355+ },
3356+ score_key = "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_score" ,
3357+ status_out_key = "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_status" ,
3358+ reason_key = "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_reason" ,
3359+ path_key = "recent_reset_reentry_rebuild_reentry_restore_rererestore_churn_path" ,
3360+ )
3361+
3362+
3363+ def closure_forecast_reset_reentry_rebuild_churn_for_target (
3364+ target : dict [str , Any ],
3365+ closure_forecast_events : list [dict [str , Any ]],
3366+ transition_history_meta : dict [str , Any ],
3367+ * ,
3368+ ordered_reset_reentry_events_for_target : Callable [
3369+ [dict [str , Any ], list [dict [str , Any ]]], list [dict [str , Any ]]
3370+ ],
3371+ closure_forecast_reset_reentry_rebuild_side_from_event : Callable [[dict [str , Any ]], str ],
3372+ class_direction_flip_count : Callable [[list [str ]], int ],
3373+ target_specific_normalization_noise : Callable [[dict [str , Any ], dict [str , Any ]], bool ],
3374+ clamp_round : Callable [..., float ],
3375+ closure_forecast_reset_reentry_rebuild_path_label : Callable [[dict [str , Any ]], str ],
3376+ class_reset_reentry_rebuild_persistence_window_runs : int ,
3377+ ) -> dict [str , Any ]:
3378+ return _churn_for_target_base (
3379+ target ,
3380+ closure_forecast_events ,
3381+ transition_history_meta ,
3382+ spec = _REBUILD_CHURN_SPEC ,
3383+ ordered_reset_reentry_events_for_target = ordered_reset_reentry_events_for_target ,
3384+ side_from_event = closure_forecast_reset_reentry_rebuild_side_from_event ,
3385+ class_direction_flip_count = class_direction_flip_count ,
3386+ target_specific_normalization_noise = target_specific_normalization_noise ,
3387+ clamp_round = clamp_round ,
3388+ path_label = closure_forecast_reset_reentry_rebuild_path_label ,
3389+ window_runs = class_reset_reentry_rebuild_persistence_window_runs ,
3390+ )
3391+
3392+
33343393def apply_reset_reentry_rebuild_persistence_and_churn_control (
33353394 target : dict [str , Any ],
33363395 * ,
@@ -5798,118 +5857,19 @@ def closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_for
57985857 clamp_round : Callable [..., float ],
57995858 class_reset_reentry_rebuild_reentry_restore_rererestore_window_runs : int ,
58005859) -> dict [str , Any ]:
5801- matching_events = ordered_reset_reentry_events_for_target (
5860+ return _churn_for_target_base (
58025861 target ,
58035862 closure_forecast_events ,
5804- )[:class_reset_reentry_rebuild_reentry_restore_rererestore_window_runs ]
5805- relevant_events = [
5806- event
5807- for event in matching_events
5808- if closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_event (
5809- event
5810- )
5811- != "none"
5812- ]
5813- side_path = [
5814- closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_event (
5815- event
5816- )
5817- for event in relevant_events
5818- ]
5819- current_side = side_path [0 ] if side_path else "none"
5820- local_noise = target_specific_normalization_noise (target , transition_history_meta )
5821- if current_side == "none" :
5822- churn_score = 0.0
5823- churn_status = "none"
5824- churn_reason = ""
5825- else :
5826- flip_count = class_direction_flip_count (
5827- [
5828- "supporting-confirmation" if side == "confirmation" else "supporting-clearance"
5829- for side in side_path
5830- ]
5831- )
5832- churn_score = float (flip_count ) * 0.20
5833- stability_status = str (target .get ("closure_forecast_stability_status" , "watch" ))
5834- momentum_status = str (
5835- target .get ("closure_forecast_momentum_status" , "insufficient-data" )
5836- )
5837- if stability_status == "oscillating" :
5838- churn_score += 0.15
5839- if momentum_status == "reversing" :
5840- churn_score += 0.10
5841- if momentum_status == "unstable" :
5842- churn_score += 0.10
5843- freshness_path = [
5844- str (
5845- event .get (
5846- "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_freshness_status" ,
5847- "insufficient-data" ,
5848- )
5849- )
5850- for event in relevant_events
5851- ]
5852- if any (
5853- previous == "fresh" and current in {"mixed-age" , "stale" , "insufficient-data" }
5854- for previous , current in zip (freshness_path , freshness_path [1 :])
5855- ):
5856- churn_score += 0.10
5857- if any (
5858- event .get (
5859- "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_reset_status" ,
5860- "none" ,
5861- )
5862- != "none"
5863- for event in relevant_events
5864- ):
5865- churn_score += 0.10
5866- if (
5867- len (relevant_events ) >= 2
5868- and side_path [0 ] == side_path [1 ]
5869- and relevant_events [0 ].get (
5870- "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_freshness_status" ,
5871- "insufficient-data" ,
5872- )
5873- == "fresh"
5874- and relevant_events [1 ].get (
5875- "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_freshness_status" ,
5876- "insufficient-data" ,
5877- )
5878- == "fresh"
5879- ):
5880- churn_score -= 0.10
5881- churn_score = clamp_round (churn_score , lower = 0.0 , upper = 0.95 )
5882- if local_noise and current_side == "confirmation" :
5883- churn_status = "blocked"
5884- churn_reason = (
5885- "Local target instability is preventing positive confirmation-side re-re-restored hold."
5886- )
5887- elif churn_score >= 0.45 or flip_count >= 2 :
5888- churn_status = "churn"
5889- churn_reason = (
5890- "Re-re-restored rebuilt re-entry is flipping enough that stronger posture should be softened quickly."
5891- )
5892- elif churn_score >= 0.20 :
5893- churn_status = "watch"
5894- churn_reason = (
5895- "Re-re-restored rebuilt re-entry is wobbling and may lose its stronger posture soon."
5896- )
5897- else :
5898- churn_status = "none"
5899- churn_reason = ""
5900-
5901- return {
5902- "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_score" : churn_score ,
5903- "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_status" : churn_status ,
5904- "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_churn_reason" : churn_reason ,
5905- "recent_reset_reentry_rebuild_reentry_restore_rererestore_churn_path" : " -> " .join (
5906- closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_path_label (
5907- event
5908- )
5909- for event in matching_events
5910- if event
5911- ),
5912- }
5863+ transition_history_meta ,
5864+ spec = _RERERESTORE_CHURN_SPEC ,
5865+ ordered_reset_reentry_events_for_target = ordered_reset_reentry_events_for_target ,
5866+ side_from_event = closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_side_from_event ,
5867+ class_direction_flip_count = class_direction_flip_count ,
5868+ target_specific_normalization_noise = target_specific_normalization_noise ,
5869+ clamp_round = clamp_round ,
5870+ path_label = closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_path_label ,
5871+ window_runs = class_reset_reentry_rebuild_reentry_restore_rererestore_window_runs ,
5872+ )
59135873
59145874
59155875def apply_reset_reentry_rebuild_reentry_restore_rererestore_persistence_and_churn_control (
0 commit comments