Skip to content

Commit fcc524c

Browse files
fistyeeclaude
andcommitted
fix(paper2assets): deterministic Microsoft logo — trust alias over opensearch + write logos.json manifest
Follow-up to the ALIASES retarget: mapping MSRA->Microsoft alone was not enough. fetch_logo_for() also ran a free-text Wikipedia opensearch on the raw name, which pulled the sibling-brand 'Microsoft Research' page into the candidate list and won nondeterministically whenever the 'Microsoft' page fetch flaked -> random drift to the MSR wordmark. - Fix B (real fix): when any candidate has an explicit ALIAS, trust it and SKIP the opensearch fallback; only allow disambiguation variants of the alias TARGET. Non-aliased institutes keep the full opensearch path. - Fix A: always persist the full manifest to assets/logos/logos.json so a stdout piped through tail/head can't drop the mapped slug (msra->microsoft). - SKILL.md: direct the agent to read slug/path from JSON/logos.json, never invent a filename. Verified: MSRA -> microsoft.png 5/5 (was drifting); Stanford/Meta/Tsinghua/ Runway ML unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b9a31ad commit fcc524c

2 files changed

Lines changed: 49 additions & 17 deletions

File tree

ResearchStudio-Reel/skills/paper2assets/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ python ~/.claude/skills/paper2assets/scripts/make_qr.py --from-metadata
597597
The deterministic Wikimedia pass gets the easy ones; obscure / non-English / newly-founded institutes routinely miss it. **You MUST close the gap** rather than shipping a logo-less header:
598598

