diff --git a/api/adapters.py b/api/adapters.py index 1e028e3..0a2d00c 100644 --- a/api/adapters.py +++ b/api/adapters.py @@ -552,13 +552,6 @@ def _apply_branch_rules( # Backwards-compat shim used by existing call sites that don't yet pass # pz_build/input_modids. Removed once all callers migrate. -def _autopick_ambiguous(mods: List[ModInfo]) -> Tuple[Set[str], List[Dict[str, Any]]]: - drop_ids, warns, _hints = _apply_branch_rules( - mods, pz_build="B42", input_modids={m.id for m in mods}, - ) - return (drop_ids, warns) - - def build_response( input_ids: List[str], hit_ids: List[str], diff --git a/api/app.py b/api/app.py index 9875f03..46f9775 100644 --- a/api/app.py +++ b/api/app.py @@ -131,10 +131,10 @@ class ResortRequest(BaseModel): def _strip_path_prefix(deps) -> List[str]: """Defensively normalize dep names. Strips leading backslashes (B42 path syntax: `\\StarlitLibrary` -> `StarlitLibrary`) and trims whitespace. - Worker._split_csv applies this at parse time, but ~14 mod_parsed rows - in the live DB were written before that fix landed and will only refresh - when their wsid's time_updated advances on Steam. Normalizing here keeps - missing-dep matching correct against legacy rows.""" + Worker._split_csv applies this at parse time, but a handful of legacy + mod_parsed rows were written before that fix landed and will only + refresh when their wsid's time_updated advances on Steam. Normalizing + here keeps missing-dep matching correct against legacy rows.""" out: List[str] = [] for d in (deps or []): s = (d or "").strip().lstrip("\\") diff --git a/api/categorize.py b/api/categorize.py deleted file mode 100644 index adda9f1..0000000 --- a/api/categorize.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Public helper for mapping pzmm content-type tags to sortof CATEGORY_ORDER. - -The same mapping is also inlined in `mlos_sort.py` (both api/ and worker/ -copies, deliberately — worker uses a separate venv with no FastAPI deps, -so it cannot import from api/). This module exposes the helper for -non-mlos consumers (e.g. /api/conflicts diagnostics output) without -forcing them to drag in the whole sorter module. - -Source: pzmm core/mods.py:detect_mod_types ordering, mapped to sortof's -CATEGORY_ORDER buckets per docs/plans/2026-05-04-pzmm-conflict-and-typing.md -§3.4. -""" - -from __future__ import annotations - -from typing import Dict, List, Optional - -# Items / Animations / Lua / Unknown intentionally absent — too generic to -# drive a category decision; callers should fall through to other heuristics. -_TYPE_TO_CAT: Dict[str, str] = { - "Maps": "map", - "Vehicles": "vehicle", - "Weapons": "weapon", - "Clothing": "wearable", - "Traits": "code", - "Professions": "profession", - "Recipes": "crafting", - "Tiles": "tile", - "Textures": "texture", - "Sounds": "sound", - "UI": "ui", - "Translations": "translation", - "Patch": "patch", - "Dependency": "tweaks", - "Framework": "tweaks", -} - - -def types_to_category(mod_types: List[str], name: str = "") -> Optional[str]: - """First mod_type that maps to a sortof CATEGORY_ORDER bucket wins. - - Returns the bucket name (e.g. "weapon", "vehicle"), or None when: - - mod_types is empty (manifest not yet built), or - - mod_types contains only skip-types (Items / Animations / Lua / Unknown). - - The `name` arg is used for the vehicle_spawn refinement only — when a - Vehicles-tagged mod is named like "spawn zone X", the more specific - `vehicle_spawn` bucket wins over the generic `vehicle`. - """ - if not mod_types: - return None - for t in mod_types: - cat = _TYPE_TO_CAT.get(t) - if cat: - if cat == "vehicle" and name and "spawn zone" in name.lower(): - return "vehicle_spawn" - return cat - return None diff --git a/api/mlos_sort.py b/api/mlos_sort.py index 7797362..b9293b0 100644 --- a/api/mlos_sort.py +++ b/api/mlos_sort.py @@ -722,11 +722,11 @@ def sort_mods( if wid and wid not in workshop_set: workshop_seen.append(wid) workshop_set.add(wid) - # MAP_LINE convention: dependencies first (leftmost), dependents last - # (rightmost). Vanilla Muldraugh, KY is the ultimate base and is - # prepended at the very front by adapters.build_response. `order` is - # already topo-sorted by mod-level deps so dependencies appear before - # their dependents — walk it forward. + # MAP_LINE convention: dependencies first (leftmost), dependents next. + # Vanilla Muldraugh, KY is ALWAYS appended at the very end by + # adapters.build_response. `order` is already topo-sorted by mod-level + # deps (require= / loadAfter= / loadBefore=), so dependencies appear + # before their dependents — walk it forward. map_folders: List[str] = [] for mod_id in order: for mf in by_id[mod_id].maps: diff --git a/docs/backlog/polling-path-pz-build.md b/docs/backlog/polling-path-pz-build.md deleted file mode 100644 index 163a4cc..0000000 --- a/docs/backlog/polling-path-pz-build.md +++ /dev/null @@ -1,54 +0,0 @@ -# Backlog: polling-path `pz_build` plumbing - -**Status:** parked. File a Gitea issue with this content when one comes up. -**Trigger to schedule:** a B41 user submits a collection URL or bare-uncached input AND complains about getting B42-flavored auto-picks (or audits the result and notices Rule A misfired). - -## What's missing - -`/api/sort`'s sync path passes `req.pz_build` into `adapters.build_response` correctly. The async path (`_route_to_job` → `expansion.run_expansion` → `_build_result_for_job`) does **not** persist `pz_build` and defaults to `"B42"` when the GET endpoint builds the final result. - -A B41 user submitting a collection URL gets: -- Sync part of `/api/sort` (validation, classify) sees `pz_build=B41`. -- Job created, expansion runs. -- GET `/api/jobs/{id}` builds `result_json` via `_build_result_for_job(conn, wsids, rules_raw)` → `adapters.build_response(...)` → defaults `pz_build="B42"` → Rule A picks B42-flavored branches. - -For the canonical fhqMotoriusZone/SZ-class fixtures this doesn't matter (those are coordinated, exempt from Rule A). The bug bites on truly-ambiguous multi-branch wsids like `zReApoModernArmor` (2 branches: unflavored + B42-flavored) when B41 user routes through cold drain. - -## Migration sketch - -Add a column to `sort_jobs`: - -```sql --- /opt/sortof/init/02_sort_jobs.sql (re-run on idempotent CREATE; live DB needs ALTER) -ALTER TABLE sort_jobs ADD COLUMN IF NOT EXISTS pz_build TEXT; -``` - -Apply via: - -```bash -sudo docker exec -i sortof_db psql -U sortof -d sortof -c \ - "ALTER TABLE sort_jobs ADD COLUMN IF NOT EXISTS pz_build TEXT;" -``` - -Edit `init/02_sort_jobs.sql` to include the column in the `CREATE TABLE IF NOT EXISTS` block so fresh deploys get it. - -## Plumbing checklist - -1. **`/opt/sortof/api/jobs.py`** — `create_job(...)` gains `pz_build: Optional[str] = None`; INSERT writes the column. `get_job_row` returns it (no code change needed if `SELECT *`). -2. **`/opt/sortof/api/app.py`** — `_route_to_job(...)` gains `pz_build: Optional[str] = None`; passes to `jobs.create_job`. Both call sites in `sort_endpoint` (collection short-circuit and bare-uncached fork) pass `req.pz_build`. -3. **`/opt/sortof/api/app.py`** — `_build_result_for_job(conn, wsids, rules_raw)` signature gains `pz_build: Optional[str] = None`. The GET handler reads `row["pz_build"]` and passes it through. `adapters.build_response(... pz_build=...)` already accepts it. -4. **No frontend change** — `pzBuild` is already in `/api/sort` POST body. - -## Acceptance criteria - -- [ ] B41 user submits collection URL containing `zReApoModernArmor` (3483407987) — final `result_json.MODS_LINE` includes `zReApoModernArmor` (un-flavored), not `zReApoModernArmorB42`. -- [ ] WARNINGS includes `build-mismatch` if appropriate. -- [ ] B42 user behavior unchanged (default). -- [ ] Existing `sort_jobs` rows with NULL `pz_build` continue to work (NULL → fall back to "B42" in the build_response default). - -## Why parked - -- No telemetry showing cold-collection B41 traffic exists. -- DB migrations against synthetic demand bitrot between writing and shipping (function signatures drift, the DDL goes stale). -- Sync-path B41 users (the dominant case in this user base) work correctly today — Rule A fires off `req.pz_build` directly. -- Schedule when a real user hits it. The migration is small and self-contained. diff --git a/init/04_required_wsids.sql b/init/04_required_wsids.sql index 81b79f9..ef40ade 100644 --- a/init/04_required_wsids.sql +++ b/init/04_required_wsids.sql @@ -1,6 +1,8 @@ --- Required Items scraped from each mod's Steam Workshop page (the "Required --- Items" section). Steam's anonymous GetPublishedFileDetails endpoint does --- not include children for individual mods, so we scrape the public HTML. +-- Required Items pulled from each mod's Steam "Required Items" sidebar. +-- Originally scraped from the public Workshop HTML page (the anonymous +-- GetPublishedFileDetails endpoint omits `children` for individual mods); +-- worker.fetch_required_wsids now uses authenticated IPublishedFileService/ +-- GetDetails with includechildren=true and gets the same data structurally. -- -- Use cases: -- 1. Auto-resolving missing-dep warnings: when a cached mod_id Y is