File size: 12,800 Bytes
f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 f545096 9721634 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# core/external_score.py
from __future__ import annotations
from typing import Dict, Any, List, Tuple
import pandas as pd
__all__ = [
"get_external_template_df",
"fill_missing_with_external",
"score_external_from_df",
"apply_llm_signals_to_df",
]
_TEMPLATE_ROWS: List[Tuple[str, str]] = [
# 成長性をより定量化
("成長率","市場CAGR(%)"),
("成長率","売上_期3(最新期)"),
("成長率","売上_期2"),
("成長率","売上_期1(最古期)"),
("成長率","営業利益_期3(最新期)"),
("成長率","営業利益_期2"),
("成長率","営業利益_期1(最古期)"),
("成長率","主力商品数"),
("成長率","成長中主力商品数"),
# 経営者能力
("経営者能力","予実達成率_3年平均(%)"),
("経営者能力","監査・内部統制の重大な不備 件数(過去3年)"),
("経営者能力","重大コンプライアンス件数(過去3年)"),
("経営者能力","社外取締役比率(%)"),
("経営者能力","代表者の業界経験年数"),
("経営者能力","現預金(円)"),
("経営者能力","月商(円)"),
("経営者能力","担保余力評価額(円)"),
("経営者能力","倒産歴の有無(TRUE/FALSE)"),
("経営者能力","倒産からの経過年数"),
("経営者能力","重大事件・事故件数(過去10年)"),
# 安定性
("安定性","自己資本比率(%)"),
("安定性","利益剰余金(円)"),
("安定性","支払遅延件数(直近12ヶ月)"),
("安定性","不渡り件数(直近12ヶ月)"),
("安定性","平均支払遅延日数"),
("安定性","メインバンク明確か(TRUE/FALSE)"),
("安定性","借入先数"),
("安定性","メインバンク借入シェア(%)"),
("安定性","コミットメントライン等の長期与信枠あり(TRUE/FALSE)"),
("安定性","担保余力評価額(円)"),
("安定性","月商(円)_再掲"),
("安定性","主要顧客上位1社売上比率(%)"),
("安定性","主要顧客上位3社売上比率(%)"),
("安定性","主要顧客の平均信用スコア(0-100)"),
("安定性","不良債権件数(直近12ヶ月)"),
("安定性","業歴(年)"),
# 公平性
("公平性・総合世評","有価証券報告書提出企業か(TRUE/FALSE)"),
("公平性・総合世評","決算公告や官報での公開あり(TRUE/FALSE)"),
("公平性・総合世評","HP/IRサイトで財務資料公開あり(TRUE/FALSE)"),
("公平性・総合世評","直近更新が定め通りか(TRUE/FALSE)"),
]
def get_external_template_df() -> pd.DataFrame:
return pd.DataFrame([(c,i,"") for c,i in _TEMPLATE_ROWS], columns=["カテゴリー","入力項目","値"])
def fill_missing_with_external(df: pd.DataFrame, company: str = "", country: str = "") -> pd.DataFrame:
return df.copy()
# === LLMの抽出結果をテンプレに反映 ===
def apply_llm_signals_to_df(df: pd.DataFrame, signals: Dict[str, Any]) -> pd.DataFrame:
df2 = df.copy()
def setv(field: str, val):
m = df2["入力項目"].eq(field)
if m.any():
df2.loc[m, "値"] = "" if val is None else val
if signals:
mkt = signals.get("market", {})
prod = signals.get("products", {})
setv("市場CAGR(%)", mkt.get("cagr_pct"))
setv("主力商品数", prod.get("count"))
setv("成長中主力商品数", prod.get("growing_count"))
return df2
# ===== スコア計算 =====
_WEIGHTS = {
# 成長の定量性を強化
("成長率","市場成長率"): 12,
("成長率","売上高伸長性"): 10,
("成長率","利益伸長性"): 10,
("成長率","商品"): 6,
("経営者能力","経営姿勢"): 8,
("経営者能力","事業経験"): 5,
("経営者能力","資産担保力"): 6,
("経営者能力","減点事項"): 7,
("安定性","自己資本"): 8,
("安定性","決済振り"): 10,
("安定性","金融取引"): 6,
("安定性","資産担保余力"): 6,
("安定性","取引先"): 6,
("安定性","業歴"): 4,
("公平性・総合世評","ディスクロージャー"): 8,
}
_WEIGHT_NORM = 100.0 / float(sum(_WEIGHTS.values()))
def _clamp(v, a, b): return max(a, min(b, v))
def _add(items, cat, name, raw, weight, reason):
if raw is None: raw = 5.0
items.append({
"category": cat, "name": name, "raw": round(raw,2),
"weight": round(weight*_WEIGHT_NORM,2),
"score": round((raw/10.0)*weight*_WEIGHT_NORM,2),
"reason": reason
})
def _to_float(x):
if x is None: return None
try: return float(str(x).replace(",","").replace("▲","-").replace("△","-"))
except Exception: return None
def _to_bool(x):
if x is None: return None
s = str(x).strip().lower()
if s in ("true","t","1","yes","y","有","あり"): return True
if s in ("false","f","0","no","n","無","なし"): return False
return None
def _ratio(a,b):
if a is None or b is None or b == 0: return None
return a/b
def _ramp(x, good, bad, lo=0.0, hi=10.0, neutral=None):
if x is None:
return neutral if neutral is not None else (lo+hi)/2.0
if good > bad:
if x <= bad: return lo
if x >= good: return hi
return lo + (hi-lo) * (x-bad)/(good-bad)
else:
if x >= bad: return lo
if x <= good: return hi
return lo + (hi-lo) * (x-good)/(bad-good)
def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
def ref(label: str):
m = df["入力項目"].eq(label)
return df.loc[m, "値"].values[0] if m.any() else None
items = []
# ---- 成長率(定量) ----
market_cagr=_to_float(ref("市場CAGR(%)"))
s1=_to_float(ref("売上_期1(最古期)")); s2=_to_float(ref("売上_期2")); s3=_to_float(ref("売上_期3(最新期)"))
p1=_to_float(ref("営業利益_期1(最古期)")); p2=_to_float(ref("営業利益_期2")); p3=_to_float(ref("営業利益_期3(最新期)"))
prod_n=_to_float(ref("主力商品数")); prod_g=_to_float(ref("成長中主力商品数"))
def cagr(v1, v3):
if v1 is None or v3 is None or v1 <= 0: return None
try: return (v3/v1)**(1/2) - 1.0
except Exception: return None
s_cagr = cagr(s1, s3); p_cagr = cagr(p1, p3)
# 市場CAGR 12%で満点、-3%で0点(景気循環を意識しやや厳しめ)
_add(items,"成長率","市場成長率", _ramp(market_cagr,12,-3), _WEIGHTS[("成長率","市場成長率")],
f"市場CAGR {market_cagr if market_cagr is not None else '—'}%")
_add(items,"成長率","売上高伸長性", _ramp(s_cagr,0.10,-0.05), _WEIGHTS[("成長率","売上高伸長性")],
f"CAGR売上{round((s_cagr or 0)*100,1) if s_cagr is not None else '—'}%")
_add(items,"成長率","利益伸長性", _ramp(p_cagr,0.10,-0.05), _WEIGHTS[("成長率","利益伸長性")],
f"CAGR営業{round((p_cagr or 0)*100,1) if p_cagr is not None else '—'}%")
# 製品ポートフォリオの定量化(成長中比率)
grow_ratio = _ratio(prod_g, prod_n)
_add(items,"成長率","商品", _ramp(grow_ratio,0.6,0.1), _WEIGHTS[("成長率","商品")],
f"成長中比率{round((grow_ratio or 0)*100,1) if grow_ratio is not None else '—'}%")
# ---- 経営者能力 ----
yoy3=_to_float(ref("予実達成率_3年平均(%)"))
audit_bad=_to_float(ref("監査・内部統制の重大な不備 件数(過去3年)"))
comp_bad=_to_float(ref("重大コンプライアンス件数(過去3年)"))
indep=_to_float(ref("社外取締役比率(%)"))
exp_years=_to_float(ref("代表者の業界経験年数"))
cash=_to_float(ref("現預金(円)"))
sales_m=_to_float(ref("月商(円)"))
collat=_to_float(ref("担保余力評価額(円)"))
has_bk=_to_bool(ref("倒産歴の有無(TRUE/FALSE)"))
bk_years=_to_float(ref("倒産からの経過年数"))
incidents=_to_float(ref("重大事件・事故件数(過去10年)"))
cash_to_ms=_ratio(cash, sales_m)
mg_att = (_ramp(yoy3, 90,50)+_ramp(0 if not audit_bad else -audit_bad,0,-3)+_ramp(0 if not comp_bad else -comp_bad,0,-2)+_ramp(indep,33,0))/4
_add(items,"経営者能力","経営姿勢",mg_att,_WEIGHTS[("経営者能力","経営姿勢")],f"予実{yoy3 or '—'}%/監査{audit_bad or 0}/違反{comp_bad or 0}/社外{indep or '—'}%")
mg_exp = _ramp(exp_years if exp_years is not None else 5.0, 15, 0)
_add(items,"経営者能力","事業経験",mg_exp,_WEIGHTS[("経営者能力","事業経験")],f"経験{exp_years if exp_years is not None else '不明→中立'}年")
mg_asset = _ramp(cash_to_ms, 1.5, 0.2)
_add(items,"経営者能力","資産担保力",mg_asset,_WEIGHTS[("経営者能力","資産担保力")],f"現預金/月商≈{round(cash_to_ms,2) if cash_to_ms else '—'}")
if incidents and incidents>0:
pen=0.0; rs=f"重大事故{int(incidents)}件→大幅減点"
elif has_bk:
pen=6.0 if (bk_years and bk_years>=10) else 3.0; rs=f"倒産歴あり({bk_years or '不明'}年)"
else:
pen=10.0; rs="事故/倒産なし"
_add(items,"経営者能力","減点事項",pen,_WEIGHTS[("経営者能力","減点事項")],rs)
# ---- 安定性 ----
equity=_to_float(ref("自己資本比率(%)"))
delay_cnt=_to_float(ref("支払遅延件数(直近12ヶ月)"))
boun_cnt=_to_float(ref("不渡り件数(直近12ヶ月)"))
delay_days=_to_float(ref("平均支払遅延日数"))
mainbank=_to_bool(ref("メインバンク明確か(TRUE/FALSE)"))
main_share=_to_float(ref("メインバンク借入シェア(%)"))
has_line=_to_bool(ref("コミットメントライン等の長期与信枠あり(TRUE/FALSE)"))
coll_to_ms=_ratio(_to_float(ref("担保余力評価額(円)")), _to_float(ref("月商(円)_再掲")) or sales_m)
_add(items,"安定性","自己資本", _ramp(equity,40,5), _WEIGHTS[("安定性","自己資本")], f"自己資本比率{equity or '—'}%")
if (delay_cnt is not None) or (boun_cnt is not None) or (delay_days is not None):
sc=( _ramp(- (delay_cnt or 0),0,-6) + _ramp(- (boun_cnt or 0),0,-1) + _ramp(- (delay_days or 0),0,-30) )/3
rs=f"遅延{int(delay_cnt or 0)}/不渡{int(boun_cnt or 0)}/平均{int(delay_days or 0)}日"
else:
sc=_ramp(cash_to_ms,1.0,0.2); rs=f"代理:現預金/月商≈{round(cash_to_ms,2) if cash_to_ms else '—'}"
_add(items,"安定性","決済振り", sc, _WEIGHTS[("安定性","決済振り")], rs)
sc_mb = 5.0
sc_mb += 2.0 if mainbank else (-0.5 if mainbank is False else 0)
sc_mb += 1.0 if has_line else 0
sc_mb = _clamp(sc_mb,0,10)
_add(items,"安定性","金融取引", sc_mb, _WEIGHTS[("安定性","金融取引")],
f"メイン{'有' if mainbank else '無' if mainbank is False else '—'}/与信枠{'有' if has_line else '無' if has_line is False else '—'}")
_add(items,"安定性","資産担保余力", _ramp(coll_to_ms,4.0,0.0), _WEIGHTS[("安定性","資産担保余力")],
f"担保/月商≈{round(coll_to_ms,2) if coll_to_ms else '—'}")
top1=_to_float(ref("主要顧客上位1社売上比率(%)"))
cust_score=_to_float(ref("主要顧客の平均信用スコア(0-100)"))
npl_cnt=_to_float(ref("不良債権件数(直近12ヶ月)"))
_add(items,"安定性","取引先", ( _ramp(- (top1 or 50),0,-80) + _ramp(cust_score,80,50) + _ramp(- (npl_cnt or 1),0,-3) )/3,
_WEIGHTS[("安定性","取引先")], f"上位1社{top1 or '—'}%/信用{cust_score or '—'}/不良{int(npl_cnt or 0)}")
years=_to_float(ref("業歴(年)"))
_add(items,"安定性","業歴", _ramp(years,20,1), _WEIGHTS[("安定性","業歴")], f"{years or '—'}年")
# ---- 公平性 ----
has_sec=_to_bool(ref("有価証券報告書提出企業か(TRUE/FALSE)"))
pub_off=_to_bool(ref("決算公告や官報での公開あり(TRUE/FALSE)"))
pub_web=_to_bool(ref("HP/IRサイトで財務資料公開あり(TRUE/FALSE)"))
upd_on=_to_bool(ref("直近更新が定め通りか(TRUE/FALSE)"))
sc_dis = 0.0
sc_dis += 10.0 if has_sec else (7.0 if (pub_off or pub_web) else 4.0)
sc_dis += 1.0 if upd_on else 0.0
sc_dis = _clamp(sc_dis,0,10)
_add(items,"公平性・総合世評","ディスクロージャー", sc_dis, _WEIGHTS[("公平性・総合世評","ディスクロージャー")],
f"{'有報' if has_sec else '公開あり' if (pub_off or pub_web) else '公開乏しい'} / 更新{'◯' if upd_on else '—'}")
total = round(sum(x["score"] for x in items),1)
return {"name":"企業評価(外部)","external_total": total, "items": items, "notes":"欠損は中立、連続スコア×重み(自動正規化)"}
|