Corin1998 commited on
Commit
fc23dfc
·
verified ·
1 Parent(s): c2ea3e1

Update core/external_scoring.py

Browse files
Files changed (1) hide show
  1. core/external_scoring.py +66 -83
core/external_scoring.py CHANGED
@@ -61,9 +61,7 @@ def get_external_template_df() -> pd.DataFrame:
61
  columns=["カテゴリー", "入力項目", "値"])
62
 
63
  def fill_missing_with_external(df: pd.DataFrame, suggestions: Dict[str, Any] | None = None) -> pd.DataFrame:
64
- """
65
- LLM等からの suggestions を {入力項目: 値} で受け取り、空欄のみ埋める
66
- """
67
  if not suggestions:
68
  return df.copy()
69
  df2 = df.copy()
@@ -83,7 +81,7 @@ _WEIGHTS = {
83
  ("成長率", "売上高伸長性"): 10,
84
  ("成長率", "利益伸長性"): 10,
85
  ("成長率", "商品"): 6,
86
- ("成長率", "市場成長調整"): 6, # ★ 追加:市場成長率を反映
87
 
88
  ("安定性", "自己資本"): 8,
89
  ("安定性", "決済振り"): 10,
@@ -103,59 +101,47 @@ def _add(items, cat, name, raw, weight, reason):
103
  w = round(weight * _WEIGHT_NORM, 2)
104
  sc = 0.0 if raw is None else round((raw / 10.0) * w, 2)
105
  items.append({
106
- "category": cat,
107
- "name": name,
108
- "raw": raw_s,
109
- "weight": w,
110
- "score": sc,
111
- "reason": reason
112
  })
113
 
114
  def _to_float(x):
115
- if x is None:
116
- return None
117
  try:
118
  return float(str(x).replace(",", "").replace("▲", "-").replace("△", "-"))
119
  except Exception:
120
  return None
121
 
122
  def _to_bool(x):
123
- if x is None:
124
- return None
125
  s = str(x).strip().lower()
126
- if s in ("true", "t", "1", "yes", "y", "有", "あり"):
127
- return True
128
- if s in ("false", "f", "0", "no", "n", "無", "なし"):
129
- return False
130
  return None
131
 
132
- def _ratio(a, b):
133
- if a is None or b is None or b == 0:
134
- return None
135
- return a / b
136
 
137
  def _ramp(x, good, bad, lo=0.0, hi=10.0, neutral=None):
138
  if x is None:
139
- return neutral if neutral is not None else (lo + hi) / 2.0
140
  if good > bad:
141
  if x <= bad: return lo
142
  if x >= good: return hi
143
- return lo + (hi - lo) * (x - bad) / (good - bad)
144
  else:
145
  if x >= bad: return lo
146
  if x <= good: return hi
147
- return lo + (hi - lo) * (x - good) / (bad - good)
148
 
149
  def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
150
- """
151
- 入力DF(カテゴリー/入力項目/値)を定量スコア化。
152
- 欠損は中立。市場の年成長率(%) を「市場成長調整」に反映し、成長率評価の過剰/過小を補正。
153
- """
154
  def ref(label: str):
155
  m = df["入力項目"].eq(label)
156
  return df.loc[m, "値"].values[0] if m.any() else None
157
 
158
- items = []
159
 
160
  yoy3 = _to_float(ref("予実達成率_3年平均(%)"))
161
  audit_bad = _to_float(ref("監査・内部統制の重大な不備 件数(過去3年)"))
@@ -199,12 +185,9 @@ def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
199
  coll_to_ms = _ratio(collat, sales_m2)
200
 
201
  def cagr(v1, v3):
202
- if v1 is None or v3 is None or v1 <= 0:
203
- return None
204
- try:
205
- return (v3 / v1) ** (1 / 2) - 1.0
206
- except Exception:
207
- return None
208
 
209
  s_cagr = cagr(s1, s3)
210
  p_cagr = cagr(p1, p3)
@@ -214,100 +197,100 @@ def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
214
  _ramp(0 if not audit_bad else -audit_bad, 0, -3) +
215
  _ramp(0 if not comp_bad else -comp_bad, 0, -2) +
216
  _ramp(indep, 33, 0)) / 4
