Commit a79604f2 by ccran

feat: 维修时间f1>80

parent 2347d107
......@@ -12,7 +12,9 @@ from core.config import LLM, MAX_WORKERS
class LLMTool(ToolBase):
"""LLM-backed processor: builds prompts, calls LLM, parses JSON."""
def __init__(self, system_prompt: str, llm_key: str = "fastgpt_segment_review") -> None:
def __init__(
self, system_prompt: str, llm_key: str = "fastgpt_segment_review"
) -> None:
super().__init__()
self.system_prompt = system_prompt
self.llm = OpenAITool(LLM[llm_key], max_workers=MAX_WORKERS)
......@@ -33,7 +35,9 @@ class LLMTool(ToolBase):
try:
return asyncio.run(coro)
except RuntimeError as e:
print(f'RuntimeError in run_with_loop: {e}, trying to get event loop and run until complete.')
print(
f"RuntimeError in run_with_loop: {e}, trying to get event loop and run until complete."
)
loop = asyncio.get_event_loop()
return loop.run_until_complete(coro)
......
......@@ -4,78 +4,36 @@ import difflib
import json
import re
import unicodedata
from typing import Any, Dict, List
from typing import Any, Callable, Dict, List, Optional
from core.tool import tool, tool_func
from core.tools.segment_llm import LLMTool
from loguru import logger
import traceback
MERGER_SYSTEM_PROMPT = """
你是合同审查结果合并智能体(SegmentMerger)。
你的任务是:接收一组 findings,按 original_text 分组后合并。
【规则】
1. 以“文本重叠关系”分组:
- 完全相同:可合并。
- 存在公共子句(如一方是另一方子串,或两句共享连续核心片段):可合并。
- 无明显公共子句、语义独立:不可合并。
2. 每个分组最终只保留 1 条 finding。
3. 对于“公共子句合并”的分组,合并后的 original_text 取“并集文本”:
- 若 A 是 B 的子串,取 B。
- 若 A、B 各有新增片段且围绕同一事实,拼接为不重复、语义通顺的一条完整原文。
4. 完全不相关的句子必须保留为不同分组。
【分组示例】
- 句子1:"A:提交数据后15日内开票并付款。"
- 句子2:"B:提交数据后15日内开票并付款。另由子公司承担服务费。"
- 句子3:"甲方:xx公司"
应分为两组:
- 分组1(句子1+句子2):"提交数据后15日内开票并付款。另由子公司承担服务费。"
- 分组2(句子3):"甲方:xx公司"
【合并要求】
- 同步合并 issue 和 suggestion,兼顾组内要点
- 保留关键信息,不要机械拼接
【字段约束】
- 输出字段固定为:rule_title, segment_id, original_text, issue, risk_level, suggestion, result
- risk_level 仅允许 H/M/L/空字符串
- result 仅允许 合格/不合格/空字符串
- original_text 必须与该组合的原文一致
【输出要求】
- 严格输出 JSON
- 顶层结构必须是:{"findings": [...]}
你将收到同一组 findings 的 issue 与 suggestion 列表,请做信息融合而非机械拼接。
要求:
1. 输入中已经包含同组条款原文`original_text`,请仅将其作为分析依据。
2. `issue`:提炼并合并组内风险点,去重、保留关键信息,语言精炼。
3. `suggestion`:合并为一条可执行建议,必须基于输入原文的具体表述来给出,避免空泛、泛化或与原文脱节,必要时按“先补充条款、再明确标准”这类逻辑组织。
4. 禁止输出与输入无关的信息。
"""
MERGER_USER_PROMPT = """
【输入 findings】
{findings_json}
【任务】
请按“文本重叠关系(完全相同或存在公共子句)”分组后合并,并返回合并结果。
若同组文本可互补,请将 original_text 扩展为覆盖组内信息的并集文本;无关文本必须分到不同组。
输入:
{payload}
输出 JSON
输出 JSON,格式如下:
"""
OUTPUT_EXAMPLE = """
```json
{
"findings": [
{
"rule_title": "付款条款完整性",
"segment_id": 3,
"original_text": "甲方应于验收后30日内支付合同款项",
"issue": "付款期限明确,但未约定逾期违约责任,风险控制不足。",
"risk_level": "M",
"suggestion": "补充约定逾期付款违约金标准及计算方式。",
"result": "不合格"
}
]
"issue": "提炼合并后的风险点",
"suggestion": "提炼合并后的建议"
}
```
"""
......@@ -173,7 +131,10 @@ def _risk_rank(level: str) -> int:
return {"": 0, "L": 1, "M": 2, "H": 3}.get(level, 0)
def _merge_group(group: List[Dict[str, Any]]) -> Dict[str, Any]:
def _merge_group(
group: List[Dict[str, Any]],
field_merger: Optional[Callable[[List[Dict[str, Any]]], Dict[str, str]]] = None,
) -> Dict[str, Any]:
if not group:
return _normalize_finding({})
......@@ -200,20 +161,41 @@ def _merge_group(group: List[Dict[str, Any]]) -> Dict[str, Any]:
else:
merged_result = ""
merged_issue = _merge_unique_text(issues)
merged_suggestion = _merge_unique_text(suggestions)
if field_merger:
try:
merged_fields = field_merger(group)
merged_issue = str(merged_fields.get("issue", "") or merged_issue)
merged_suggestion = str(
merged_fields.get("suggestion", "") or merged_suggestion
)
except Exception:
error_msg = traceback.format_exc()
logger.error(
f"Error in field_merger, fallback to unique merge:{error_msg}",
exc_info=True,
)
# Fall back to deterministic merge when model merge is unavailable.
pass
return _normalize_finding(
{
"rule_title": " / ".join([t for t in dict.fromkeys(rule_titles) if t]),
"segment_id": min(segment_ids) if segment_ids else 0,
"original_text": merged_text,
"issue": _merge_unique_text(issues),
"issue": merged_issue,
"risk_level": best_risk,
"suggestion": _merge_unique_text(suggestions),
"suggestion": merged_suggestion,
"result": merged_result,
}
)
def _rule_based_merge(findings: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
def _rule_based_merge(
findings: List[Dict[str, Any]],
field_merger: Optional[Callable[[List[Dict[str, Any]]], Dict[str, str]]] = None,
) -> List[Dict[str, Any]]:
n = len(findings)
if n <= 1:
return findings
......@@ -243,7 +225,16 @@ def _rule_based_merge(findings: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
groups.append([findings[idx] for idx in group_idx])
return [_merge_group(group) for group in groups]
return [_merge_group(group, field_merger=field_merger) for group in groups]
def _deterministic_field_merge(group: List[Dict[str, Any]]) -> Dict[str, str]:
return {
"issue": _merge_unique_text([item.get("issue", "") for item in group]),
"suggestion": _merge_unique_text(
[item.get("suggestion", "") for item in group]
),
}
@tool("segment_merger", "同证据 findings 合并")
......@@ -268,39 +259,62 @@ class SegmentMergerTool(LLMTool):
def run(
self, findings: List[Dict[str, Any]], merge_mode: str = "llm"
) -> Dict[str, List[Dict[str, Any]]]:
normalized_input = [
_normalize_finding(_as_dict(item)) for item in (findings or [])
]
if not normalized_input:
normalized_findings = self._normalize_findings(findings)
if not normalized_findings:
return {"findings": []}
mode = str(merge_mode or "llm").lower()
if mode == "rule":
return {"findings": _rule_based_merge(normalized_input)}
msgs = self._build_prompt(normalized_input)
field_merger = self._resolve_field_merger(mode)
merged = _rule_based_merge(normalized_findings, field_merger=field_merger)
return {"findings": merged}
def _normalize_findings(
self, findings: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
return [_normalize_finding(_as_dict(item)) for item in (findings or [])]
def _resolve_field_merger(
self, mode: str
) -> Callable[[List[Dict[str, Any]]], Dict[str, str]]:
if mode == "llm":
return self._safe_llm_field_merge
return _deterministic_field_merge
def _safe_llm_field_merge(self, group: List[Dict[str, Any]]) -> Dict[str, str]:
try:
resp = self.run_with_loop(self.chat_async(msgs))
data = self.parse_first_json(resp)
raw_findings = data.get("findings") or data.get("merged_findings") or []
if not isinstance(raw_findings, list):
raw_findings = []
normalized_output = [
_normalize_finding(_as_dict(item)) for item in raw_findings
]
return {"findings": normalized_output}
except Exception as e:
logger.error(f"SegmentMergerTool run error: {e}")
return {"findings": _rule_based_merge(normalized_input)}
def _build_prompt(self, findings: List[Dict[str, Any]]) -> List[Dict[str, str]]:
return self._merge_issue_suggestion_with_llm(group)
except Exception:
error_msg = traceback.format_exc()
logger.error(
f"LLM field merge failed, fallback to deterministic merge:{error_msg}",
exc_info=True,
)
return _deterministic_field_merge(group)
def _merge_issue_suggestion_with_llm(
self, group: List[Dict[str, Any]]
) -> Dict[str, str]:
payload = {
"original_texts": [
str(item.get("original_text", "") or "") for item in group
],
"issues": [str(item.get("issue", "") or "") for item in group],
"suggestions": [str(item.get("suggestion", "") or "") for item in group],
}
user_content = (
MERGER_USER_PROMPT.format(
findings_json=json.dumps(findings, ensure_ascii=False, indent=2)
payload=json.dumps(payload, ensure_ascii=False, indent=2)
)
+ OUTPUT_EXAMPLE
)
return self.build_messages(user_content)
msgs = self.build_messages(user_content)
resp = self.run_with_loop(self.chat_async(msgs))
data = self.parse_first_json(resp)
issue = str(data.get("issue", "") or "").strip()
suggestion = str(data.get("suggestion", "") or "").strip()
if not issue and not suggestion:
raise ValueError("empty issue/suggestion returned by model")
return {"issue": issue, "suggestion": suggestion}
if __name__ == "__main__":
......@@ -334,4 +348,4 @@ if __name__ == "__main__":
"result": "不合格",
},
]
print(json.dumps(tool.run(sample, merge_mode="rule"), ensure_ascii=False, indent=2))
print(json.dumps(tool.run(sample), ensure_ascii=False, indent=2))
......@@ -5,11 +5,132 @@ import json
from typing import Dict, List, Optional
from core.tool import tool, tool_func
from core.config import use_lufa
from core.tools.segment_llm import LLMTool
import re
from loguru import logger
REVIEW_SYSTEM_PROMPT = """
REVIEW_SYSTEM_PROMPT_LF = """
你是一个专业的合同分段审查智能体(SegmentReview)。
你的任务是:基于给定审查规则,对“当前分段”进行审查,识别其中与规则相关且证据充分的条款,并判断其结果为“合格”或“不合格”,输出审查结论及必要的修改建议。
【审查范围】
你只能审查当前分段自身已经明确体现的内容。
你只能识别以下两类结果:
1. 合格条款:当前分段中存在与审查规则相关的明确表述,且该表述符合规则要求;
2. 不合格条款:当前分段中存在与审查规则相关的明确表述,且该表述不符合规则要求,例如:对我方不利、表述不清、逻辑冲突、责任失衡、触发条件不明确、关键限制缺失等。
【审查原则】
- 严格基于给定的审查规则进行审查,不得脱离规则自行扩展审查标准。
- 只审查当前分段原文,不得使用上下文信息补充、修正或推断当前分段含义。
- 优先识别“确定成立”的合格或不合格结论,不输出模糊怀疑类表述。
【立场约束规则(强制)】
你必须严格站在“我方利益最大化”的立场进行审查与建议生成,遵循以下规则:
一、禁止削弱我方权益(硬性约束)
在任何情况下,suggestion 不得包含或导致以下结果:
- 将“我方完成后付款”修改为分期付款或预付款
- 新增或加重我方违约金、赔偿责任、利息等负担
- 新增我方履约义务、配合义务或缩短我方时限
- 削弱我方验收权(如“默示验收”“逾期视为验收”)
- 新增对方权利或免除对方责任
- 以“平衡双方权利义务”为理由削弱我方优势
- 给予对方奖励、补偿或额外利益
- 将原本由对方承担的风险转移至我方
若原条款已存在上述问题,应判定为“不合格”,并提出纠偏建议。
二、建议方向约束(必须遵守)
当 result="不合格" 时,suggestion 必须优先朝以下方向优化:
- 强化我方权利(如单方决定权、解释权、验收权、解除权)
- 降低我方责任(限制责任范围、金额、触发条件)
- 增加对方义务与违约责任
- 延长我方期限、缩短对方期限
- 增加对方违约成本(违约金/赔偿/利息)
- 明确触发条件,避免我方被动承担风险
三、禁止中立化建议(非常重要)
- 不得提出“双方协商一致”“友好协商解决”等中性建议
- 不得提出“建议双方平衡”“建议公平调整”等弱化我方立场的建议
- 所有 suggestion 必须体现明确的偏向我方的修改方向
【完整性要求(非常重要)】
你必须对当前分段进行“穷举式审查”,不得只输出部分结果。
执行方式:
- 应逐句扫描当前分段
- 对每一句或关键子句,判断其是否与审查规则相关
- 只要存在证据充分的问题或合格表述,必须全部列出,不得遗漏
特别要求:
- 不得因为已找到1条或少量finding而提前停止
- 若一个段落中存在多处问题,必须分别输出多个 findings
- findings 数量应与段落中实际存在的问题数量大致一致,不得明显偏少
错误示例(禁止):
- 一个段落有多个风险点,但只输出1条
正确行为:
- 覆盖所有可以独立成立的审查点
【结果判定规则】
- result 只能取以下两个值之一:
- "合格":当前分段存在与规则相关的明确内容,且符合该规则要求;
- "不合格":当前分段存在与规则相关的明确内容,且不符合该规则要求。
- 如果当前分段与某条审查规则无关,或虽疑似相关但证据不足,则不得生成 finding。
【证据要求】
每个 findings 都必须包含 original_text,且必须是合同原文的直接引用。
【单一证据约束(非常重要)】
每一个 finding 必须只对应一个“独立判断点”和一个“最小证据句”。
具体要求:
- 一个 finding 只能基于一个关键句或一个最小语义单元;
- 若多个句子分别支持不同问题,必须拆分为多个 findings;
- 严禁将多个不同问题合并为一个 finding;
- 严禁在 original_text 中拼接多个不连续句子作为证据;
- 若 original_text 涉及跨句或跨段内容,必须拆分为多个 findings。
判断标准:
- 如果去掉 original_text 中的一部分,仍能形成一个独立判断 → 说明应该拆分
【issue 要求】
- issue 必须说明:该条款为什么合格或为什么不合格。
- 当 result="合格" 时,issue 应说明该表述满足了什么规则要求、为什么可认定为合格。
- 当 result="不合格" 时,issue 应说明该表述违反了什么规则要求、为什么构成风险或缺陷。
- issue 必须紧扣规则和原文,不得空泛评价。
【建议要求】
- suggestion 必须具体、可执行。
- 当 result="不合格" 时:
- 若能在当前分段内直接修正,请给出可直接替换或新增的条款措辞;
- 若无法直接改写,请给出明确修改方向和应补充的关键要素;
- 严禁提出削弱我方权益或中立化的建议;
- 当 result="合格" 时:
- suggestion 应简洁填写,可写“无需修改”;
- 不得为了凑内容而提出与审查结论无关的修改建议。
【输出约束】
- 严格按照指定 JSON Schema 输出。
- 不得输出任何 JSON 之外的解释性文字。
- 若未发现证据充分的合格或不合格条款,返回 {"findings": []}。
在生成最终 JSON 之前,你必须执行以下内部步骤(不输出):
Step A:将当前分段拆分为若干句子或语义单元
Step B:逐句判断该句是否涉及任一审查规则
Step C:若涉及规则,判断其为合格或不合格
Step D:为每一个成立的判断生成一个 finding
只有完成上述穷举后,才允许输出最终结果
提示:在合同审查中,一个分段通常可能包含多个独立风险点或合规点,findings 数量通常大于1,除非该段确实只涉及单一事项。
"""
REVIEW_SYSTEM_PROMPT_JP = """
你是一个专业的合同分段审查智能体(SegmentReview)。
你的任务是:基于给定审查规则,对“当前分段”进行审查,识别其中与规则相关且证据充分的条款,并判断其结果为“合格”或“不合格”,输出审查结论及必要的修改建议。
......@@ -113,8 +234,8 @@ REVIEW_USER_PROMPT = """
【特别要求】
- 仅基于当前分段原文进行判断,不得参考任何上下文、摘要或记忆信息。
- 若一个段落中存在多处问题,必须分别输出多个 findings。
- findings 中每一项都必须包含 result 字段,且 result 只能为 "合格" 或 "不合格"。
- 只有当前分段中存在与规则相关且证据充分的内容时,才输出 finding。
- findings 中的 original_text 必须为合同原文直接引用,且应为最小充分证据片段。
- 当 result="合格" 时,suggestion 填写“无需修改”。
- 当 result="不合格" 时,suggestion 应尽量提供可直接落地的修改文本;若无法安全地直接改写,请给出明确的修改方向和应补充的关键要素。
......@@ -157,7 +278,10 @@ def _has_evidence(f: Dict) -> bool:
@tool("segment_review", "合同分段审查")
class SegmentReviewTool(LLMTool):
def __init__(self):
super().__init__(REVIEW_SYSTEM_PROMPT)
if use_lufa:
super().__init__(REVIEW_SYSTEM_PROMPT_LF)
else:
super().__init__(REVIEW_SYSTEM_PROMPT_JP)
@tool_func(
{
......@@ -369,48 +493,42 @@ class SegmentReviewTool(LLMTool):
if __name__ == "__main__":
tool = SegmentReviewTool()
segment_text = """
变压器本体漏(渗)油,每起扣除质保金2000元,发现5台及以上变压器本体出现漏油情况,除应赔偿的质保金外,需延长该批次变压器的保质期5年。
箱变出现漏水或渗水现象每起扣4000元。
箱变散热片出现生锈现象每起扣3000元。发现5台及以上变压器散热片出现生锈情况,除应赔偿的质保金外,招标方可拒收该批次变压器,由招标方重新生产该批次变压器散热片。
交接试验期间,出现设备不能满足或因设备自身问题没有通过交接试验项目复测仍无法通过的情况,招标方可无条件拒收该设备,投标人应赔偿由设备损坏或性能不达标对招标方造成的工期延误、劳务费用、发电量和信誉等所有损失并重新生产该设备。
因设备自身故障停电超过7天的,每起扣除质保金5000元。
高低压室、变压器等温升超出技术协议要求值5k以上并持续时间超过3小时的,每起扣除质保金10000元。
箱变满载运行时,高低压室、变压器等温升超出厂家承诺值3k以上并持续时间超过1小时的,每起扣除质保金10000元,单批次出现两台以上的,业主单位可拒绝签收该批次设备。
UPS在散热设计上着重考虑,出现UPS因过热死机现象,每起扣除质保金2000元。
按照项目所用箱式变压器年度统计可用率达到99%为基准,每降低5‰扣除质保金10000元。
"""
val: 买方需在卖方产品验收通过后24个月内,买方在收到卖方提交的下列全部单据并经审核无误后60日内,向卖方支付合同价格的10%
val: (1)银行开具的质保金保函,保函期限不少于质量保证期。
val: 3.2.1.5质保金:采购合同内设备验收投运满24个月后,买方验收无质量问题及索赔事项,经买方同意后,卖方可向买方提交质保金保函等额替代质保金,买方向卖方支付合同价格的10%作为质保金。
val: 8.2(补充为)如在质量保证期内发现合同设备部件出现缺陷但不影响合同设备的正常运行,经维修或更换后的部件的质量保证期重新计算。在质量保证期内,由于卖方责任导致合同设备停运时,该台合同设备的质量保证期自卖方消除该缺陷后重新计算。
"""
result = tool.run(
segment_id=1,
segment_text=segment_text,
rules=[
{
"title": "技术性能审查",
"title": "质保期审查",
"rule": """
## 背景知识
技术性能(性能指标)
包括:一些泛指:"技术性能","性能指标","参数标准",或者具体的技术性能参数比如散热、工艺、负载、用料、漏水、渗水等
不包括:一些泛指,比如设备质量问题,设备验收问题,设备故障,设备考核等
## 审查规则
1)前提条件:明确说明“设备(货物)的技术性能(性能指标)不能满足/达到保证值(合同要求)”
2)后果条件:明确链接到“惩罚性经济责任”(违约,赔偿等责任)或“合同解约权”(合同解约)。
同时满足前提与后果条件,则审查不合格,否则,审查合格。
1)质保期(而非寿命期)必须提及“到货/交付/运行XX天/月”条件作为起算时间,如果有多个条件必须提及“先到为准”
2)质保期(而非寿命期)期限/时长不超过(小于等于)“到货24个月/2年”或“交付后18个月/1年半”或"运行后12个月/1年",最长不超过2年
3)没有明确提及质保期的起算点和时长,审查合格
""",
"suggestion_template": """
质保期为交付之日起18个月或运行之日起12个月或到货后24个月,先到为准
""",
"level": "M",
"suggestion_template": "1、提醒不合规的技术性能要求",
"case": """
## 案例1:
原文:承揽人根据附件2《技术协议》进行性能考核
结论:前提条件不完整(只提及了性能考核)并且没有后果条件,因此审查合格。
## 案例2
原文:产品散热设计故障,每起扣款2000元。
结论:同时满足前提与后果条件,散热设计故障属于技术性能不能满足保证值,扣款表明需要损失赔偿,因此审查不合格。
## 案例3
原文:出现设备故障或者设备质量问题导致验收不合格,需要扣除违约金。
结论:前提条件不完整,没有明确提及设备故障、质量问题、验收不合格为技术性能(性能指标)不满足保证值(合同要求),因此审查合格。
""",
原文:质保期期限为产品交付之日起48个月,或产品运行之日起40个月,两者以先到时间为准。
结论:质保期的起算时间提及了交付XX月,以及先到为准,满足条件;原文的交付后48个月超过了24个月,运行后40个月超过了12个月,不满足时长要求,因此审查不合格。
## 案例2:
原文:质保期期限为产品交付之日起两年。
结论:质保期时长最长时间交付后1年半,原文要求2年,超过质保期时长要求,因此审查不合格。
## 案例3:
原文:质保期期限为通电验收起两年。
结论:质保期起算时间必须提及“到货/交付/运行XX天/月”,而非通电验收,因此审查不合格。
## 案例4:
原文:质保期出现问题需要赔偿违约金。
结论:没有明确提及质保期的起算时间和时长,因此审查合格。
""",
}
],
party_role="金盘",
party_role="金盘(卖方、供方、乙方)",
)
print(json.dumps(result, ensure_ascii=False, indent=2))
......
......@@ -13,30 +13,35 @@ from loguru import logger
from utils.common_util import random_str
from utils.http_util import upload_file, fastgpt_openai_chat, download_file
SUFFIX = "_麓发迁移"
batch_input_dir_path = "jp-input"
batch_output_dir_path = f"/home/ccran/lufa-contract/data/benchmark/results/jp-output-lufa-{time.strftime('%Y%m%d-%H%M%S', time.localtime())}"
use_lufa = False
if not use_lufa:
SUFFIX = "_麓发迁移"
batch_input_dir_path = "jp-input"
batch_output_dir_path = f"/home/ccran/lufa-contract/data/benchmark/results/jp-output-lufa-{time.strftime('%Y%m%d-%H%M%S', time.localtime())}"
# 金盘fastgpt接口
url = "http://192.168.252.71:18088/api/v1/chat/completions"
# 金盘迁移麓发合同审查测试token
token = "fastgpt-vykT6qs07g7hR4tL2MNJE6DdNCIxaQjEu3Cxw9nuTBFg8MAG3CkByvnXKxSNEyMK7"
# 人机交互测试(测试环境)
# token = 'fastgpt-p189K5zoTX5wjp0dBybFCwsbWm3juIwlJxt2wTGyiaOWOANI5Y10pKEZzyt'
# 人机交互测试(生产环境)
# token = 'fastgpt-ry4jIjgNwmNgufMr5jR0ncvJVmSS4GZl4bx2ItsNPoncdQzW9Na3IP1Xrankr'
# 提取后审查测试
# token = 'fastgpt-n74gGX5ZqLT6o1ysMBSGUTjIciswYOWDRfQ75krMkE5gDVDkpzsbz8u'
else:
SUFFIX = "_麓发"
batch_input_dir_path = "lufa-input"
batch_output_dir_path = "lufa-output-standard"
# 麓发fastgpt接口
url = "http://192.168.252.71:18089/api/v1/chat/completions"
# 麓发合同审查生产token
# token = "fastgpt-ek3Z6PxI6sXgYc0jxzZ5bVGqrxwM6aVyfSmA6JVErJYBMr2KmYxrHwEUOIMSYz"
# 麓发合同审查生产token-标准化
token = "fastgpt-mg5tQUgreJeF7peoOr5zqP0NR4EIrfS2bEVXge6FUL94Suu1TvEMR1sGNRSiV"
# SUFFIX = "_麓发"
# batch_input_dir_path = "lufa-input"
# batch_output_dir_path = "lufa-output-standard"
batch_size = 5
# 麓发fastgpt接口
# url = "http://192.168.252.71:18089/api/v1/chat/completions"
# 金盘fastgpt接口
url = "http://192.168.252.71:18088/api/v1/chat/completions"
# 麓发合同审查生产token
# token = "fastgpt-ek3Z6PxI6sXgYc0jxzZ5bVGqrxwM6aVyfSmA6JVErJYBMr2KmYxrHwEUOIMSYz"
# 麓发合同审查生产token-标准化
# token = "fastgpt-mg5tQUgreJeF7peoOr5zqP0NR4EIrfS2bEVXge6FUL94Suu1TvEMR1sGNRSiV"
# 金盘迁移麓发合同审查测试token
token = "fastgpt-vykT6qs07g7hR4tL2MNJE6DdNCIxaQjEu3Cxw9nuTBFg8MAG3CkByvnXKxSNEyMK7"
# 人机交互测试(测试环境)
# token = 'fastgpt-p189K5zoTX5wjp0dBybFCwsbWm3juIwlJxt2wTGyiaOWOANI5Y10pKEZzyt'
# 人机交互测试(生产环境)
# token = 'fastgpt-ry4jIjgNwmNgufMr5jR0ncvJVmSS4GZl4bx2ItsNPoncdQzW9Na3IP1Xrankr'
# 提取后审查测试
# token = 'fastgpt-n74gGX5ZqLT6o1ysMBSGUTjIciswYOWDRfQ75krMkE5gDVDkpzsbz8u'
def extract_url(text):
......@@ -94,10 +99,16 @@ def process_single_file(
excel_url, doc_url = extract_url(result)
if excel_url and doc_url:
download_file(
excel_url.replace("218.77.58.8", "192.168.252.71"), des_excel_file
excel_url.replace("218.77.58.8", "192.168.252.71").replace(
"znkf.lgfzgroup.com", "192.168.252.71"
),
des_excel_file,
)
download_file(
doc_url.replace("218.77.58.8", "192.168.252.71"), des_doc_file
doc_url.replace("218.77.58.8", "192.168.252.71").replace(
"znkf.lgfzgroup.com", "192.168.252.71"
),
des_doc_file,
)
logger.info(
f"第{counter}个文件下载:{excel_url}到{des_excel_file} {des_doc_file}"
......
......@@ -24,8 +24,19 @@ def _load_rows(path: Path) -> list[tuple[str, str]]:
for col in first_two.columns:
first_two[col] = first_two[col].map(_normalize_cell)
first_two = first_two.replace("", pd.NA).dropna(how="all")
rows: list[tuple[str, str]] = []
seen: set[tuple[str, str]] = set()
for item, text in first_two.itertuples(index=False, name=None):
row = (
"" if pd.isna(item) else str(item).strip(),
"" if pd.isna(text) else str(text).strip(),
)
if row in seen:
continue
seen.add(row)
rows.append(row)
return list(map(tuple, first_two.itertuples(index=False, name=None)))
return rows
def _compare_impl(val_dir: Path, answer_dir: Path) -> None:
......
......@@ -121,7 +121,7 @@ def _parse_args() -> argparse.Namespace:
parser.add_argument(
"--datasets-dir",
type=Path,
default=base / "results" / "jp-output-lufa-20260408-182708",
default=base / "results" / "jp-output-lufa-20260416-000112",
help="Directory containing Word files with annotations.",
)
parser.add_argument(
......
No preview for this file type
......@@ -394,9 +394,7 @@ def merge_segment_findings(payload: MergerRequest) -> MergerResponse:
unqualified_findings = [
f for f in segment_findings if (f.result or "").strip() == "不合格"
]
merged_result = merger_tool.run(
[f.__dict__ for f in unqualified_findings], merge_mode="rule"
)
merged_result = merger_tool.run([f.__dict__ for f in unqualified_findings])
merged_findings = merged_result.get("findings", []) or []
for f in merged_findings:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment