deliver: 交付收口(引擎代为提交)

This commit is contained in:
agent.develop 2026-09-22 17:00:53 +08:00
parent 87bbac9c7a
commit 786f3a2f9d
9 changed files with 67 additions and 52 deletions

View File

@ -1,15 +1,16 @@
debug(f'pbl_artifact_options.dspy: START params_kw={dict(params_kw)}')
debug('pbl_artifact_options.dspy: START params_kw=' + str(dict(params_kw)))
# 下拉数据源(json/pbl_evidence.json -> browserfields.alters.artifact_id.dataurl)
# 契约:必须返回**裸 JSON 数组** [{value,text},...],异常/空数据返回 [],
# 不许包 status/data(crud-definition-spec Pitfall:包了会让下拉静默不渲染)。
# 后端函数 pbl_artifact_options 由 load_pbl_evidence() 注册到 ServerEnv(init.py ③)。
# dspy 禁项:无 import、无 f-string(日志一律字符串拼接)。
try:
_rows = await pbl_artifact_options(**dict(params_kw))
if not isinstance(_rows, list):
_rows = []
debug(f'pbl_artifact_options.dspy: OK rows={len(_rows)}')
debug('pbl_artifact_options.dspy: OK rows=' + str(len(_rows)))
return json.dumps(_rows, ensure_ascii=False)
except Exception as e:
debug(f'pbl_artifact_options.dspy: FAIL {e}')
debug(f'pbl_artifact_options.dspy: TRACE {format_exc()}')
debug('pbl_artifact_options.dspy: FAIL ' + str(e))
debug('pbl_artifact_options.dspy: TRACE ' + str(format_exc()))
return json.dumps([], ensure_ascii=False)

View File

@ -1,8 +1,9 @@
# pbl_evidence/api/pbl_evidence_aggregate.dspy
# M5b 契约②:证据聚合(四类计数 + 覆盖度 + 分组 + 时间水位)。
# Q-OPEN-10:预览 / Playtest 数据默认剔除,需显式 include_preview=true 才纳入。
# dspy 铁律:无 import、无 ServerEnv()、显式 return。
debug(f'pbl_evidence/api/pbl_evidence_aggregate.dspy: START params_kw={dict(params_kw)}')
# dspy 铁律:无 import、无 ServerEnv()、无 f-string、显式 return;
# 日志/异常一律字符串拼接(dspy-file-implementation-spec 硬规则 6)。
debug('pbl_evidence/api/pbl_evidence_aggregate.dspy: START params_kw=' + str(dict(params_kw)))
try:
kw = dict(params_kw)
@ -11,10 +12,11 @@ try:
kw[flag] = kw[flag].strip().lower() in ('1', 'true', 'yes', 'y')
res = await pbl_evidence_aggregate(**kw)
status = 'OK' if res.get('ok') else 'ERROR'
debug(f'pbl_evidence_aggregate.dspy: DONE total={res.get("total")} '
f'groups={len(res.get("groups") or [])}')
return {'status': status, 'data': res, 'message': res.get('message') or '',
'total': res.get('total')}
debug('pbl_evidence_aggregate.dspy: DONE total=' + str(res.get('total')) +
' groups=' + str(len(res.get('groups') or [])))
result = {'status': status, 'data': res, 'message': res.get('message') or '',
'total': res.get('total')}
return result
except Exception as e:
error(f'pbl_evidence_aggregate.dspy: FAIL {format_exc()}')
error('pbl_evidence_aggregate.dspy: FAIL ' + str(format_exc()))
return {'status': 'ERROR', 'data': None, 'message': str(e)}

View File

