fix: missing-dep [add] button respects MOD_ID_ALIASES on build mismatch
When user is on B42 and a mod requires the B41 mod_id (e.g. truemusic_mixtape_megapack require=truemusic), the canonical wsid (2613146550, B41-only) is rightly filtered out by the build guard — but we were silently dropping the suggestion. Now we fall back to MOD_ID_ALIASES: truemusic → TrueMoozic, suggest wsid 3632610172. - _lookup_wsids_for_missing returns Dict[str, Tuple[wsid, suggested_mod_id]] - build_warnings unpacks tuple, label clarifies rename when alias used - adapters.build_response signature updated for new shape
This commit is contained in:
@@ -84,17 +84,20 @@ def _resolve_satisfied_via_alias(input_modids: Set[str]) -> Set[str]:
|
|||||||
|
|
||||||
def build_warnings(
|
def build_warnings(
|
||||||
mlos_warnings: Dict[str, Any],
|
mlos_warnings: Dict[str, Any],
|
||||||
wsid_lookup: Dict[str, str] | None = None,
|
wsid_lookup: Dict[str, Tuple[str, str]] | None = None,
|
||||||
source_wsids: Dict[str, str] | None = None,
|
source_wsids: Dict[str, str] | None = None,
|
||||||
input_modids: Set[str] | None = None,
|
input_modids: Set[str] | None = None,
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""Translate mlos_sort warnings to the SORTOF_DATA WARNINGS shape.
|
"""Translate mlos_sort warnings to the SORTOF_DATA WARNINGS shape.
|
||||||
|
|
||||||
wsid_lookup: optional {mod_id -> workshop_id} map of *deps* we already
|
wsid_lookup: optional {missing_mod_id -> (workshop_id, suggested_mod_id)}
|
||||||
have cached. When provided, each 'missing' warning is augmented with
|
map produced by app._lookup_wsids_for_missing. The `suggested_mod_id`
|
||||||
`actions: [{type: "add-wsid", wsid, label}]` so the frontend can render
|
differs from the missing mod_id when a MOD_ID_ALIASES alternative was
|
||||||
a click-to-add button. Unknown deps fall back to a Steam Workshop search
|
used because the canonical mod_id's only cached wsid is wrong-build
|
||||||
link the user follows manually.
|
(e.g. user on B42 needs `truemusic` → suggest `TrueMoozic`'s wsid).
|
||||||
|
When provided, each 'missing' warning is augmented with `actions:
|
||||||
|
[{type: "add-wsid", wsid, modId, label}]`. Unknown deps fall back to a
|
||||||
|
Steam Workshop search link the user follows manually.
|
||||||
|
|
||||||
source_wsids: optional {mod_id -> workshop_id} map of *every cached mod
|
source_wsids: optional {mod_id -> workshop_id} map of *every cached mod
|
||||||
in the current sort*. Used to attach the source mod's wsid onto
|
in the current sort*. Used to attach the source mod's wsid onto
|
||||||
@@ -120,13 +123,19 @@ def build_warnings(
|
|||||||
continue
|
continue
|
||||||
actions: List[Dict[str, str]] = []
|
actions: List[Dict[str, str]] = []
|
||||||
for dep in deps:
|
for dep in deps:
|
||||||
wsid = lookup.get(dep)
|
entry = lookup.get(dep)
|
||||||
if wsid:
|
if entry:
|
||||||
|
wsid, suggested = entry
|
||||||
|
# If MOD_ID_ALIASES rewrote the suggestion (B42 user needing
|
||||||
|
# B41 mod_id `truemusic` gets `TrueMoozic` instead), surface
|
||||||
|
# both names so the user understands what's being added.
|
||||||
|
label = (f"add {suggested}" if suggested == dep
|
||||||
|
else f"add {suggested} (renamed from {dep})")
|
||||||
actions.append({
|
actions.append({
|
||||||
"type": "add-wsid",
|
"type": "add-wsid",
|
||||||
"wsid": wsid,
|
"wsid": wsid,
|
||||||
"modId": dep,
|
"modId": suggested,
|
||||||
"label": f"add {dep}",
|
"label": label,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# No cache hit -> link to Steam Workshop search so the user
|
# No cache hit -> link to Steam Workshop search so the user
|
||||||
@@ -558,7 +567,7 @@ def build_response(
|
|||||||
mods: List[ModInfo],
|
mods: List[ModInfo],
|
||||||
sort_result: Dict[str, Any],
|
sort_result: Dict[str, Any],
|
||||||
status: str,
|
status: str,
|
||||||
wsid_lookup: Dict[str, str] | None = None,
|
wsid_lookup: Dict[str, Tuple[str, str]] | None = None,
|
||||||
pz_build: str = "B42",
|
pz_build: str = "B42",
|
||||||
is_resort: bool = False,
|
is_resort: bool = False,
|
||||||
selected_modids: Optional[Set[str]] = None,
|
selected_modids: Optional[Set[str]] = None,
|
||||||
|
|||||||
65
api/app.py
65
api/app.py
@@ -228,20 +228,29 @@ async def _lookup_wsids_for_missing(
|
|||||||
conn,
|
conn,
|
||||||
mlos_warnings: Dict[str, Any],
|
mlos_warnings: Dict[str, Any],
|
||||||
pz_build: Optional[str] = None,
|
pz_build: Optional[str] = None,
|
||||||
) -> Dict[str, str]:
|
) -> Dict[str, Tuple[str, str]]:
|
||||||
"""Resolve missing-requirement mod_ids to wsids via mod_parsed cache.
|
"""Resolve missing-requirement mod_ids to wsids via mod_parsed cache.
|
||||||
|
|
||||||
Returns {mod_id -> workshop_id} for any missing dep we've previously
|
Returns {missing_mod_id -> (workshop_id, suggested_mod_id)} for any
|
||||||
cached. Used to render an [add to list] action button next to the
|
missing dep we've previously cached. `suggested_mod_id` matches the
|
||||||
'missing' warning. Unknown deps just get no button.
|
missing mod_id by default, but when the canonical wsid is wrong-build
|
||||||
|
AND `MOD_ID_ALIASES` (adapters.py) has an alternative whose cached
|
||||||
|
wsid IS build-correct, we redirect to the alias. Example: user on B42
|
||||||
|
has `truemusic_mixtape_megapack` (require=truemusic). truemusic only
|
||||||
|
exists as a B41 wsid; the alias `TrueMoozic` exists as a B42 wsid.
|
||||||
|
Result: {"truemusic": ("3632610172", "TrueMoozic")} — frontend renders
|
||||||
|
[add TrueMoozic] pointing at the correct build's wsid.
|
||||||
|
|
||||||
pz_build filters out wrong-build resolutions: if the user is on B42 and
|
pz_build filters out wrong-build resolutions: if the user is on B42 and
|
||||||
the only cached wsid for `dep` is tagged `Build 41` only, we DROP the
|
the only cached wsid for `dep` is tagged `Build 41` only, the canonical
|
||||||
suggestion. Adding it would just trigger a build-mismatch warning;
|
suggestion is DROPPED (and the alias path may rescue it). Adding the
|
||||||
worse, in cases like `TacHold requires modoptions` where the author's
|
wrong-build dep would just trigger a build-mismatch warning; worse, in
|
||||||
`require=` is over-declared, the suggestion misleads the user toward
|
cases like `TacHold requires modoptions` where `require=` is over-
|
||||||
a B41 mod that doesn't actually need to be in the load order.
|
declared, the suggestion misleads the user toward a B41 mod that
|
||||||
|
doesn't actually need to be in the load order.
|
||||||
"""
|
"""
|
||||||
|
from adapters import MOD_ID_ALIASES
|
||||||
|
|
||||||
missing = mlos_warnings.get("missing_requirements") or {}
|
missing = mlos_warnings.get("missing_requirements") or {}
|
||||||
if not missing:
|
if not missing:
|
||||||
return {}
|
return {}
|
||||||
@@ -252,6 +261,11 @@ async def _lookup_wsids_for_missing(
|
|||||||
wanted.add(d)
|
wanted.add(d)
|
||||||
if not wanted:
|
if not wanted:
|
||||||
return {}
|
return {}
|
||||||
|
# Expand the fetch set with alias targets so we can resolve in one round.
|
||||||
|
for dep in list(wanted):
|
||||||
|
for alias_target in MOD_ID_ALIASES.get(dep, []):
|
||||||
|
wanted.add(alias_target)
|
||||||
|
|
||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT DISTINCT ON (mp.mod_id) mp.mod_id, mp.workshop_id, wm.tags
|
SELECT DISTINCT ON (mp.mod_id) mp.mod_id, mp.workshop_id, wm.tags
|
||||||
@@ -268,15 +282,36 @@ async def _lookup_wsids_for_missing(
|
|||||||
other_tag = "Build 41" if target_tag == "Build 42" else (
|
other_tag = "Build 41" if target_tag == "Build 42" else (
|
||||||
"Build 42" if target_tag == "Build 41" else None
|
"Build 42" if target_tag == "Build 41" else None
|
||||||
)
|
)
|
||||||
out: Dict[str, str] = {}
|
|
||||||
|
def _is_build_correct(tags: List[str]) -> bool:
|
||||||
|
if not target_tag or not other_tag:
|
||||||
|
return True
|
||||||
|
return not (other_tag in tags and target_tag not in tags)
|
||||||
|
|
||||||
|
# candidates: mod_id -> (wsid, build_correct)
|
||||||
|
candidates: Dict[str, Tuple[str, bool]] = {}
|
||||||
for r in rows:
|
for r in rows:
|
||||||
if target_tag and other_tag:
|
|
||||||
tags = list(r["tags"] or [])
|
tags = list(r["tags"] or [])
|
||||||
if other_tag in tags and target_tag not in tags:
|
candidates[r["mod_id"]] = (r["workshop_id"], _is_build_correct(tags))
|
||||||
# Wrong-build only — adding it would just trigger a
|
|
||||||
# build-mismatch. Skip the suggestion.
|
deps_seen: set = set()
|
||||||
|
for deps in missing.values():
|
||||||
|
for d in deps:
|
||||||
|
if d:
|
||||||
|
deps_seen.add(d)
|
||||||
|
|
||||||
|
out: Dict[str, Tuple[str, str]] = {}
|
||||||
|
for dep in deps_seen:
|
||||||
|
cand = candidates.get(dep)
|
||||||
|
if cand and cand[1]:
|
||||||
|
out[dep] = (cand[0], dep)
|
||||||
continue
|
continue
|
||||||
out[r["mod_id"]] = r["workshop_id"]
|
# Canonical missing or wrong-build; try aliases in declaration order.
|
||||||
|
for alias_target in MOD_ID_ALIASES.get(dep, []):
|
||||||
|
alt = candidates.get(alias_target)
|
||||||
|
if alt and alt[1]:
|
||||||
|
out[dep] = (alt[0], alias_target)
|
||||||
|
break
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user