feat: complete Day 2 handoff with API service, PM2 process daemons, and test suites
This commit is contained in:
1 parent
564262340a
commit
12f0786be9
27 files changed
+3234
-1013
No files matched your search
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
core/cpi_engine.py
|
||||
Commercial Power Index (CPI) & Multi-Scenario Simulation Engine.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Literal
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class CommercialPowerEngine:
|
||||
GLOBAL_MIN_LOG_EAS: float = 5.000000 # log10(100,000 EAS)
|
||||
GLOBAL_MAX_LOG_EAS: float = 8.397940 # log10(250,000,000 EAS)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min_log_eas: float = GLOBAL_MIN_LOG_EAS,
|
||||
max_log_eas: float = GLOBAL_MAX_LOG_EAS,
|
||||
):
|
||||
self.min_log_eas = min_log_eas
|
||||
self.max_log_eas = max_log_eas
|
||||
|
||||
def calculate_cpi(self, df: pd.DataFrame, eas_col: str = "total_eas_cspc") -> pd.Series:
|
||||
if eas_col not in df.columns:
|
||||
return pd.Series(index=df.index, dtype=float)
|
||||
|
||||
eas_series = pd.to_numeric(df[eas_col], errors="coerce")
|
||||
valid_mask = eas_series.notna() & (eas_series > 0)
|
||||
|
||||
cpi_series = pd.Series(index=df.index, dtype=float)
|
||||
if not valid_mask.any():
|
||||
return cpi_series
|
||||
|
||||
log_eas = np.log10(np.maximum(eas_series[valid_mask], 1.0))
|
||||
normalized = ((log_eas - self.min_log_eas) / (self.max_log_eas - self.min_log_eas)) * 100.0
|
||||
cpi_series[valid_mask] = np.clip(normalized, 0.0, 100.0)
|
||||
|
||||
return cpi_series.round(2)
|
||||
|
||||
def simulate_ranking(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
commercial_weight: float = 0.15,
|
||||
mode: Literal["matched_only", "global_redistribution"] = "matched_only",
|
||||
score_col: str = "hiffi_xp_score",
|
||||
cpi_col: str = "commercial_power_index",
|
||||
base_rank_col: str = "hiffi_xp_published_rank",
|
||||
) -> pd.DataFrame:
|
||||
sim_df = df.copy()
|
||||
|
||||
if cpi_col not in sim_df.columns:
|
||||
sim_df[cpi_col] = self.calculate_cpi(sim_df)
|
||||
|
||||
if mode == "matched_only":
|
||||
sim_df = sim_df[sim_df[cpi_col].notna()].copy()
|
||||
if sim_df.empty:
|
||||
return sim_df
|
||||
sim_df["baseline_matched_rank"] = (
|
||||
pd.to_numeric(sim_df[score_col], errors="coerce")
|
||||
.rank(ascending=False, method="min")
|
||||
.astype(int)
|
||||
)
|
||||
comparison_rank_col = "baseline_matched_rank"
|
||||
else:
|
||||
comparison_rank_col = base_rank_col if base_rank_col in sim_df.columns else "hiffi_xp_rank"
|
||||
|
||||
base_scores = pd.to_numeric(sim_df[score_col], errors="coerce").fillna(0.0)
|
||||
cpi_scores = pd.to_numeric(sim_df[cpi_col], errors="coerce")
|
||||
|
||||
has_cpi = cpi_scores.notna()
|
||||
w_sales = max(0.0, min(commercial_weight, 0.50))
|
||||
w_base = 1.0 - w_sales
|
||||
|
||||
sim_scores = pd.Series(index=sim_df.index, dtype=float)
|
||||
sim_scores[has_cpi] = (w_base * base_scores[has_cpi]) + (w_sales * cpi_scores[has_cpi])
|
||||
sim_scores[~has_cpi] = base_scores[~has_cpi]
|
||||
|
||||
sim_df["simulated_hiffi_score"] = sim_scores.round(2)
|
||||
sim_df["simulated_rank"] = (
|
||||
sim_df["simulated_hiffi_score"].rank(ascending=False, method="min").astype(int)
|
||||
)
|
||||
|
||||
if comparison_rank_col in sim_df.columns:
|
||||
sim_df["simulation_rank_delta"] = (
|
||||
pd.to_numeric(sim_df[comparison_rank_col], errors="coerce") - sim_df["simulated_rank"]
|
||||
)
|
||||
|
||||
return sim_df
|
||||
|
||||
# Backward-compatible alias
|
||||
simulate_composite_ranking = simulate_ranking
|
||||
|
||||
def compute_simulation_metrics(self, sim_df: pd.DataFrame) -> Dict[str, any]:
|
||||
delta_series = sim_df.get("simulation_rank_delta", pd.Series(dtype=float)).dropna()
|
||||
if delta_series.empty:
|
||||
return {
|
||||
"top_gainer_name": "None", "top_gainer_delta": 0,
|
||||
"top_loser_name": "None", "top_loser_delta": 0,
|
||||
"mean_abs_delta": 0.0, "matched_coverage": 0, "total_cohort": len(sim_df)
|
||||
}
|
||||
|
||||
max_delta = delta_series.max()
|
||||
min_delta = delta_series.min()
|
||||
|
||||
top_gainer_rows = sim_df[sim_df["simulation_rank_delta"] == max_delta]
|
||||
top_loser_rows = sim_df[sim_df["simulation_rank_delta"] == min_delta]
|
||||
|
||||
top_gainer_name = top_gainer_rows["name"].iloc[0] if not top_gainer_rows.empty else "None"
|
||||
top_loser_name = top_loser_rows["name"].iloc[0] if not top_loser_rows.empty else "None"
|
||||
|
||||
matched_count = int(sim_df["commercial_power_index"].notna().sum()) if "commercial_power_index" in sim_df.columns else 0
|
||||
|
||||
return {
|
||||
"top_gainer_name": top_gainer_name,
|
||||
"top_gainer_delta": int(max_delta),
|
||||
"top_loser_name": top_loser_name,
|
||||
"top_loser_delta": int(min_delta),
|
||||
"mean_abs_delta": round(float(delta_series.abs().mean()), 2),
|
||||
"matched_coverage": matched_count,
|
||||
"total_cohort": len(sim_df),
|
||||
}
|
||||
|
||||
def run_sensitivity_matrix(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
weights: List[float] = [0.0, 0.05, 0.10, 0.15, 0.20],
|
||||
mode: Literal["matched_only", "global_redistribution"] = "matched_only",
|
||||
) -> pd.DataFrame:
|
||||
base_sim = self.simulate_ranking(df, commercial_weight=0.0, mode=mode)
|
||||
matrix_df = pd.DataFrame({"name": base_sim["name"]})
|
||||
|
||||
for w in weights:
|
||||
run_df = self.simulate_ranking(df, commercial_weight=w, mode=mode)
|
||||
w_label = f"rank_{int(w*100)}pct"
|
||||
name_to_rank = dict(zip(run_df["name"], run_df["simulated_rank"]))
|
||||
matrix_df[w_label] = matrix_df["name"].map(name_to_rank)
|
||||
|
||||
rank_cols = [c for c in matrix_df.columns if c.startswith("rank_")]
|
||||
matrix_df["rank_volatility_spread"] = matrix_df[rank_cols].max(axis=1) - matrix_df[rank_cols].min(axis=1)
|
||||
return matrix_df.sort_values(by="rank_volatility_spread", ascending=False)
|
||||
Reference in new issue
Block a user