@ -1,7 +1,8 @@
# pbl_evidence/api/pbl_evidence_collect_from_events.dspy
# M5a 主能力:批量从 pbl_runtime_event(M11b 产物)采集学习证据,幂等落 pbl_evidence。
# 铁律:dspy 内不 import、不 new ServerEnv();ServerEnv 注册的契约函数是直接全局;必须显式 return。
debug(f'pbl_evidence_collect_from_events.dspy: START params_kw={dict(params_kw)}')
# 铁律:dspy 内不 import、不 new ServerEnv();ServerEnv 注册的契约函数是直接全局;必须显式 return;
# 禁 f-string(硬规则 6),日志一律字符串拼接。
debug('pbl_evidence_collect_from_events.dspy: START params_kw=' + str(dict(params_kw)))
try:
tenant = params_kw.get('tenant_id') or (await get_userorgid()) or '0'
@ -27,11 +28,16 @@ try:
update_existing=params_kw.get('update_existing'),
)
status = 'OK' if res.get('ok') else 'ERROR'
debug(f'pbl_evidence_collect_from_events.dspy: DONE status={status} msg={res.get("message")}')
debug('pbl_evidence_collect_from_events.dspy: DONE status=' + str(status) +
' msg=' + str(res.get('message')))
data = {}
for _k, _v in res.items():
if _k != 'items':
data[_k] = _v
return {'status': status,
'data': {k: v for k, v in res.items() if k != 'items'},
'data': data,
'items': res.get('items') or [],
'message': res.get('message') or ''}
except Exception as e:
error(f'pbl_evidence_collect_from_events.dspy: FAIL {format_exc()}')
error('pbl_evidence_collect_from_events.dspy: FAIL ' + str(format_exc()))
return {'status': 'ERROR', 'data': None, 'message': str(e)}

View File

@ -1,15 +1,17 @@
# pbl_evidence/api/pbl_evidence_collect_idempotent.dspy
# M5b 契约①:四类证据(decision/action/observation/artifact_version)幂等采集。
# 重复采集(同 idempotency_key / 同唯一索引三元组)不报错,返回 created=false。
# dspy 铁律:无 import、无 ServerEnv()、显式 return;函数由 load_pbl_evidence() 挂 ServerEnv。
debug(f'pbl_evidence/api/pbl_evidence_collect_idempotent.dspy: START params_kw={dict(params_kw)}')
# dspy 铁律:无 import、无 ServerEnv()、无 f-string、显式 return;
# 函数由 load_pbl_evidence() 挂 ServerEnv;日志一律字符串拼接。
debug('pbl_evidence/api/pbl_evidence_collect_idempotent.dspy: START params_kw=' + str(dict(params_kw)))
try:
res = await pbl_evidence_collect_idempotent(**params_kw)
res = await pbl_evidence_collect_idempotent(**dict(params_kw))
status = 'OK' if res.get('ok') else 'ERROR'
debug(f'pbl_evidence_collect_idempotent.dspy: DONE created={res.get("created")} '
f'deduped={res.get("deduped")} id={res.get("id")}')
return {'status': status, 'data': res, 'message': res.get('message') or ''}
debug('pbl_evidence_collect_idempotent.dspy: DONE created=' + str(res.get('created')) +
' deduped=' + str(res.get('deduped')) + ' id=' + str(res.get('id')))
result = {'status': status, 'data': res, 'message': res.get('message') or ''}
return result
except Exception as e:
error(f'pbl_evidence_collect_idempotent.dspy: FAIL {format_exc()}')
error('pbl_evidence_collect_idempotent.dspy: FAIL ' + str(format_exc()))
return {'status': 'ERROR', 'data': None, 'message': str(e)}

View File

