fix(m5a): pyproject deps + SKILL.md 10契约 + README去幽灵引用 + init/data.json种子
This commit is contained in:
parent
35e272b01a
commit
f654cc2115
20
README.md
20
README.md
@ -127,8 +127,24 @@ RBAC_SET_PERM=/path/to/set_role_perm.py python3 scripts/load_path.py # 实际
|
||||
|
||||
自检(无需连库,可放 CI):
|
||||
|
||||
> **占位说明**:离线自检脚本(三处同步 / RBAC 清单 vs 磁盘 / 模型四段式 / 幂等键语义)
|
||||
> 由后续子任务 M5a-2 产出,**当前磁盘上尚不存在**,故本 README 不引用其文件名。
|
||||
> 在它落地前,用下列**当前磁盘上真实存在**的命令完成同等自检:
|
||||
|
||||
```bash
|
||||
python3 scripts/selfcheck_m5a.py # 三处同步 / RBAC 清单 vs 磁盘 / 模型四段式 / 幂等键语义
|
||||
# ① 依赖可安装(pyproject 只声明 sqlor,不拉基础包)
|
||||
pip install .
|
||||
|
||||
# ② 编码种子 JSON 合法 + parentid 与 models/*.json 的 codes.cond 一致
|
||||
python3 -c "import json;d=json.load(open('init/data.json'));print([g['parentid'] for g in d['appcodes']])"
|
||||
grep -o "parentid='[a-z_]*'" models/*.json | sort -u
|
||||
|
||||
# ③ 三处同步:api.py 契约定义数 == init.py env 契约注册数(对外契约应为 10)
|
||||
grep -c "^async def pbl_" pbl_evidence/api.py
|
||||
grep -c "^ env.pbl_" pbl_evidence/init.py
|
||||
|
||||
# ④ RBAC 清单与 wwwroot 磁盘一致性核对(不连库)
|
||||
python3 scripts/load_path.py --check
|
||||
```
|
||||
|
||||
## Integration(宿主挂载方式)
|
||||
@ -179,9 +195,9 @@ pbl_evidence/
|
||||
├── models/ # pbl_artifact.json / pbl_evidence.json(四段式表定义)
|
||||
├── json/ # CRUD 定义(tblname + params + editable)
|
||||
├── wwwroot/ # index.ui + api/ 9 个 .dspy(自动路由 /pbl_evidence/...)
|
||||
├── init/data.json # 编码种子(Format B:3 组 appcodes + appcodes_kv 子项)
|
||||
├── sql/pbl_evidence.sql # 建表 SQL 快照
|
||||
├── scripts/load_path.py # RBAC 登记(模块层,禁通配符)+ --check 一致性核对
|
||||
├── scripts/selfcheck_m5a.py # 离线自检(三处同步 / RBAC / 模型 / 幂等键)
|
||||
├── skill/SKILL.md # 面向 agent 的模块技能文档
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
44
init/data.json
Normal file
44
init/data.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"appcodes": [
|
||||
{
|
||||
"parentid": "pbl_artifact_type",
|
||||
"parentname": "PBL产出物类型",
|
||||
"items": [
|
||||
{"k": "document", "v": "文档报告"},
|
||||
{"k": "code", "v": "程序代码"},
|
||||
{"k": "model", "v": "模型/场景"},
|
||||
{"k": "presentation", "v": "演示汇报"},
|
||||
{"k": "poster", "v": "海报展板"},
|
||||
{"k": "video", "v": "音视频作品"},
|
||||
{"k": "dataset", "v": "数据/素材集"},
|
||||
{"k": "other", "v": "其他"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"parentid": "pbl_artifact_status",
|
||||
"parentname": "PBL产出物状态",
|
||||
"items": [
|
||||
{"k": "draft", "v": "草稿"},
|
||||
{"k": "submitted", "v": "已提交"},
|
||||
{"k": "reviewing", "v": "评审中"},
|
||||
{"k": "returned", "v": "退回修改"},
|
||||
{"k": "graded", "v": "已评分"},
|
||||
{"k": "archived", "v": "已归档"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"parentid": "pbl_evidence_type",
|
||||
"parentname": "PBL学习证据类型",
|
||||
"items": [
|
||||
{"k": "artifact", "v": "产出物"},
|
||||
{"k": "assessment", "v": "测评作答"},
|
||||
{"k": "peer_review", "v": "同伴互评"},
|
||||
{"k": "reflection", "v": "反思日志"},
|
||||
{"k": "milestone", "v": "里程碑达成"},
|
||||
{"k": "collaboration", "v": "协作行为"},
|
||||
{"k": "resource", "v": "资源使用"},
|
||||
{"k": "observation", "v": "一般观测"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -3,7 +3,12 @@ name = "pbl_evidence"
|
||||
version = "0.1.0"
|
||||
description = "产出物与证据幂等采集(M5a/M5b)"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["apppublic", "sqlor", "ahserver", "appbase", "rbac"]
|
||||
# 只声明直接代码依赖(PyPI 可解析)。
|
||||
# 基础包 apppublic / ahserver / appbase / rbac 由宿主应用 build.sh 单独 git 安装,
|
||||
# 不在 PyPI —— 写进来会让 `pip install .` 直接失败(module-development-spec 铁律)。
|
||||
# bricks_for_python:本模块 .py 未直接 import(前端仅 .ui/.dspy 静态资源),故不声明。
|
||||
# pbl_common:同机构 modules/ 下的兄弟模块,由宿主 pip install -e 装入,非 PyPI 包,故不声明。
|
||||
dependencies = ["sqlor"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61"]
|
||||
|
||||
@ -1,26 +1,86 @@
|
||||
# pbl_evidence 模块技能(自动生成骨架 + 人工补充)
|
||||
---
|
||||
name: pbl_evidence
|
||||
description: 产出物与证据幂等采集模块(M5a/M5b)——pbl_artifact/pbl_evidence 两表 CRUD、事件→证据幂等采集、证据列表/统计/水位查询。改本模块前先读完「契约接口」「陷阱」两节。
|
||||
---
|
||||
|
||||
# pbl_evidence 模块技能
|
||||
|
||||
## 定位
|
||||
产出物与证据幂等采集(M5a/M5b)
|
||||
产出物(artifact)管理与学习证据(evidence)**幂等采集**。M5a 提供产出物 CRUD + 单事件/批量事件落证据;
|
||||
M5b(回放/增量续采)直接复用本模块的采集器与水位接口。
|
||||
|
||||
## 挂载
|
||||
`from pbl_evidence.init import load_pbl_evidence` → `load_pbl_evidence()`(应用 app/pbls.py init() 中按序调用)
|
||||
```python
|
||||
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)
|
||||
- `pbl_evidence`:学习证据(幂等采集,唯一索引防重)
|
||||
- `pbl_artifact`:学生产出物(版本内联 `version_no`,唯一索引 `uk_art_uid(tenant_id, artifact_uid)`)
|
||||
- `pbl_evidence`:学习证据(唯一索引 `uk_ev_dedup(tenant_id, source_event_id, evidence_type)` 防重)
|
||||
|
||||
## 契约接口(7 个,路径 `/pbl_evidence/api/<name>.dspy`)
|
||||
- `pbl_artifact_create`
|
||||
- `pbl_artifact_read`
|
||||
- `pbl_artifact_update`
|
||||
- `pbl_artifact_delete`
|
||||
- `pbl_artifact_list`
|
||||
- `pbl_evidence_collect`
|
||||
- `pbl_evidence_list`
|
||||
表定义:`models/{table}.json`(四段式 summary/fields/indexes/codes);CRUD:`json/evidence_*.json`。
|
||||
编码种子:`init/data.json`(Format B,3 组 appcodes,见下「编码字典」)。
|
||||
|
||||
## 契约接口(10 个,与 `pbl_evidence/init.py` 契约段 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 增量续采 |
|
||||
|
||||
**watermark 的调用方式(第 10 条契约刻意不生成 .dspy)**:
|
||||
- 内部调用:宿主/cron/M5b 通过 `ServerEnv().pbl_evidence_watermark(...)` 直调;
|
||||
- 备选(若需走 HTTP):`pbl_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。
|
||||
|
||||
> 除上述 10 个对外契约外,`init.py` 还注册了 4 个**内部能力**(供 M5b 回放 / M6 评估复用,不算对外契约、
|
||||
> 无 .dspy):`pbl_collect_evidence_from_events`、`pbl_resolve_event_table`、`pbl_evidence_types`、
|
||||
> `pbl_event_to_evidence_map`。核对口径:`api.py` 契约定义 10 == `__init__.py` 契约导出 10
|
||||
> == `init.py` 契约 env 注册 10 == `wwwroot/api/*.dspy` 9(watermark 无 .dspy)。
|
||||
|
||||
## 编码字典(init/data.json,Format B)
|
||||
| parentid(≤22 字符) | 含义 | 子项 |
|
||||
|---|---|---|
|
||||
| `pbl_artifact_type` | 产出物类型 | document/code/model/presentation/poster/video/dataset/other(8) |
|
||||
| `pbl_artifact_status` | 产出物状态 | draft/submitted/reviewing/returned/graded/archived(6) |
|
||||
| `pbl_evidence_type` | 证据类型 | 取自 `evidence_map.EVIDENCE_TYPES` 8 类:artifact/assessment/peer_review/reflection/milestone/collaboration/resource/observation |
|
||||
|
||||
`models/*.json` 的 `codes[].cond` 一律 `parentid='...'`(**绝不用 `id=`**),且三组 parentid 与
|
||||
`models/pbl_artifact.json`、`models/pbl_evidence.json` 中的 cond 完全一致。
|
||||
|
||||
## 陷阱
|
||||
- 库名一律 `ServerEnv().get_module_dbname('pbl_evidence')`,禁止硬编码 DBNAME。
|
||||
- sqlor 只有 `C/U/D/R/I/sqlExe`;查询走 pbl_common.api 的 q_all/q_one(已适配)。
|
||||
- 所有读写强制带 `tenant_id`(pbl_common.api.tenant_id()),缺失即 fail-closed 报错。
|
||||
- 新增契约需同步三处:api.py 定义 + __init__.py 导出 + init.py env 注册 + scripts/load_path.py 路径。
|
||||
- 库名一律 `get_module_dbname('pbl_evidence')`(.dspy 直接调用;.py 用 `ServerEnv().get_module_dbname(...)`),
|
||||
**禁止硬编码 DBNAME**;取不到库名直接抛 `PblError`(fail-closed),不退化成查错库。
|
||||
- sqlor 只有 `C/U/D/R/I/sqlExe`;查询统一走 `pbl_common.api` 的 `q_all/q_one`(已适配 sqlor 上下文)。
|
||||
`sor.I` 只接 1 个参数(表名由上下文决定),`sor.I('t', data)` 会 TypeError。
|
||||
- 所有读写强制带 `tenant_id`(`pbl_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`(本地环境未联调,勿当成代码缺陷)。
|
||||
- `wwwroot/{alias}/`(xls2ui 生成物)是只读构建产物,改逻辑改 `json/*.json` + `models/*.json` 后重跑 build。
|
||||
|
||||
## 依赖
|
||||
- 直接:`sqlor`(见 `pyproject.toml`,仅此一项)
|
||||
- 基础包(宿主 build.sh 安装,不写进 pyproject):`apppublic`、`ahserver`、`appbase`、`rbac`
|
||||
- 兄弟模块:`pbl_common`(租户上下文/错误码/时间序列化/查询封装)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user