217
- _add(items, "経営者能力", "経営姿勢", mg_att, _WEIGHTS[("経営者能力", "経営姿勢")],
218
  f"予実{yoy3 or '—'}%/監査{audit_bad or 0}/違反{comp_bad or 0}/社外{indep or '—'}%")
219
 
220
  mg_exp = _ramp(exp_years if exp_years is not None else 5.0, 15, 0)
221
- _add(items, "経営者能力", "事業経験", mg_exp, _WEIGHTS[("経営者能力", "事業経験")],
222
  f"経験{exp_years if exp_years is not None else '不明→中立'}年")
223
 
224
  mg_asset = _ramp(cash_to_ms, 1.5, 0.2)
225
- _add(items, "経営者能力", "資産担保力", mg_asset, _WEIGHTS[("経営者能力", "資産担保力")],
226
- f"現預金/月商≈{round(cash_to_ms, 2) if cash_to_ms else '—'}")
227
 
228
- if incidents and incidents > 0:
229
- pen = 0.0; rs = f"重大事故{int(incidents)}件→大幅減点"
230
  elif has_bk:
231
- pen = 6.0 if (bk_years and bk_years >= 10) else 3.0; rs = f"倒産歴あり({bk_years or '不明'}年)"
232
  else:
233
- pen = 10.0; rs = "事故/倒産なし"
234
- _add(items, "経営者能力", "減点事項", pen, _WEIGHTS[("経営者能力", "減点事項")], rs)
235
 
236
- # 成長率(市場成長で調整)
237
- _add(items, "成長率", "売上高伸長性", _ramp(s_cagr, 0.08, -0.05),
238
- _WEIGHTS[("成長率", "売上高伸長性")],
239
  f"CAGR売上{round((s_cagr or 0)*100,1) if s_cagr is not None else '—'}%")
240
 
241
- _add(items, "成長率", "利益伸長性", _ramp(p_cagr, 0.08, -0.05),
242
- _WEIGHTS[("成長率", "利益伸長性")],
243
  f"CAGR営業{round((p_cagr or 0)*100,1) if p_cagr is not None else '—'}%")
244
 
245
- # 商品スコア:総数と成長中の比率で
246
  if prod_total is None or prod_total <= 0:
247
  pr_sc = 5.0; rs = "不明→中立"
248
  else:
249
  ratio = _ratio(prod_growing, prod_total) or 0.0
250
  pr_sc = ( _ramp(prod_total, 3, 0) + _ramp(ratio, 0.7, 0.1) ) / 2
251
  rs = f"主力{int(prod_total)}/成長中比{round(ratio*100,1)}%"
252
- _add(items, "成長率", "商品", pr_sc, _WEIGHTS[("成長率", "商品")], rs)
253
 
254
- # 市場成長調整:市場 >10% なら高評価、マイナス成長は減点
255
- _add(items, "成長率", "市場成長調整",
256
- _ramp(market_growth, 15, -5),
257
- _WEIGHTS[("成長率", "市場成長調整")],
258
  f"市場年成長{market_growth or '—'}%")
259
 
260
  # 安定性
261
- _add(items, "安定性", "自己資本", _ramp(equity, 40, 5),
262
- _WEIGHTS[("安定性", "自己資本")], f"自己資本比率{equity or '—'}%")
263
 
264
  if (delay_cnt is not None) or (boun_cnt is not None) or (delay_days is not None):
265
- sc = ( _ramp(-(delay_cnt or 0), 0, -6) +
266
- _ramp(-(boun_cnt or 0), 0, -1) +
267
- _ramp(-(delay_days or 0), 0, -30) ) / 3
268
- rs = f"遅延{int(delay_cnt or 0)}/不渡{int(boun_cnt or 0)}/平均{int(delay_days or 0)}日"
269
  else:
270
- sc = _ramp(cash_to_ms, 1.0, 0.2)
271
- rs = f"代理:現預金/月商≈{round(cash_to_ms,2) if cash_to_ms else '—'}"
272
- _add(items, "安定性", "決済振り", sc, _WEIGHTS[("安定性", "決済振り")], rs)
273
 
274
  sc_mb = 5.0
275
  sc_mb += 2.0 if mainbank else (-0.5 if mainbank is False else 0)
276
  sc_mb += 1.0 if has_line else 0
