# account_resource — 账号资源模块 ## 模块定位 账号类资源的**规格 / 权益配额 / 定价映射**模块,宿主为 pipeline-app(也可装 sage)。参照 llmage 资源模块范式: ``` llmage: llm(主表) → llm_api_map(llmid×apiname→ppid) → llmusage(计费六件套) account_resource: acctres_spec → acctres_pricing_map(spec×charge_mode→ppid) → acctres_usage ``` 账号按「计费方式」挂价:`charge_mode` = trial(试用)/ month(月付)/ year(年付)。`RESOURCE_TYPE = 'account_spec'`(product_resource.resource_type 用这个值)。业务口径:账号产品免费管并发(试用/月租/年租三档权益:工作空间数、赠送容量、并发任务数、GPU、成员数上限)。 宿主集成: ```python from account_resource.init import load_account_resource load_account_resource() ``` ## 表清单(models/*.json,共 3 张) | 表 | 用途 | 关键字段 | |---|---|---| | `acctres_spec` | 账号规格(资源主表) | `id`(pk32), `spec_code`(唯一64), `spec_name`, `spec_type`(trial/standard), `workspace_max`(可建工作空间数), `workspace_gb`(赠送容量GB), `concurrent_task`(并发任务数), `gpu_allowed`('0'/'1'), `member_max`, `privilege_json`(其他权益JSON), `sort_order`, `status`(active) | | `acctres_pricing_map` | 规格×计费方式 → 定价项目 | `spec_id`, `charge_mode`(唯一键 spec_id+charge_mode), `charge_unit`(次/月/年), `valid_days`(7/30/365), `ppid`(→pricing_program), `is_default`('1'=缺省方式), `status` | | `acctres_usage` | 账号资源用量(计费落账凭证) | `spec_id`, `charge_mode`, `subscription_id`(订购ID), `userid`/`userorgid`, `use_date`/`use_time`, `period_start/end`, `quantity`, `usages`(JSON), `transno`, `amount`+`amount_currency`+`amount_base`, `cost`+`cost_currency`+`cost_base`, `discount_rate`, `accounting_status`(pending→accounted), `org_id` | 种子数据 `init/data.json`:3 条规格(ACC-TRIAL-7D / ACC-MONTHLY / ACC-YEARLY)+ 3 条定价映射(trial/month/year,各 is_default='1')。**种子 ppid 为空串**——建好 pricing_program 后必须回填,否则计费金额记 0。 ## 对外 API(load_account_resource() 注册到 ServerEnv) ```python # 规格 CRUD env.acctres_spec_list(ns) # ns: {status, spec_type} env.acctres_spec_create(data) / acctres_spec_update(data) / acctres_spec_delete(data) env.get_account_spec(spec_id_or_code) # id 或 spec_code 均可 env.get_account_privileges(spec_id_or_code) # → {workspace_max, workspace_gb, concurrent_task, # gpu_allowed(bool), member_max, **privilege_json} # 定价映射 CRUD env.acctres_pricing_map_list(ns) # ns: {spec_id};LEFT JOIN 带出 spec_code/spec_name env.acctres_pricing_map_create/update/delete(data) env.get_account_ppid(spec_id_or_code, charge_mode=None) # charge_mode空→取is_default='1' env.account_charging(spec_id_or_code, charge_mode, userid, userorgid, subscription_id='', quantity=1, usages=None, period_start=None, period_end=None, discount_rate=None) # 解析ppid → env.buffered_charging(ppid, usages) → 原价×discount_rate # → 写 acctres_usage(accounting_status='pending'),返回 usage dict # 记账对接 env.get_accounting_acctres_usages(limit=200) # pending 用量,供 accounting 出账扫描 env.mark_acctres_accounted(usage_id, status='accounted') ``` 另有 `resolve_ppid(resource_ref_id, ctx)`(ctx: {'charge_mode': ...})作为**统一定价解析器**注册:优先 `env.register_resource_pricing_resolver('account_spec', resolve_ppid)`;产品层未加载时兜底暂存到 `env._resource_pricing_resolvers`。 ## 页面/CRUD(json/*.json) `json/acctres_spec.json`、`acctres_pricing_map.json`、`acctres_usage.json` 是 xls2ui 格式的 Bricks CRUD 表定义(editable 指向 `./add_*.dspy`、`./update_*.dspy`、`./delete_*.dspy`——**这些 dspy 端点不在本仓库**,由宿主 wwwroot 提供)。`scripts/load_path.py` 注册 RBAC 路径(全部 logined):`/account_resource`、`/account_resource/{acctres_spec,acctres_pricing_map,acctres_usage}/index.ui`。宿主 `load_path.sh` 自动扫描 `pkgs/*/scripts/load_path.py`。 ## 与产品/计费链路的集成关系 - **product_management**:`load_account_resource()` 末尾把 `env.product_interface` 设为本模块 `product_interface.py` 的接口字典(module_name / get_product_display / check_product_availability / check_product_consumable / execute_product_service(_stream) / calculate_product_cost / load_product_category_product)。`resource_ref_id = acctres_spec.id`(产品表存这个)。 - `load_product_category_product(parent_category_id)`:导出「账号服务」类别 + 每规格一条 product_type='account' 产品,供产品导入。 - `execute_product_service(...)`:request_data 必须带 `charge_mode`,内部调 `account_charging` 落 usage,返回 `entitlement`(duration_days: trial=7/month=30/year=365 + 各项配额),供产品层写 product_subscription。 - `calculate_product_cost(resource_ref_id, usage_data, user_org_id=None)`:**只算原价**(buffered_charging 求和,discount 恒 1.0);客户/分销商折扣由产品层 product_management.core 按 product_id 精确计算(commit f2972db 特意去掉了资源层折扣)。 - **accounting(计费落账链)**:`account_charging` 写 acctres_usage(pending) → 产品层 `*_resource_accounting(usage_record)` 按 spec_id→product_id → product_accounting_generic/calculate_product_cost → biz_order/biz_orderdetail → consume_accounting 复式记账(acc_balance/acc_detail/bill_detail)→ `mark_acctres_accounted` 置 accounted。前置条件:客户已开户(openCustomerAccounts)且余额充足,否则 AccountIdNone/AccountOverDraw。详见 product-billing-accounting-chain 技能。 - **定价引擎(pricing)**:铁律——出账用 `env.buffered_charging(ppid, data)` **两参**、返回价格对象列表,金额 `sum(getattr(p,'amount',0) for p in prices)`;用三参 `pricing_program_charging` 会报缺参且金额静默记 0(commit 3d5fb2c 已修)。 ## 部署注意事项 1. **库名**:一律 `env.get_module_dbname('account_resource')`(pipeline-app 返回 'pipeline'),禁写死 'sage'/'pipeline'。 2. **建表**:models/*.json 交宿主建表流程;accounting 相关表幂等不 DROP,product_management 在 TABLE_MODULES 会 DROP 重建——生产库勿跑完整 create_tables.py。 3. **种子导入**:init/data.json 由宿主 import_init 导入;导完**必须回填 ppid**(先建 pricing_program + pricing_program_timing.pricing_data)。 4. **RBAC**:部署后在宿主根目录执行 `py3/bin/python pkgs/account_resource/scripts/load_path.py`。 5. **依赖**:sqlor、bricks_for_python(pyproject),运行时依赖宿主提供 ahserver/appPublic/pricing/product_management。 6. i18n/{zh,en,jp,ko}/msg.txt 已备好词条(账号规格/计费方式/试用/月租/年租等)。 7. `init.py` 尾部的 `from .product_interface import load_product_category_product` 是刻意放在所有函数定义之后的延迟 import(避免与 product_interface 循环导入),改代码时勿上移。