learning-world-goliath / scripts /pull_feedback.py
defenestrate's picture
Feedback loop + demo polish (repo commit c46a8e3)
dcd4298 verified
Raw History Blame Contribute Delete
3.59 kB
"""Pull playtest feedback down from the private HF Dataset and summarise it.
python scripts/pull_feedback.py # summary to stdout
python scripts/pull_feedback.py --out fb.jsonl # also write a merged JSONL
Requires LWC_FEEDBACK_DATASET (e.g. "defenestrate/lwc-feedback") and HF_TOKEN.
Each feedback submission is stored as its own file under records/YYYY/MM/DD/
because the Space's local disk is ephemeral — see world_compiler/ui/feedback.py.
This script globs them back together in timestamp order.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
def load_records(local_dir: Path) -> list[dict]:
records = []
for path in sorted((local_dir / "records").rglob("*.json")):
try:
records.append(json.loads(path.read_text(encoding="utf-8")))
except (OSError, json.JSONDecodeError) as exc:
print(f"skipping unreadable {path.name}: {exc}", file=sys.stderr)
records.sort(key=lambda r: r.get("timestamp", ""))
return records
def summarise(records: list[dict]) -> None:
if not records:
print("No feedback yet.")
return
print(f"{len(records)} feedback records\n")
by_version: dict[str, int] = {}
for r in records:
version = f"{r.get('scenario_id', '?')} v{r.get('scenario_version', '?')}"
by_version[version] = by_version.get(version, 0) + 1
print("By scenario version:")
for version, count in sorted(by_version.items()):
print(f" {version}: {count}")
print()
for r in records:
print("=" * 78)
print(
f"{r.get('timestamp', '?')} {r.get('username', '?')} "
f"[{r.get('status', '?')}] talking to: {r.get('character') or '(nobody)'}"
)
for student_text, actor_text in r.get("last_exchange") or []:
print(f" they asked: {student_text[:160]}")
print(f" reply began: {actor_text[:160]}")
print(f"COMMENT: {str(r.get('comment', '')).strip()[:600]}")
print()
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, help="write merged JSONL here")
parser.add_argument(
"--local-dir",
type=Path,
default=Path("feedback_pull"),
help="where to download the dataset snapshot",
)
args = parser.parse_args(argv)
repo = os.getenv("LWC_FEEDBACK_DATASET")
token = os.getenv("HF_TOKEN")
if not repo or not token:
print(
"Set LWC_FEEDBACK_DATASET and HF_TOKEN first.\n"
" LWC_FEEDBACK_DATASET the dataset repo id, e.g. defenestrate/lwc-feedback\n"
" HF_TOKEN a token with read access to it",
file=sys.stderr,
)
return 1
from huggingface_hub import snapshot_download
snapshot_download(
repo_id=repo,
repo_type="dataset",
local_dir=str(args.local_dir),
token=token,
)
records = load_records(args.local_dir)
summarise(records)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
with args.out.open("w", encoding="utf-8") as fh:
for r in records:
fh.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"wrote {len(records)} records -> {args.out}")
return 0
if __name__ == "__main__":
sys.exit(main())