Update core/external_scoring.py
Browse files- core/external_scoring.py +16 -66
core/external_scoring.py
CHANGED
|
@@ -10,6 +10,7 @@ __all__ = [
|
|
| 10 |
"score_external_from_df",
|
| 11 |
]
|
| 12 |
|
|
|
|
| 13 |
_TEMPLATE_ROWS: List[Tuple[str, str]] = [
|
| 14 |
("経営者能力", "予実達成率_3年平均(%)"),
|
| 15 |
("経営者能力", "監査・内部統制の重大な不備 件数(過去3年)"),
|
|
@@ -71,26 +72,22 @@ def fill_missing_with_external(df: pd.DataFrame, suggestions: Dict[str, Any] | N
|
|
| 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 =
|
| 79 |
if m.any():
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 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 {}
|
|
@@ -100,11 +97,10 @@ def merge_market_into_external_df(ext_df: pd.DataFrame, market: Dict[str, Any],
|
|
| 100 |
growing += 1
|
| 101 |
except Exception:
|
| 102 |
pass
|
| 103 |
-
df = _set("成長中主力商品数", growing)
|
| 104 |
-
|
| 105 |
return df
|
| 106 |
|
| 107 |
-
# =====
|
| 108 |
_WEIGHTS = {
|
| 109 |
("経営者能力", "経営姿勢"): 8,
|
| 110 |
("経営者能力", "事業経験"): 5,
|
|
@@ -170,6 +166,7 @@ def _add(items, cat, name, raw, weight, reason):
|
|
| 170 |
})
|
| 171 |
|
| 172 |
def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
|
|
|
|
| 173 |
def ref(label: str):
|
| 174 |
m = df["入力項目"].eq(label)
|
| 175 |
return df.loc[m, "値"].values[0] if m.any() else None
|
|
@@ -252,7 +249,6 @@ def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
|
|
| 252 |
_add(items,"成長率","売上高伸長性", _ramp(s_cagr,0.08,-0.05),
|
| 253 |
_WEIGHTS[("成長率","売上高伸長性")],
|
| 254 |
f"CAGR売上{round((s_cagr or 0)*100,1) if s_cagr is not None else '—'}%")
|
| 255 |
-
|
| 256 |
_add(items,"成長率","利益伸長性", _ramp(p_cagr,0.08,-0.05),
|
| 257 |
_WEIGHTS[("成長率","利益伸長性")],
|
| 258 |
f"CAGR営業{round((p_cagr or 0)*100,1) if p_cagr is not None else '—'}%")
|
|
@@ -282,57 +278,11 @@ def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
|
|
| 282 |
_ramp(-(delay_days or 0),0,-30) )/3
|
| 283 |
rs=f"遅延{int(delay_cnt or 0)}/不渡{int(boun_cnt or 0)}/平均{int(delay_days or 0)}日"
|
| 284 |
else:
|
| 285 |
-
sc=_ramp(
|
| 286 |
_add(items,"安定性","決済振り", sc, _WEIGHTS[("安定性","決済振り")], rs)
|
| 287 |
|
| 288 |
sc_mb = 5.0
|
| 289 |
sc_mb += 2.0 if mainbank else (-0.5 if mainbank is False else 0)
|
| 290 |
sc_mb += 1.0 if has_line else 0
|
| 291 |
sc_mb = _clamp(sc_mb,0,10)
|
| 292 |
-
|
| 293 |
-
f"メイン{'有' if mainbank else '無' if mainbank is False else '—'}/与信枠{'有' if has_line else '無' if has_line is False else '—'}")
|
| 294 |
-
|
| 295 |
-
_add(items,"安定性","資産担保余力", _ramp(coll_to_ms,4.0,0.0),
|
| 296 |
-
_WEIGHTS[("安定性","資産担保余力")], f"担保/月商≈{round(coll_to_ms,2) if coll_to_ms else '—'}")
|
| 297 |
-
|
| 298 |
-
_add(items,"安定性","取引先",
|
| 299 |
-
( _ramp(-(top1 or 50),0,-80) +
|
| 300 |
-
_ramp(cust_score,80,50) +
|
| 301 |
-
_ramp(-(npl_cnt or 1),0,-3) )/3,
|
| 302 |
-
_WEIGHTS[("安定性","取引先")],
|
| 303 |
-
f"上位1社{top1 or '—'}%/信用{cust_score or '—'}/不良{int(npl_cnt or 0)}")
|
| 304 |
-
|
| 305 |
-
_add(items,"安定性","業歴", _ramp(years,20,1),
|
| 306 |
-
_WEIGHTS[("安定性","業歴")], f"{years or '—'}年")
|
| 307 |
-
|
| 308 |
-
# 公平性
|
| 309 |
-
sc_dis = 0.0
|
| 310 |
-
has_sec = _to_bool(ref("有価証券報告書提出企業か(TRUE/FALSE)"))
|
| 311 |
-
sc_dis += 10.0 if has_sec else 0.0
|
| 312 |
-
if sc_dis == 0.0:
|
| 313 |
-
pub_off = _to_bool(ref("決算公告や官報での公開あり(TRUE/FALSE)"))
|
| 314 |
-
pub_web = _to_bool(ref("HP/IRサイトで財務資料公開あり(TRUE/FALSE)"))
|
| 315 |
-
sc_dis += 7.0 if (pub_off or pub_web) else 4.0
|
| 316 |
-
upd_on = _to_bool(ref("直近更新が定め通りか(TRUE/FALSE)"))
|
| 317 |
-
if upd_on: sc_dis += 1.0
|
| 318 |
-
sc_dis = _clamp(sc_dis,0,10)
|
| 319 |
-
_add(items,"公平性・総合世評","ディスクロージャー", sc_dis,
|
| 320 |
-
_WEIGHTS[("公平性・総合世評","ディスクロージャー")],
|
| 321 |
-
f"{'有報' if has_sec else '公開あり' if sc_dis>=7.0 else '公開乏しい'} / 更新{'◯' if upd_on else '—'}")
|
| 322 |
-
|
| 323 |
-
total = round(sum(x["score"] for x in items),1)
|
| 324 |
-
|
| 325 |
-
from collections import defaultdict
|
| 326 |
-
cat_sum, cat_w = defaultdict(float), defaultdict(float)
|
| 327 |
-
for it in items:
|
| 328 |
-
cat_sum[it["category"]] += it["score"]
|
| 329 |
-
cat_w[it["category"]] += it["weight"]
|
| 330 |
-
cat_scores = {c: round((cat_sum[c] / cat_w[c]) * 100.0 if cat_w[c] > 0 else 0.0, 1) for c in cat_sum}
|
| 331 |
-
|
| 332 |
-
return {
|
| 333 |
-
"name": "企業評価(外部・定量)",
|
| 334 |
-
"external_total": total,
|
| 335 |
-
"items": items,
|
| 336 |
-
"category_scores": cat_scores,
|
| 337 |
-
"notes": "欠損は中立+市場成長/商品構成を反映。ストレッチでばらつきを拡大。",
|
| 338 |
-
}
|
|
|
|
| 10 |
"score_external_from_df",
|
| 11 |
]
|
| 12 |
|
| 13 |
+
# ひな形(+市場成長率系の列を追加)
|
| 14 |
_TEMPLATE_ROWS: List[Tuple[str, str]] = [
|
| 15 |
("経営者能力", "予実達成率_3年平均(%)"),
|
| 16 |
("経営者能力", "監査・内部統制の重大な不備 件数(過去3年)"),
|
|
|
|
| 72 |
return df2
|
| 73 |
|
| 74 |
def merge_market_into_external_df(ext_df: pd.DataFrame, market: Dict[str, Any], products: List[str]) -> pd.DataFrame:
|
| 75 |
+
"""市場推定結果と商品リストをext_dfへ反映(必ずDataFrameを返す)"""
|
| 76 |
df = ext_df.copy()
|
| 77 |
|
| 78 |
+
def _set(df_: pd.DataFrame, label: str, val: Any, cat_hint: str = "成長率") -> pd.DataFrame:
|
| 79 |
+
m = df_["入力項目"].eq(label)
|
| 80 |
if m.any():
|
| 81 |
+
df_.loc[m, "値"] = val
|
| 82 |
+
return df_
|
| 83 |
+
# 行がない場合は追加
|
| 84 |
+
return pd.concat([df_, pd.DataFrame([[cat_hint, label, val]], columns=df_.columns)], ignore_index=True)
|
| 85 |
+
|
|
|
|
|
|
|
|
|
|
| 86 |
if market.get("市場の年成長率(%)") is not None:
|
| 87 |
+
df = _set(df, "市場の年成長率(%)", float(market["市場の年成長率(%)"]), "成長率")
|
| 88 |
|
|
|
|
| 89 |
prods = [p for p in products if str(p).strip()]
|
| 90 |
+
df = _set(df, "主力商品数", len(prods), "成長率")
|
| 91 |
|
| 92 |
growing = 0
|
| 93 |
prod_growth: Dict[str, float] = market.get("製品別年成長率(%)") or {}
|
|
|
|
| 97 |
growing += 1
|
| 98 |
except Exception:
|
| 99 |
pass
|
| 100 |
+
df = _set(df, "成長中主力商品数", growing, "成長率")
|
|
|
|
| 101 |
return df
|
| 102 |
|
| 103 |
+
# ===== スコア計算(定量化+ばらつきストレッチ) =====
|
| 104 |
_WEIGHTS = {
|
| 105 |
("経営者能力", "経営姿勢"): 8,
|
| 106 |
("経営者能力", "事業経験"): 5,
|
|
|
|
| 166 |
})
|
| 167 |
|
| 168 |
def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
|
| 169 |
+
# 必ず dict を返す。途中で例外にならないよう to_x で吸収。
|
| 170 |
def ref(label: str):
|
| 171 |
m = df["入力項目"].eq(label)
|
| 172 |
return df.loc[m, "値"].values[0] if m.any() else None
|
|
|
|
| 249 |
_add(items,"成長率","売上高伸長性", _ramp(s_cagr,0.08,-0.05),
|
| 250 |
_WEIGHTS[("成長率","売上高伸長性")],
|
| 251 |
f"CAGR売上{round((s_cagr or 0)*100,1) if s_cagr is not None else '—'}%")
|
|
|
|
| 252 |
_add(items,"成長率","利益伸長性", _ramp(p_cagr,0.08,-0.05),
|
| 253 |
_WEIGHTS[("成長率","利益伸長性")],
|
| 254 |
f"CAGR営業{round((p_cagr or 0)*100,1) if p_cagr is not None else '—'}%")
|
|
|
|
| 278 |
_ramp(-(delay_days or 0),0,-30) )/3
|
| 279 |
rs=f"遅延{int(delay_cnt or 0)}/不渡{int(boun_cnt or 0)}/平均{int(delay_days or 0)}日"
|
| 280 |
else:
|
| 281 |
+
sc=_ramp(_ratio(cash, sales_m2),1.0,0.2); rs=f"代理:現預金/月商≈—"
|
| 282 |
_add(items,"安定性","決済振り", sc, _WEIGHTS[("安定性","決済振り")], rs)
|
| 283 |
|
| 284 |
sc_mb = 5.0
|
| 285 |
sc_mb += 2.0 if mainbank else (-0.5 if mainbank is False else 0)
|
| 286 |
sc_mb += 1.0 if has_line else 0
|
| 287 |
sc_mb = _clamp(sc_mb,0,10)
|
| 288 |
+
_a_
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|