Skip to content

refactor(config): 日常读写改由 Daily 承担(声明层 + 接线) - #52

Open
LevelDownRefine wants to merge 27 commits into
mainfrom
refactor/daily-object
Open

LevelDownRefine wants to merge 27 commits into
mainfrom
refactor/daily-object

Conversation

@LevelDownRefine

@LevelDownRefine LevelDownRefine commented Sep 13, 2026

Copy link
Copy Markdown
Owner

日常配置重构 P4:把「一个日常」抽成 Daily 对象,并把读写接到它上面。13 文件 / +1390 −1191(净减约 600 行)/ 7 commit。

行为等价由 P3 的 golden 基线守住 —— 关键证据是:tests/golden/daily_baseline.json 本步一行未改,而 golden 测试仍绿(菜单内容、每个 (日常, 一级项, 二级项) 的落盘结果、反读记录三段逐项一致)。

设计:Daily 只出规则、不碰盘

从 7 个脚本子类的手写落点表里抽出「一个日常」:

承担
Daily 解析自己的声明节点 → name / physical_name / options / task_field / task_map / option_fields(+ _sequence_values / _sequence_required / _single_field);出读写规则 fields / write / read / read_enabled / set_enabled / section / section_exists —— 一律「吃 dict 吐 dict/值」,不认识 ScriptConfig
NoopDaily 绝区零 / 崩铁:上游自身已支持,不读不写
SegmentedDaily 共同实现:数据在自己那段(段名 = 各自声明的物理名)、开关在第二份文件里自己那条 Routine Item 上
MaaDaily 粥:TaskQueue / StagePlan + 借槽改写

每个日常一个类,身份写在类上daily_display_name)。7 个脚本共 8 个日常类,类定义紧挨各自 config:

WutheringWavesDaily(Daily) / GenshinDaily(Daily) / EndfieldDaily(Daily)
ZenlessZoneZeroDaily(NoopDaily) / StarRailDaily(NoopDaily) / ArknightsDaily(MaaDaily)
AnomalyDaily(SegmentedDaily) / AnomalyHunterDaily(SegmentedDaily)     ← 异环两个

于是 config 里的绑定不再重复写名字 —— _daily_types类的元组(AnomalyDaily, AnomalyHunterDaily)),名字只由类给;_build_dailies 按类身份与声明一一对应:类缺 daily_display_name/身份重复/与声明对不上,三者都 assert。这就是「日常数 = 类数 = 实例数」的可执行形式。daily.py 只留机制类(Daily / NoopDaily / SegmentedDaily / MaaDaily),各脚本的日常类在 set_config.py 里各自 config 旁边。

两个类的类体只有 docstring 与身份,区分不靠类体、靠身份与实例数据section() / _routine_item() 都以 self.physical_name 为键(来自声明),所以两个实例各取各的段与 Routine Item。类上的身份是绑定键(哪条声明配哪个类),实例上的 physical_name落点(写哪段)——「住哪」这份实现知识在类(SegmentedDaily)里,「是哪个日常」这份数据在声明里。

菜单只用声明get_daily_options 用声明 + 基类 Daily 解析,不经 _daily_types。菜单只需要"有哪些日常、各有那些选项"(声明就够),落点才需要类(要按该日常的读写机制来)。所以「加一个日常」仍是改声明即可在界面出现;写入若没配类则当场报错,而不是悄悄写错位置。

文件 I/O(路径、两份文件、_verify_saved)全部留在 ScriptConfig(唯一碰盘的一层)——所以 daily.py → set_config 不存在(也不需要 TYPE_CHECKING 绕环)。