277
- sc_mb = _clamp(sc_mb, 0, 10)
278
- _add(items, "安定性", "金融取引", sc_mb, _WEIGHTS[("安定性", "金融取引")],
279
  f"メイン{'有' if mainbank else '無' if mainbank is False else '—'}/与信枠{'有' if has_line else '無' if has_line is False else '—'}")
280
 
281
- _add(items, "安定性", "資産担保余力", _ramp(coll_to_ms, 4.0, 0.0),
282
- _WEIGHTS[("安定性", "資産担保余力")], f"担保/月商≈{round(coll_to_ms,2) if coll_to_ms else '—'}")
283
 
284
- _add(items, "安定性", "取引先",
285
- ( _ramp(-(top1 or 50), 0, -80) +
286
- _ramp(cust_score, 80, 50) +
287
- _ramp(-(npl_cnt or 1), 0, -3) ) / 3,
288
- _WEIGHTS[("安定性", "取引先")],
289
  f"上位1社{top1 or '—'}%/信用{cust_score or '—'}/不良{int(npl_cnt or 0)}")
290
 
291
- _add(items, "安定性", "業歴", _ramp(years, 20, 1),
292
- _WEIGHTS[("安定性", "業歴")], f"{years or '—'}年")
293
 
294
- # 公平性
295
  sc_dis = 0.0
296
- sc_dis += 10.0 if has_sec := _to_bool(ref("有価証券報告書提出企業か(TRUE/FALSE)")) else 0.0
 
297
  if sc_dis == 0.0:
298
  pub_off = _to_bool(ref("決算公告や官報での公開あり(TRUE/FALSE)"))
299
  pub_web = _to_bool(ref("HP/IRサイトで財務資料公開あり(TRUE/FALSE)"))
300
  sc_dis += 7.0 if (pub_off or pub_web) else 4.0
301
  upd_on = _to_bool(ref("直近更新が定め通りか(TRUE/FALSE)"))
302
  if upd_on: sc_dis += 1.0
303
- sc_dis = _clamp(sc_dis, 0, 10)
304
- _add(items, "公平性・総合世評", "ディスクロージャー", sc_dis,
305
- _WEIGHTS[("公平性・総合世評", "ディスクロージャー")],
306
  f"{'有報' if has_sec else '公開あり' if sc_dis>=7.0 else '公開乏しい'} / 更新{'◯' if upd_on else '—'}")
307
 
308
- total = round(sum(x["score"] for x in items), 1)
309
 
310
- # レーダ用にカテゴリ集計(重み付き平均→0-100)
311
  from collections import defaultdict
312
  cat_sum, cat_w = defaultdict(float), defaultdict(float)
313
  for it in items:
 
61
  columns=["カテゴリー", "入力項目", "値"])
62
 
63
  def fill_missing_with_external(df: pd.DataFrame, suggestions: Dict[str, Any] | None = None) -> pd.DataFrame:
64
+ """LLM等からの suggestions を {入力項目: 値} で受け取り、空欄のみ埋める"""
 
 
65
  if not suggestions:
66
  return df.copy()
67
  df2 = df.copy()
 
81
  ("成長率", "売上高伸長性"): 10,
82
  ("成長率", "利益伸長性"): 10,
83
  ("成長率", "商品"): 6,
84
+ ("成長率", "市場成長調整"): 6, # 市場成長率を反映
85
 
86
  ("安定性", "自己資本"): 8,
87
  ("安定性", "決済振り"): 10,
 
101
  w = round(weight * _WEIGHT_NORM, 2)
102
  sc = 0.0 if raw is None else round((raw / 10.0) * w, 2)
103
  items.append({
104
+ "category": cat, "name": name, "raw": raw_s,
105
+ "weight": w, "score": sc, "reason": reason
 
 
 
 
106
  })
107
 
108
  def _to_float(x):
109
+ if x is None: return None
 
110
  try:
111
  return float(str(x).replace(",", "").replace("▲", "-").replace("△", "-"))
112
  except Exception:
113
  return None
114
 
115
  def _to_bool(x):
116
+ if x is None: return None
 
117
  s = str(x).strip().lower()
118
+ if s in ("true","t","1","yes","y","有","あり"): return True
119
+ if s in ("false","f","0","no","n","無","なし"): return False
 
 
120
  return None
121
 
122
+ def _ratio(a,b):
123
+ if a is None or b is None or b == 0: return None
124
+ return a/b
 
125
 
