这是一份为您量身定制的**《oTree混合组队实验需求与技术实现文档》**。这份文档采用了高度结构化的技术语言,包含了核心业务逻辑和oTree底层的实现细节。 您在后续让任何大语言模型(如 ChatGPT、Claude)帮您生成具体代码时,只需将以下文档完整发送给它,它就能写出零错误且完全符合您预期的 oTree 5.x/5.11 代码。 --- # oTree 混合组队实验需求与技术设计文档 ## 1. 项目背景与总体要求 * **框架版本**:oTree 5.11及以上版本。 * **核心目标**:在一个唯一的 Session 中,同时支持“单人模式(Single)”和“双人团队模式(Team)”的实验流程。 * **应用结构**:实验分为两个 App。 * **App 1 (Assign App)**:专门负责计算到达顺序,分配 Treatment 并打上状态标签。 * **App 2 (Task App)**:专门负责根据标签进行真正的组队(大小为1或2),并执行具体实验任务。 * **避免代码冗余**:App 2 必须是高度通用的,不能在 App 2 中重复定义或硬编码 Treatment 的名称。所有的 Treatment 判定依赖 App 1 传递的全局标签。 ## 2. 实验参数与 Treatment 设定 * 共有 5 个 Treatments,分为两类: * **单人模式(Single)**:`['Single_A', 'Single_B']` * **双人模式(Team)**:`['Team_A', 'Team_B', 'Team_C']` * 需要在 `settings.py` 的 `SESSION_CONFIGS` 中配置自定义变量 `expected_workers`(如 `expected_workers=50`),用于向系统声明本场实验的预计总人数,以计算单人模式的配额。 ## 3. App 1:Treatment 分配逻辑 (Assign App) **核心机制:基于到达顺序 (group_by_arrival_time) 的两阶段分配** * **需求规则**:必须先将“单人模式”的配额全部分配完,然后再开始分配“双人模式”。 * **配额计算**:单人模式人数上限 = `expected_workers` × (单人Treatment数量 / 总Treatment数量)。例如 50 * (2/5) = 20人。 * **到达计数器机制(必须遵守的 oTree 规范)**: * 严禁使用动态挂载属性(如 `subsession.player_count`),防止多进程并发时数据错乱。 * 必须使用全局变量 `subsession.session.vars.get('arrived_count', 0)` 来记录累计到达人数,每次分配后执行 `+1` 并存回。 * **分配算法**: * **第一阶段(配额内)**:分配单人模式。无需同伴,采用**轮流交替**分配(如 A, B, A, B...),基于 `arrived_count % 2` 决定。 * **第二阶段(配额外)**:分配双人模式。需要两人同组,必须采用**两两一对**分配(如 A, A, B, B, C, C...),基于 `((arrived_count - 单人配额) // 2) % 3` 决定。 * **变量传递(必须遵守的 oTree 规范)**: * 决定好 Treatment 后,必须将信息存入 `participant.vars` 中以供 App 2 使用: * `player.participant.vars['treatment'] = current_treatment` * `player.participant.vars['is_single'] = is_single_boolean` * 分配完成后,App 1 直接 `return [player]` 将玩家单人放行至下一页。 ## 4. App 2:动态组队逻辑 (Task App - Grouping) **核心机制:混合大小分组** * **底层设定**:必须在 `BaseConstants` 中设置 `PLAYERS_PER_GROUP = None`,否则 oTree 将拒绝同一 Session 中存在大小不同的组。 * **GBAT (group_by_arrival_time) 逻辑流程**: 1. **异常拦截**:遍历 `waiting_players`,剔除 `is_dropout` 或 `waiting_too_long` 的玩家(返回单人组 `[p]` 引导至淘汰页)。 2. **单人秒过**:遍历 `waiting_players`,如果 `p.participant.vars.get('is_single')` 为 `True`,无需等待队友,直接返回 `[p]` 创建大小为 1 的 Group。 3. **双人撮合(字典匹配法)**: * 对于非单人玩家,读取其 `p.participant.vars.get('treatment')`。 * 建立一个临时字典 `waiting_teams = {}`,将相同 treatment 的玩家放入同一个列表中。 * 一旦某个 treatment 的列表长度达到 2,立刻 `return waiting_teams[treatment]`,完成同 Treatment 双人组队。 ## 5. App 2:实验页面与数据处理逻辑 (Task App - Pages) **核心机制:条件等待与计算** * 玩家在决策页 (`DecisionPage`) 提交一个数值(如 `decision`)。 * 需要为每组计算 `average_decision`,但单人和双人的处理时机必须分开: * **单人处理**:在 `DecisionPage` 的 `before_next_page` 中拦截。如果 `player.participant.vars['is_single'] == True`,将其自身的 `decision` 直接赋给 `group.average_decision`。 * **双人处理**:必须创建一个 `TeamWaitPage`。 * 必须设置 `is_displayed` 判定:仅当 `player.participant.vars['is_single'] == False` 时显示。 * 在 `after_all_players_arrive` 中,获取 `group.get_players()`,计算两人的 `decision` 平均值并赋给 `group.average_decision`。 * **结果页展示**:所有玩家最终统一进入 `Results` 页面,展示组别的 `treatment` 名称和计算出的 `average_decision`。 --- ### 给您的使用建议(复制以下提示词发给 LLM) > **提示词模板:** > "你好,我需要你作为 oTree (版本 5.11) 高级开发工程师,帮我编写一个混合组队实验的完整代码。 > > 请仔细阅读上面的《oTree 混合组队实验需求与技术设计文档》。请按照文档中的 5 个章节的要求,为我生成以下文件的完整 Python 代码(请使用 oTree 5.x 最新的 `__init__.py` 格式,而不是旧版的 `models.py/pages.py`): > 1. `settings.py` (只写出关键配置即可) > 2. `app_1_assign/__init__.py` (包含完整分配逻辑和安全的全局计数器) > 3. `app_2_task/__init__.py` (包含字典匹配法 GBAT 和条件等待页) > 4. `app_2_task/DecisionPage.html` 及 `Results.html` (基本的展示页面) > > 请严格遵守文档中提到的 oTree 底层技术约束(如 `PLAYERS_PER_GROUP = None`,`session.vars` 替代 `player_count` 等)。代码请加上必要的中文注释。"