feat: 收口知识库治理配置与模板映射
This commit is contained in:
@@ -1,3 +1,93 @@
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def _governance_defaults() -> dict:
|
||||
return {
|
||||
"owner_mappings": [
|
||||
{
|
||||
"owner_role": "注册资料负责人",
|
||||
"owner_name": "张三",
|
||||
"department": "注册事务部",
|
||||
"chapter_scope": "CH1",
|
||||
"risk_scope": "字段冲突 / 缺失项",
|
||||
"feishu_user_id": "ou_demo_1",
|
||||
"feishu_open_id": "on_demo_1",
|
||||
"feishu_name": "张三",
|
||||
"notify_enabled": "是",
|
||||
},
|
||||
{
|
||||
"owner_role": "注册申报负责人",
|
||||
"owner_name": "李四",
|
||||
"department": "临床注册组",
|
||||
"chapter_scope": "CH2-CH6",
|
||||
"risk_scope": "完整性风险 / 导出阻断",
|
||||
"feishu_user_id": "ou_demo_2",
|
||||
"feishu_open_id": "on_demo_2",
|
||||
"feishu_name": "李四",
|
||||
"notify_enabled": "是",
|
||||
},
|
||||
],
|
||||
"feishu_configs": [
|
||||
{
|
||||
"config_name": "注册审核完成通知",
|
||||
"notify_reason": "task_completed",
|
||||
"channel": "群机器人",
|
||||
"message_template": "审核完成摘要 + @处理人",
|
||||
"status": "启用",
|
||||
},
|
||||
{
|
||||
"config_name": "注册审核异常通知",
|
||||
"notify_reason": "task_failed",
|
||||
"channel": "群机器人",
|
||||
"message_template": "异常摘要 + @处理人",
|
||||
"status": "启用",
|
||||
},
|
||||
],
|
||||
"template_mappings": [
|
||||
{
|
||||
"template_name": "注册证导出模板",
|
||||
"output_type": "registration_word_export_report",
|
||||
"version": "V1.0",
|
||||
"placeholder_count": 18,
|
||||
"status": "启用",
|
||||
"field_mapping_summary": "产品名称 / 注册人 / 适用机型 / 储存条件",
|
||||
},
|
||||
{
|
||||
"template_name": "风险摘要导出模板",
|
||||
"output_type": "registration_word_export_report",
|
||||
"version": "V0.9",
|
||||
"placeholder_count": 10,
|
||||
"status": "待校验",
|
||||
"field_mapping_summary": "风险等级 / 批次号 / 责任人 / 证据摘要",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _read_governance_yaml() -> dict:
|
||||
raw_path = getattr(settings, "GOVERNANCE_CONFIG_PATH", "")
|
||||
if not raw_path:
|
||||
return {}
|
||||
config_path = Path(raw_path)
|
||||
if not config_path.exists() or not config_path.is_file():
|
||||
return {}
|
||||
with config_path.open("r", encoding="utf-8") as file:
|
||||
return yaml.safe_load(file) or {}
|
||||
|
||||
|
||||
def _load_governance_config() -> dict:
|
||||
defaults = _governance_defaults()
|
||||
config = _read_governance_yaml()
|
||||
for key, default_value in defaults.items():
|
||||
configured_value = config.get(key)
|
||||
if isinstance(default_value, list) and configured_value:
|
||||
defaults[key] = configured_value
|
||||
return defaults
|
||||
|
||||
|
||||
def get_platform_demo_context():
|
||||
batch = {
|
||||
"name": "2026Q2-呼吸道多联检测试剂注册批次",
|
||||
@@ -151,46 +241,10 @@ def get_platform_demo_context():
|
||||
"status": "待校订",
|
||||
},
|
||||
]
|
||||
owner_mappings = [
|
||||
{
|
||||
"owner_role": "注册资料负责人",
|
||||
"owner_name": "张三",
|
||||
"department": "注册事务部",
|
||||
"chapter_scope": "CH1",
|
||||
"risk_scope": "字段冲突 / 缺失项",
|
||||
"feishu_user_id": "ou_demo_1",
|
||||
"feishu_open_id": "on_demo_1",
|
||||
"feishu_name": "张三",
|
||||
"notify_enabled": "是",
|
||||
},
|
||||
{
|
||||
"owner_role": "注册申报负责人",
|
||||
"owner_name": "李四",
|
||||
"department": "临床注册组",
|
||||
"chapter_scope": "CH2-CH6",
|
||||
"risk_scope": "完整性风险 / 导出阻断",
|
||||
"feishu_user_id": "ou_demo_2",
|
||||
"feishu_open_id": "on_demo_2",
|
||||
"feishu_name": "李四",
|
||||
"notify_enabled": "是",
|
||||
},
|
||||
]
|
||||
feishu_configs = [
|
||||
{
|
||||
"config_name": "注册审核完成通知",
|
||||
"notify_reason": "task_completed",
|
||||
"channel": "群机器人",
|
||||
"message_template": "审核完成摘要 + @处理人",
|
||||
"status": "启用",
|
||||
},
|
||||
{
|
||||
"config_name": "注册审核异常通知",
|
||||
"notify_reason": "task_failed",
|
||||
"channel": "群机器人",
|
||||
"message_template": "异常摘要 + @处理人",
|
||||
"status": "启用",
|
||||
},
|
||||
]
|
||||
governance_config = _load_governance_config()
|
||||
owner_mappings = governance_config["owner_mappings"]
|
||||
feishu_configs = governance_config["feishu_configs"]
|
||||
template_mappings = governance_config["template_mappings"]
|
||||
mcp_connectors = [
|
||||
{"name": "飞书任务通知", "kind": "协同办公", "auth": "App Token", "status": "已连接", "sync": "5 分钟前"},
|
||||
{"name": "法规规则源导入", "kind": "法规服务", "auth": "文件轮询", "status": "待验证", "sync": "今天 08:50"},
|
||||
@@ -420,6 +474,7 @@ def get_platform_demo_context():
|
||||
"field_schemas": field_schemas,
|
||||
"owner_mappings": owner_mappings,
|
||||
"feishu_configs": feishu_configs,
|
||||
"template_mappings": template_mappings,
|
||||
"knowledge_filters": knowledge_filters,
|
||||
"knowledge_form": knowledge_form,
|
||||
"rule_form": rule_form,
|
||||
|
||||
@@ -108,6 +108,9 @@ MEDIA_ROOT = Path(os.environ.get("UPLOAD_ROOT", BASE_DIR / "data" / "uploads"))
|
||||
|
||||
# 配置目录和 Chroma 数据目录都允许外部覆盖,方便复试现场快速切换。
|
||||
SCENARIO_CONFIG_DIR = Path(os.environ.get("SCENARIO_CONFIG_DIR", BASE_DIR / "configs"))
|
||||
GOVERNANCE_CONFIG_PATH = Path(
|
||||
os.environ.get("GOVERNANCE_CONFIG_PATH", BASE_DIR / "configs" / "governance.yaml")
|
||||
)
|
||||
CHROMA_PATH = Path(os.environ.get("CHROMA_PATH", BASE_DIR / "data" / "chroma"))
|
||||
|
||||
# LLM 与 Embedding 默认遵循“尽量少配置也能跑”的策略:
|
||||
|
||||
45
configs/governance.yaml
Normal file
45
configs/governance.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
owner_mappings:
|
||||
- owner_role: 注册资料负责人
|
||||
owner_name: 张三
|
||||
department: 注册事务部
|
||||
chapter_scope: CH1
|
||||
risk_scope: 字段冲突 / 缺失项
|
||||
feishu_user_id: ou_demo_1
|
||||
feishu_open_id: on_demo_1
|
||||
feishu_name: 张三
|
||||
notify_enabled: 是
|
||||
- owner_role: 注册申报负责人
|
||||
owner_name: 李四
|
||||
department: 临床注册组
|
||||
chapter_scope: CH2-CH6
|
||||
risk_scope: 完整性风险 / 导出阻断
|
||||
feishu_user_id: ou_demo_2
|
||||
feishu_open_id: on_demo_2
|
||||
feishu_name: 李四
|
||||
notify_enabled: 是
|
||||
|
||||
feishu_configs:
|
||||
- config_name: 注册审核完成通知
|
||||
notify_reason: task_completed
|
||||
channel: 群机器人
|
||||
message_template: 审核完成摘要 + @处理人
|
||||
status: 启用
|
||||
- config_name: 注册审核异常通知
|
||||
notify_reason: task_failed
|
||||
channel: 群机器人
|
||||
message_template: 异常摘要 + @处理人
|
||||
status: 启用
|
||||
|
||||
template_mappings:
|
||||
- template_name: 注册证导出模板
|
||||
output_type: registration_word_export_report
|
||||
version: V1.0
|
||||
placeholder_count: 18
|
||||
status: 启用
|
||||
field_mapping_summary: 产品名称 / 注册人 / 适用机型 / 储存条件
|
||||
- template_name: 风险摘要导出模板
|
||||
output_type: registration_word_export_report
|
||||
version: V0.9
|
||||
placeholder_count: 10
|
||||
status: 待校验
|
||||
field_mapping_summary: 风险等级 / 批次号 / 责任人 / 证据摘要
|
||||
@@ -164,6 +164,43 @@
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2 class="section-title">Word 模板与字段映射</h2>
|
||||
<p class="section-copy">维护输出模板版本、占位符数量和字段映射摘要。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>模板名称</th>
|
||||
<th>输出类型</th>
|
||||
<th>版本</th>
|
||||
<th>占位符数量</th>
|
||||
<th>字段映射摘要</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in template_mappings %}
|
||||
<tr>
|
||||
<td>{{ item.template_name }}</td>
|
||||
<td>{{ item.output_type }}</td>
|
||||
<td>{{ item.version }}</td>
|
||||
<td>{{ item.placeholder_count }}</td>
|
||||
<td>{{ item.field_mapping_summary }}</td>
|
||||
<td>{{ item.status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid-1">
|
||||
<article class="panel">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from django.urls import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from apps.platform_ui.services import get_platform_demo_context
|
||||
|
||||
|
||||
def test_knowledge_base_page_shows_governance_sections(client):
|
||||
@@ -30,3 +33,70 @@ def test_knowledge_base_page_shows_owner_mapping_fields_and_notify_reasons(clien
|
||||
assert "notify_enabled" in content
|
||||
assert "task_completed" in content
|
||||
assert "task_failed" in content
|
||||
|
||||
|
||||
@override_settings(GOVERNANCE_CONFIG_PATH="")
|
||||
def test_get_platform_demo_context_reads_governance_yaml(tmp_path):
|
||||
config_path = tmp_path / "governance.yaml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
owner_mappings:
|
||||
- owner_role: 临床注册负责人
|
||||
owner_name: 王五
|
||||
department: 临床事务部
|
||||
chapter_scope: CH3-CH6
|
||||
risk_scope: 高风险阻断
|
||||
feishu_user_id: ou_test_owner
|
||||
feishu_open_id: on_test_owner
|
||||
feishu_name: 王五
|
||||
notify_enabled: 是
|
||||
feishu_configs:
|
||||
- config_name: Demo 完成通知
|
||||
notify_reason: task_completed
|
||||
channel: 群机器人
|
||||
message_template: 审核完成摘要 + @处理人
|
||||
status: 启用
|
||||
template_mappings:
|
||||
- template_name: 注册证导出模板
|
||||
output_type: registration_word_export_report
|
||||
version: V1.0
|
||||
placeholder_count: 12
|
||||
status: 启用
|
||||
field_mapping_summary: 产品名称 / 注册人 / 适用机型
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with override_settings(GOVERNANCE_CONFIG_PATH=config_path):
|
||||
context = get_platform_demo_context()
|
||||
|
||||
assert context["owner_mappings"][0]["owner_name"] == "王五"
|
||||
assert context["feishu_configs"][0]["notify_reason"] == "task_completed"
|
||||
assert context["template_mappings"][0]["template_name"] == "注册证导出模板"
|
||||
assert context["template_mappings"][0]["field_mapping_summary"] == "产品名称 / 注册人 / 适用机型"
|
||||
|
||||
|
||||
@override_settings(GOVERNANCE_CONFIG_PATH="")
|
||||
def test_knowledge_base_page_shows_template_mappings_from_governance_config(client, tmp_path):
|
||||
config_path = tmp_path / "governance.yaml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
template_mappings:
|
||||
- template_name: 风险摘要导出模板
|
||||
output_type: registration_word_export_report
|
||||
version: V2.0
|
||||
placeholder_count: 8
|
||||
status: 灰度中
|
||||
field_mapping_summary: 风险等级 / 产品名称 / 责任人
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with override_settings(GOVERNANCE_CONFIG_PATH=config_path):
|
||||
response = client.get(reverse("platform_ui:knowledge-base"))
|
||||
|
||||
content = response.content.decode("utf-8")
|
||||
assert response.status_code == 200
|
||||
assert "Word 模板与字段映射" in content
|
||||
assert "风险摘要导出模板" in content
|
||||
assert "风险等级 / 产品名称 / 责任人" in content
|
||||
|
||||
Reference in New Issue
Block a user