126
  def _ramp(x, good, bad, lo=0.0, hi=10.0, neutral=None):
127
  if x is None:
128
+ return neutral if neutral is not None else (lo+hi)/2.0
129
  if good > bad:
130
  if x <= bad: return lo
131
  if x >= good: return hi
132
+ return lo + (hi-lo) * (x-bad)/(good-bad)
133
  else:
134
  if x >= bad: return lo
135
  if x <= good: return hi
136
+ return lo + (hi-lo) * (x-good)/(bad-good)
137
 
138
  def score_external_from_df(df: pd.DataFrame) -> Dict[str, Any]:
139
+ """入力DF(カテゴリー/入力項目/値)を定量スコア化。欠損は中立。市場成長率も反映。"""
 
 
 
140
  def ref(label: str):
141
  m = df["入力項目"].eq(label)
142
  return df.loc[m, "値"].values[0] if m.any() else None
143
 
144
+ items: List[Dict[str, Any]] = []
145
 
146
  yoy3 = _to_float(ref("予実達成率_3年平均(%)"))
147
  audit_bad = _to_float(ref("監査・内部統制の重大な不備 件数(過去3年)"))
 
185
  coll_to_ms = _ratio(collat, sales_m2)
186
 
187
  def cagr(v1, v3):
188
+ if v1 is None or v3 is None or v1 <= 0: return None
189
+ try: return (v3/v1)**(1/2) - 1.0
190
+ except Exception: return None
 
 
 
191
 
192
  s_cagr = cagr(s1, s3)
193
  p_cagr = cagr(p1, p3)
 
197
  _ramp(0 if not audit_bad else -audit_bad, 0, -3) +
198
  _ramp(0 if not comp_bad else -comp_bad, 0, -2) +
199
  _ramp(indep, 33, 0)) / 4
200
+ _add(items, "経営者能力", "経営姿勢", mg_att, _WEIGHTS[("経営者能力","経営姿勢")],
201
  f"予実{yoy3 or '—'}%/監査{audit_bad or 0}/違反{comp_bad or 0}/社外{indep or '—'}%")
202
 
203
  mg_exp = _ramp(exp_years if exp_years is not None else 5.0, 15, 0)
204
+ _add(items, "経営者能力", "事業経験", mg_exp, _WEIGHTS[("経営者能力","事業経験")],
205
  f"経験{exp_years if exp_years is not None else '不明→中立'}年")
206
 
207
  mg_asset = _ramp(cash_to_ms, 1.5, 0.2)
208
+ _add(items, "経営者能力", "資産担保力", mg_asset, _WEIGHTS[("経営者能力","資産担保力")],
209
+ f"現預金/月商≈{round(cash_to_ms,2) if cash_to_ms else '—'}")
210
 
211
+ if incidents and incidents>0:
212
+ pen=0.0; rs=f"重大事故{int(incidents)}件→大幅減点"
213
  elif has_bk:
214
+ pen=6.0 if (bk_years and bk_years>=10) else 3.0; rs=f"倒産歴あり({bk_years or '不明'}年)"
215
  else:
216
+ pen=10.0; rs="事故/倒産なし"
217
+ _add(items,"経営者能力","減点事項",pen,_WEIGHTS[("経営者能力","減点事項")],rs)
218
 
219
+ # 成長率
220
+ _add(items,"成長率","売上高伸長性", _ramp(s_cagr,0.08,-0.05),
221
+ _WEIGHTS[("成長率","売上高伸長性")],
222
  f"CAGR売上{round((s_cagr or 0)*100,1) if s_cagr is not None else '—'}%")
223
 
224
+ _add(items,"成長率","利益伸長性", _ramp(p_cagr,0.08,-0.05),
225
+ _WEIGHTS[("成長率","利益伸長性")],
226
  f"CAGR営業{round((p_cagr or 0)*100,1) if p_cagr is not None else '—'}%")
227
 
228
+ # 商品スコア
229
  if prod_total is None or prod_total <= 0:
230
  pr_sc = 5.0; rs = "不明→中立"
231
  else:
232
  ratio = _ratio(prod_growing, prod_total) or 0.0
233
  pr_sc = ( _ramp(prod_total, 3, 0) + _ramp(ratio, 0.7, 0.1) ) / 2
234
  rs = f"主力{int(prod_total)}/成長中比{round(ratio*100,1)}%"
