Corin1998 commited on
Commit
cbdf486
·
verified ·
1 Parent(s): 45b5c62

Update core/scoring.py

Browse files
Files changed (1) hide show
  1. core/scoring.py +53 -48
core/scoring.py CHANGED
@@ -1,55 +1,60 @@
 
1
  from __future__ import annotations
2
  from typing import Dict, Any
3
 
4
- def _nz(x):
5
  try:
6
- return float(x) if x is not None else 0.0
 
 
7
  except Exception:
8
- return 0.0
9
-
10
- def clamp(x, lo=0, hi=100): return max(lo, min(hi, x))
 
 
 
 
 
 
 
 
 
 
11
 
12
  def score_company(fin: Dict[str, Any]) -> Dict[str, Any]:
13
- """
14
- すべて『円』換算済みの fin を前提
15
- 数量スケールの違いで比率が歪まないよう、原則として比率/マージン/回転率ベースで採点
16
- """
17
- bs = fin.get("balance_sheet", {}) or {}
18
- pl = fin.get("income_statement", {}) or {}
19
-
20
- assets = _nz(bs.get("total_assets"))
21
- equity = _nz(bs.get("total_equity"))
22
- curA = _nz(bs.get("current_assets"))
23
- curL = _nz(bs.get("current_liabilities"))
24
- sales = _nz(pl.get("sales"))
25
- opinc = _nz(pl.get("operating_income"))
26
- netinc = _nz(pl.get("net_income"))
27
-
28
- # 比率
29
- equity_ratio = equity / assets if assets > 0 else 0.0
30
- current_ratio = curA / curL if curL > 0 else (2.0 if curA > 0 else 0.0)
31
- op_margin = opinc / sales if sales > 0 else 0.0
32
- net_margin = netinc / sales if sales > 0 else 0.0
33
- roa = netinc / assets if assets > 0 else 0.0
34
-
35
- # スコア(100点換算)
36
- s_equity = clamp(equity_ratio * 200) # 50%で100
37
- s_liquid = clamp((current_ratio / 2.0) * 100) # 200%で100
38
- s_opm = clamp(op_margin * 400) # 25%で100
39
- s_npm = clamp(net_margin * 400) # 25%で100
40
- s_roa = clamp(roa * 1000) # 10%で100
41
-
42
- details = [
43
- {"metric": "自己資本比率", "value": round(equity_ratio*100, 2), "score": round(s_equity,1), "unit":"%"},
44
- {"metric": "流動比率", "value": round(current_ratio*100, 1), "score": round(s_liquid,1), "unit":"%"},
45
- {"metric": "営業利益率", "value": round(op_margin*100, 2), "score": round(s_opm,1), "unit":"%"},
46
- {"metric": "純利益率", "value": round(net_margin*100, 2), "score": round(s_npm,1), "unit":"%"},
47
- {"metric": "ROA", "value": round(roa*100, 2), "score": round(s_roa,1), "unit":"%"},
48
- ]
49
-
50
- total = round(sum(d["score"] for d in details) / len(details), 1)
51
- grade = ("S" if total >= 85 else
52
- "A" if total >= 70 else
53
- "B" if total >= 55 else
54
- "C" if total >= 40 else "D")
55
- return {"total_score": total, "grade": grade, "details": details, "assumption":"金額は円換算済み"}
 
1
+ # core/scoring.py
2
  from __future__ import annotations
3
  from typing import Dict, Any
4
 
5
+ def _safe_ratio(a, b):
6
  try:
7
+ if a is None or b in (None, 0):
8
+ return None
9
+ return float(a) / float(b)
10
  except Exception:
11
+ return None
12
+
13
+ def _ramp(x, good, bad, lo=0.0, hi=100.0, neutral=50.0):
14
+ if x is None:
15
+ return neutral
16
+ if good > bad:
17
+ if x <= bad: return lo
18
+ if x >= good: return hi
19
+ return lo + (hi-lo) * (x-bad)/(good-bad)
20
+ else:
21
+ if x >= bad: return lo
22
+ if x <= good: return hi
23
+ return lo + (hi-lo) * (x-good)/(bad-good)
24
 
25
  def score_company(fin: Dict[str, Any]) -> Dict[str, Any]:
26
+ bs = fin.get("balance_sheet") or {}
27
+ is_ = fin.get("income_statement") or {}
28
+ cf = fin.get("cash_flows") or {}
29
+
30
+ ca = bs.get("current_assets")
31
+ cl = bs.get("current_liabilities")
32
+ ta = bs.get("total_assets")
33
+ tl = bs.get("total_liabilities")
34
+
35
+ sales = is_.get("sales")
36
+ opinc = is_.get("operating_income")
37
+ ni = is_.get("net_income")
38
+
39
+ ocf = cf.get("operating_cash_flow")
40
+ fcf = None
41
+ if ocf is not None and is_.get("operating_expenses") is not None:
42
+ fcf = ocf - is_.get("operating_expenses")
43
+
44
+ # 単位ばらつき吸収:比率中心
45
+ cr = _safe_ratio(ca, cl) # 流動比率
46
+ lev = _safe_ratio(tl, ta) # 負債比率
47
+ opm = _safe_ratio(opinc, sales) # 営業利益率
48
+ npm = _safe_ratio(ni, sales) # 純利益率
49
+ ocf_ms = _safe_ratio(ocf, sales) # 営業CF/売上
50
+
51
+ details = []
52
+ details.append({"metric": "流動比率", "score": round(_ramp(cr, 2.0, 0.8),1)})
53
+ details.append({"metric": "負債比率(低い程良)", "score": round(_ramp(lev, 0.4, 1.2),1)})
54
+ details.append({"metric": "営業利益率", "score": round(_ramp(opm, 0.12, 0.00),1)})
55
+ details.append({"metric": "純利益率", "score": round(_ramp(npm, 0.08, -0.05),1)})
56
+ details.append({"metric": "営業CF/売上", "score": round(_ramp(ocf_ms, 0.10, -0.05),1)})
57
+
58
+ total = round(sum(d["score"] for d in details)/len(details), 1)
59
+ grade = "S" if total>=85 else "A" if total>=75 else "B" if total>=65 else "C" if total>=50 else "D"
60
+ return {"total_score": total, "grade": grade, "details": details}