finpy1789's picture
Agentic Financial Document Analyst: multi-agent RAG + MCP, agent-coloured UI
aa4269d verified
Raw
History Blame Contribute Delete
3.2 kB
"""Risk scoring: converts verifier findings + ratio results into a risk
heatmap and an overall reliability score for the Advanced Analysis mode.
"""
from __future__ import annotations
from dataclasses import dataclass, field
RISK_LEVELS = ("low", "medium", "high")
@dataclass
class RiskItem:
category: str # e.g. "Consistency", "Liquidity", "Leverage"
level: str # low | medium | high
rationale: str
citations: list[str] = field(default_factory=list)
@dataclass
class RiskAssessment:
items: list[RiskItem]
reliability_score: int # 0-100, document-set reliability
def heatmap_rows(self) -> list[dict]:
return [{"Category": i.category, "Risk": i.level.upper(),
"Rationale": i.rationale} for i in self.items]
def _ratio_risks(ratio_results: list[dict]) -> list[RiskItem]:
items = []
by_name = {r["name"]: r for r in ratio_results if r.get("value") is not None}
cr = by_name.get("Current Ratio")
if cr:
v = cr["value"]
level = "high" if v < 1.0 else "medium" if v < 1.2 else "low"
items.append(RiskItem("Liquidity", level,
f"Current ratio {v:.2f}. {cr.get('interpretation', '')}"))
de = by_name.get("Debt / Equity")
if de:
v = de["value"]
level = "high" if v > 2.0 else "medium" if v > 1.0 else "low"
items.append(RiskItem("Leverage", level,
f"Debt/Equity {v:.2f}. {de.get('interpretation', '')}"))
ic = by_name.get("Interest Coverage")
if ic:
v = ic["value"]
level = "high" if v < 1.5 else "medium" if v < 3.0 else "low"
items.append(RiskItem("Solvency", level,
f"Interest coverage {v:.2f}x. {ic.get('interpretation', '')}"))
return items
def _consistency_risk(findings: list[dict]) -> RiskItem:
differing = [f for f in findings if f.get("status") == "differ"]
if not findings:
return RiskItem("Consistency", "medium",
"No cross-checkable claims found — consistency could not be assessed.")
if not differing:
return RiskItem("Consistency", "low",
f"All {len(findings)} cross-checked claims agree across sources.")
high_conf = [f for f in differing if f.get("confidence", 0) >= 70]
level = "high" if high_conf else "medium"
topics = ", ".join(sorted({f.get("topic", "?") for f in differing}))
return RiskItem("Consistency", level,
f"{len(differing)} potential inconsistency(ies) flagged for review: {topics}.")
def assess(findings: list[dict], ratio_results: list[dict] | None = None) -> RiskAssessment:
items = [_consistency_risk(findings)]
items.extend(_ratio_risks(ratio_results or []))
penalty = {"low": 0, "medium": 12, "high": 30}
reliability = max(0, 100 - sum(penalty[i.level] for i in items))
# small bonus when many claims were actually verifiable
verifiable = len([f for f in findings if f.get("status") in ("agree", "differ")])
reliability = min(100, reliability + min(verifiable, 5))
return RiskAssessment(items=items, reliability_score=reliability)