commit

  1. refactor(config): 抽出字段工具到 utils_dict —— safe_update / get_field / _scalar_kind 搬到 src/utils/utils_dict.py。理由是硬的:Daily 要用,而 daily.py → set_config 会成环。纯搬:机器验证「main 原文切片 vs 新模块体,前缀归一后逐字节相同」= True,唯一差异是日志前缀随模块名,消息正文不变。

  2. refactor(config): 加 Daily 规则对象(声明层) —— src/config/daily.py。此 commit 单独看是「新增模块」,等价性由下面那组差分测试证明。

  3. refactor(config): 日常读写改由 Daily 承担(接线) —— ScriptConfig_build_dailies / _dispatch_daily / _daily_types;四个入口(set_daily_task / set_daily_enabled / _read_daily_enabled / _read_daily_tasks)退化成「读盘 → 交给日常 → 有改动才落盘」;删掉 7 个子类全部手写落点表与覆写(_task_key / _task_map / _update_task / _read_daily_task / set_daily_task / NTE 的段与 Routine Items 读写 / _daily_section_dict / _routine_item / _write_routine_enabled / _daily_physical_name)。task_config.get_daily_configs 从「落点 dict」改为「该脚本的日常声明列表」,单数的 get_daily_config 随之删除 —— 这顺带解掉了上一步留下的命名问题:现在「单数=一份声明 / 复数=该脚本全部声明」是同一件事的两个数。

  4. refactor(config): 异环两个日常各一个类,共同实现留在 SegmentedDaily —— review 提出「每个日常应该有自己的类」。原状是两个类各写一遍(AST 去 docstring 后逐字相同,差 0 行),所以按「一个日常一个类 + 共同实现只留一份」重排:共同部分(取自己那段 + 第二份文件的开关读写)提到 SegmentedDaily,两个日常类各只留自己的身份。

    一处必须说清:声明没有蕴含实现类。声明给的是展示名/物理名/选项;「数据住自己那段、开关在第二份文件」是本工具的实现选择,按既有约定(脚本内路径与文件内键名不进声明)本就不该塞进那份共享 yml。所以绑定仍在 Python 里 —— 但它是显式绑定(类身份与声明一一对应,对不上就 assert),而不是原先那种「表外日常静默回落到基类 Daily、把副本写进顶层 config 且不报错」。

  5. refactor(config): _daily_types 覆盖每个脚本,删掉 _daily_cls —— 之前是「特例脚本填 _daily_types、普通脚本填 _daily_cls(3 个脚本连这行都没写、吃基类默认 Daily)」两套写法。现在一个机制:每个脚本都填表、每个日常一个类;_daily_cls 及其回落分支删除,一致性断言随之变成无条件。

    顺带调了一处方向:get_daily_options(菜单)改成只用声明 + 基类 Daily不经过 _daily_types。原因是「日常数 = 类数」的强断言会连带把菜单也打死 —— 而菜单本来只需要声明。分层后:菜单(声明驱动,新增日常照常显示)/落点(需要类,缺了就报错)。这也正是 P5 的方向,先做掉这一小块(已验证 7 个脚本菜单逐项不变)。

  6. refactor(config): _daily_types 去掉基类默认值 —— 7 个脚本都已各自声明,基类那个空表默认只会让人以为「可以不给」(漏声明时拿空表,报出来的是「与声明的日常不一致」,读着像数据问题)。改成裸注解(无默认值)+ _build_dailies 显式校验,漏声明直接报「未声明 _daily_types」。

  7. refactor(config): 日常的身份写在类上,每个日常一个类 —— review 指出「区分日常应该靠类,类有自己的属性」:于是 _daily_types 从「名 → 类」的表改成类的元组(config 不再重复写名字),身份由类自带的 daily_display_name 给出;6 个单日常脚本也各建一个类,daily.py 只留机制类。

两处顺带收益

  • _read_daily_tasks 一次读盘(主 config 与开关文件各一次,再分发给各日常):异环反读 4→2 次读盘、单日常脚本 2→1;
  • 菜单不再需要实例化日常对象就能给出选项(声明 + 基类解析),且与写入侧读的是同一份声明。

自证

行为等价(golden 未改仍绿)之外,接线前另有一组差分验证:拿现有实现当独立裁判 —— 落点对比现有落点读取器;写入对比 cfg.set_daily_task(用公开入口而非 _update_task,因为子类会预归一:原神把「二级 or 一级」当一级传);反读/开关对比 _read_daily_task / _read_daily_enabled / set_daily_enabled(含「无变化即不落盘」的判定)。范围 7 脚本 × 90 个组合、两边跑同一份种子、比全量 config变异验证:对 daily.py 做 4 处定向变异(落点解析 / 写入 / 反读 / 开关)→ 四组测试各组独立报红(failures=4/1/1/1),还原即 OK。

该差分部分的裁判(_update_task 等)就是本步删掉的东西,故它随本步收敛为 Daily 自身的语义测试;端到端等价改由 golden 永久接管(PR 正文预先声明,不是事后找补)。

ruff check / format --check → 全绿(159 files)
测试                        → 1015 tests / 6F+2E(与合并前同批;6F+2E 是环境依赖的既有失败,另 test_no_game_leftover 偶发抖动)
golden                      → 未改动仍绿
GUI 路径复核                → 菜单 [('异象界域',4),('追猎目标',1)]、chip「经验与甲硬币 · 甲硬币」/「不启用」、
                              daily_options 四项,与接线前逐项一致
