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