235
+ _add(items,"成長率","商品", pr_sc, _WEIGHTS[("成長率","商品")], rs)
236
 
237
+ # 市場成長調整
238
+ _add(items,"成長率","市場成長調整",
239
+ _ramp(market_growth,15,-5),
240
+ _WEIGHTS[("成長率","市場成長調整")],
241
  f"市場年成長{market_growth or '—'}%")
242
 
243
  # 安定性
244
+ _add(items,"安定性","自己資本", _ramp(equity,40,5),
245
+ _WEIGHTS[("安定性","自己資本")], f"自己資本比率{equity or '—'}%")
246
 
247
  if (delay_cnt is not None) or (boun_cnt is not None) or (delay_days is not None):
248
+ sc=( _ramp(-(delay_cnt or 0),0,-6) +
249
+ _ramp(-(boun_cnt or 0),0,-1) +
250
+ _ramp(-(delay_days or 0),0,-30) )/3
251
+ rs=f"遅延{int(delay_cnt or 0)}/不渡{int(boun_cnt or 0)}/平均{int(delay_days or 0)}日"
252
  else:
253
+ sc=_ramp(cash_to_ms,1.0,0.2); rs=f"代理:現預金/月商≈{round(cash_to_ms,2) if cash_to_ms else '—'}"
254
+ _add(items,"安定性","決済振り", sc, _WEIGHTS[("安定性","決済振り")], rs)
 
255
 
256
  sc_mb = 5.0
257
  sc_mb += 2.0 if mainbank else (-0.5 if mainbank is False else 0)
258
  sc_mb += 1.0 if has_line else 0
259
+ sc_mb = _clamp(sc_mb,0,10)
260
+ _add(items,"安定性","金融取引", sc_mb, _WEIGHTS[("安定性","金融取引")],
261
  f"メイン{'有' if mainbank else '無' if mainbank is False else '—'}/与信枠{'有' if has_line else '無' if has_line is False else '—'}")
262
 
263
+ _add(items,"安定性","資産担保余力", _ramp(coll_to_ms,4.0,0.0),
264
+ _WEIGHTS[("安定性","資産担保余力")], f"担保/月商≈{round(coll_to_ms,2) if coll_to_ms else '—'}")
265
 
266
+ _add(items,"安定性","取引先",
267
+ ( _ramp(-(top1 or 50),0,-80) +
268
+ _ramp(cust_score,80,50) +
269
+ _ramp(-(npl_cnt or 1),0,-3) )/3,
270
+ _WEIGHTS[("安定性","取引先")],
271
  f"上位1社{top1 or '—'}%/信用{cust_score or '—'}/不良{int(npl_cnt or 0)}")
272
 
273
+ _add(items,"安定性","業歴", _ramp(years,20,1),
274
+ _WEIGHTS[("安定性","業歴")], f"{years or '—'}年")
275
 
276
+ # 公平性(←ここを二行に分割して修正)
277
  sc_dis = 0.0
278
+ has_sec = _to_bool(ref("有価証券報告書提出企業か(TRUE/FALSE)"))
279
+ sc_dis += 10.0 if has_sec else 0.0
280
  if sc_dis == 0.0:
281
  pub_off = _to_bool(ref("決算公告や官報での公開あり(TRUE/FALSE)"))
282
  pub_web = _to_bool(ref("HP/IRサイトで財務資料公開あり(TRUE/FALSE)"))
283
  sc_dis += 7.0 if (pub_off or pub_web) else 4.0
284
  upd_on = _to_bool(ref("直近更新が定め通りか(TRUE/FALSE)"))
285
  if upd_on: sc_dis += 1.0
286
+ sc_dis = _clamp(sc_dis,0,10)
287
+ _add(items,"公平性・総合世評","ディスクロージャー", sc_dis,
288
+ _WEIGHTS[("公平性・総合世評","ディスクロージャー")],
289
  f"{'有報' if has_sec else '公開あり' if sc_dis>=7.0 else '公開乏しい'} / 更新{'◯' if upd_on else '—'}")
290
 
291
+ total = round(sum(x["score"] for x in items),1)
292
 
293
+ # レーダ用カテゴリスコア(重み付き)
294
  from collections import defaultdict
295
  cat_sum, cat_w = defaultdict(float), defaultdict(float)
296
  for it in items: