feat: migrate rule bindings to group-based flow

This commit is contained in:
wren
2026-05-07 17:43:20 +08:00
parent 75c2111209
commit f8eb2dc817
8 changed files with 871 additions and 361 deletions
@@ -44,6 +44,67 @@ def _normalize_speed(speed: str | None) -> str:
class AuditServiceImpl(IAuditService):
"""评查服务实现。"""
async def _resolve_rule_binding_from_group(self, session, group_id: int | None) -> dict | None:
"""按二级分组解析正式规则绑定。"""
if not group_id:
return None
result = await session.execute(
text(
"""
SELECT
rs.id AS rule_set_id,
COALESCE(rs.current_version_id, fallback_rv.id) AS rule_version_id,
COALESCE(current_rv.oss_url, fallback_rv.oss_url) AS rule_source_oss_url,
COALESCE(current_rv.file_sha256, fallback_rv.file_sha256) AS rule_source_sha256,
COALESCE(current_rv.metadata_type_id, fallback_rv.metadata_type_id) AS rule_type_id
FROM leaudit_rule_group_bindings rgb
JOIN leaudit_rule_sets rs ON rs.id = rgb.rule_set_id
LEFT JOIN leaudit_rule_versions current_rv ON current_rv.id = rs.current_version_id
LEFT JOIN LATERAL (
SELECT
rv.id,
rv.oss_url,
rv.file_sha256,
rv.metadata_type_id
FROM leaudit_rule_versions rv
WHERE rv.rule_set_id = rs.id
AND rv.status IN ('published', 'rollback')
ORDER BY rv.version_seq DESC, rv.id DESC
LIMIT 1
) fallback_rv ON TRUE
WHERE rgb.group_id = :group_id
AND rgb.is_active = TRUE
AND rgb.deleted_at IS NULL
ORDER BY rgb.priority DESC, rgb.id ASC
LIMIT 1
"""
),
{"group_id": int(group_id)},
)
return result.mappings().first()
async def _resolve_unique_group_binding_by_doc_type(self, session, doc_type_id: int | None) -> dict | None:
"""当文档尚未落 group_id 时,按文档类型唯一子组兜底解析正式绑定。"""
if not doc_type_id:
return None
group_row = (
await session.execute(
text(
"""
SELECT CASE WHEN COUNT(*) = 1 THEN MIN(id) END AS group_id
FROM leaudit_evaluation_point_groups
WHERE document_type_id = :doc_type_id
AND deleted_at IS NULL
AND is_enabled = TRUE
AND COALESCE(pid, 0) <> 0
"""
),
{"doc_type_id": int(doc_type_id)},
)
).mappings().first()
resolved_group_id = int(group_row["group_id"]) if group_row and group_row.get("group_id") is not None else None
return await self._resolve_rule_binding_from_group(session, resolved_group_id)
async def Run(
self,
DocumentId: int,
@@ -125,44 +186,14 @@ class AuditServiceImpl(IAuditService):
)
latestRunNo = runNoResult.scalar_one_or_none() or 0
binding = None
if getattr(document, "groupId", None):
groupBindingResult = await session.execute(
text(
"""
SELECT
rs.id AS rule_set_id,
COALESCE(rs.current_version_id, fallback_rv.id) AS rule_version_id,
COALESCE(current_rv.oss_url, fallback_rv.oss_url) AS rule_source_oss_url,
COALESCE(current_rv.file_sha256, fallback_rv.file_sha256) AS rule_source_sha256,
COALESCE(current_rv.metadata_type_id, fallback_rv.metadata_type_id) AS rule_type_id
FROM leaudit_rule_group_bindings rgb
JOIN leaudit_rule_sets rs ON rs.id = rgb.rule_set_id
LEFT JOIN leaudit_rule_versions current_rv ON current_rv.id = rs.current_version_id
LEFT JOIN LATERAL (
SELECT
rv.id,
rv.oss_url,
rv.file_sha256,
rv.metadata_type_id
FROM leaudit_rule_versions rv
WHERE rv.rule_set_id = rs.id
AND rv.status IN ('published', 'rollback')
ORDER BY rv.version_seq DESC, rv.id DESC
LIMIT 1
) fallback_rv ON TRUE
WHERE rgb.group_id = :group_id
AND rgb.is_active = TRUE
AND rgb.deleted_at IS NULL
ORDER BY rgb.priority DESC, rgb.id ASC
LIMIT 1
"""
),
{"group_id": int(document.groupId)},
)
binding = groupBindingResult.mappings().first()
binding = await self._resolve_rule_binding_from_group(session, getattr(document, "groupId", None))
if binding is None:
binding = await self._resolve_unique_group_binding_by_doc_type(session, getattr(document, "typeId", None))
if binding and getattr(document, "groupId", None) is None:
logger.info("文档未显式记录 group_id,已按文档类型唯一子组解析正式规则绑定")
if not binding or not binding["rule_set_id"] or not binding["rule_version_id"]:
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "当前子类型未绑定可执行规则集,请先检查二级分组规则配置")
if getattr(document, "groupId", None):
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "当前子类型未绑定可执行规则集,请先检查二级分组规则配置")
if binding is None:
bindingResult = await session.execute(