Reorganize repository into pc web shared monorepo
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>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user