# core/scoring.py 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) # 営業CF/売上 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}