Separate the local recognition, web publishing, and shared data paths while preserving direct script execution and existing site content. Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
4.6 KiB
Python
159 lines
4.6 KiB
Python
"""Import qualitative relations from a Chinese draft spreadsheet.
|
|
|
|
Usage:
|
|
python import_relations_xlsx.py
|
|
python import_relations_xlsx.py "C:\\Users\\Administrator\\Downloads\\dota2.xlsx"
|
|
|
|
Writes data/relations.json. Skips category phrases (辅助/幻想系/...).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
import argparse
|
|
import json
|
|
from datetime import datetime, timezone
|
|
|
|
from shared.paths import SHARED_DATA
|
|
from shared.relations import (
|
|
DEFAULT_RELATIONS,
|
|
build_name_index,
|
|
load_relations,
|
|
resolve_name,
|
|
save_relations,
|
|
set_counter,
|
|
set_synergy,
|
|
split_names,
|
|
)
|
|
|
|
# Phrases that are roles/tags, not single heroes.
|
|
SKIP_TOKENS = {
|
|
"辅助",
|
|
"推进",
|
|
"幻想系",
|
|
"幻想",
|
|
"所有肉核",
|
|
"肉核",
|
|
"高爆发",
|
|
"奶妈",
|
|
"核心",
|
|
}
|
|
|
|
|
|
def _should_skip(token: str) -> bool:
|
|
t = token.strip()
|
|
if not t:
|
|
return True
|
|
if t in SKIP_TOKENS:
|
|
return True
|
|
if "系" in t and t.endswith("系"):
|
|
return True
|
|
if t.startswith("所有"):
|
|
return True
|
|
if "魔晶" in t or "a仗" in t.lower():
|
|
# item/facet interactions in hero columns — skip for edge seed
|
|
return True
|
|
return False
|
|
|
|
|
|
def import_xlsx(path: Path) -> tuple[dict, list[str]]:
|
|
import openpyxl
|
|
|
|
wb = openpyxl.load_workbook(path, data_only=True)
|
|
index = build_name_index()
|
|
data = load_relations()
|
|
data["meta"] = {
|
|
"source": str(path),
|
|
"imported_at": datetime.now(timezone.utc).isoformat(),
|
|
"note": "qualitative counters/synergies; no winrate",
|
|
}
|
|
unresolved: list[str] = []
|
|
sheets = ["力量", "敏捷", "智力", "全才"]
|
|
|
|
def resolve_list(cell, context: str) -> list[str]:
|
|
keys = []
|
|
for tok in split_names(cell):
|
|
if _should_skip(tok):
|
|
unresolved.append(f"{context}: skip tag '{tok}'")
|
|
continue
|
|
key = resolve_name(tok, index)
|
|
if key is None:
|
|
unresolved.append(f"{context}: unresolved '{tok}'")
|
|
continue
|
|
keys.append(key)
|
|
return keys
|
|
|
|
for sheet in sheets:
|
|
if sheet not in wb.sheetnames:
|
|
continue
|
|
ws = wb[sheet]
|
|
rows = list(ws.iter_rows(values_only=True))
|
|
# find header
|
|
header_i = None
|
|
for i, row in enumerate(rows):
|
|
if row and row[0] == "英雄":
|
|
header_i = i
|
|
break
|
|
if header_i is None:
|
|
continue
|
|
for row in rows[header_i + 1 :]:
|
|
if not row or not row[0]:
|
|
continue
|
|
hero_raw = str(row[0]).strip()
|
|
hero = resolve_name(hero_raw, index)
|
|
if hero is None:
|
|
unresolved.append(f"{sheet}: hero unresolved '{hero_raw}'")
|
|
continue
|
|
# col1 被克制, col2 克制, col3 搭档
|
|
countered_by = resolve_list(row[1] if len(row) > 1 else None, f"{hero_raw}/被克")
|
|
counters = resolve_list(row[2] if len(row) > 2 else None, f"{hero_raw}/克制")
|
|
partners = resolve_list(row[3] if len(row) > 3 else None, f"{hero_raw}/搭档")
|
|
|
|
for other in counters:
|
|
set_counter(data, hero, other, reason="")
|
|
for other in countered_by:
|
|
# other counters hero
|
|
set_counter(data, other, hero, reason="")
|
|
for other in partners:
|
|
set_synergy(data, hero, other, reason="")
|
|
|
|
return data, unresolved
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument(
|
|
"xlsx",
|
|
nargs="?",
|
|
default=str(Path.home() / "Downloads" / "dota2.xlsx"),
|
|
help="path to dota2.xlsx",
|
|
)
|
|
ap.add_argument("--out", default=str(DEFAULT_RELATIONS))
|
|
args = ap.parse_args()
|
|
path = Path(args.xlsx)
|
|
if not path.is_file():
|
|
raise SystemExit(f"file not found: {path}")
|
|
|
|
data, unresolved = import_xlsx(path)
|
|
out = save_relations(data, args.out)
|
|
print(f"wrote {out}")
|
|
print(f"counters={len(data['counters'])} synergies={len(data['synergies'])}")
|
|
report = SHARED_DATA / "relations_import_report.json"
|
|
report.write_text(
|
|
json.dumps({"unresolved": unresolved, "count": len(unresolved)}, ensure_ascii=False, indent=2) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
print(f"unresolved notes: {len(unresolved)} → {report}")
|
|
for line in unresolved[:40]:
|
|
print(" ", line)
|
|
if len(unresolved) > 40:
|
|
print(f" ... +{len(unresolved) - 40} more")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|