File size: 2,258 Bytes
cbdf486
ae9ce4e
bd0726d
5031695
cbdf486
5031695
cbdf486
 
 
5031695
cbdf486
 
 
 
 
 
 
 
 
 
 
 
 
5031695
 
cbdf486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 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}