@ -1,4 +1,4 @@
debug(f'pbl_evidence_delete.dspy: START params_kw={dict(params_kw)}')
debug('pbl_evidence_delete.dspy: START params_kw=' + str(dict(params_kw)))
# 证据删除端点(json/pbl_evidence.json -> delete_data_url,DataViewer editable 提交目标)。
# 薄包装:无 import、无业务逻辑,直接委托 init.py 注册的 pbl_evidence_delete
# (支持单条 id 与批量 ids;每条都按 tenant_id + id 双条件校验归属,防越权删除)。
@ -7,8 +7,7 @@ debug(f'pbl_evidence_delete.dspy: START params_kw={dict(params_kw)}')
# 1) dspy-file-implementation-spec「DataViewer CRUD endpoints 必须返回 Message widget
# JSON 字符串」——本端点是 editable 提交端点,故返回 bricks Message widget,
# 删除提交后能正确渲染成功/失败提示;不得返回裸数据 dict。
# 2) 禁项 f-string 出现在 dict 字面量内会被 ahserver exec() 误判提前闭合外层 dict
# (SyntaxError: '{' was never closed,整文件编译失败),异常分支一律用字符串拼接。
# 2) dspy 禁项 f-string(硬规则 6):一律字符串拼接,异常分支亦然。
try:
_data = await pbl_evidence_delete(**dict(params_kw))
_msg = (_data or {}).get('message') or '证据已删除'
@ -17,8 +16,8 @@ try:
debug('pbl_evidence_delete.dspy: OK deleted=' + str((_data or {}).get('deleted')))
return json.dumps(_widget, ensure_ascii=False)
except Exception as e:
debug(f'pbl_evidence_delete.dspy: FAIL {e}')
debug(f'pbl_evidence_delete.dspy: TRACE {format_exc()}')
debug('pbl_evidence_delete.dspy: FAIL ' + str(e))
debug('pbl_evidence_delete.dspy: TRACE ' + str(format_exc()))
_widget = {'widgettype': 'Message',
'options': {'title': '失败',
'message': '证据删除失败:' + str(e),

View File

@ -2,8 +2,9 @@
# M5b 契约③:证据分页查询(含 timestamp + payload,US-17「证据不只是分数」)。
# 支持 blueprint_id / learner_id / session_id / artifact_id / evidence_type /
# evidence_types / time_from,time_to / page / rows;预览数据默认过滤(Q-OPEN-10)。
# dspy 铁律:无 import、无 ServerEnv()、显式 return。
debug(f'pbl_evidence/api/pbl_evidence_query.dspy: START params_kw={dict(params_kw)}')
# dspy 铁律:无 import、无 ServerEnv()、无 f-string、显式 return;
# 日志/异常一律字符串拼接(dspy-file-implementation-spec 硬规则 6)。
debug('pbl_evidence/api/pbl_evidence_query.dspy: START params_kw=' + str(dict(params_kw)))
try:
kw = dict(params_kw)
@ -12,10 +13,11 @@ try:
kw[flag] = kw[flag].strip().lower() in ('1', 'true', 'yes', 'y')
res = await pbl_evidence_query(**kw)
status = 'OK' if res.get('ok') else 'ERROR'
debug(f'pbl_evidence_query.dspy: DONE total={res.get("total")} '
f'page={res.get("page")} rows={len(res.get("rows") or [])}')
return {'status': status, 'data': res, 'message': res.get('message') or '',
'total': res.get('total')}
debug('pbl_evidence_query.dspy: DONE total=' + str(res.get('total')) +
' page=' + str(res.get('page')) + ' rows=' + str(len(res.get('rows') or [])))
result = {'status': status, 'data': res, 'message': res.get('message') or '',
'total': res.get('total')}
return result
except Exception as e:
error(f'pbl_evidence_query.dspy: FAIL {format_exc()}')
error('pbl_evidence_query.dspy: FAIL ' + str(format_exc()))
return {'status': 'ERROR', 'data': None, 'message': str(e)}

View File

@ -1,6 +1,7 @@
# pbl_evidence/api/pbl_evidence_stats.dspy
# 证据类型分布统计(供 M6 评估 / 概览卡片)。dspy 无 import、无 ServerEnv()、显式 return。
debug(f'pbl_evidence_stats.dspy: START params_kw={dict(params_kw)}')
# 证据类型分布统计(供 M6 评估 / 概览卡片)。
# dspy 铁律:无 import、无 ServerEnv()、无 f-string、显式 return(日志字符串拼接)。
debug('pbl_evidence_stats.dspy: START params_kw=' + str(dict(params_kw)))
try:
tenant = params_kw.get('tenant_id') or (await get_userorgid()) or '0'
@ -10,8 +11,9 @@ try:
blueprint_id=params_kw.get('blueprint_id'),
)
status = 'OK' if res.get('ok') else 'ERROR'
debug(f'pbl_evidence_stats.dspy: DONE total={res.get("total")}')
return {'status': status, 'data': res, 'message': res.get('message') or ''}
debug('pbl_evidence_stats.dspy: DONE total=' + str(res.get('total')))
result = {'status': status, 'data': res, 'message': res.get('message') or ''}
return result
except Exception as e:
error(f'pbl_evidence_stats.dspy: FAIL {format_exc()}')
error('pbl_evidence_stats.dspy: FAIL ' + str(format_exc()))
return {'status': 'ERROR', 'data': None, 'message': str(e)}

View File

@ -2,8 +2,9 @@
# M5b 契约④:学生 / 团队证据档案(设计 §3.2 list_evidence_by_student / by_team 等价实现)。
# 入参:subject_type=student|team + subject_id(或 student_id / team_id)+ blueprint_id(可选)
# 出参:四类分组明细 + 产出物计数 + 水位;预览/Playtest 数据默认过滤(Q-OPEN-10)。
# dspy 铁律:无 import、无 ServerEnv()、显式 return。
debug(f'pbl_evidence/api/pbl_evidence_subject_summary.dspy: START params_kw={dict(params_kw)}')
# dspy 铁律:无 import、无 ServerEnv()、无 f-string、显式 return;
# 日志/异常一律字符串拼接(dspy-file-implementation-spec 硬规则 6)。
debug('pbl_evidence/api/pbl_evidence_subject_summary.dspy: START params_kw=' + str(dict(params_kw)))
try:
kw = dict(params_kw)
@ -13,10 +14,11 @@ try:
kw['include_student_private'] = kw['include_student_private'].strip().lower() in ('1', 'true', 'yes', 'y')
res = await pbl_evidence_subject_summary(**kw)
status = 'OK' if res.get('ok') else 'ERROR'
debug(f'pbl_evidence_subject_summary.dspy: DONE subject={res.get("subject_type")}:'
f'{res.get("subject_id")} total={res.get("evidence_total")}')
return {'status': status, 'data': res, 'message': res.get('message') or '',
'total': res.get('evidence_total')}
debug('pbl_evidence_subject_summary.dspy: DONE subject=' + str(res.get('subject_type')) +
':' + str(res.get('subject_id')) + ' total=' + str(res.get('evidence_total')))
result = {'status': status, 'data': res, 'message': res.get('message') or '',
'total': res.get('evidence_total')}
return result
except Exception as e:
error(f'pbl_evidence_subject_summary.dspy: FAIL {format_exc()}')
error('pbl_evidence_subject_summary.dspy: FAIL ' + str(format_exc()))
return {'status': 'ERROR', 'data': None, 'message': str(e)}

View File

@ -1,4 +1,4 @@
debug(f'pbl_evidence_update.dspy: START params_kw={dict(params_kw)}')
debug('pbl_evidence_update.dspy: START params_kw=' + str(dict(params_kw)))
# 证据人工修订端点(json/pbl_evidence.json -> update_data_url,DataViewer editable 提交目标)。
# 薄包装:无 import、无业务逻辑,直接委托 init.py 注册的 pbl_evidence_update
# (强制 tenant_id 打头校验归属;改唯一索引三元组时同步重算 dedup_key)。
@ -7,8 +7,7 @@ debug(f'pbl_evidence_update.dspy: START params_kw={dict(params_kw)}')
# 1) dspy-file-implementation-spec「DataViewer CRUD endpoints 必须返回 Message widget
# JSON 字符串」——本端点是 editable 提交端点,故返回 bricks Message widget,
# 表单提交后能正确渲染成功/失败提示;不得返回裸数据 dict。
# 2) 禁项 f-string 出现在 dict 字面量内会被 ahserver exec() 误判提前闭合外层 dict
# (SyntaxError: '{' was never closed,整文件编译失败),异常分支一律用字符串拼接。
# 2) dspy 禁项 f-string(硬规则 6):一律字符串拼接,异常分支亦然。
try:
_data = await pbl_evidence_update(**dict(params_kw))
_msg = (_data or {}).get('message') or '证据已更新'
@ -17,8 +16,8 @@ try:
debug('pbl_evidence_update.dspy: OK')
return json.dumps(_widget, ensure_ascii=False)
except Exception as e:
debug(f'pbl_evidence_update.dspy: FAIL {e}')
debug(f'pbl_evidence_update.dspy: TRACE {format_exc()}')
debug('pbl_evidence_update.dspy: FAIL ' + str(e))
debug('pbl_evidence_update.dspy: TRACE ' + str(format_exc()))
_widget = {'widgettype': 'Message',
'options': {'title': '失败',
'message': '证据更新失败:' + str(e),