#!/usr/bin/env python3"""tags.jsonl -> カテゴリ別 ID リスト(決定的な写像)"""import json, sysfrom pathlib import PathCATEGORY_ORDER = ["人気", "風景", "夜景", "花", "動物", "抽象"]RULES = [ ("夜景", {"all": ["night"], "any": ["city", "skyline", "street"]}), ("風景", {"any": ["mountain", "sea", "sky", "forest"]}), ("花", {"any": ["flower", "petal"]}), ("動物", {"any": ["cat", "dog", "bird"]}), ("抽象", {"any": ["gradient", "geometric", "texture"], "none": ["cat", "dog", "bird"]}), ("人気", {"any": ["featured"]}),]VOCAB = {t for _, r in RULES for k in ("all", "any", "none") for t in r.get(k, [])}def match(rule, tags): if any(t not in tags for t in rule.get("all", [])): return False if rule.get("any") and not any(t in tags for t in rule["any"]): return False if any(t in tags for t in rule.get("none", [])): return False return Truedef main(path): buckets = {c: [] for c in CATEGORY_ORDER} unknown, unassigned = {}, [] for line in Path(path).read_text(encoding="utf-8").splitlines(): if not line.strip(): continue rec = json.loads(line) wid, tags = str(rec["id"]), set(rec["tags"]) for t in tags - VOCAB: # 語彙外は捨てずに数える unknown[t] = unknown.get(t, 0) + 1 hit = [c for c, r in RULES if match(r, tags)] if not hit: unassigned.append(wid) # 穴を明示的に残す for c in hit: buckets[c].append(wid) for c in CATEGORY_ORDER: # 出力順はテンプレート固定 print(f"{c}: " + ", ".join(buckets[c])) print(f"[unassigned] {len(unassigned)} -> {unassigned}", file=sys.stderr) print(f"[unknown-tags] {sorted(unknown.items())}", file=sys.stderr)if __name__ == "__main__": main(sys.argv[1])
#!/usr/bin/env python3"""新旧の写像結果を比べ、所属が変わったIDだけを出す"""import subprocess, sys, collectionsdef load(script, data): out = subprocess.run([sys.executable, script, data], capture_output=True, text=True).stdout m = collections.defaultdict(set) for line in out.splitlines(): cat, _, ids = line.partition(":") for i in ids.split(","): if i.strip(): m[i.strip()].add(cat.strip()) return mold, new = load(sys.argv[1], sys.argv[3]), load(sys.argv[2], sys.argv[3])moved = 0for wid in sorted(set(old) | set(new)): a, b = old.get(wid, set()), new.get(wid, set()) if a != b: moved += 1 print(f"{wid}: {sorted(a) or ['-']} -> {sorted(b) or ['-']}")print(f"moved={moved} / total={len(set(old) | set(new))}")