反读读盘次数                → 异环 4→2、单日常 2→1

测试方法数 −23 / +28:删掉的是「落点手写表」与那组差分用例(其裁判已不存在),新增的是 Daily 的单元用例与「_daily_types 必须覆盖声明」那条不变量的用例(含逐脚本断言「日常数 = 类数」)。

一处 API 收紧(唯一的行为变化,请留意)

set_daily_task(daily_display_name, task_name, sequence) 现在要求 task_name一级项sequence 是二级项。旧实现里 原神/终末地 的子类会把「二级名当一级传」预归一(target = sequence or task_name),新实现没有了这层兜底 —— 因为 Daily.options/option_fields 是层级数据,必须按菜单层级传。

已核实不影响用户可见路径:仓库里没有 --task/--sequence CLI 参数(cli.py 只传 weekly_start),GUI 从菜单下行本就直接给 (一级项, 二级项)。受影响的只有 tests/test_endfield_config_safety.py 里那两处旧写法(本步已按新 API 改)。

注:src/config/set_config.md 里有一句「CLI --task/--sequence 覆盖」是本来就不成立的旧描述(仓库里没有该参数),与本次收紧无关。

本步不做(下一步 = P5 菜单链)

src/config/set_config.md 的日常段(连同 gui/README.md / service/README.md / CONVENTIONS.md 里的相关句)现在描述的还是已被删除的 API_update_task / _daily_tasks / _task_key / _daily_physical_name / 落点读取器),18 处引用失效。

文档重写之所以再往后挪一步:P5 要动的正是这份文档描述的 APIdaily_config.get_daily_map() 的形状、get_daily_options 的有无)。先写文档 = 写两遍。所以顺序是 本 PR 合入 → P5(菜单链收成一层)→ 文档。如果你更希望文档同 PR 一起进,我立刻补上,只是本 PR 会变大。

safe_update / get_field / _scalar_kind 从 set_config.py 搬到 src/utils/utils_dict.py,
好让配置规则层(daily.Daily)也能用而不与 set_config 循环导入。函数体逐字节不变,
唯一差异是日志前缀随模块名([set_config] → [safe_update] / [get_field]),消息正文不变。
把「一个日常」从 7 个脚本子类的手写落点表里抽成对象:daily.Daily 解析自己的声明节点得出
落点(task_field / task_map / option_fields / options / 静态二级枚举),并出读写规则——
只出规则、不碰盘(fields / write / read / read_enabled / set_enabled / section / section_exists
一律吃 dict 吐 dict/值),文件 I/O 仍归 ScriptConfig。声明表达不了的三类各一个子类:
NoopDaily(绝区零/崩铁)、AnomalyDaily + AnomalyHunterDaily(异环两日常各一段 + 第二份文件
里的开关)、MaaDaily(TaskQueue / StagePlan)。

本步不接线(无生产调用方),配套 tests/test_daily.py 拿现有实现当独立裁判做差分验证:
落点逐字段对比现有落点读取器,写入/反读/开关逐组合(7 脚本 90 个组合)对比现有公开入口,
且都在同一份种子上跑、比全量 config。这组差分测试是临时脚手架,接线后由 golden 基线接管。
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f7bd1f33-0ca5-495e-9837-ad5bd840d2b0


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@LevelDownRefine LevelDownRefine changed the title probe refactor(config): 加 Daily 规则对象(声明层) Sep 13, 2026
ScriptConfig 不再手写落点,改为:_daily_cls / _daily_types / _daily_type / _build_dailies /
_dispatch_daily 按声明构造日常对象;set_daily_task / set_daily_enabled / _read_daily_enabled /
_read_daily_tasks 四个入口全部退化成「读盘 → 交给日常 → 有改动才落盘」(文件 I/O 仍只在这一层)。
删掉 7 个子类的手写落点表与覆写:_task_key / _task_map / _update_task / _read_daily_task /
set_daily_task(绝区零/崩铁 log、原神/终末地 预归一)/ NTE 的段与 Routine Items 读写 /
set_daily_enabled / _read_daily_enabled,以及 _daily_section_dict / _routine_item /
_write_routine_enabled / _daily_physical_name。task_config.get_daily_configs 从「落点 dict」
改为「该脚本的日常声明列表」,get_daily_config(单数)随之删除。

