| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- from __future__ import annotations
- import math
- import re
- from typing import Any
- def _clamp(v: float, lo: float = 0, hi: float = 100) -> int:
- return int(max(lo, min(hi, v)))
- def _has(v: Any) -> bool:
- if v is None:
- return False
- if isinstance(v, str):
- return bool(v.strip())
- if isinstance(v, (list, dict)):
- return len(v) > 0
- return True
- def _keyword_tags(elements: dict[str, Any]) -> list[dict[str, Any]]:
- """
- 多级标签体系(MVP):一级维度 + 二级标签 + 具体值
- """
- cause = elements.get("case_cause")
- tags: list[dict[str, Any]] = []
- if cause:
- tags.append(
- {
- "level1": "法律维度",
- "level2": "争议焦点类型",
- "value": str(cause),
- }
- )
- if elements.get("overtime_desc"):
- tags.append({"level1": "事实维度", "level2": "关键事实", "value": "加班事实"})
- if elements.get("termination_reason"):
- tags.append({"level1": "事实维度", "level2": "解除情形", "value": "已识别解除原因"})
- return tags
- def _extract_keywords(text: str, top_k: int = 15) -> list[dict[str, Any]]:
- """
- 用 jieba 分词提取高频关键词,返回连贯的中文词语。
- """
- if not text:
- return []
- try:
- import jieba
- # 添加法律领域自定义词,避免复合词被切分
- for w in ["劳动合同", "经济补偿", "赔偿金", "拖欠工资", "违法解除",
- "加班费", "工伤认定", "社会保险", "住房公积金",
- "解除劳动合同", "双倍工资", "违法辞退", "劳动仲裁",
- "科技有限公司", "有限公司", "劳动争议", "仲裁委员会",
- "年终奖", "二倍工资"]:
- jieba.add_word(w)
- words = jieba.cut(text[:12000])
- toks = [w.strip() for w in words if len(w.strip()) >= 2]
- except ImportError:
- toks = re.findall(r"[一-鿿]{2,6}|[A-Za-z]{3,}", text[:12000])
- stop = {
- "申请人", "被申请人", "仲裁", "请求", "事实", "理由", "事项",
- "劳动", "争议", "委员会", "申请", "本案", "仲裁庭",
- "经审理", "查明", "如下", "予以", "认定",
- "支持", "驳回", "维持", "的", "了", "在", "和",
- "是", "不", "与", "及", "年", "月", "日", "元",
- "支付", "公司", "人民", "法院", "原告", "被告",
- }
- freq: dict[str, int] = {}
- for t in toks:
- if t in stop or len(t) <= 1:
- continue
- freq[t] = freq.get(t, 0) + 1
- items = sorted(freq.items(), key=lambda x: x[1], reverse=True)[:top_k]
- return [{"name": k, "value": v} for k, v in items]
- def generate_portrait(elements: dict[str, Any], raw_text: str = "", evidence_count: int = 0) -> dict[str, Any]:
- laws = elements.get("law_refs") or []
- claims = elements.get("claims") or {}
- claim_items = claims.get("items") or []
- # 法律维度:争议焦点明确度 / 法条引用准确性(用“有无+数量”粗略替代) / 证据法律效力(用 evidence_count 近似)
- dispute_focus = 60 + (20 if _has(elements.get("case_cause")) else 0) + (10 if len(claim_items) >= 2 else 0)
- law_ref_score = 40 + min(40, 10 * len(laws))
- evidence_legal_power = 30 + min(50, evidence_count * 10)
- legal_score = _clamp(dispute_focus * 0.4 + law_ref_score * 0.35 + evidence_legal_power * 0.25)
- # 事实维度:时间线完整性 / 事实清晰度 / 证据完备度
- timeline_complete = 30 + (35 if _has(elements.get("entry_date")) else 0) + (35 if _has(elements.get("leave_date")) else 0)
- fact_clear = 40 + (25 if _has(elements.get("termination_reason")) else 0) + (20 if _has(elements.get("overtime_desc")) else 0)
- evidence_complete = 20 + min(60, evidence_count * 12)
- fact_score = _clamp(timeline_complete * 0.35 + fact_clear * 0.4 + evidence_complete * 0.25)
- # 风险维度:诉求支持可能性 / 矛盾激化程度
- amount_total = claims.get("amount_total")
- month_salary = elements.get("month_salary") or 0
- support_prob = 60
- if amount_total and month_salary:
- ratio = amount_total / max(month_salary, 1)
- if ratio > 12:
- support_prob -= 20
- elif ratio > 6:
- support_prob -= 10
- else:
- support_prob += 5
- if elements.get("case_cause") == "违法解除劳动合同" and not _has(elements.get("termination_reason")):
- support_prob -= 10
- support_prob = max(5, min(95, support_prob))
- escalation = 40 + min(40, len(claim_items) * 8) + (10 if (amount_total or 0) > 50000 else 0)
- risk_score = _clamp((100 - support_prob) * 0.6 + escalation * 0.4)
- # KPI:风险等级(给前端标签)
- risk_level = "低"
- if risk_score >= 70:
- risk_level = "高"
- elif risk_score >= 45:
- risk_level = "中"
- portrait = {
- "scores": {
- "legal": legal_score,
- "fact": fact_score,
- "risk": risk_score,
- },
- "legal_dimension": {
- "score": legal_score,
- "subscores": {
- "争议焦点明确度": _clamp(dispute_focus),
- "法律条款引用充分度": _clamp(law_ref_score),
- "证据法律效力(近似)": _clamp(evidence_legal_power),
- },
- },
- "fact_dimension": {
- "score": fact_score,
- "subscores": {
- "时间线完整性": _clamp(timeline_complete),
- "事实描述清晰度": _clamp(fact_clear),
- "证据完备度(近似)": _clamp(evidence_complete),
- },
- },
- "risk_dimension": {
- "score": risk_score,
- "subscores": {
- "诉求支持可能性(规则近似)": _clamp(support_prob),
- "矛盾激化程度(规则近似)": _clamp(escalation),
- },
- },
- "tags": _keyword_tags(elements),
- "keywords": _extract_keywords(raw_text),
- "risk_level": risk_level,
- }
- return portrait
|