Update core/scoring.py
Browse files- 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
|
| 5 |
try:
|
| 6 |
-
|
|
|
|
|
|
|
| 7 |
except Exception:
|
| 8 |
-
return
|
| 9 |
-
|
| 10 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
def score_company(fin: Dict[str, Any]) -> Dict[str, Any]:
|
| 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 |
-
details
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 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}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|