Update core/external_scoring.py
Browse files- core/external_scoring.py +40 -13
core/external_scoring.py
CHANGED
|
@@ -6,6 +6,7 @@ import pandas as pd
|
|
| 6 |
__all__ = [
|
| 7 |
"get_external_template_df",
|
| 8 |
"fill_missing_with_external",
|
|
|
|
| 9 |
"score_external_from_df",
|
| 10 |
]
|
| 11 |
|
|
@@ -60,16 +61,49 @@ def get_external_template_df() -> pd.DataFrame:
|
|
| 60 |
columns=["カテゴリー", "入力項目", "値"])
|
| 61 |
|
| 62 |
def fill_missing_with_external(df: pd.DataFrame, suggestions: Dict[str, Any] | None = None) -> pd.DataFrame:
|
| 63 |
-
"""LLM等からの suggestions を {入力項目: 値} で受け取り、空欄のみ埋める"""
|
| 64 |
if not suggestions:
|
| 65 |
return df.copy()
|
| 66 |
df2 = df.copy()
|
| 67 |
for idx, row in df2.iterrows():
|
| 68 |
-
|
| 69 |
-
if (row["値"] in (None, "", "—")) and (
|
| 70 |
-
df2.at[idx, "値"] = suggestions[
|
| 71 |
return df2
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
# ===== スコア計算(定量・ばらつき強化) =====
|
| 74 |
_WEIGHTS = {
|
| 75 |
("経営者能力", "経営姿勢"): 8,
|
|
@@ -120,17 +154,11 @@ def _ramp(x, good, bad, lo=0.0, hi=10.0, neutral=None):
|
|
| 120 |
if x >= bad: return lo
|
| 121 |
if x <= good: return hi
|
| 122 |
return lo + (hi-lo) * (x-good)/(bad-good)
|
| 123 |
-
|
| 124 |
-
def _stretch_0_10(x: float, k: float = 1.2) -> float:
|
| 125 |
-
"""
|
| 126 |
-
0-10 を保ちつつ中央付近(4-6)の密集をほぐす単調変換。
|
| 127 |
-
k>1で両端を強調。k≈1.2~1.4 推奨。
|
| 128 |
-
"""
|
| 129 |
if x is None: return None
|
| 130 |
t = (x/10.0)
|
| 131 |
t = t**(1.0/k) if t >= 0.5 else (t**k)
|
| 132 |
return _clamp(t*10.0, 0.0, 10.0)
|
| 133 |
-
|
| 134 |
def _add(items, cat, name, raw, weight, reason):
|
| 135 |
raw2 = _stretch_0_10(raw, k=1.25) if raw is not None else None
|
| 136 |
w = round(weight * _WEIGHT_NORM, 2)
|
|
@@ -193,7 +221,6 @@ def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
|
|
| 193 |
if v1 is None or v3 is None or v1 <= 0: return None
|
| 194 |
try: return (v3/v1)**(1/2) - 1.0
|
| 195 |
except Exception: return None
|
| 196 |
-
|
| 197 |
s_cagr = cagr(s1, s3)
|
| 198 |
p_cagr = cagr(p1, p3)
|
| 199 |
|
|
@@ -307,5 +334,5 @@ def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
|
|
| 307 |
"external_total": total,
|
| 308 |
"items": items,
|
| 309 |
"category_scores": cat_scores,
|
| 310 |
-
"notes": "
|
| 311 |
}
|
|
|
|
| 6 |
__all__ = [
|
| 7 |
"get_external_template_df",
|
| 8 |
"fill_missing_with_external",
|
| 9 |
+
"merge_market_into_external_df",
|
| 10 |
"score_external_from_df",
|
| 11 |
]
|
| 12 |
|
|
|
|
| 61 |
columns=["カテゴリー", "入力項目", "値"])
|
| 62 |
|
| 63 |
def fill_missing_with_external(df: pd.DataFrame, suggestions: Dict[str, Any] | None = None) -> pd.DataFrame:
|
|
|
|
| 64 |
if not suggestions:
|
| 65 |
return df.copy()
|
| 66 |
df2 = df.copy()
|
| 67 |
for idx, row in df2.iterrows():
|
| 68 |
+
k = row["入力項目"]
|
| 69 |
+
if (row["値"] in (None, "", "—")) and (k in suggestions):
|
| 70 |
+
df2.at[idx, "値"] = suggestions[k]
|
| 71 |
return df2
|
| 72 |
|
| 73 |
+
def merge_market_into_external_df(ext_df: pd.DataFrame, market: Dict[str, Any], products: List[str]) -> pd.DataFrame:
|
| 74 |
+
"""市場推定結果と商品リストをext_dfへ反映"""
|
| 75 |
+
df = ext_df.copy()
|
| 76 |
+
|
| 77 |
+
def _set(label: str, val):
|
| 78 |
+
m = df["入力項目"].eq(label)
|
| 79 |
+
if m.any():
|
| 80 |
+
df.loc[m, "値"] = val
|
| 81 |
+
else:
|
| 82 |
+
# 念のため行が無ければ追加
|
| 83 |
+
cat = "成長率" if "成長" in label else "安定性"
|
| 84 |
+
df = pd.concat([df, pd.DataFrame([[cat, label, val]], columns=df.columns)], ignore_index=True)
|
| 85 |
+
return df
|
| 86 |
+
|
| 87 |
+
# 市場成長率
|
| 88 |
+
if market.get("市場の年成長率(%)") is not None:
|
| 89 |
+
df = _set("市場の年成長率(%)", float(market["市場の年成長率(%)"]))
|
| 90 |
+
|
| 91 |
+
# 主力商品数 / 成長中主力商品数
|
| 92 |
+
prods = [p for p in products if str(p).strip()]
|
| 93 |
+
df = _set("主力商品数", len(prods))
|
| 94 |
+
|
| 95 |
+
growing = 0
|
| 96 |
+
prod_growth: Dict[str, float] = market.get("製品別年成長率(%)") or {}
|
| 97 |
+
for p in prods:
|
| 98 |
+
try:
|
| 99 |
+
if float(prod_growth.get(p, 0.0)) > 10.0:
|
| 100 |
+
growing += 1
|
| 101 |
+
except Exception:
|
| 102 |
+
pass
|
| 103 |
+
df = _set("成長中主力商品数", growing)
|
| 104 |
+
|
| 105 |
+
return df
|
| 106 |
+
|
| 107 |
# ===== スコア計算(定量・ばらつき強化) =====
|
| 108 |
_WEIGHTS = {
|
| 109 |
("経営者能力", "経営姿勢"): 8,
|
|
|
|
| 154 |
if x >= bad: return lo
|
| 155 |
if x <= good: return hi
|
| 156 |
return lo + (hi-lo) * (x-good)/(bad-good)
|
| 157 |
+
def _stretch_0_10(x: float, k: float = 1.25) -> float:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
if x is None: return None
|
| 159 |
t = (x/10.0)
|
| 160 |
t = t**(1.0/k) if t >= 0.5 else (t**k)
|
| 161 |
return _clamp(t*10.0, 0.0, 10.0)
|
|
|
|
| 162 |
def _add(items, cat, name, raw, weight, reason):
|
| 163 |
raw2 = _stretch_0_10(raw, k=1.25) if raw is not None else None
|
| 164 |
w = round(weight * _WEIGHT_NORM, 2)
|
|
|
|
| 221 |
if v1 is None or v3 is None or v1 <= 0: return None
|
| 222 |
try: return (v3/v1)**(1/2) - 1.0
|
| 223 |
except Exception: return None
|
|
|
|
| 224 |
s_cagr = cagr(s1, s3)
|
| 225 |
p_cagr = cagr(p1, p3)
|
| 226 |
|
|
|
|
| 334 |
"external_total": total,
|
| 335 |
"items": items,
|
| 336 |
"category_scores": cat_scores,
|
| 337 |
+
"notes": "欠損は中立+市場成長/商品構成を反映。ストレッチでばらつきを拡大。",
|
| 338 |
}
|