651 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""pbl_domain_ext 契约接口层([M8] 基础域薄扩展 world/scene/entity
对外 13 个契约函数,与 ``wwwroot/api/*.dspy`` 一一对应:
关联表 CRUD/查询6
pbl_domain_ref_bind / pbl_domain_ref_unbind / pbl_domain_ref_update
pbl_domain_ref_get / pbl_domain_ref_list / pbl_domain_ref_check_access
基础域只读视图4
pbl_world_list_by_tenant / pbl_world_get_context
pbl_scene_list_by_world / pbl_entity_list_by_scene
团队-世界绑定3
pbl_team_bind_world / pbl_team_world_list / pbl_team_list_by_class
编译期物化1供 pbl_compiler import 闭包)
pbl_domain_materialize_game_definition
铁律:
* 所有函数第一个业务参数是 ``tenant_id``,缺失/非法即 ``PBL_DE_TENANT_MISSING``
* 只写 ``pbl_domain_ref`` 一张表world/scene/entity 基表**只读**
* 扩展字段名统一 ``ext_json``(设计 data-model.md §J1禁止旧名 ``ext``
* 返回统一 ``{"success": True, "code": "PBL_DE_OK", "data": ...}``,异常转错误体不裸抛。
"""
from . import db
from .base import (TABLE, BASE_TABLES, REF_TYPES, BIND_STATES, EXT_FIELD,
LIST_FIELDS, gen_id, now_str, require_tenant, check_ref_type,
check_bind_state, normalize_ext_json, audit_on_create,
audit_on_update, page_args, as_text, as_list)
from .errors import PblDomainExtError, err_payload
__all__ = [
"TABLE", "EXT_FIELD", "REF_TYPES", "BIND_STATES",
"pbl_domain_ref_bind", "pbl_domain_ref_unbind", "pbl_domain_ref_update",
"pbl_domain_ref_get", "pbl_domain_ref_list", "pbl_domain_ref_check_access",
"pbl_world_list_by_tenant", "pbl_world_get_context",
"pbl_scene_list_by_world", "pbl_entity_list_by_scene",
"pbl_team_bind_world", "pbl_team_world_list", "pbl_team_list_by_class",
"pbl_domain_materialize_game_definition",
"ok", "fail", "dispatch",
]
#: 基表只读视图对外暴露的列(基表列名差异大,取交集常见列,缺失自动跳过)
_BASE_VIEW_FIELDS = ("id", "tenant_id", "code", "name", "title", "status",
"state", "world_id", "scene_id", "parent_id",
"created_at", "updated_at")
# --------------------------------------------------------------------------
# 统一响应
# --------------------------------------------------------------------------
def ok(data=None, **extra):
"""成功响应体。"""
payload = {"success": True, "code": "PBL_DE_OK", "message": "成功"}
if data is not None:
payload["data"] = data
payload.update(extra)
return payload
def fail(code, message=None, detail=None):
"""失败响应体(不抛异常)。"""
return err_payload(code, message=message, detail=detail)
def _base_view(row):
"""把基表原始行裁剪成只读视图 dict只保留白名单列不改基表"""
out = {}
if not isinstance(row, dict):
return out
for key in _BASE_VIEW_FIELDS:
if key in row and row[key] is not None:
out[key] = row[key]
if "id" not in out:
for alt in ("world_id", "scene_id", "entity_id"):
if row.get(alt):
out["id"] = row[alt]
break
return out
def _ref_out(row):
"""关联表行 -> 对外 dict保证 ext_json 键名存在且为文本)。"""
out = {}
if not isinstance(row, dict):
return out
for key in LIST_FIELDS:
if key in row:
out[key] = row[key]
if EXT_FIELD in out and out[EXT_FIELD] is None:
out[EXT_FIELD] = ""
out["is_deleted"] = int(row.get("is_deleted") or 0)
return out
def _operator(params):
"""取操作人creator_id/updater_id 审计用)。"""
if not isinstance(params, dict):
return ""
for key in ("operator_id", "user_id", "creator_id", "updater_id", "uid"):
val = as_text(params.get(key)).strip()
if val:
return val
return ""
# --------------------------------------------------------------------------
# 1. 关联表:绑定 / 解绑 / 更新 / 查询
# --------------------------------------------------------------------------
def pbl_domain_ref_bind(params):
"""绑定基础域对象到 PBL 蓝图/团队/班级(幂等 upsert
入参tenant_id*, ref_type*(world|scene|entity), ref_id*,
ref_code, ref_name, blueprint_id, team_id, class_id, ext_json, operator_id
出参data = 关联记录 dict含 id / bind_state=bound
"""
try:
tenant_id = require_tenant(params)
ref_type = check_ref_type(params.get("ref_type"))
ref_id = as_text(params.get("ref_id")).strip()
if not ref_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="ref_id 为空")
if len(ref_id) > 64:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="ref_id 超长(>64)")
ref_code = as_text(params.get("ref_code")).strip()[:64]
ref_name = as_text(params.get("ref_name")).strip()[:128]
# 未显式给编码/名称时,从基表只读快照(不改基表)
if not ref_code or not ref_name:
base_row = db.read_base_row(ref_type, ref_id, tenant_id)
if base_row is None:
raise PblDomainExtError("PBL_DE_BASE_MISSING",
detail="%s.%s 未命中" % (BASE_TABLES[ref_type], ref_id))
view = _base_view(base_row)
ref_code = ref_code or as_text(view.get("code") or view.get("id"))[:64]
ref_name = ref_name or as_text(view.get("name") or view.get("title"))[:128]
ext_json = normalize_ext_json(params.get(EXT_FIELD) or params.get("ext_json"))
bind_state = check_bind_state(params.get("bind_state")) or BIND_STATES[0]
operator_id = _operator(params)
exist = db.select_ref_by_key(tenant_id, ref_type, ref_id)
if exist:
row = {
"ref_code": ref_code, "ref_name": ref_name,
"blueprint_id": as_text(params.get("blueprint_id")).strip()[:32],
"team_id": as_text(params.get("team_id")).strip()[:32],
"class_id": as_text(params.get("class_id")).strip()[:32],
"bind_state": bind_state, "bind_at": now_str(),
EXT_FIELD: ext_json, "is_deleted": 0,
}
audit_on_update(row, operator_id)
db.update_ref({"tenant_id": tenant_id, "id": exist.get("id")}, row)
fresh = db.select_ref_by_id(tenant_id, exist.get("id"))
return ok(_ref_out(fresh or dict(exist, **row)), rebound=True)
row = {
"id": gen_id(), "tenant_id": tenant_id,
"ref_type": ref_type, "ref_id": ref_id,
"ref_code": ref_code, "ref_name": ref_name,
"blueprint_id": as_text(params.get("blueprint_id")).strip()[:32],
"team_id": as_text(params.get("team_id")).strip()[:32],
"class_id": as_text(params.get("class_id")).strip()[:32],
"bind_state": bind_state, "bind_at": now_str(),
EXT_FIELD: ext_json,
}
audit_on_create(row, operator_id)
db.insert_ref(row)
return ok(_ref_out(row), rebound=False)
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_domain_ref_unbind(params):
"""解绑(逻辑删除 is_deleted=1 + bind_state=unbound绝不物理删。
入参tenant_id*, id 或 (ref_type*+ref_id*)operator_id
"""
try:
tenant_id = require_tenant(params)
ref_pk = as_text(params.get("id")).strip()
if ref_pk:
exist = db.select_ref_by_id(tenant_id, ref_pk)
else:
ref_type = check_ref_type(params.get("ref_type"))
ref_id = as_text(params.get("ref_id")).strip()
if not ref_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID",
detail="需 id 或 ref_type+ref_id")
exist = db.select_ref_by_key(tenant_id, ref_type, ref_id)
if not exist:
raise PblDomainExtError("PBL_DE_NOT_FOUND", detail="关联记录不存在")
row = {"bind_state": "unbound", "is_deleted": 1}
audit_on_update(row, _operator(params))
affected = db.update_ref({"tenant_id": tenant_id, "id": exist.get("id")}, row)
return ok({"id": exist.get("id"), "bind_state": "unbound",
"is_deleted": 1, "affected": int(affected or 0)})
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_domain_ref_update(params):
"""更新关联记录的可变字段ref_type/ref_id/tenant_id 不可改)。
入参tenant_id*, id*, 以及 ref_code/ref_name/blueprint_id/team_id/class_id/
bind_state/ext_json 中任意子集operator_id
"""
try:
tenant_id = require_tenant(params)
ref_pk = as_text(params.get("id")).strip()
if not ref_pk:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="id 为空")
exist = db.select_ref_by_id(tenant_id, ref_pk)
if not exist:
raise PblDomainExtError("PBL_DE_NOT_FOUND", detail="id=%s" % ref_pk)
row = {}
for key in ("ref_code", "ref_name", "blueprint_id", "team_id", "class_id"):
if key in params:
row[key] = as_text(params.get(key)).strip()[:128]
if "bind_state" in params:
state = check_bind_state(params.get("bind_state"), allow_empty=False)
row["bind_state"] = state
if state == "bound" and not exist.get("bind_at"):
row["bind_at"] = now_str()
if EXT_FIELD in params or "ext_json" in params:
raw = params.get(EXT_FIELD) if EXT_FIELD in params else params.get("ext_json")
row[EXT_FIELD] = normalize_ext_json(raw)
if not row:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="无可更新字段")
audit_on_update(row, _operator(params))
affected = db.update_ref({"tenant_id": tenant_id, "id": ref_pk}, row)
fresh = db.select_ref_by_id(tenant_id, ref_pk)
return ok(_ref_out(fresh or dict(exist, **row)), affected=int(affected or 0))
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_domain_ref_get(params):
"""取单条关联记录。入参tenant_id*, id 或 ref_type+ref_id。"""
try:
tenant_id = require_tenant(params)
ref_pk = as_text(params.get("id")).strip()
if ref_pk:
row = db.select_ref_by_id(tenant_id, ref_pk)
else:
ref_type = check_ref_type(params.get("ref_type"), allow_empty=True)
ref_id = as_text(params.get("ref_id")).strip()
if not ref_type or not ref_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID",
detail="需 id 或 ref_type+ref_id")
row = db.select_ref_by_key(tenant_id, ref_type, ref_id)
if not row:
raise PblDomainExtError("PBL_DE_NOT_FOUND")
data = _ref_out(row)
if params.get("with_base"):
data["base"] = _base_view(db.read_base_row(row.get("ref_type"),
row.get("ref_id"), tenant_id) or {})
return ok(data)
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_domain_ref_list(params):
"""分页查询关联记录。
入参tenant_id*, ref_type, bind_state, blueprint_id, team_id, class_id,
ref_ids(list), keyword, page, page_size
出参data = {"items": [...], "total": n, "page": p, "page_size": s}
"""
try:
tenant_id = require_tenant(params)
conds = {}
ref_type = check_ref_type(params.get("ref_type"), allow_empty=True)
if ref_type:
conds["ref_type"] = ref_type
state = check_bind_state(params.get("bind_state"))
if state:
conds["bind_state"] = state
for key in ("blueprint_id", "team_id", "class_id", "ref_id"):
val = as_text(params.get(key)).strip()
if val:
conds[key] = val
ref_ids = [as_text(x).strip() for x in as_list(params.get("ref_ids"))]
ref_ids = [x for x in ref_ids if x]
if ref_ids:
conds["ref_id"] = ref_ids
page, page_size, offset = page_args(params)
total = db.count_refs(tenant_id, conds)
items = db.select_refs(tenant_id, conds=conds, offset=offset, limit=page_size)
rows = [_ref_out(r) for r in items]
keyword = as_text(params.get("keyword")).strip().lower()
if keyword:
rows = [r for r in rows
if keyword in as_text(r.get("ref_name")).lower()
or keyword in as_text(r.get("ref_code")).lower()]
return ok({"items": rows, "total": int(total),
"page": page, "page_size": page_size})
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_domain_ref_check_access(params):
"""访问判定:某主体(蓝图/团队/班级)能否访问某基础域对象。
规则fail-closed
1. tenant_id 必须一致;
2. 必须存在 bind_state=bound 且 is_deleted=0 的关联记录;
3. 若传了 blueprint_id/team_id/class_id关联记录对应列必须匹配。
出参data = {"allowed": bool, "reason": str, "ref": dict|None}
"""
try:
tenant_id = require_tenant(params)
ref_type = check_ref_type(params.get("ref_type"))
ref_id = as_text(params.get("ref_id")).strip()
if not ref_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="ref_id 为空")
row = db.select_ref_by_key(tenant_id, ref_type, ref_id)
if not row:
return ok({"allowed": False, "reason": "no_binding", "ref": None})
if as_text(row.get("bind_state")) != "bound" or int(row.get("is_deleted") or 0):
return ok({"allowed": False, "reason": "not_bound", "ref": _ref_out(row)})
for key in ("blueprint_id", "team_id", "class_id"):
want = as_text(params.get(key)).strip()
if not want:
continue
got = as_text(row.get(key)).strip()
if got and got != want:
return ok({"allowed": False, "reason": "%s_mismatch" % key,
"ref": _ref_out(row)})
return ok({"allowed": True, "reason": "ok", "ref": _ref_out(row)})
except PblDomainExtError as exc:
return exc.to_payload()
# --------------------------------------------------------------------------
# 2. 基础域只读视图world / scene / entity
# --------------------------------------------------------------------------
def pbl_world_list_by_tenant(params):
"""只读列出租户下 world 基表记录,并标注 PBL 绑定状态。"""
try:
tenant_id = require_tenant(params)
limit = page_args(params)[1]
rows = db.read_base_rows("world", tenant_id, limit=limit)
bound = {}
for r in db.select_refs(tenant_id, conds={"ref_type": "world"}, limit=limit * 2 or 200):
bound[as_text(r.get("ref_id"))] = _ref_out(r)
items = []
for raw in rows:
view = _base_view(raw)
wid = as_text(view.get("id"))
ref = bound.get(wid)
view["pbl_bound"] = bool(ref and ref.get("bind_state") == "bound")
view["pbl_ref_id"] = (ref or {}).get("id", "")
view["pbl_blueprint_id"] = (ref or {}).get("blueprint_id", "")
items.append(view)
keyword = as_text(params.get("keyword")).strip().lower()
if keyword:
items = [i for i in items
if keyword in as_text(i.get("name")).lower()
or keyword in as_text(i.get("code")).lower()
or keyword in as_text(i.get("title")).lower()]
if params.get("only_bound"):
items = [i for i in items if i.get("pbl_bound")]
return ok({"items": items, "total": len(items), "ref_type": "world",
"readonly": True, "table": TABLE})
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_world_get_context(params):
"""取世界上下文world 只读视图 + 其下 scene/entity 关联 + PBL 绑定信息。"""
try:
tenant_id = require_tenant(params)
world_id = as_text(params.get("world_id") or params.get("ref_id")
or params.get("id")).strip()
if not world_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="world_id 为空")
base = db.read_base_row("world", world_id, tenant_id)
world_view = _base_view(base or {})
ref = db.select_ref_by_key(tenant_id, "world", world_id)
scenes = [_base_view(r) for r in db.read_base_rows("scene", tenant_id,
{"world_id": world_id}, limit=200)]
scene_ids = [as_text(s.get("id")) for s in scenes if s.get("id")]
entities = []
if params.get("with_entities") and scene_ids:
for raw in db.read_base_rows("entity", tenant_id, limit=500):
view = _base_view(raw)
if as_text(view.get("scene_id")) in scene_ids:
entities.append(view)
return ok({
"world": world_view,
"pbl_ref": _ref_out(ref) if ref else None,
"scenes": scenes,
"entities": entities,
"counts": {"scene": len(scenes), "entity": len(entities)},
"readonly": True,
})
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_scene_list_by_world(params):
"""只读列出某 world 下的 scene基表按 world_id 过滤)+ 绑定标注。"""
try:
tenant_id = require_tenant(params)
world_id = as_text(params.get("world_id")).strip()
if not world_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="world_id 为空")
rows = db.read_base_rows("scene", tenant_id, {"world_id": world_id}, limit=200)
bound = {}
for r in db.select_refs(tenant_id, conds={"ref_type": "scene"}, limit=500):
bound[as_text(r.get("ref_id"))] = _ref_out(r)
items = []
for raw in rows:
view = _base_view(raw)
ref = bound.get(as_text(view.get("id")))
view["world_id"] = world_id
view["pbl_bound"] = bool(ref and ref.get("bind_state") == "bound")
view["pbl_ref_id"] = (ref or {}).get("id", "")
items.append(view)
return ok({"items": items, "total": len(items), "world_id": world_id,
"ref_type": "scene", "readonly": True})
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_entity_list_by_scene(params):
"""只读列出某 scene 下的 entity基表按 scene_id 过滤)+ 绑定标注。"""
try:
tenant_id = require_tenant(params)
scene_id = as_text(params.get("scene_id")).strip()
if not scene_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="scene_id 为空")
rows = db.read_base_rows("entity", tenant_id, {"scene_id": scene_id}, limit=500)
bound = {}
for r in db.select_refs(tenant_id, conds={"ref_type": "entity"}, limit=500):
bound[as_text(r.get("ref_id"))] = _ref_out(r)
items = []
for raw in rows:
view = _base_view(raw)
ref = bound.get(as_text(view.get("id")))
view["scene_id"] = scene_id
view["pbl_bound"] = bool(ref and ref.get("bind_state") == "bound")
view["pbl_ref_id"] = (ref or {}).get("id", "")
items.append(view)
return ok({"items": items, "total": len(items), "scene_id": scene_id,
"ref_type": "entity", "readonly": True})
except PblDomainExtError as exc:
return exc.to_payload()
# --------------------------------------------------------------------------
# 3. 团队-世界绑定
# --------------------------------------------------------------------------
def pbl_team_bind_world(params):
"""把 world 绑定到团队/班级(写 pbl_domain_refref_type=world
入参tenant_id*, world_id*, team_id 或 class_id至少一个, blueprint_id, ext_json
"""
try:
tenant_id = require_tenant(params)
world_id = as_text(params.get("world_id") or params.get("ref_id")).strip()
if not world_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="world_id 为空")
team_id = as_text(params.get("team_id")).strip()
class_id = as_text(params.get("class_id")).strip()
if not team_id and not class_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID",
detail="team_id / class_id 至少给一个")
inner = dict(params)
inner.update({"tenant_id": tenant_id, "ref_type": "world",
"ref_id": world_id, "team_id": team_id,
"class_id": class_id, "bind_state": "bound"})
result = pbl_domain_ref_bind(inner)
if not result.get("success"):
return result
data = result.get("data") or {}
data["world_id"] = world_id
data["team_id"] = team_id
data["class_id"] = class_id
return ok(data, action="team_bind_world")
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_team_world_list(params):
"""列出团队(或班级)已绑定的 world。入参tenant_id*, team_id 或 class_id。"""
try:
tenant_id = require_tenant(params)
team_id = as_text(params.get("team_id")).strip()
class_id = as_text(params.get("class_id")).strip()
if not team_id and not class_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID",
detail="team_id / class_id 至少给一个")
conds = {"ref_type": "world", "bind_state": "bound"}
if team_id:
conds["team_id"] = team_id
if class_id:
conds["class_id"] = class_id
rows = db.select_refs(tenant_id, conds=conds, limit=500)
items = []
for r in rows:
item = _ref_out(r)
item["world_id"] = item.get("ref_id")
item["world_code"] = item.get("ref_code")
item["world_name"] = item.get("ref_name")
items.append(item)
return ok({"items": items, "total": len(items),
"team_id": team_id, "class_id": class_id})
except PblDomainExtError as exc:
return exc.to_payload()
def pbl_team_list_by_class(params):
"""按班级列出团队及其绑定世界数(聚合自 pbl_domain_ref不新建表
入参tenant_id*, class_id*
出参data = {"items": [{"team_id", "class_id", "world_count", "worlds": [...]}]}
"""
try:
tenant_id = require_tenant(params)
class_id = as_text(params.get("class_id")).strip()
if not class_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID", detail="class_id 为空")
rows = db.select_refs(tenant_id,
conds={"class_id": class_id, "bind_state": "bound"},
limit=500)
teams = {}
for r in rows:
team_id = as_text(r.get("team_id")).strip() or "(unassigned)"
bucket = teams.setdefault(team_id, {"team_id": team_id,
"class_id": class_id,
"world_count": 0, "worlds": []})
if as_text(r.get("ref_type")) == "world":
bucket["world_count"] += 1
bucket["worlds"].append({"world_id": r.get("ref_id"),
"code": r.get("ref_code"),
"name": r.get("ref_name")})
items = sorted(teams.values(), key=lambda x: x["team_id"])
return ok({"items": items, "total": len(items), "class_id": class_id})
except PblDomainExtError as exc:
return exc.to_payload()
# --------------------------------------------------------------------------
# 4. 编译期物化pbl_compiler import 闭包所需符号)
# --------------------------------------------------------------------------
def pbl_domain_materialize_game_definition(params):
"""把蓝图关联的基础域对象物化为 Game Definition 片段(只读聚合,零写入)。
供 pbl_compiler 在编译期调用:按 blueprint_id 取全部 bound 关联,
按 ref_type 分组,输出 ``{"worlds": [...], "scenes": [...], "entities": [...]}``
每项含 ref_id/ref_code/ref_name/ext_json解析为对象与基表只读视图。
入参tenant_id*, blueprint_id*(或 team_id/class_id 二选一), with_base(bool)
"""
try:
tenant_id = require_tenant(params)
blueprint_id = as_text(params.get("blueprint_id")).strip()
team_id = as_text(params.get("team_id")).strip()
class_id = as_text(params.get("class_id")).strip()
if not blueprint_id and not team_id and not class_id:
raise PblDomainExtError("PBL_DE_PARAM_INVALID",
detail="blueprint_id / team_id / class_id 至少给一个")
conds = {"bind_state": "bound"}
if blueprint_id:
conds["blueprint_id"] = blueprint_id
if team_id:
conds["team_id"] = team_id
if class_id:
conds["class_id"] = class_id
rows = db.select_refs(tenant_id, conds=conds, limit=500)
grouped = {"worlds": [], "scenes": [], "entities": []}
bucket_of = {"world": "worlds", "scene": "scenes", "entity": "entities"}
with_base = bool(params.get("with_base"))
for r in rows:
ref_type = as_text(r.get("ref_type"))
bucket = bucket_of.get(ref_type)
if not bucket:
continue
ext_raw = as_text(r.get(EXT_FIELD)).strip()
try:
import json as _json
ext_obj = _json.loads(ext_raw) if ext_raw else {}
except (ValueError, TypeError):
ext_obj = {}
item = {
"ref_id": r.get("ref_id"),
"ref_type": ref_type,
"code": r.get("ref_code"),
"name": r.get("ref_name"),
"ext_json": ext_obj,
"pbl_domain_ref_id": r.get("id"),
}
if with_base:
item["base"] = _base_view(db.read_base_row(ref_type, r.get("ref_id"),
tenant_id) or {})
grouped[bucket].append(item)
return ok({
"tenant_id": tenant_id,
"blueprint_id": blueprint_id,
"team_id": team_id,
"class_id": class_id,
"game_definition_fragment": grouped,
"counts": {k: len(v) for k, v in grouped.items()},
"readonly": True,
"source_table": TABLE,
})
except PblDomainExtError as exc:
return exc.to_payload()
# --------------------------------------------------------------------------
# dspy 分发入口
# --------------------------------------------------------------------------
_DISPATCH = {
"pbl_domain_ref_bind": pbl_domain_ref_bind,
"pbl_domain_ref_unbind": pbl_domain_ref_unbind,
"pbl_domain_ref_update": pbl_domain_ref_update,
"pbl_domain_ref_get": pbl_domain_ref_get,
"pbl_domain_ref_list": pbl_domain_ref_list,
"pbl_domain_ref_check_access": pbl_domain_ref_check_access,
"pbl_world_list_by_tenant": pbl_world_list_by_tenant,
"pbl_world_get_context": pbl_world_get_context,
"pbl_scene_list_by_world": pbl_scene_list_by_world,
"pbl_entity_list_by_scene": pbl_entity_list_by_scene,
"pbl_team_bind_world": pbl_team_bind_world,
"pbl_team_world_list": pbl_team_world_list,
"pbl_team_list_by_class": pbl_team_list_by_class,
"pbl_domain_materialize_game_definition": pbl_domain_materialize_game_definition,
}
def dispatch(action, params):
"""按 action 名分发到契约函数;未知 action 返回 PBL_DE_PARAM_INVALID。"""
func = _DISPATCH.get(as_text(action).strip())
if func is None:
return fail("PBL_DE_PARAM_INVALID", detail="unknown action=%r" % (action,))
return func(params if isinstance(params, dict) else {})