feat: M4 seed — upload & publish 20 rule sets, fix config/schema column names
- Fix _export_settings for pydantic v2 compatibility (model_fields) - Fix delete_time→deleted_at, update_time→updated_at in RuleServiceImpl - Add OssClient.EnsureBucket method - Replace contract_lease/sale/tech rules.yaml from new-rules - Seed script: batch upload 20 rule YAMLs to OSS + write DB + publish - Config: fix OSS import chain
This commit is contained in:
@@ -19,15 +19,25 @@ from ._settings import app, jwt, db, redis, oss, llm, vlm, ocr, leaudit as _leau
|
||||
def _export_settings(instance: object, prefix: str = "") -> dict[str, object]:
|
||||
"""将 Settings 实例的所有字段和 @property 导出为模块级变量。"""
|
||||
result: dict[str, object] = {}
|
||||
|
||||
# pydantic v2 model_fields
|
||||
model_fields = getattr(instance, "model_fields", None)
|
||||
if isinstance(model_fields, dict):
|
||||
for key in model_fields:
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
result[key] = getattr(instance, key)
|
||||
|
||||
# @property 和普通属性
|
||||
for key in dir(type(instance)):
|
||||
if key.startswith("_"):
|
||||
if key.startswith("_") or key in result:
|
||||
continue
|
||||
value = getattr(instance, key, None)
|
||||
if callable(value) and not isinstance(value, property):
|
||||
attr = getattr(type(instance), key, None)
|
||||
if attr is None:
|
||||
continue
|
||||
if isinstance(value, property):
|
||||
value = value.__get__(instance)
|
||||
result[key] = value
|
||||
if isinstance(attr, property):
|
||||
result[key] = attr.__get__(instance)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -144,6 +144,18 @@ class OssClient:
|
||||
TempFile.write(Content)
|
||||
return TempFile.name
|
||||
|
||||
def EnsureBucket(self, Bucket: str | None = None) -> str:
|
||||
"""确保 bucket 存在,不存在则创建。返回 bucket 名。"""
|
||||
TargetBucket = Bucket or self.bucket
|
||||
Client = self._GetMinioClient()
|
||||
from minio import Minio
|
||||
try:
|
||||
if not Client.bucket_exists(TargetBucket):
|
||||
Client.make_bucket(TargetBucket)
|
||||
except Minio.S3Error:
|
||||
pass
|
||||
return TargetBucket
|
||||
|
||||
def ObjectExists(self, Source: str, Bucket: str | None = None) -> bool:
|
||||
"""判断对象是否存在。"""
|
||||
Ref = self.ResolveObjectRef(Source=Source, Bucket=Bucket)
|
||||
|
||||
@@ -46,7 +46,7 @@ class RuleServiceImpl(IRuleService):
|
||||
current_version_id,
|
||||
status
|
||||
FROM leaudit_rule_sets
|
||||
WHERE delete_time IS NULL
|
||||
WHERE deleted_at IS NULL
|
||||
ORDER BY id DESC
|
||||
"""
|
||||
)
|
||||
@@ -80,7 +80,7 @@ class RuleServiceImpl(IRuleService):
|
||||
FROM leaudit_rule_versions rv
|
||||
JOIN leaudit_rule_sets rs ON rs.id = rv.rule_set_id
|
||||
WHERE rs.rule_type = :rule_type
|
||||
AND rs.delete_time IS NULL
|
||||
AND rs.deleted_at IS NULL
|
||||
ORDER BY rv.version_seq DESC, rv.id DESC
|
||||
"""
|
||||
),
|
||||
@@ -208,7 +208,7 @@ class RuleServiceImpl(IRuleService):
|
||||
rule_name = :rule_name,
|
||||
domain_type = :domain_type,
|
||||
description = :description,
|
||||
update_time = now()
|
||||
updated_at = now()
|
||||
WHERE id = :rule_set_id
|
||||
"""
|
||||
),
|
||||
@@ -361,7 +361,7 @@ class RuleServiceImpl(IRuleService):
|
||||
FROM leaudit_rule_type_bindings b
|
||||
JOIN leaudit_rule_sets rs ON rs.id = b.rule_set_id
|
||||
WHERE rs.rule_type = :rule_type
|
||||
AND rs.delete_time IS NULL
|
||||
AND rs.deleted_at IS NULL
|
||||
ORDER BY b.priority DESC, b.id DESC
|
||||
"""
|
||||
),
|
||||
@@ -384,7 +384,7 @@ class RuleServiceImpl(IRuleService):
|
||||
rs.rule_name
|
||||
FROM leaudit_rule_type_bindings b
|
||||
JOIN leaudit_rule_sets rs ON rs.id = b.rule_set_id
|
||||
WHERE rs.delete_time IS NULL
|
||||
WHERE rs.deleted_at IS NULL
|
||||
ORDER BY rs.rule_type, b.priority DESC, b.id DESC
|
||||
"""
|
||||
),
|
||||
@@ -417,7 +417,7 @@ class RuleServiceImpl(IRuleService):
|
||||
"""创建规则类型绑定。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
RuleSet = await Session.execute(
|
||||
text("SELECT id, rule_type, rule_name FROM leaudit_rule_sets WHERE id = :rid AND delete_time IS NULL LIMIT 1"),
|
||||
text("SELECT id, rule_type, rule_name FROM leaudit_rule_sets WHERE id = :rid AND deleted_at IS NULL LIMIT 1"),
|
||||
{"rid": RuleSetId},
|
||||
)
|
||||
RsRow = RuleSet.mappings().first()
|
||||
@@ -531,7 +531,7 @@ class RuleServiceImpl(IRuleService):
|
||||
Params["note"] = Note
|
||||
|
||||
if SetClauses:
|
||||
SetClauses.append("update_time = now()")
|
||||
SetClauses.append("updated_at = now()")
|
||||
await Session.execute(
|
||||
text(f"UPDATE leaudit_rule_type_bindings SET {', '.join(SetClauses)} WHERE id = :bid"),
|
||||
Params,
|
||||
@@ -615,7 +615,7 @@ class RuleServiceImpl(IRuleService):
|
||||
WHEN id = :version_id THEN now()
|
||||
ELSE published_at
|
||||
END,
|
||||
update_time = now()
|
||||
updated_at = now()
|
||||
WHERE rule_set_id = :rule_set_id
|
||||
"""
|
||||
),
|
||||
@@ -634,7 +634,7 @@ class RuleServiceImpl(IRuleService):
|
||||
SET
|
||||
current_version_id = :version_id,
|
||||
status = 'active',
|
||||
update_time = now()
|
||||
updated_at = now()
|
||||
WHERE id = :rule_set_id
|
||||
"""
|
||||
),
|
||||
@@ -656,7 +656,7 @@ class RuleServiceImpl(IRuleService):
|
||||
SELECT id, rule_type, rule_name, domain_type, current_version_id, status
|
||||
FROM leaudit_rule_sets
|
||||
WHERE rule_type = :rule_type
|
||||
AND delete_time IS NULL
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,963 @@
|
||||
metadata:
|
||||
type_id: contract.sale
|
||||
name: 通用买卖合同
|
||||
version: "2.1"
|
||||
last_updated: "2026-04-12"
|
||||
description: |
|
||||
依据《中华人民共和国民法典》合同编·通则(第470条)及买卖合同章(第595-647条)。
|
||||
适用于一般货物/商品/设备/IT系统采购类买卖合同的评查。
|
||||
原始规则来源:旧系统 01_买卖合同.json(10条买卖专项评查点)+ 通用合同评查点。
|
||||
tags: [合同, 买卖, 采购, 通用]
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# 字段抽取声明
|
||||
# required_from: 该字段从哪个阶段开始必需
|
||||
# draft → 起草阶段就必需(草稿没有也会被标记)
|
||||
# executed → 仅已执行阶段必需(草稿可以缺失)
|
||||
# 未声明 → 默认 executed
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
|
||||
extract:
|
||||
# ── 合同成立要素 ──
|
||||
- {name: 合同名称, type: verbatim, required_from: draft, description: 合同的完整名称/项目名称}
|
||||
- {name: 甲方, type: verbatim, required_from: draft, description: 买方/采购方公司全称}
|
||||
- {name: 乙方, type: verbatim, required_from: draft, description: 卖方/供应商公司全称}
|
||||
- {name: 合同标的描述, type: string, required_from: draft, description: 合同交易的标的物/服务内容概述}
|
||||
- {name: 合同金额, type: money, required_from: draft, description: 合同总金额(数字)。框架/年度采购合同无固定总价时填 0 或 null}
|
||||
- {name: 合同金额大写, type: verbatim, required_from: draft, description: 合同总金额中文大写}
|
||||
|
||||
# ── 主体资格 ──
|
||||
- {name: 甲方法定代表人, type: verbatim, required_from: draft, description: 甲方法定代表人姓名}
|
||||
- {name: 乙方法定代表人, type: verbatim, required_from: draft, description: 乙方法定代表人姓名}
|
||||
- {name: 甲方地址, type: verbatim, required_from: draft, description: 甲方注册/办公地址}
|
||||
- {name: 乙方地址, type: verbatim, required_from: draft, description: 乙方注册/办公地址}
|
||||
- {name: 甲方统一社会信用代码, type: uscc, required_from: executed, description: 甲方18位统一社会信用代码}
|
||||
- {name: 乙方统一社会信用代码, type: uscc, required_from: executed, description: 乙方18位统一社会信用代码}
|
||||
|
||||
# ── 履约核心条款 ──
|
||||
- {name: 付款方式, type: string, required_from: draft, description: 付款条件、比例、节点、方式的完整描述}
|
||||
- {name: 交货期限, type: string, required_from: draft, description: 交货/交付时间要求}
|
||||
- {name: 交货地点, type: verbatim, required_from: draft, description: 交货/送达地点}
|
||||
- {name: 验收条款, type: string, required_from: draft, description: 验收标准、验收流程、初验终验时间和不合格处理}
|
||||
- {name: 质保期条款, type: string, description: 质保期限、质保范围、故障响应时间和运维服务内容}
|
||||
|
||||
# ── 买卖合同特有条款 ──
|
||||
- {name: 风险转移条款, type: string, description: 标的物风险转移时点和交付确认方式}
|
||||
- {name: 履约保证金条款, type: string, description: 保证金金额、缴纳方式、缴纳时间和退还条件}
|
||||
- {name: 知识产权条款, type: string, description: 知识产权归属、使用许可范围和侵权责任}
|
||||
- {name: 培训条款, type: string, description: 培训内容、培训方式和培训安排}
|
||||
- {name: 标的清单明细, type: string, description: 标的清单(序号、名称、数量、单价等明细及总价)}
|
||||
- {name: 招投标信息, type: string, description: 招标文件编号、项目编号、中标通知书等招投标依据}
|
||||
|
||||
# ── 法定/必备条款 ──
|
||||
- {name: 违约责任条款, type: string, required_from: draft, description: 违约责任的完整条款内容}
|
||||
- {name: 争议解决条款, type: string, required_from: draft, description: 争议解决方式(法院/仲裁)的完整描述}
|
||||
- {name: 不可抗力条款, type: string, description: 不可抗力相关条款的完整内容}
|
||||
|
||||
# ── 签署要素 ──
|
||||
- {name: 签约日期, type: date, required_from: executed, description: 合同签订日期}
|
||||
- {name: 合同编号, type: verbatim, required_from: executed, description: 合同唯一编号}
|
||||
|
||||
# ── 辅助信息 ──
|
||||
- {name: 甲方联系人, type: verbatim, description: 甲方项目联系人姓名}
|
||||
- {name: 甲方联系电话, type: verbatim, description: 甲方联系电话}
|
||||
- {name: 乙方联系人, type: verbatim, description: 乙方项目联系人姓名}
|
||||
- {name: 乙方联系电话, type: verbatim, description: 乙方联系电话}
|
||||
- {name: 甲方开户银行, type: verbatim, description: 甲方银行开户行名称}
|
||||
- {name: 甲方银行账号, type: verbatim, description: 甲方银行账号}
|
||||
|
||||
# ── 其他条款 ──
|
||||
- {name: 保密条款, type: string, description: 保密义务相关条款内容,如有附件总结内容限制在100字内}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# 规则列表
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
|
||||
rules:
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# MM-SALE-001 · 合同主体齐全
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-001
|
||||
name: 合同主体齐全
|
||||
risk: high
|
||||
score: 7
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 甲方
|
||||
- id: "2"
|
||||
check: required
|
||||
field: 乙方
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 甲乙方信息完整
|
||||
fail: 缺少甲方或乙方信息
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-002 · 标的物与金额必填
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-002
|
||||
name: 标的物与金额必填
|
||||
risk: high
|
||||
score: 7
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 合同标的描述
|
||||
- id: "2"
|
||||
check: required
|
||||
field: 合同金额
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 标的物与金额信息完整
|
||||
fail: 缺少标的物描述或合同金额
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-003 · 合同名称必填
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-003
|
||||
name: 合同名称必填
|
||||
risk: medium
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 合同名称
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 合同名称已填写
|
||||
fail: 缺少合同名称
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-004 · 法定代表人齐全
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-004
|
||||
name: 法定代表人齐全
|
||||
risk: medium
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 甲方法定代表人
|
||||
- id: "2"
|
||||
check: required
|
||||
field: 乙方法定代表人
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 甲乙方法定代表人信息完整
|
||||
fail: 缺少甲方或乙方法定代表人信息
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-005 · 交货期限必填
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-005
|
||||
name: 交货期限必填
|
||||
risk: high
|
||||
score: 6
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 交货期限
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 交货期限已约定
|
||||
fail: 交货期限未约定
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-006 · 验收条款存在
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-006
|
||||
name: 验收条款存在
|
||||
risk: high
|
||||
score: 5
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 验收条款
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 验收条款存在
|
||||
fail: 缺少验收条款
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-007 · 违约责任条款存在
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-007
|
||||
name: 违约责任条款存在
|
||||
risk: high
|
||||
score: 6
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 违约责任条款
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 违约责任条款存在
|
||||
fail: 缺少违约责任条款
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-008 · 争议解决条款存在
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-008
|
||||
name: 争议解决条款存在
|
||||
risk: medium
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 争议解决条款
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 争议解决条款存在
|
||||
fail: 缺少争议解决条款
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-009 · 培训条款存在
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-009
|
||||
name: 培训条款存在
|
||||
risk: low
|
||||
score: 1
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 培训条款
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 培训条款已约定
|
||||
fail: 培训条款缺失
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-010 · 签约日期必填
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-010
|
||||
name: 签约日期必填
|
||||
risk: high
|
||||
score: 5
|
||||
applies_in:
|
||||
- executed
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 签约日期
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 签约日期已填写
|
||||
fail: 缺少签约日期
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-011 · 合同编号必填
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-011
|
||||
name: 合同编号必填
|
||||
risk: medium
|
||||
score: 1
|
||||
applies_in:
|
||||
- executed
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 合同编号
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 合同编号已填写
|
||||
fail: 缺少合同编号
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-012 · 甲方信用代码校验
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-012
|
||||
name: 甲方信用代码校验
|
||||
risk: medium
|
||||
score: 3
|
||||
applies_in:
|
||||
- executed
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: format
|
||||
field: 甲方统一社会信用代码
|
||||
format: uscc
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 甲方统一社会信用代码校验通过
|
||||
fail: 甲方统一社会信用代码校验位错误
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-013 · 乙方信用代码校验
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-013
|
||||
name: 乙方信用代码校验
|
||||
risk: medium
|
||||
score: 3
|
||||
applies_in:
|
||||
- executed
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: format
|
||||
field: 乙方统一社会信用代码
|
||||
format: uscc
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 乙方统一社会信用代码校验通过
|
||||
fail: 乙方统一社会信用代码校验位错误
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-014 · 金额大小写一致
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-014
|
||||
name: 金额大小写一致
|
||||
risk: high
|
||||
score: 6
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: amount_match
|
||||
number: 合同金额
|
||||
chinese: 合同金额大写
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 金额大小写一致
|
||||
fail: 合同金额数字与大写不一致
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-015 · 金额为正数
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-015
|
||||
name: 金额为正数
|
||||
risk: low
|
||||
score: 1
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: compare
|
||||
left: 合同金额
|
||||
op: ">"
|
||||
right: 0
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 合同金额为正数
|
||||
fail: 合同金额不为正数
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-016 · 签约日期不是未来
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-016
|
||||
name: 签约日期不是未来
|
||||
risk: low
|
||||
score: 1
|
||||
applies_in:
|
||||
- executed
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: assert
|
||||
expr: "parse_date(签约日期) != None and (today() - parse_date(签约日期)).days >= 0 and (today() - parse_date(签约日期)).days <= 3650"
|
||||
|
||||
logic: "1"
|
||||
|
||||
messages:
|
||||
pass: 签约日期在合理范围内
|
||||
fail: 签约日期为未来日期或距今超过10年
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-017 · 验收条款完整(标的物检验期限约定)
|
||||
# 来源: NR-MM-002 · 民法典第620-622条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-017
|
||||
name: 验收条款完整
|
||||
risk: high
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 验收条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请检查合同的验收/检验条款是否完整。
|
||||
|
||||
验收条款:{{验收条款}}
|
||||
|
||||
评查要点(依据民法典第620-622条):
|
||||
1. 是否约定了明确的检验/验收期限
|
||||
2. 是否约定了验收标准(国家标准、行业标准、招标文件要求等)
|
||||
3. 是否约定了验收流程(谁组织、谁参与)
|
||||
4. 检验期限是否合理
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 验收条款完整
|
||||
fail: 验收条款不完整
|
||||
|
||||
# 来源: NR-MM-003 交货期限明确性 — §601-602(已被 MM-SALE-005 覆盖)
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-018 · 风险转移条款明确
|
||||
# 来源: NR-MM-004 · 民法典第604-607条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-018
|
||||
name: 风险转移条款明确
|
||||
risk: medium
|
||||
score: 1
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 风险转移条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请检查合同中是否有关于标的物/服务交付后风险转移的约定。
|
||||
|
||||
风险转移条款:{{风险转移条款}}
|
||||
|
||||
评查要点(依据民法典第604-607条):
|
||||
1. 是否明确了风险转移的时点(交付时、验收时或其他约定时点)
|
||||
2. 对于软件/系统类标的,风险转移通常与验收挂钩
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 风险转移条款约定明确
|
||||
fail: 风险转移条款缺失或不明确
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-019 · 质保期条款完整
|
||||
# 来源: NR-MM-005 · 民法典第617、621条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-019
|
||||
name: 质保期条款完整
|
||||
risk: high
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 质保期条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请检查合同的质保条款是否完整。
|
||||
|
||||
质保条款:{{质保期条款}}
|
||||
|
||||
评查要点(依据民法典第617、621条):
|
||||
1. 质保期限是否明确(起算时间、结束时间)
|
||||
2. 质保范围是否清晰(哪些属于质保范围内、哪些除外)
|
||||
3. 故障响应时间是否合理
|
||||
4. 是否约定了质保期内的服务标准
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 质保期条款完整
|
||||
fail: 质保期条款不完整
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-020 · 履约保证金条款完整
|
||||
# 来源: NR-MM-006 · 民法典第586-587条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-020
|
||||
name: 履约保证金条款完整
|
||||
risk: medium
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 履约保证金条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请检查合同中履约保证金条款是否完整。
|
||||
|
||||
保证金条款:{{履约保证金条款}}
|
||||
|
||||
评查要点(依据民法典第586-587条):
|
||||
1. 保证金金额是否明确
|
||||
2. 缴纳时间和方式是否清楚
|
||||
3. 退还条件是否合理、具体
|
||||
4. 退还时间是否明确
|
||||
5. 保证金比例一般不超过合同金额的10%
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 履约保证金条款完整
|
||||
fail: 履约保证金条款不完整
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-021 · 分期付款条款合理
|
||||
# 来源: NR-MM-009 · 民法典第626-634条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-021
|
||||
name: 分期付款条款合理
|
||||
risk: high
|
||||
score: 4
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 付款方式
|
||||
- id: "2"
|
||||
check: required
|
||||
field: 合同金额
|
||||
- id: "3"
|
||||
check: ai
|
||||
prompt: |
|
||||
请审查合同分期付款条款的合理性。
|
||||
|
||||
付款条款:{{付款方式}}
|
||||
合同总金额:{{合同金额}}
|
||||
联合采购信息:{{联合采购信息}}
|
||||
|
||||
评查要点(依据民法典第626-634条):
|
||||
1. 各期付款比例之和是否覆盖应付总额(联合采购时:各期比例之和=本单位分摊比例即为100%覆盖,如4单位各付25%,则5%+10%+10%=25%=该单位全额,判为pass)
|
||||
2. 预付款不超过30%
|
||||
3. 付款节点与交付验收挂钩
|
||||
4. 有付款前置条件(发票、验收报告等)
|
||||
请简洁回答,reason不超过100字。
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2 AND 3"
|
||||
|
||||
messages:
|
||||
pass: 分期付款条款合理
|
||||
fail: 分期付款条款存在问题
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-022 · 知识产权条款完整
|
||||
# 来源: NR-MM-007 · 民法典第600条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-022
|
||||
name: 知识产权条款完整
|
||||
risk: high
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 知识产权条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请检查合同中知识产权条款是否完整。
|
||||
|
||||
知识产权条款:{{知识产权条款}}
|
||||
|
||||
评查要点(依据民法典第600条):
|
||||
1. 是否明确了知识产权的归属(买方/卖方/共有)
|
||||
2. 是否约定了使用许可的范围和方式
|
||||
3. 是否约定了第三方知识产权侵权的责任承担
|
||||
4. 对于软件/系统类采购,应特别关注源代码、数据归属
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 知识产权条款完整
|
||||
fail: 知识产权条款不完整
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-023 · 标的清单金额校验
|
||||
# 来源: NR-MM-012 · 民法典第595-596条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-023
|
||||
name: 标的清单金额校验
|
||||
risk: high
|
||||
score: 4
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 标的清单明细
|
||||
- id: "2"
|
||||
check: required
|
||||
field: 合同金额
|
||||
- id: "3"
|
||||
check: ai
|
||||
prompt: |
|
||||
请校验合同标的清单的金额一致性。
|
||||
|
||||
标的清单明细:{{标的清单明细}}
|
||||
合同总金额:{{合同金额}}
|
||||
|
||||
评查要点(依据民法典第595-596条):
|
||||
1. 各项单价x数量是否等于对应项总价(逐项计算校验)
|
||||
2. 标的清单总价是否等于合同总金额
|
||||
3. 服务范围描述是否足够具体(非含糊表述)
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2 AND 3"
|
||||
|
||||
messages:
|
||||
pass: 标的清单金额校验通过
|
||||
fail: 标的清单金额不一致或服务范围不明确
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-024 · 招投标信息引用完整
|
||||
# 来源: NR-MM-014 · 民法典第644条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-024
|
||||
name: 招投标信息引用完整
|
||||
risk: high
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 招投标信息
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请检查合同是否明确引用了招投标文件。
|
||||
|
||||
招投标信息:{{招投标信息}}
|
||||
|
||||
评查要点:
|
||||
1. 合同是否引用了招标文件编号/项目编号
|
||||
2. 合同是否将招标文件、投标文件作为合同附件或组成部分
|
||||
3. 合同主要条款不应实质性变更招投标内容
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 合同与招投标文件一致
|
||||
fail: 合同与招投标文件引用不完整
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-025 · 违约责任条款充分
|
||||
# 合规性 · AI 语义判断 · 民法典第577-585条
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-025
|
||||
name: 违约责任条款充分
|
||||
risk: medium
|
||||
score: 4
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 违约责任条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请判断以下违约责任条款是否充分、合规。
|
||||
|
||||
条款内容:{{违约责任条款}}
|
||||
|
||||
充分的违约责任条款应当(依据民法典第577-585条):
|
||||
1. 明确违约情形(如逾期付款、逾期交货、质量不合格等)
|
||||
2. 明确违约金计算方式或赔偿标准
|
||||
3. 不能只是笼统的模糊表述
|
||||
4. 应当对双方的违约责任都有约定
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 违约责任条款充分
|
||||
fail: 违约责任条款不充分
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-026 · 争议解决方式明确
|
||||
# 合规性 · AI 语义判断
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-026
|
||||
name: 争议解决方式明确
|
||||
risk: medium
|
||||
score: 4
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 争议解决条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请判断以下争议解决条款是否符合法律要求。
|
||||
|
||||
条款内容:{{争议解决条款}}
|
||||
|
||||
合规的争议解决条款应当:
|
||||
1. 明确指定具体的争议解决方式(仲裁或诉讼,二选一)
|
||||
2. 如选择仲裁,应明确仲裁机构名称
|
||||
3. 如选择诉讼,应明确管辖法院
|
||||
4. 不能同时约定仲裁和诉讼
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 争议解决方式明确
|
||||
fail: 争议解决条款未明确具体的仲裁机构/管辖法院
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-027 · 付款条款明确
|
||||
# 合规性 · AI 语义判断
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-027
|
||||
name: 付款条款明确
|
||||
risk: medium
|
||||
score: 4
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 付款方式
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请判断以下付款条款是否明确。
|
||||
|
||||
条款内容:{{付款方式}}
|
||||
|
||||
明确的付款条款应当包含:
|
||||
1. 付款金额或比例
|
||||
2. 付款时间节点或触发条件
|
||||
3. 付款方式(如银行转账)
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 付款条款明确
|
||||
fail: 付款条款不够明确
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# MM-SALE-028 · 保密条款完整
|
||||
# 合规性 · AI 语义判断
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
- rule_id: MM-SALE-028
|
||||
name: 保密条款完整
|
||||
risk: low
|
||||
score: 3
|
||||
|
||||
stages:
|
||||
- id: "1"
|
||||
check: required
|
||||
field: 保密条款
|
||||
- id: "2"
|
||||
check: ai
|
||||
prompt: |
|
||||
请判断以下保密条款是否完整。
|
||||
|
||||
条款内容:{{保密条款}}
|
||||
|
||||
完整的保密条款应当包含:
|
||||
1. 保密信息的范围定义
|
||||
2. 保密义务的期限
|
||||
3. 违反保密义务的法律后果
|
||||
|
||||
请以JSON格式回答:{"result": "pass/warn/fail", "reason": "简要说明", "suggestion": "改进建议(仅warn/fail时填写)"}
|
||||
判断标准:
|
||||
- pass:条款基本合理,能达到法律基本要求,道理上说得通即可
|
||||
- warn:条款主体合理但有改进空间,不影响合同效力(如缺少锦上添花的条款、表述可以更精确等)
|
||||
- fail:条款存在严重缺陷,可能导致法律风险或合同纠纷(如完全缺失关键要素、违反强制性规定、金额计算错误等)
|
||||
schema:
|
||||
type: object
|
||||
required: [result, reason]
|
||||
properties:
|
||||
result: { type: string }
|
||||
reason: { type: string }
|
||||
suggestion: { type: string }
|
||||
pass_when: "result != 'fail'"
|
||||
|
||||
logic: "1 AND 2"
|
||||
|
||||
messages:
|
||||
pass: 保密条款完整
|
||||
fail: 保密条款不够完整
|
||||
File diff suppressed because it is too large
Load Diff
+1476
-1362
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+714
-771
File diff suppressed because it is too large
Load Diff
+1339
-670
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
"""M4 种子数据初始化 — 上传全部规则 YAML + 创建入口模块 + 文档类型 + 绑定。
|
||||
|
||||
用法: cd /home/wren-dev/Porject/leaudit-platform
|
||||
PYTHONPATH=src:/home/wren-dev/Porject/docauditai \
|
||||
python scripts/m4_seed_rules.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
||||
sys.path.insert(0, "/home/wren-dev/Porject/docauditai")
|
||||
|
||||
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
|
||||
from fastapi_common.fastapi_common_storage.oss_client import OssClient
|
||||
from fastapi_common.fastapi_common_storage.oss_path_utils import OssPathUtils
|
||||
from sqlalchemy import text
|
||||
|
||||
RULES_DIR = Path(__file__).resolve().parent.parent / "rules"
|
||||
|
||||
|
||||
def _read_metadata(yaml_path: Path) -> dict:
|
||||
import yaml
|
||||
with open(yaml_path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
return data.get("metadata", {})
|
||||
|
||||
|
||||
def _type_id_to_rule_type(type_id: str) -> str:
|
||||
"""type_id → rule_type (rule_sets 表唯一 key)"""
|
||||
return type_id
|
||||
|
||||
|
||||
async def _get_or_create_rule_set(
|
||||
session, rule_type: str, rule_name: str, domain_type: str, description: str
|
||||
) -> dict:
|
||||
result = await session.execute(
|
||||
text("SELECT * FROM leaudit_rule_sets WHERE rule_type = :rt AND deleted_at IS NULL LIMIT 1"),
|
||||
{"rt": rule_type},
|
||||
)
|
||||
row = result.mappings().first()
|
||||
if row:
|
||||
return dict(row)
|
||||
|
||||
result = await session.execute(
|
||||
text(
|
||||
"""INSERT INTO leaudit_rule_sets (rule_type, rule_name, domain_type, description, status, is_builtin)
|
||||
VALUES (:rt, :rn, :dt, :desc, 'draft', false)
|
||||
RETURNING id, rule_type, rule_name, domain_type, current_version_id, status"""
|
||||
),
|
||||
{"rt": rule_type, "rn": rule_name, "dt": domain_type, "desc": description or ""},
|
||||
)
|
||||
return dict(result.mappings().first())
|
||||
|
||||
|
||||
async def _version_exists(session, rule_set_id: int, version_no: str) -> bool:
|
||||
result = await session.execute(
|
||||
text("SELECT id FROM leaudit_rule_versions WHERE rule_set_id = :rsid AND version_no = :vn LIMIT 1"),
|
||||
{"rsid": rule_set_id, "vn": version_no},
|
||||
)
|
||||
return result.mappings().first() is not None
|
||||
|
||||
|
||||
async def upload_one(yaml_path: Path, oss: OssClient) -> dict:
|
||||
meta = _read_metadata(yaml_path)
|
||||
type_id = meta.get("type_id", "")
|
||||
rule_type = _type_id_to_rule_type(type_id)
|
||||
rule_name = meta.get("name", rule_type)
|
||||
version_no = meta.get("version", "1.0")
|
||||
description = meta.get("description", "")
|
||||
domain_type = type_id.split(".", 1)[0] if "." in type_id else type_id
|
||||
yaml_text = yaml_path.read_text(encoding="utf-8")
|
||||
|
||||
file_sha256 = hashlib.sha256(yaml_text.encode()).hexdigest()
|
||||
file_size = len(yaml_text.encode())
|
||||
|
||||
async with GetAsyncSession() as session:
|
||||
rs = await _get_or_create_rule_set(session, rule_type, rule_name, domain_type, description)
|
||||
|
||||
if await _version_exists(session, rs["id"], version_no):
|
||||
return {"rule_type": rule_type, "status": "skipped", "reason": f"version {version_no} exists"}
|
||||
|
||||
# Upload to OSS
|
||||
object_key = OssPathUtils.BuildRuleYamlKey(rule_type, version_no)
|
||||
oss_url = oss.UploadText(
|
||||
ObjectKey=object_key,
|
||||
Content=yaml_text,
|
||||
ContentType="application/x-yaml; charset=utf-8",
|
||||
)
|
||||
|
||||
# Get next version_seq
|
||||
seq_result = await session.execute(
|
||||
text("SELECT COALESCE(MAX(version_seq), 0) + 1 AS ns FROM leaudit_rule_versions WHERE rule_set_id = :rsid"),
|
||||
{"rsid": rs["id"]},
|
||||
)
|
||||
next_seq = int(seq_result.mappings().first()["ns"])
|
||||
|
||||
# Insert version
|
||||
await session.execute(
|
||||
text(
|
||||
"""INSERT INTO leaudit_rule_versions (
|
||||
rule_set_id, version_no, version_seq, status, source_type, dsl_format,
|
||||
oss_url, file_sha256, file_size, metadata_type_id, metadata_name, metadata_version
|
||||
) VALUES (
|
||||
:rsid, :vn, :vs, 'draft', 'oss_yaml', 'yaml',
|
||||
:url, :sha, :fs, :mtid, :mn, :mv
|
||||
)"""
|
||||
),
|
||||
{
|
||||
"rsid": rs["id"], "vn": version_no, "vs": next_seq,
|
||||
"url": oss_url, "sha": file_sha256, "fs": file_size,
|
||||
"mtid": type_id, "mn": rule_name, "mv": version_no,
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
return {"rule_type": rule_type, "status": "created", "version": version_no, "oss_url": oss_url}
|
||||
|
||||
|
||||
async def publish_one(rule_type: str, version_no: str | None = None) -> dict:
|
||||
async with GetAsyncSession() as session:
|
||||
rs = await session.execute(
|
||||
text("SELECT id FROM leaudit_rule_sets WHERE rule_type = :rt AND deleted_at IS NULL LIMIT 1"),
|
||||
{"rt": rule_type},
|
||||
)
|
||||
rs_row = rs.mappings().first()
|
||||
if not rs_row:
|
||||
return {"rule_type": rule_type, "status": "error", "reason": "rule_set not found"}
|
||||
|
||||
if version_no:
|
||||
v = await session.execute(
|
||||
text("SELECT id FROM leaudit_rule_versions WHERE rule_set_id = :rsid AND version_no = :vn LIMIT 1"),
|
||||
{"rsid": rs_row["id"], "vn": version_no},
|
||||
)
|
||||
else:
|
||||
v = await session.execute(
|
||||
text("SELECT id FROM leaudit_rule_versions WHERE rule_set_id = :rsid ORDER BY version_seq DESC LIMIT 1"),
|
||||
{"rsid": rs_row["id"]},
|
||||
)
|
||||
v_row = v.mappings().first()
|
||||
if not v_row:
|
||||
return {"rule_type": rule_type, "status": "error", "reason": "version not found"}
|
||||
|
||||
version_id = int(v_row["id"])
|
||||
await session.execute(
|
||||
text("UPDATE leaudit_rule_versions SET status='published', published_at=now(), updated_at=now() WHERE id=:vid"),
|
||||
{"vid": version_id},
|
||||
)
|
||||
await session.execute(
|
||||
text("UPDATE leaudit_rule_sets SET current_version_id=:vid, status='active', updated_at=now() WHERE id=:rsid"),
|
||||
{"vid": version_id, "rsid": rs_row["id"]},
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
return {"rule_type": rule_type, "status": "published", "version_id": version_id}
|
||||
|
||||
|
||||
async def main():
|
||||
oss = OssClient()
|
||||
rules_dirs = sorted(d for d in RULES_DIR.iterdir() if d.is_dir() and (d / "rules.yaml").exists())
|
||||
|
||||
print(f"找到 {len(rules_dirs)} 套规则\n")
|
||||
|
||||
# Step 1: Upload all
|
||||
print("=" * 60)
|
||||
print("Step 1: 上传规则 YAML → OSS + 写 DB")
|
||||
print("=" * 60)
|
||||
for d in rules_dirs:
|
||||
yaml_path = d / "rules.yaml"
|
||||
try:
|
||||
result = await upload_one(yaml_path, oss)
|
||||
icon = "✓" if result["status"] != "skipped" else "⊙"
|
||||
print(f" {icon} {result['rule_type']:40s} {result['status']:8s} v{result.get('version', '?')}")
|
||||
except Exception as e:
|
||||
print(f" ✗ {d.name:40s} ERROR: {e}")
|
||||
|
||||
# Step 2: Publish all
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("Step 2: 发布规则版本")
|
||||
print("=" * 60)
|
||||
for d in rules_dirs:
|
||||
meta = _read_metadata(d / "rules.yaml")
|
||||
type_id = meta.get("type_id", "")
|
||||
rule_type = _type_id_to_rule_type(type_id)
|
||||
try:
|
||||
result = await publish_one(rule_type)
|
||||
icon = "✓" if result["status"] == "published" else "✗"
|
||||
print(f" {icon} {rule_type:40s} {result['status']}")
|
||||
except Exception as e:
|
||||
print(f" ✗ {rule_type:40s} ERROR: {e}")
|
||||
|
||||
print()
|
||||
print("完成!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user