599599
1. Run `fetch_logos.py` (command above). It prints a **✓/✗ CHECKLIST** to stderr and a JSON object `{"logos": [...], "missing": ["Institute A", ...]}` to stdout.
600+
> **ALWAYS take each logo's `path`/`slug` from the JSON — or from `assets/logos/logos.json`, which the script now always writes — never invent a filename from the institute name.** The script canonicalizes + maps names (e.g. *Microsoft Research Asia* → slug `microsoft``assets/logos/microsoft.png`, the four-square corporate mark). If you piped stdout through `tail`/`head` and lost entries, read `assets/logos/logos.json` — it always has the full, correct list.
600601
2. **Read the `"missing"` array.** For **EVERY** name in it, run the web-search fallback — this is required, not optional:
601602
- `WebSearch` `"<institute> official logo png"` (try `svg`, or the institute's English name / acronym expansion if the raw string is a department or non-English name).
602603
- Pick the **official** mark from the institute's own site / brand-resources page, Wikipedia/Wikimedia, or an official social profile — **skip** stock-photo aggregators, third-party redraws, photos, campus/building shots, and flags. `WebFetch` the page if you need to locate the direct image URL.

ResearchStudio-Reel/skills/paper2assets/scripts/fetch_logos.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -590,22 +590,37 @@ def _add(t: str) -> None:
590590
titles.append(t)
591591
for cand in candidates:
592592
_add(resolve_wikipedia_title(cand))
593-
# Disambiguation-aware company/org variants (GENERAL -- no per-company alias):
594-
# an ambiguous short name ("Runway", "Cohere") often has its company article
595-
# under a "(company)" / "(organization)" title while the BARE article is a
596-
# dictionary word or disambiguation page. Strip a trailing "ML/AI/Inc/Labs/..."
597-
# so "Runway ML" -> "Runway (company)". Tried BEFORE the noisy opensearch
598-
# results so e.g. "Runway (company)" beats "Runway (song)".
599-
core = re.sub(r"\s+(ml|ai|inc\.?|llc|ltd\.?|gmbh|labs?|research|technologies)$",
600-
"", name.strip(), flags=re.I).strip()
601-
for base in dict.fromkeys([name.strip(), core]):
602-
if base and 1 <= len(base.split()) <= 3:
603-
for suf in ("(company)", "(organization)", "(software)"):
604-
_add(f"{base} {suf}")
605-
for q in dict.fromkeys([name.strip(), candidates[0]]):
606-
for t in search_wikipedia_titles(q):
607-
if search_title_relevant(q, t):
608-
_add(t)
593+
# If ANY candidate has an explicit ALIAS mapping, that alias IS the answer --
594+
# a curated brand->parent decision (e.g. "microsoft research asia" -> "Microsoft"
595+
# for the four-square corporate mark, NOT the Microsoft Research wordmark).
596+
# Trust it and SKIP the opensearch fallback below, which otherwise searches
597+
# the raw name and drags in the sibling-brand page ("Microsoft Research"),
598+
# winning nondeterministically whenever the aliased page fetch flakes.
599+
aliased = any(c.strip().lower() in ALIASES for c in candidates)
600+
if not aliased:
601+
# Disambiguation-aware company/org variants (GENERAL -- no per-company alias):
602+
# an ambiguous short name ("Runway", "Cohere") often has its company article
603+
# under a "(company)" / "(organization)" title while the BARE article is a
604+
# dictionary word or disambiguation page. Strip a trailing "ML/AI/Inc/Labs/..."
605+
# so "Runway ML" -> "Runway (company)". Tried BEFORE the noisy opensearch
606+
# results so e.g. "Runway (company)" beats "Runway (song)".
607+
core = re.sub(r"\s+(ml|ai|inc\.?|llc|ltd\.?|gmbh|labs?|research|technologies)$",
608+
"", name.strip(), flags=re.I).strip()
609+
for base in dict.fromkeys([name.strip(), core]):
610+
if base and 1 <= len(base.split()) <= 3:
611+
for suf in ("(company)", "(organization)", "(software)"):
612+
_add(f"{base} {suf}")
613+
for q in dict.fromkeys([name.strip(), candidates[0]]):
614+
for t in search_wikipedia_titles(q):
615+
if search_title_relevant(q, t):
616+
_add(t)
617+
else:
618+
# Aliased: only allow the disambiguation variants of the alias TARGET.
619+
for cand in candidates:
620+
tgt = ALIASES.get(cand.strip().lower())
621+
if tgt and 1 <= len(tgt.split()) <= 3:
622+
for suf in ("(company)", "(organization)"):
623+
_add(f"{tgt} {suf}")
609624

610625
for title in titles:
611626
page = "https://en.wikipedia.org/wiki/" + urllib.parse.quote(title.replace(" ", "_"))
@@ -878,7 +893,23 @@ def main() -> int:
878893
if missing:
879894
print(f"[fetch_logos] ✗ MISSING — WEB-SEARCH FALLBACK REQUIRED (Step 6): {', '.join(missing)}", file=sys.stderr)
880895

881-
print(json.dumps({"logos": results, "missing": missing}, indent=2))
896+
# Persist the FULL manifest to disk as well as stdout. Callers sometimes
897+
# pipe stdout through `tail`/`head` to save tokens, which silently drops
898+
# early entries (e.g. the first institute's slug) -- the agent then invents a
899+
# wrong logo filename (msra -> 'microsoft-research-asia.png') instead of the
900+
# mapped slug ('microsoft'). A stable file lets the poster stage read the
901+
# correct slug regardless of any stdout truncation.
902+
manifest = {"logos": results, "missing": missing}
903+
try:
904+
(logos_dir / "logos.json").write_text(
905+
json.dumps(manifest, indent=2), encoding="utf-8"
906+
)
907+
print(f"[fetch_logos] manifest -> {layout.LOGOS}/logos.json "
908+
f"({len(results)} logo(s))", file=sys.stderr)
909+
except Exception as exc: # noqa: BLE001
910+
print(f"[fetch_logos] WARNING: could not write logos.json: {exc}", file=sys.stderr)
911+
912+
print(json.dumps(manifest, indent=2))
882913
return 0 if results else 1
883914

884915

0 commit comments

Comments
 (0)