pbl_evidence/skill/SKILL.md
2026-09-22 13:21:06 +08:00

10 KiB
Raw Blame History

name description
pbl_evidence 产出物与证据幂等采集模块M5a/M5b——pbl_artifact/pbl_evidence 两表 CRUD、事件→证据幂等采集、证据列表/统计/水位查询。改本模块前先读完「契约接口」「陷阱」两节。

pbl_evidence 模块技能

定位

产出物artifact管理与学习证据evidence幂等采集。M5a 提供产出物 CRUD + 单事件/批量事件落证据; M5b回放/增量续采)直接复用本模块的采集器与水位接口。

挂载

from pbl_evidence.init import load_pbl_evidence
load_pbl_evidence()   # 宿主 apps/pbls 的 app/pbls.py init() 中按拓扑序调用

模块是 Python 包(无 app.py / 无端口 / 无独立部署单元),前端仅 wwwroot/ 下 .ui/.dspy 静态资源。

数据表2 张)

  • pbl_artifact:学生产出物(版本内联 version_no,唯一索引 uk_art_uid(tenant_id, artifact_uid)
  • pbl_evidence:学习证据(唯一索引 uk_ev_dedup(tenant_id, source_event_id, evidence_type) 防重)

表定义:models/{table}.json(四段式 summary/fields/indexes/codesCRUDjson/pbl_artifact.json + json/pbl_evidence.json(文件名 = tblname不要给两张不同的表共用同一 alias xls2ui 会生成到同一目录互相覆盖)。 编码种子:init/data.jsonFormat B3 组 appcodes见下「编码字典」

契约接口13 个,与 pbl_evidence/init.pyload_pbl_evidence() env 注册一一对应)

# 契约函数 .dspy 路径(/pbl_evidence/api/... 说明
1 pbl_artifact_create pbl_artifact_create.dspy 新建产出物,自动补 tenant_id/artifact_uid/version_no=1/status=draft/created_at
2 pbl_artifact_read pbl_artifact_read.dspy 按 id 读,强制租户过滤
3 pbl_artifact_update pbl_artifact_update.dspy 白名单列更新,禁改 tenant_id/id
4 pbl_artifact_delete pbl_artifact_delete.dspy 带归属校验的删除
5 pbl_artifact_list pbl_artifact_list.dspy 分页 + 会话/蓝图/作者/类型/状态过滤
6 pbl_evidence_collect pbl_evidence_collect.dspy 单事件 → 一条证据;重复调用返回 deduped=True
7 pbl_evidence_collect_from_events pbl_evidence_collect_from_events.dspy M5a 主能力:扫 pbl_runtime_event → 映射 → 幂等落库,返回 scanned/created/skipped/updated/ignored/failed + watermark
8 pbl_evidence_list pbl_evidence_list.dspy 学习者/产出物/会话/蓝图/类型/时间窗过滤 + 分页
9 pbl_evidence_stats pbl_evidence_stats.dspy 证据类型分布统计,供 M6 评估与概览页
10 pbl_evidence_watermark 无独立 .dspy(见下) 返回某会话/蓝图已采集到的时间水位,供 M5b/cron 增量续采
11 pbl_artifact_options pbl_artifact_options.dspy CRUD 适配crud_api.py):产出物下拉数据源,返回裸数组 [{value,text}],异常降级 [];供 json/pbl_evidence.jsonalters.artifact_id.dataurl
12 pbl_evidence_update pbl_evidence_update.dspy CRUD 适配crud_api.py):人工修订证据,强制 tenant_id 归属校验;改唯一索引三元组时同步重算 dedup_key;供 editable.update_data_urldspy 返回 bricks Message widget JSON 字符串(成功 type=success / 异常 type=error不是裸数据
13 pbl_evidence_delete pbl_evidence_delete.dspy CRUD 适配crud_api.py):按 id/ids 删除证据,逐条校验归属;供 editable.delete_data_urldspy 返回 bricks Message widget JSON 字符串(成功 type=success / 异常 type=error不是裸数据

watermark 的调用方式(第 10 条契约刻意不生成 .dspy

  • 内部调用:宿主/cron/M5b 通过 ServerEnv().pbl_evidence_watermark(...) 直调;
  • 备选(若需走 HTTPpbl_evidence_stats.dspy 返回值中扩展 watermark 字段stats 内嵌), 当前实现 collector.evidence_stats 只返回 total/by_type尚未内嵌 —— 需要时改 stats 的 返回聚合(res['watermark'] = await _watermark(tid))即可,不必新增 .dspy
  • 若将来需要前端直接轮询,再新增 wwwroot/api/pbl_evidence_watermark.dspy,并同步登记 scripts/load_path.py + 中央 apps/pbls/scripts/load_path.py(双层 RBAC否则 403。

第 11~13 条是 CRUD 框架适配契约(定义在 pbl_evidence/crud_api.py,不改 api.py 同样走「三处同步」:crud_api.py 定义 → __init__.py 导出 → init.py env.xxx = xxx 并各自有 wwwroot/api/*.dspy 薄包装与 scripts/load_path.py 登记。漏任一处 = dspy 调用 NameError / 端点 404 / 登录后 403QC 硬门禁三条,实测都踩过)。 json/pbl_artifact.json 的 new/update/delete 指向既有 pbl_artifact_{create,update,delete}.dspy (委托 api.py 第 1/3/4 条契约),不经过 crud_api

除上述 13 个对外契约外,init.py 还注册了 4 个内部能力(供 M5b 回放 / M6 评估复用,不算对外契约、 无 .dspypbl_collect_evidence_from_eventspbl_resolve_event_tablepbl_evidence_typespbl_event_to_evidence_map。核对口径: api.py 契约定义 10 + crud_api.py 适配定义 3 == __init__.py 契约导出 13 == init.py 契约 env 注册 13 == wwwroot/api/*.dspy 12watermark 无 .dspy。 自查命令:grep -rn 'pbl_evidence_update' pbl_evidence/ --include='*.py' 必须命中 定义(crud_api.py) / init.py import / all / init.py import / env 注册。

editable 提交端点返回形态QC #15 收口,勿回退) pbl_evidence_update.dspy / pbl_evidence_delete.dspyjson/pbl_evidence.jsonupdate_data_url / delete_data_url 目标,即 DataViewer editable 提交端点,按 dspy-file-implementation-spec 必须返回 {"widgettype":"Message","options":{"title","message","type"}}JSON 字符串 json.dumps(..., ensure_ascii=False)),成功 type='success'、异常 type='error'。 后端 crud_api.py 仍返回结构化 dictok/status/id/message)供程序化调用, 由 dspy 负责转成 Message widget——这是 Pitfall 25「直接 return 被委托 helper」之外的 自行组装形态,故必须走 Message不得返回裸数据。 pbl_artifact_options.dspy下拉数据源(非 editable 端点),按 crud-definition-spec 继续返回裸数组 [{value,text}],异常降级 []不要改成 Message。 json/pbl_artifact.json 的 new/update/delete 走 api.py 契约端点(框架生成语义), 与上面两者形态不同属有意区分editable 提交 vs 契约直调),非目录内格式分裂。

编码字典init/data.jsonFormat B

parentid≤22 字符) 含义 子项
pbl_artifact_type 产出物类型 document/code/model/presentation/poster/video/dataset/other8
pbl_artifact_status 产出物状态 draft/submitted/reviewing/returned/graded/archived6
pbl_evidence_type 证据类型 取自 evidence_map.EVIDENCE_TYPES 8 类artifact/assessment/peer_review/reflection/milestone/collaboration/resource/observation

models/*.jsoncodes[].cond 一律 parentid='...'绝不用 id=),且三组 parentid 与 models/pbl_artifact.jsonmodels/pbl_evidence.json 中的 cond 完全一致。

陷阱

  • 库名一律 get_module_dbname('pbl_evidence').dspy 直接调用;.py 用 ServerEnv().get_module_dbname(...) 禁止硬编码 DBNAME;取不到库名直接抛 PblErrorfail-closed不退化成查错库。
  • sqlor 只有 C/U/D/R/I/sqlExe;查询统一走 pbl_common.apiq_all/q_one(已适配 sqlor 上下文)。 sor.I 只接 1 个参数(表名由上下文决定),sor.I('t', data) 会 TypeError。
  • 所有读写强制带 tenant_idpbl_common.api.tenant_id()),缺失即 fail-closed 报错。
  • dedup_key = md5(tenant_id|source_event_id|evidence_type)32 位定长,必须与 uk_ev_dedup 语义对齐; 改唯一索引必须同步改 evidence_map.build_dedup_key
  • 时间统一格式化为 YYYY-MM-DD HH:MM:SS 字符串(normalize_dt),避免 datetime/str 混用导致 sqlor 占位符绑定失败。
  • 未知事件类型降级为 observation(不丢事件);IGNORED_EVENTS 白名单外置在 evidence_map.py 新增噪声事件改这里,别在 SQL 里写死过滤。
  • 函数注册三处同步:① api.py 定义 → ② __init__.py 导出 → ③ init.py env.<name>=<name> 新增对外路径再加第 ④ 处 scripts/load_path.py+ 中央宿主清单,双层 RBAC禁通配符。 漏 ② → import 期 ImportError漏 ③ → .dspy NameError漏 ④ → 403。
  • 批量采集依赖 M11b 的 pbl_runtime_event;该表不存在时按设计抛 CollectError → PBL_E_DB_UNAVAILABLE(本地环境未联调,勿当成代码缺陷)。
  • dspy 内 dict 字面量禁写 f-stringQC #14{'message': f'...{e}'} 的花括号嵌在 dict 字面量里会被 ahserver 的 exec() 误判提前闭合外层 dict触发 SyntaxError: '{' was never closed整文件编译失败(不只是异常路径失效)。一律写字符串拼接 '证据更新失败:' + str(e)。顶部 debug(f'...') 前缀日志是推荐写法,不受此限。自查:grep -n "f'" wwwroot/api/*.dspy | grep -v debug → 无输出即合格。
  • wwwroot/{alias}/xls2ui 生成物)是只读构建产物,改逻辑改 json/*.json + models/*.json 后重跑 build。

依赖

  • 直接:sqlor(见 pyproject.toml,仅此一项)
  • 基础包(宿主 build.sh 安装,不写进 pyprojectapppublicahserverappbaserbac
  • 兄弟模块:pbl_common(租户上下文/错误码/时间序列化/查询封装)