|
|
|
|
|
from __future__ import annotations |
|
|
from typing import Dict, Any |
|
|
|
|
|
def _safe_ratio(a, b): |
|
|
try: |
|
|
if a is None or b in (None, 0): |
|
|
return None |
|
|
return float(a) / float(b) |
|
|
except Exception: |
|
|
return None |
|
|
|
|
|
def _ramp(x, good, bad, lo=0.0, hi=100.0, neutral=50.0): |
|
|
if x is None: |
|
|
return neutral |
|
|
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_company(fin: Dict[str, Any]) -> Dict[str, Any]: |
|
|
bs = fin.get("balance_sheet") or {} |
|
|
is_ = fin.get("income_statement") or {} |
|
|
cf = fin.get("cash_flows") or {} |
|
|
|
|
|
ca = bs.get("current_assets") |
|
|
cl = bs.get("current_liabilities") |
|
|
ta = bs.get("total_assets") |
|
|
tl = bs.get("total_liabilities") |
|
|
|
|
|
sales = is_.get("sales") |
|
|
opinc = is_.get("operating_income") |
|
|
ni = is_.get("net_income") |
|
|
|
|
|
ocf = cf.get("operating_cash_flow") |
|
|
fcf = None |
|
|
if ocf is not None and is_.get("operating_expenses") is not None: |
|
|
fcf = ocf - is_.get("operating_expenses") |
|
|
|
|
|
|
|
|
cr = _safe_ratio(ca, cl) |
|
|
lev = _safe_ratio(tl, ta) |
|
|
opm = _safe_ratio(opinc, sales) |
|
|
npm = _safe_ratio(ni, sales) |
|
|
ocf_ms = _safe_ratio(ocf, sales) |
|
|
|
|
|
details = [] |
|
|
details.append({"metric": "流動比率", "score": round(_ramp(cr, 2.0, 0.8),1)}) |
|
|
details.append({"metric": "負債比率(低い程良)", "score": round(_ramp(lev, 0.4, 1.2),1)}) |
|
|
details.append({"metric": "営業利益率", "score": round(_ramp(opm, 0.12, 0.00),1)}) |
|
|
details.append({"metric": "純利益率", "score": round(_ramp(npm, 0.08, -0.05),1)}) |
|
|
details.append({"metric": "営業CF/売上", "score": round(_ramp(ocf_ms, 0.10, -0.05),1)}) |
|
|
|
|
|
total = round(sum(d["score"] for d in details)/len(details), 1) |
|
|
grade = "S" if total>=85 else "A" if total>=75 else "B" if total>=65 else "C" if total>=50 else "D" |
|
|
return {"total_score": total, "grade": grade, "details": details} |
|
|
|