7.8 KiB
| name | description |
|---|---|
| pbl_blueprint | PBL 蓝图聚合根与子对象、版本、模板(M1a)。蓝图 CRUD/树/fork、7 类子对象泛化契约、版本 change_delta、模板实例化与离线兜底。所有读写 tenant_id 强制打头,缺失即 fail-closed 拒绝。 |
pbl_blueprint 模块技能
1. 模块定位
pbl_blueprint 是 PBL(Project-Based Learning)平台的蓝图域聚合根模块,负责:
- 蓝图主表
pbl_blueprint的 CRUD / 列表 / 树 / fork - 7 类子对象的泛化契约(一套 CRUD 覆盖 7 张表)
- 版本快照
pbl_blueprint_version(含change_delta增量、diff、rollback) - 模板
pbl_blueprint_template/pbl_blueprint_template_item(实例化 + 离线兜底)
模块是 Python 包(无 app.py / 无端口 / 无 Dockerfile),通过 load_pbl_blueprint(env) 挂到应用 apps/pbls。
2. 目录结构
modules/pbl_blueprint/
├── pyproject.toml
├── scripts/load_path.py # RBAC 路径注册(API/页面/表权限)
└── pbl_blueprint/ # 包目录 = 模块名(禁 src/)
├── __init__.py # 导出 load_pbl_blueprint
├── init.py # 装配入口 + env 注册 + 契约字典
├── db.py # 库名解析 / sqlor 获取 / 异常体系 / 建表
├── blueprint_crud.py # 蓝图 CRUD + 树 + fork + 版本契约
├── subobjects.py # 7 类子对象泛化 CRUD
├── templates.py # 模板 CRUD + 实例化 + 离线兜底
├── api.py # HTTP API 层 + API_PATHS + HANDLERS
├── models/*.json # 表定义(四段式 summary/fields/indexes/codes)
├── json/*.json # CRUD 定义(根键 tblname + params.editable/browserfields)
├── sql/pbl_blueprint.core.sql # 核心 4 表 DDL
├── sql/pbl_blueprint.subobjects.sql # 子对象 7 表 DDL
├── init/data.json # 初始化数据(内置模板)
└── wwwroot/index.ui # 前端页面
3. 数据表(11 张)
核心 4 张:pbl_blueprint、pbl_blueprint_version、pbl_blueprint_template、pbl_blueprint_template_item
子对象 7 张:pbl_blueprint_task、pbl_blueprint_mission、pbl_blueprint_role、
pbl_blueprint_learning_goal、pbl_blueprint_evidence_spec、pbl_blueprint_artifact_spec、
pbl_blueprint_reflection_spec
统一约定:
- 主键
id= varchar(32),32 位无横线 UUID(db.new_id()) tenant_idvarchar(32) NOT NULL,每个索引首列- 软删除
deletedtinyint(0 正常 / 1 已删除) - 结构化字段用
json类型(spec/config/snapshot/change_delta/rule/permissions) - 审计五件套
created_by/created_at/updated_by/updated_at/deleted
4. 子对象泛化契约(重点)
7 类子对象共用一套函数,obj_type 分派到表:
| obj_type | 表 | 编码前缀 | 特有字段 |
|---|---|---|---|
| task | pbl_blueprint_task | TASK | parent_id |
| mission | pbl_blueprint_mission | MISSION | task_id(必填), unlock_condition |
| role | pbl_blueprint_role | ROLE | task_id, permissions |
| learning_goal | pbl_blueprint_learning_goal | GOAL | task_id, dimension, weight |
| evidence_spec | pbl_blueprint_evidence_spec | EVI | task_id, mission_id, learning_goal_id, evidence_type, collect_mode, rule, weight |
| artifact_spec | pbl_blueprint_artifact_spec | ART | task_id, mission_id, artifact_type, accept_criteria, required, weight |
| reflection_spec | pbl_blueprint_reflection_spec | REF | task_id, mission_id, trigger_point, reflection_type, rubric_ref, weight |
from pbl_blueprint import load_pbl_blueprint
contract = load_pbl_blueprint(env)
contract['create_subobject'](tenant_id, 'mission', {'blueprint_id': bp_id, 'task_id': t_id, 'name': '关卡1'}, env=env)
contract['list_subobjects'](tenant_id, 'task', bp_id, env=env)
contract['batch_upsert_subobjects'](tenant_id, 'task', bp_id, rows, env=env) # Designer Agent 整树写入
5. 蓝图契约
create_blueprint(tenant_id, data, env, operator) # 自动生成 code PBL-{yyyyMMdd}-{seq4}
update_blueprint(tenant_id, blueprint_id, data, ...) # 状态流转校验 STATUS_FLOW
delete_blueprint(tenant_id, blueprint_id, ...) # 软删(连带子对象+版本)
get_blueprint(tenant_id, blueprint_id, with_children) # with_children=True 附带 7 类子树
list_blueprints(tenant_id, filters, page, page_size) # 分页 + keyword/status/subject 过滤
get_blueprint_tree(tenant_id, blueprint_id) # {obj_type: [rows]}
fork_blueprint(tenant_id, blueprint_id, new_name, target_tenant_id) # 跨租户复制,ID 重映射
状态机:draft → validating → validated → compiling → compiled → published → archived(非法流转抛 PblValidationError)。
6. 版本契约
save_version(tenant_id, blueprint_id, remark) # 存全量 snapshot + 计算 change_delta,主表 version_no 自增
list_versions(tenant_id, blueprint_id, page, page_size)
diff_versions(tenant_id, blueprint_id, version_a, version_b) # {added, updated, removed}
rollback_version(tenant_id, blueprint_id, version_no) # 先自动存当前版本再回滚
change_delta 结构:{"added":[{"obj_type","id","name"}], "updated":[{"obj_type","id","fields"}], "removed":[...]}
7. 模板与离线兜底
create_template(tenant_id, {'name':..., 'items':[{'obj_type':'task','ref_key':'T1','name':...}, ...]})
instantiate_template(tenant_id, template_id, name, overrides) # ref_key/parent_ref 解析为真实 ID,use_count 自增
build_offline_fallback_blueprint(tenant_id, name) # Agent/网络不可用时的最小可用蓝图(9 条子对象)
模板条目用 ref_key / parent_ref 做模板内相对引用,实例化时按 SUBOBJECT_TYPES 顺序解析为真实 ID(父先于子)。
8. tenant_id 强制打头(铁律)
- 每个契约函数第一入参
tenant_id,内部db.require_tenant()校验 - 缺失/空/非字符串 → 抛
PblTenantRequired(code=PBL_TENANT_REQUIRED),fail-closed 不放行 - 所有 where 条件由
_where(tenant_id, extra)构造,tenant_id=永远在最前 - API 层
_tid(params)从入参或context.tenant_id取,取不到直接失败 - 平台内置模板
tenant_id='platform',list_templates(include_platform=True)才并入
9. 异常码
| code | 含义 |
|---|---|
| PBL_TENANT_REQUIRED | 租户上下文缺失 |
| PBL_VALIDATION_ERROR | 入参校验失败 |
| PBL_NOT_FOUND / PBL_BLUEPRINT_NOT_FOUND / PBL_SUBOBJECT_NOT_FOUND / PBL_VERSION_NOT_FOUND / PBL_TEMPLATE_NOT_FOUND | 记录不存在 |
| PBL_UNKNOWN_OBJ_TYPE | 未知子对象类型 |
| PBL_DB_UNAVAILABLE | sqlor 不可用 |
10. 数据访问约束
- 库名:
ServerEnv().get_module_dbname('pbl_blueprint'),禁止硬编码 DBNAME - SQL 只用 sqlor 标准 API:
sor.C / sor.U / sor.D / sor.R / sor.sqlExe,禁编造 save/list/insert - 建表:
db.ensure_tables()幂等执行sql/*.sql(CREATE TABLE IF NOT EXISTS)
11. API 路径(25 个,见 api.py API_PATHS)
前缀 /pbl_blueprint/,分四组:blueprint/*(7)、version/*(4)、subobject/*(7)、template/*(7)。
RBAC 注册:python -m pbl_blueprint.scripts.load_path(幂等,注册 API + 页面 + 表权限共 39 条路径)。
12. 下游模块依赖
pbl_validation(M2)读get_blueprint_tree做 14 维校验,回写quality_levelpbl_compiler(M3)读蓝图树生成 Game Definitionpbl_evidence(M5)按evidence_spec/artifact_spec采集pbl_assessment(M6)按learning_goal.weight/reflection_spec.rubric_ref评分pbl_agent_runtime(M4)用batch_upsert_subobjects写 Designer 产出,fail-closed 裁决