feat(pbl_assessment): 初始提交 - pbls 项目模块代码入库
This commit is contained in:
parent
da6edabf03
commit
1e6f5ee38a
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
.DS_Store
|
||||
*.log
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
.venv/
|
||||
venv/
|
||||
244
json/assessment_pbl_assessment_record.json
Normal file
244
json/assessment_pbl_assessment_record.json
Normal file
@ -0,0 +1,244 @@
|
||||
{
|
||||
"tblname": "pbl_assessment_record",
|
||||
"alias": "pbl_assessment",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"record_uid": {
|
||||
"label": "记录UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"learner_id": {
|
||||
"label": "学习者",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"session_id": {
|
||||
"label": "会话",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "蓝图",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"artifact_id": {
|
||||
"label": "产出物",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"evidence_id": {
|
||||
"label": "主证据",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"rubric_id": {
|
||||
"label": "Rubric",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"scores_json": {
|
||||
"label": "逐准则得分",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"total_score": {
|
||||
"label": "加权总分",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"max_score": {
|
||||
"label": "满分",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"band": {
|
||||
"label": "等级",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"alignment_json": {
|
||||
"label": "与学习目标对齐",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"feedback_txt": {
|
||||
"label": "反馈",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"assessor_type": {
|
||||
"label": "评估者类型",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"assessor_id": {
|
||||
"label": "评估者",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"record_uid": {
|
||||
"label": "记录UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"learner_id": {
|
||||
"label": "学习者",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"session_id": {
|
||||
"label": "会话",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "蓝图",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"artifact_id": {
|
||||
"label": "产出物",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"evidence_id": {
|
||||
"label": "主证据",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"rubric_id": {
|
||||
"label": "Rubric",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"scores_json": {
|
||||
"label": "逐准则得分",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"total_score": {
|
||||
"label": "加权总分",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"max_score": {
|
||||
"label": "满分",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"band": {
|
||||
"label": "等级",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"alignment_json": {
|
||||
"label": "与学习目标对齐",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"feedback_txt": {
|
||||
"label": "反馈",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"assessor_type": {
|
||||
"label": "评估者类型",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"assessor_id": {
|
||||
"label": "评估者",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"record_uid": {
|
||||
"label": "记录UID",
|
||||
"type": "text"
|
||||
},
|
||||
"learner_id": {
|
||||
"label": "学习者",
|
||||
"type": "text"
|
||||
},
|
||||
"session_id": {
|
||||
"label": "会话",
|
||||
"type": "number"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "蓝图",
|
||||
"type": "number"
|
||||
},
|
||||
"artifact_id": {
|
||||
"label": "产出物",
|
||||
"type": "number"
|
||||
},
|
||||
"evidence_id": {
|
||||
"label": "主证据",
|
||||
"type": "number"
|
||||
},
|
||||
"rubric_id": {
|
||||
"label": "Rubric",
|
||||
"type": "number"
|
||||
},
|
||||
"scores_json": {
|
||||
"label": "逐准则得分",
|
||||
"type": "text"
|
||||
},
|
||||
"total_score": {
|
||||
"label": "加权总分",
|
||||
"type": "number"
|
||||
},
|
||||
"max_score": {
|
||||
"label": "满分",
|
||||
"type": "number"
|
||||
},
|
||||
"band": {
|
||||
"label": "等级",
|
||||
"type": "select"
|
||||
},
|
||||
"alignment_json": {
|
||||
"label": "与学习目标对齐",
|
||||
"type": "text"
|
||||
},
|
||||
"feedback_txt": {
|
||||
"label": "反馈",
|
||||
"type": "text"
|
||||
},
|
||||
"assessor_type": {
|
||||
"label": "评估者类型",
|
||||
"type": "select"
|
||||
},
|
||||
"assessor_id": {
|
||||
"label": "评估者",
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
146
json/assessment_pbl_rubric.json
Normal file
146
json/assessment_pbl_rubric.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"tblname": "pbl_rubric",
|
||||
"alias": "pbl_assessment",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"code": {
|
||||
"label": "编码",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"title": {
|
||||
"label": "标题",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "蓝图",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"criteria_json": {
|
||||
"label": "准则数组(含 learning_goal_id/weight)",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"total_weight": {
|
||||
"label": "权重合计",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"version_no": {
|
||||
"label": "版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"approval_id": {
|
||||
"label": "变更审批单",
|
||||
"type": "number",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"code": {
|
||||
"label": "编码",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"title": {
|
||||
"label": "标题",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "蓝图",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"criteria_json": {
|
||||
"label": "准则数组(含 learning_goal_id/weight)",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"total_weight": {
|
||||
"label": "权重合计",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"version_no": {
|
||||
"label": "版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"approval_id": {
|
||||
"label": "变更审批单",
|
||||
"type": "number",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"code": {
|
||||
"label": "编码",
|
||||
"type": "select"
|
||||
},
|
||||
"title": {
|
||||
"label": "标题",
|
||||
"type": "select"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "蓝图",
|
||||
"type": "number"
|
||||
},
|
||||
"criteria_json": {
|
||||
"label": "准则数组(含 learning_goal_id/weight)",
|
||||
"type": "text"
|
||||
},
|
||||
"total_weight": {
|
||||
"label": "权重合计",
|
||||
"type": "number"
|
||||
},
|
||||
"version_no": {
|
||||
"label": "版本",
|
||||
"type": "number"
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select"
|
||||
},
|
||||
"approval_id": {
|
||||
"label": "变更审批单",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
167
models/pbl_assessment_record.json
Normal file
167
models/pbl_assessment_record.json
Normal file
@ -0,0 +1,167 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_assessment_record",
|
||||
"comment": "评估记录(total_score 派生 + 对齐判定)",
|
||||
"module": "pbl_assessment",
|
||||
"engine": "mariadb",
|
||||
"charset": "utf8mb4",
|
||||
"tenant_scoped": true
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"comment": "租户ID(强制打头)",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"comment": "主键",
|
||||
"null": false,
|
||||
"type": "int",
|
||||
"length": 20,
|
||||
"unsigned": true,
|
||||
"auto_increment": true
|
||||
},
|
||||
{
|
||||
"name": "record_uid",
|
||||
"comment": "记录UID",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "learner_id",
|
||||
"comment": "学习者",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "session_id",
|
||||
"comment": "会话",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "blueprint_id",
|
||||
"comment": "蓝图",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "artifact_id",
|
||||
"comment": "产出物",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "evidence_id",
|
||||
"comment": "主证据",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "rubric_id",
|
||||
"comment": "Rubric",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "scores_json",
|
||||
"comment": "逐准则得分",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"name": "total_score",
|
||||
"comment": "加权总分",
|
||||
"null": false,
|
||||
"type": "double",
|
||||
"precision": 18,
|
||||
"scale": 2
|
||||
},
|
||||
{
|
||||
"name": "max_score",
|
||||
"comment": "满分",
|
||||
"null": false,
|
||||
"type": "double",
|
||||
"precision": 18,
|
||||
"scale": 2
|
||||
},
|
||||
{
|
||||
"name": "band",
|
||||
"comment": "等级",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "alignment_json",
|
||||
"comment": "与学习目标对齐",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"name": "feedback_txt",
|
||||
"comment": "反馈",
|
||||
"null": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "assessor_type",
|
||||
"comment": "评估者类型",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "assessor_id",
|
||||
"comment": "评估者",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"comment": "创建时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"comment": "更新时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "uk_ar_uid",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"record_uid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_ar_learner",
|
||||
"unique": false,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"learner_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
114
models/pbl_rubric.json
Normal file
114
models/pbl_rubric.json
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_rubric",
|
||||
"comment": "Rubric(准则内联 criteria_json,权重合计=100%)",
|
||||
"module": "pbl_assessment",
|
||||
"engine": "mariadb",
|
||||
"charset": "utf8mb4",
|
||||
"tenant_scoped": true
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"comment": "租户ID(强制打头)",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"comment": "主键",
|
||||
"null": false,
|
||||
"type": "int",
|
||||
"length": 20,
|
||||
"unsigned": true,
|
||||
"auto_increment": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"comment": "编码",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"comment": "标题",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 128
|
||||
},
|
||||
{
|
||||
"name": "blueprint_id",
|
||||
"comment": "蓝图",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "criteria_json",
|
||||
"comment": "准则数组(含 learning_goal_id/weight)",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"name": "total_weight",
|
||||
"comment": "权重合计",
|
||||
"null": false,
|
||||
"type": "double",
|
||||
"precision": 18,
|
||||
"scale": 2
|
||||
},
|
||||
{
|
||||
"name": "version_no",
|
||||
"comment": "版本",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"comment": "状态",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "approval_id",
|
||||
"comment": "变更审批单",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"comment": "创建时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"comment": "更新时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "uk_ru_code",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"code",
|
||||
"version_no"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
27
pbl_assessment/__init__.py
Normal file
27
pbl_assessment/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_assessment —— Rubric 加权评估与报告(M6)
|
||||
|
||||
注册三处同步之 ②:必须导出 init.py 里的全部契约函数,漏一行 .dspy 调用即 NameError。
|
||||
"""
|
||||
from pbl_assessment.init import load_pbl_assessment
|
||||
from pbl_assessment.api import (
|
||||
pbl_rubric_save,
|
||||
pbl_rubric_get,
|
||||
pbl_rubric_list,
|
||||
pbl_assessment_score,
|
||||
pbl_assessment_record_get,
|
||||
pbl_assessment_report,
|
||||
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'load_pbl_assessment',
|
||||
'pbl_rubric_save',
|
||||
'pbl_rubric_get',
|
||||
'pbl_rubric_list',
|
||||
'pbl_assessment_score',
|
||||
'pbl_assessment_record_get',
|
||||
'pbl_assessment_report',
|
||||
|
||||
]
|
||||
180
pbl_assessment/api.py
Normal file
180
pbl_assessment/api.py
Normal file
@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_assessment.api —— Rubric 加权评估与报告(第34章,M6)。
|
||||
|
||||
- criteria_json 内联准则数组(含 learning_goal_id/weight),**权重合计必须 = 100%**(Q-OPEN-3);
|
||||
- 准则变更需人工审批(14.2 modify_assessment_criteria),无 approval_id 的已存在 Rubric 变更直接拒绝;
|
||||
- total_score 为派生值(Σ 得分×权重),不接收客户端直传。
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from pbl_common.api import (PblError, actor_id, crud, json_dump, now_str, sql_exec,
|
||||
sql_rows, sql_scalar, tenant_id)
|
||||
|
||||
BANDS = [(90, 'excellent'), (75, 'proficient'), (60, 'developing'), (0, 'beginning')]
|
||||
|
||||
|
||||
def _loads(v, d=None):
|
||||
if isinstance(v, (dict, list)):
|
||||
return v
|
||||
try:
|
||||
return json.loads(v) if v else (d or {})
|
||||
except ValueError:
|
||||
return d or {}
|
||||
|
||||
|
||||
async def _tid():
|
||||
return await tenant_id()
|
||||
|
||||
|
||||
RUB = crud('pbl_rubric', 'pbl', ['code', 'title', 'blueprint_id', 'criteria_json',
|
||||
'total_weight', 'version_no', 'status', 'approval_id'])
|
||||
REC = crud('pbl_assessment_record', 'pbl', ['record_uid', 'learner_id', 'session_id',
|
||||
'blueprint_id', 'artifact_id', 'evidence_id',
|
||||
'rubric_id', 'scores_json', 'total_score',
|
||||
'max_score', 'band', 'alignment_json',
|
||||
'feedback_txt', 'assessor_type', 'assessor_id'])
|
||||
|
||||
|
||||
async def pbl_rubric_save(**kw):
|
||||
tid = await _tid()
|
||||
criteria = _loads(kw.get('criteria_json'), [])
|
||||
if not isinstance(criteria, list) or not criteria:
|
||||
raise PblError('PBL_RUBRIC_EMPTY', 'criteria_json 须为非空数组')
|
||||
total = sum(float(c.get('weight') or 0) for c in criteria)
|
||||
if abs(total - 100.0) > 0.01:
|
||||
raise PblError('PBL_RUBRIC_WEIGHT_INVALID', '权重合计须=100%%,实际 %.2f' % total)
|
||||
for c in criteria:
|
||||
if not (c.get('learning_goal_id') or c.get('learning_goal_uid')):
|
||||
raise PblError('PBL_RUBRIC_ALIGNMENT_MISSING',
|
||||
'准则 %s 缺 learning_goal_id(无法做对齐判定)' % c.get('code'))
|
||||
payload = dict(kw)
|
||||
payload['criteria_json'] = json_dump(criteria)
|
||||
payload['total_weight'] = round(total, 2)
|
||||
payload.setdefault('version_no', 1)
|
||||
payload.setdefault('status', 'draft')
|
||||
if kw.get('id'):
|
||||
cur = await RUB['read'](id=kw['id'])
|
||||
if cur['data'].get('version_no', 1) >= 1 and kw.get('status') in ('published', 'active') \
|
||||
and not kw.get('approval_id'):
|
||||
raise PblError('PBL_APPROVAL_REQUIRED',
|
||||
'评估标准变更须人工审批(14.2),缺 approval_id')
|
||||
return await RUB['update'](**payload)
|
||||
payload.setdefault('code', 'RU%s' % hashlib.sha256(
|
||||
(str(kw.get('title')) + now_str()).encode()).hexdigest()[:16])
|
||||
r = await RUB['create'](**payload)
|
||||
r['code'] = payload['code']
|
||||
r['weight_total_ok'] = True
|
||||
return r
|
||||
|
||||
|
||||
async def pbl_rubric_get(**kw):
|
||||
tid = await _tid()
|
||||
rows = await sql_rows('SELECT * FROM `pbl_rubric` WHERE `tenant_id` = ${t}$'
|
||||
' AND (`id` = ${id}$ OR `code` = ${c}$) LIMIT 1',
|
||||
{'t': tid, 'id': kw.get('id') or 0, 'c': kw.get('code') or ''}, 'pbl')
|
||||
if not rows:
|
||||
raise PblError('PBL_NOT_FOUND', 'Rubric 不存在')
|
||||
rows[0]['criteria_json'] = _loads(rows[0].get('criteria_json'), [])
|
||||
return {'ok': True, 'data': rows[0]}
|
||||
|
||||
|
||||
async def pbl_rubric_list(**kw):
|
||||
return await RUB['list'](**kw)
|
||||
|
||||
|
||||
async def pbl_assessment_score(**kw):
|
||||
"""按 Rubric 逐准则打分 → 加权总分 + 等级 + 学习目标对齐结论。"""
|
||||
tid = await _tid()
|
||||
rubric_id = kw.get('rubric_id')
|
||||
rr = await sql_rows('SELECT * FROM `pbl_rubric` WHERE `tenant_id` = ${t}$'
|
||||
' AND `id` = ${id}$ LIMIT 1', {'t': tid, 'id': rubric_id}, 'pbl')
|
||||
if not rr:
|
||||
raise PblError('PBL_NOT_FOUND', 'Rubric 不存在')
|
||||
rubric = rr[0]
|
||||
criteria = _loads(rubric['criteria_json'], [])
|
||||
scores = _loads(kw.get('scores_json'), {}) or {}
|
||||
rows, weighted, max_w = [], 0.0, 0.0
|
||||
for c in criteria:
|
||||
w = float(c.get('weight') or 0)
|
||||
raw = scores.get(c.get('code') or c.get('uid'))
|
||||
pts = float(raw) if raw not in (None, '') else 0.0
|
||||
cmax = float(c.get('max_points') or 5)
|
||||
ratio = min(1.0, pts / cmax) if cmax else 0.0
|
||||
weighted += ratio * 100 * w / 100.0
|
||||
max_w += w
|
||||
rows.append({'criterion': c.get('code'), 'learning_goal_id':
|
||||
c.get('learning_goal_id') or c.get('learning_goal_uid'),
|
||||
'weight': w, 'points': pts, 'max_points': cmax,
|
||||
'contribution': round(ratio * w, 2)})
|
||||
total = round(weighted, 2)
|
||||
band = next(b[1] for b in BANDS if total >= b[0])
|
||||
ev = await sql_rows('SELECT `evidence_type`,`id` FROM `pbl_evidence` WHERE `tenant_id` = ${t}$'
|
||||
' AND `learner_id` = ${l}$ ORDER BY `occurred_at` DESC LIMIT ${n}$',
|
||||
{'t': tid, 'l': kw.get('learner_id') or await actor_id(), 'n': 50}, 'pbl')
|
||||
have = {r['evidence_type'] for r in ev}
|
||||
spec_rows = await sql_rows('SELECT DISTINCT `evidence_type` FROM `pbl_blueprint_evidence_spec`'
|
||||
' WHERE `tenant_id` = ${t}$ AND `blueprint_id` = ${b}$',
|
||||
{'t': tid, 'b': rubric.get('blueprint_id') or 0}, 'pbl')
|
||||
need = {s['evidence_type'] for s in spec_rows}
|
||||
alignment = {'criteria_count': len(criteria), 'scored': len([r for r in rows if r['points']]),
|
||||
'goals_covered': len({r['learning_goal_id'] for r in rows if r['points']}),
|
||||
'evidence_present': sorted(have), 'evidence_required': sorted(need),
|
||||
'evidence_missing': sorted(need - have)}
|
||||
payload = {'record_uid': 'AS%s' % hashlib.sha256(
|
||||
('%s%s%s' % (rubric_id, kw.get('learner_id'), now_str())).encode())
|
||||
.hexdigest()[:20],
|
||||
'learner_id': kw.get('learner_id') or await actor_id(),
|
||||
'session_id': kw.get('session_id'), 'blueprint_id': rubric.get('blueprint_id'),
|
||||
'artifact_id': kw.get('artifact_id'), 'evidence_id': kw.get('evidence_id'),
|
||||
'rubric_id': rubric_id, 'scores_json': json_dump(rows),
|
||||
'total_score': total, 'max_score': round(max_w * 100 / 100.0, 2), 'band': band,
|
||||
'alignment_json': json_dump(alignment), 'feedback_txt': kw.get('feedback_txt'),
|
||||
'assessor_type': kw.get('assessor_type') or 'teacher',
|
||||
'assessor_id': await actor_id()}
|
||||
r = await REC['create'](**payload)
|
||||
r['record_uid'] = payload['record_uid']
|
||||
r['total_score'], r['band'], r['alignment'] = total, band, alignment
|
||||
r['derived'] = 'total_score 由 Σ(得分率×权重) 派生,客户端不可直传'
|
||||
return r
|
||||
|
||||
|
||||
async def pbl_assessment_record_get(**kw):
|
||||
r = await REC['read'](**kw)
|
||||
r['data']['scores_json'] = _loads(r['data'].get('scores_json'), [])
|
||||
r['data']['alignment_json'] = _loads(r['data'].get('alignment_json'), {})
|
||||
return r
|
||||
|
||||
|
||||
async def pbl_assessment_report(**kw):
|
||||
"""教师视图:按学习者聚合评估记录 + 证据覆盖 + 质量状态联动建议。"""
|
||||
tid = await _tid()
|
||||
where, params = ['`tenant_id` = ${t}$'], {'t': tid}
|
||||
for col in ('learner_id', 'blueprint_id', 'session_id'):
|
||||
if kw.get(col) not in (None, ''):
|
||||
where.append('`%s` = ${%s}$' % (col, col))
|
||||
params[col] = kw[col]
|
||||
rows = await sql_rows('SELECT * FROM `pbl_assessment_record` WHERE %s'
|
||||
' ORDER BY `total_score` DESC LIMIT ${n}$' % ' AND '.join(where),
|
||||
dict(params, n=min(int(kw.get('limit') or 100), 500)), 'pbl')
|
||||
learners = {}
|
||||
for r in rows:
|
||||
learners.setdefault(r['learner_id'], []).append(r)
|
||||
summary = [{'learner_id': k, 'records': len(v),
|
||||
'avg_score': round(sum(float(x['total_score'] or 0) for x in v) / len(v), 2),
|
||||
'best_band': v[0]['band'],
|
||||
'missing_evidence': sorted({m for x in v for m in
|
||||
(_loads(x.get('alignment_json'), {})
|
||||
.get('evidence_missing') or [])})}
|
||||
for k, v in sorted(learners.items())]
|
||||
bp_state = None
|
||||
if kw.get('blueprint_id'):
|
||||
bp_state = await sql_scalar('SELECT `quality_state` FROM `pbl_blueprint`'
|
||||
' WHERE `tenant_id` = ${t}$ AND `id` = ${b}$',
|
||||
{'t': tid, 'b': kw['blueprint_id']}, 'pbl')
|
||||
return {'ok': True, 'learners': summary, 'record_count': len(rows),
|
||||
'blueprint_quality_state': bp_state,
|
||||
'playtest_ready_hint': ('已具备 playtest_ready 证据链' if rows and
|
||||
all(not s['missing_evidence'] for s in summary)
|
||||
else '存在证据缺口,建议保持 pbl_ready 并补证据')}
|
||||
29
pbl_assessment/init.py
Normal file
29
pbl_assessment/init.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""`load_pbl_assessment()` —— pbl_assessment 模块唯一挂载入口。
|
||||
|
||||
注册三处同步之 ③:env.<契约名> = <契约名>(① 定义在 api.py,② 导出在 __init__.py)。
|
||||
"""
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
from pbl_assessment.api import (
|
||||
pbl_rubric_save,
|
||||
pbl_rubric_get,
|
||||
pbl_rubric_list,
|
||||
pbl_assessment_score,
|
||||
pbl_assessment_record_get,
|
||||
pbl_assessment_report,
|
||||
|
||||
)
|
||||
|
||||
|
||||
def load_pbl_assessment():
|
||||
env = ServerEnv()
|
||||
env.pbl_rubric_save = pbl_rubric_save
|
||||
env.pbl_rubric_get = pbl_rubric_get
|
||||
env.pbl_rubric_list = pbl_rubric_list
|
||||
env.pbl_assessment_score = pbl_assessment_score
|
||||
env.pbl_assessment_record_get = pbl_assessment_record_get
|
||||
env.pbl_assessment_report = pbl_assessment_report
|
||||
|
||||
return 'pbl_assessment'
|
||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "pbl_assessment"
|
||||
version = "0.1.0"
|
||||
description = "Rubric 加权评估与报告(M6)"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["apppublic", "sqlor", "ahserver", "appbase", "rbac"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["pbl_assessment"]
|
||||
45
scripts/load_path.py
Normal file
45
scripts/load_path.py
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_assessment RBAC 路径注册(硬门禁 6.6 / QC #11)。
|
||||
|
||||
约定:
|
||||
- 路径 = 模块自动路由 `/pbl_assessment/api/<契约>.dspy`,不带端口、不带 /wss 前缀;
|
||||
- 角色 `logined` = 登录即可访问的读接口;写接口按角色分级(teacher/admin);
|
||||
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过)。
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
MODULE = 'pbl_assessment'
|
||||
|
||||
# (path, role)
|
||||
PATHS = [
|
||||
('/pbl_assessment/api/pbl_rubric_save.dspy', 'logined'),
|
||||
('/pbl_assessment/api/pbl_rubric_get.dspy', 'logined'),
|
||||
('/pbl_assessment/api/pbl_rubric_list.dspy', 'logined'),
|
||||
('/pbl_assessment/api/pbl_assessment_score.dspy', 'logined'),
|
||||
('/pbl_assessment/api/pbl_assessment_record_get.dspy', 'logined'),
|
||||
('/pbl_assessment/api/pbl_assessment_report.dspy', 'logined'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
|
||||
done, missing = 0, []
|
||||
for path, role in PATHS:
|
||||
if subprocess.call([sys.executable if os.environ.get('PY') else 'python3',
|
||||
tool, role, path],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
|
||||
done += 1
|
||||
else:
|
||||
missing.append((path, role))
|
||||
print('[%s] rbac paths: total=%%d ok=%%d pending=%%d' %% (len(PATHS), done, len(missing)))
|
||||
for path, role in missing:
|
||||
print(' PENDING %%-12s %%s' %% (role, path))
|
||||
return len(missing) == 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(0 if register() else 1)
|
||||
25
skill/SKILL.md
Normal file
25
skill/SKILL.md
Normal file
@ -0,0 +1,25 @@
|
||||
# pbl_assessment 模块技能(自动生成骨架 + 人工补充)
|
||||
|
||||
## 定位
|
||||
Rubric 加权评估与报告(M6)
|
||||
|
||||
## 挂载
|
||||
`from pbl_assessment.init import load_pbl_assessment` → `load_pbl_assessment()`(应用 app/pbls.py init() 中按序调用)
|
||||
|
||||
## 数据表(2 张)
|
||||
- `pbl_rubric`:Rubric(准则内联 criteria_json,权重合计=100%)
|
||||
- `pbl_assessment_record`:评估记录(total_score 派生 + 对齐判定)
|
||||
|
||||
## 契约接口(6 个,路径 `/pbl_assessment/api/<name>.dspy`)
|
||||
- `pbl_rubric_save`
|
||||
- `pbl_rubric_get`
|
||||
- `pbl_rubric_list`
|
||||
- `pbl_assessment_score`
|
||||
- `pbl_assessment_record_get`
|
||||
- `pbl_assessment_report`
|
||||
|
||||
## 陷阱
|
||||
- 库名一律 `ServerEnv().get_module_dbname('pbl_assessment')`,禁止硬编码 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 路径。
|
||||
43
sql/pbl_assessment.sql
Normal file
43
sql/pbl_assessment.sql
Normal file
@ -0,0 +1,43 @@
|
||||
-- pbl_assessment 表 DDL(自动生成,与 apps/pbls/scripts/ddl/pbls_tables.sql 同源)
|
||||
CREATE TABLE IF NOT EXISTS `pbl_rubric` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`code` VARCHAR(64) NOT NULL COMMENT "编码",
|
||||
`title` VARCHAR(128) NOT NULL COMMENT "标题",
|
||||
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "蓝图",
|
||||
`criteria_json` LONGTEXT NULL COMMENT "准则数组(含 learning_goal_id/weight)",
|
||||
`total_weight` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT "权重合计",
|
||||
`version_no` INT NOT NULL DEFAULT 0 COMMENT "版本",
|
||||
`status` VARCHAR(64) NOT NULL COMMENT "状态",
|
||||
`approval_id` BIGINT UNSIGNED NOT NULL COMMENT "变更审批单",
|
||||
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_ru_code` (`tenant_id`, `code`, `version_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="Rubric(准则内联 criteria_json,权重合计=100%)";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pbl_assessment_record` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`record_uid` VARCHAR(32) NOT NULL COMMENT "记录UID",
|
||||
`learner_id` VARCHAR(32) NOT NULL COMMENT "学习者",
|
||||
`session_id` BIGINT UNSIGNED NOT NULL COMMENT "会话",
|
||||
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "蓝图",
|
||||
`artifact_id` BIGINT UNSIGNED NOT NULL COMMENT "产出物",
|
||||
`evidence_id` BIGINT UNSIGNED NOT NULL COMMENT "主证据",
|
||||
`rubric_id` BIGINT UNSIGNED NOT NULL COMMENT "Rubric",
|
||||
`scores_json` LONGTEXT NULL COMMENT "逐准则得分",
|
||||
`total_score` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT "加权总分",
|
||||
`max_score` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT "满分",
|
||||
`band` VARCHAR(64) NOT NULL COMMENT "等级",
|
||||
`alignment_json` LONGTEXT NULL COMMENT "与学习目标对齐",
|
||||
`feedback_txt` TEXT NULL COMMENT "反馈",
|
||||
`assessor_type` VARCHAR(64) NOT NULL COMMENT "评估者类型",
|
||||
`assessor_id` VARCHAR(32) NOT NULL COMMENT "评估者",
|
||||
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_ar_uid` (`tenant_id`, `record_uid`),
|
||||
KEY `idx_ar_learner` (`tenant_id`, `learner_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="评估记录(total_score 派生 + 对齐判定)";
|
||||
|
||||
4
wwwroot/api/pbl_assessment_record_get.dspy
Normal file
4
wwwroot/api/pbl_assessment_record_get.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_assessment/api/pbl_assessment_record_get.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_assessment/api/pbl_assessment_record_get.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_assessment_record_get(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_assessment_report.dspy
Normal file
4
wwwroot/api/pbl_assessment_report.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_assessment/api/pbl_assessment_report.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_assessment/api/pbl_assessment_report.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_assessment_report(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_assessment_score.dspy
Normal file
4
wwwroot/api/pbl_assessment_score.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_assessment/api/pbl_assessment_score.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_assessment/api/pbl_assessment_score.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_assessment_score(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_rubric_get.dspy
Normal file
4
wwwroot/api/pbl_rubric_get.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_assessment/api/pbl_rubric_get.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_assessment/api/pbl_rubric_get.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_rubric_get(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_rubric_list.dspy
Normal file
4
wwwroot/api/pbl_rubric_list.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_assessment/api/pbl_rubric_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_assessment/api/pbl_rubric_list.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_rubric_list(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_rubric_save.dspy
Normal file
4
wwwroot/api/pbl_rubric_save.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_assessment/api/pbl_rubric_save.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_assessment/api/pbl_rubric_save.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_rubric_save(**params_kw)
|
||||
return data
|
||||
203
wwwroot/index.ui
Normal file
203
wwwroot/index.ui
Normal file
@ -0,0 +1,203 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "Rubric 加权评估与报告(M6)",
|
||||
"fontSize": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"gap": "16px",
|
||||
"minWidth": "250px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_assessment_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_rubric_save.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_rubric_save"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_assessment_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_rubric_get.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_rubric_get"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_assessment_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_rubric_list.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_rubric_list"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_assessment_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_assessment_score.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_assessment_score"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_assessment_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_assessment_record_get.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_assessment_record_get"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_assessment_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_assessment_report.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_assessment_report"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "pbl_assessment_content",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"flex": "1",
|
||||
"marginTop": "20px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user