fix(fleet): support selector rules and disambiguate same-name ships#419
fix(fleet): support selector rules and disambiguate same-name ships#419
Conversation
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Pull request overview
This PR adds backend support for slot-level fleet selector rules (candidates/search_name/level range) and updates the UI fleet-change flow to better handle same-name ship scenarios by using selector-aware matching and optional search keywords.
Changes:
- Introduce
FleetRuleRequestandfleet_rulesin task request schemas and propagate them through task execution and ops runners. - Enhance fleet-change logic to be selector-aware (candidate reuse, one-to-one matching, refill handling) and pass selector info down into the choose-ship UI flow.
- Extend ship list OCR utilities to optionally keep duplicate ship-name rows and provide a
row_keyto correlate name hits with level OCR.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| autowsgr/ui/utils/ship_list.py | Adds optional dedup control and row_key to support same-name rows and level correlation. |
| autowsgr/ui/choose_ship_page.py | Adds selector-aware ship selection (candidates/search_name/min/max level) and returns matched ship name. |
| autowsgr/ui/battle/fleet_change/_change.py | Updates fleet-change algorithm to consume selector rules and propagate them to UI selection. |
| autowsgr/server/schemas.py | Adds FleetRuleRequest and fleet_rules to CombatPlanRequest. |
| autowsgr/server/routes/task.py | Propagates fleet_rules overrides into normal/event fight task execution. |
| autowsgr/ops/normal_fight.py | Adds fleet_rules support and prefers it over fleet during preparation. |
| autowsgr/ops/event_fight.py | Adds fleet_rules support and prefers it over fleet during preparation. |
| autowsgr/init.py | Bumps package version to 2.1.9.post5. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """为槽位挑选一个“未被当前编队占用”的候选舰船。""" | ||
| if name is None: | ||
| return None, None | ||
|
|
||
| candidates = cls._slot_candidates(name, selector) | ||
| occupied = { | ||
| ship for idx, ship in enumerate(current) if ship is not None and idx != slot_to_replace | ||
| } | ||
| available = [candidate for candidate in candidates if candidate not in occupied] | ||
| if len(available) == 0: | ||
| return None, None | ||
|
|
||
| chosen = available[0] | ||
| if selector is None: | ||
| return chosen, None | ||
|
|
||
| narrowed_selector = dict(selector) | ||
| narrowed_selector['candidates'] = available |
There was a problem hiding this comment.
_select_available_candidate() builds an occupied set of ship names and filters out any candidate already present elsewhere in the fleet. This makes it impossible to intentionally field duplicate ship names (same ship name appearing in multiple slots), even though the API accepts repeated names and the whole PR aims to handle same-name cases. Consider tracking occupancy by slot/instance (e.g., counts per name vs. required count, or allowing duplicates and only avoiding selecting the exact same slot), rather than treating a name as globally unique.
| """为槽位挑选一个“未被当前编队占用”的候选舰船。""" | |
| if name is None: | |
| return None, None | |
| candidates = cls._slot_candidates(name, selector) | |
| occupied = { | |
| ship for idx, ship in enumerate(current) if ship is not None and idx != slot_to_replace | |
| } | |
| available = [candidate for candidate in candidates if candidate not in occupied] | |
| if len(available) == 0: | |
| return None, None | |
| chosen = available[0] | |
| if selector is None: | |
| return chosen, None | |
| narrowed_selector = dict(selector) | |
| narrowed_selector['candidates'] = available | |
| """为槽位挑选一个候选舰船,不按舰名排除同名舰。""" | |
| if name is None: | |
| return None, None | |
| candidates = cls._slot_candidates(name, selector) | |
| if len(candidates) == 0: | |
| return None, None | |
| chosen = candidates[0] | |
| if selector is None: | |
| return chosen, None | |
| narrowed_selector = dict(selector) | |
| narrowed_selector['candidates'] = candidates |
| candidates: list[str] = Field(default_factory=list, description='候选舰船名(按优先级)') | ||
| search_name: str | None = Field(default=None, description='选船搜索关键词(用于同名舰船区分)') | ||
| min_level: int | None = Field(default=None, ge=1, description='等级下限(含)') | ||
| max_level: int | None = Field(default=None, ge=1, description='等级上限(含)') | ||
|
|
There was a problem hiding this comment.
FleetRuleRequest.candidates defaults to an empty list, which allows a rules object that can't actually select anything; downstream _extract_selector() treats empty candidates as "no selector" and the slot effectively becomes None. If candidates are required for a rule, consider validating candidates as non-empty (e.g., min_length=1 / Field(..., min_length=1) or a model_validator that rejects empty lists) to fail fast on malformed requests.
| ship_names = ( | ||
| self._primary_names_from_rules(self._fleet_rules) | ||
| if self._fleet_rules is not None | ||
| else self._fleet | ||
| ) |
There was a problem hiding this comment.
When fleet_rules are used, _primary_names_from_rules() always picks the first candidate per slot, but the UI selection may fall back to a later candidate (or a different match) at runtime. This can cause self._fleet_ships (and thus result/context reporting) to record incorrect ship names. Consider deriving ship_names from the actual post-change fleet (e.g., page.detect_fleet() after change_fleet()), or have change_fleet() return the final resolved names.
| ship_names = ( | |
| self._primary_names_from_rules(self._fleet_rules) | |
| if self._fleet_rules is not None | |
| else self._fleet | |
| ) | |
| # 舰名以实际换船后的页面检测结果为准,避免规则匹配的首选候选与 UI | |
| # 运行时实际选择不一致,导致上下文/结果记录错误。 | |
| ship_names = page.detect_fleet() | |
| if not ship_names: | |
| ship_names = ( | |
| self._primary_names_from_rules(self._fleet_rules) | |
| if self._fleet_rules is not None | |
| else self._fleet | |
| ) |
| self._primary_names_from_rules(self._fleet_rules) | ||
| if self._fleet_rules is not None | ||
| else self._fleet | ||
| ) | ||
| self._fleet_ships = fleet_info.to_ships(ship_names) |
There was a problem hiding this comment.
When fleet_rules are provided, _primary_names_from_rules() uses only the first candidate per slot, which may not be the ship that was actually selected. This can make self._fleet_ships inaccurate in results/context. Prefer populating ship names from the real fleet after change_fleet() (e.g., page.detect_fleet()), or return resolved names from change_fleet().
|
Closing to recreate PR with same base/head. |
…420) * fix(fleet): support fleet_rules selectors and disambiguate same-name ships * chore(release): bump autowsgr to 2.1.9.post5 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(review): address Copilot feedback for fleet rules * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(review): address new Copilot comments on PR #419 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: X-vedeax <65024689+veadex@users.noreply.github.com>
Summary
Why
Frontend now sends slot-level fleet selector rules (candidates/search_name). Without backend support, same-name ships can be treated as already matched and fail to replace, causing repeated fleet setup failures.
Validation