顺带两处收益:
- _read_daily_tasks 一次读盘(主 config 与开关文件各一次,分发给各日常):异环反读 4→2 次读盘,
  单日常脚本 2→1;
- get_daily_options(菜单)直接取 Daily.options,与写入侧同源对象。

行为等价由 golden 基线(tests/golden/daily_baseline.json,本步未改动)守住:菜单内容、每个
(日常, 一级项, 二级项) 的落盘结果、反读记录三段逐项一致。tests/test_daily.py 收敛为 Daily
自身的语义;原差分验证的裁判(_update_task 等)随本步删除,故其差分部分一并收敛。
@LevelDownRefine LevelDownRefine changed the title refactor(config): 加 Daily 规则对象(声明层) refactor(config): 日常读写改由 Daily 承担(声明层 + 接线) Sep 13, 2026
一个日常一个类(日常数 = 类数 = 实例数):共同部分(取自己那段 + 第二份文件的开关读写)
提到 SegmentedDaily,AnomalyDaily / AnomalyHunterDaily 各只留类名与说明 —— 相比接线时那版,
不再有 87 行逐字重复,且每个日常有自己命名的类作为将来分歧的落点。绑定写回脚本级
_daily_types(按日常展示名,与声明主键一致)。

_daily_types 非空时加一致性断言:必须与声明里的日常一一对应(多一个少一个都报错)——
既是「有多少个日常就有多少个类」这条不变量的可执行形式,也堵住了表外日常静默落到
基类 Daily(把副本写进顶层 config、不报错)的隐患。
一个机制代替两个:每个脚本都填「日常展示名 → 实现类」(单日常脚本也就是一行),
日常数 = 类数。随之删掉 _daily_cls 与它的回落分支,_build_dailies 的一致性断言变成
无条件 —— 声明里多一个日常而没配类时直接报错(原先会静默回落到基类 Daily)。

菜单(get_daily_options)反向调整:它只用声明 + 基类 Daily 解析,不经 _daily_types ——
菜单只需要声明(有哪些日常、各有那些选项),声明里新增日常时照常显示;落点才需要类
(要按该日常的读写机制来)。这样「加日常」仍只是改声明,界面立刻能显示,而写入不会
悄悄写错位置。
7 个脚本都已各自声明,基类那个空表默认只会让人以为「可以不给」——漏声明时会静默拿空表,
报出来的是「与声明的日常不一致」,读着像数据问题而不是漏声明。改成裸注解(无默认值),
并在 _build_dailies 里显式校验:漏声明直接说「未声明 _daily_types」。新增 1 例用例钉住。
每个日常一个实现类、身份(展示名)由类自带:daily_display_name。于是 config 里不再重复
写名字 —— _daily_types 从「名 → 类」的表改成类的元组,名字只由类给;_build_dailies 按类
身份与声明一一对应(类缺身份、身份重复、与声明对不上,三者都 assert)。

6 个单日常脚本也各建一个类(WutheringWavesDaily / GenshinDaily / EndfieldDaily /
ZenlessZoneZeroDaily / StarRailDaily / ArknightsDaily,各三行:docstring + 身份),异环两个
(AnomalyDaily / AnomalyHunterDaily,各继承 SegmentedDaily)。这些类紧挨各自 config 定义;
daily.py 只留机制类(Daily / NoopDaily / SegmentedDaily / MaaDaily)。
get_weekly_map 输出物化声明节点(不再产 {name, tasks} 旧形状);
顺带修 _materialize_daily 日常级 source 组缺 values 的 KeyError。
update/read 改收整份 config;NoopDaily 覆写 update 恒无改动,
类型检查消失;set_daily_task 退化为读盘→update→有改动才落盘。
_daily_types 元组改单个 _daily_type(默认 Daily);8 个身份子类删除;
daily_display_name 类属性删除,名字只来自声明;形状解析归各机制类。
Daily 构造接管所属 ScriptConfig(路径/display_name/_load/_save);
update/read/set_enabled 各自完成读盘→改内存→有改动才落盘的闭环。
_daily_type/_daily_flat_type 按两层/单层选类,跨日常混用合法;
单层带 key 的解析/反读归 AnomalyHunterDaily;_dailies_cache 改名 _dailies_data。
声明标注机制类(DAILY_CLASSES 注册表查表),config 子类零日常配置;
物化时剥离 class;utils_config 测试改单变量环境还原,避开本机超长变量。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant