Download code_release/code/memebench/dataset.py from clinical-agent-memory-bench/meme-bench: direct link, hf CLI and curl.
- Browser
- Download file 6.5 kB
-
https://huggingface.co/spaces/clinical-agent-memory-bench/meme-bench/resolve/main/code_release/code/memebench/dataset.py
- Command line
-
hf download hf://spaces/clinical-agent-memory-bench/meme-bench/code_release/code/memebench/dataset.py
-
curl -L -o dataset.py https://huggingface.co/spaces/clinical-agent-memory-bench/meme-bench/resolve/main/code_release/code/memebench/dataset.py
6.5 kB
| """Dataset loading: patient registry, visit sessions, questions and checkpoints. | |
| Each patient directory holds `dialogues.jsonl` (one record per visit) and `questions.jsonl` | |
| (one record per question, carrying its checkpoint visit). Sessions are sorted by | |
| (date, visit_number) and records without turns are dropped. A visit is rendered as | |
| `=== Visit v{n} 路 {date} 路 {department} ===` followed by `Speaker: text` lines; annotation | |
| fields (`meta`, per-turn `covers`) never enter memory text. A question's checkpoint must | |
| name an existing visit, and its checkpoint date must equal that visit's date. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| def read_jsonl(path) -> list[dict]: | |
| return [json.loads(l) for l in open(path, encoding="utf-8") if l.strip()] | |
| def load_sessions(path) -> list[dict]: | |
| """Return non-empty sessions sorted by (date, visit_number), keeping only the fields memory may see.""" | |
| rows = read_jsonl(path) | |
| rows = [r for r in rows if r.get("turns")] | |
| rows.sort(key=lambda r: (r["date"], r["visit_number"])) | |
| return [{"visit_number": r["visit_number"], "date": r["date"], | |
| "department": r["department"], "session_id": r.get("session_id"), | |
| "turns": [{"speaker": t["speaker"], "text": t["text"]} for t in r["turns"]]} | |
| for r in rows] | |
| def render_dialogue(rec: dict) -> str: | |
| return "\n".join(f"{t['speaker']}: {t['text']}" for t in rec["turns"]) | |
| def render_visit(rec: dict) -> str: | |
| return f"=== Visit v{rec['visit_number']} 路 {rec['date']} 路 {rec['department']} ===\n" + render_dialogue(rec) | |
| class Question: | |
| qid: int | |
| raw: dict | |
| ckpt_visit: int = -1 | |
| ckpt_date: str = "" | |
| def axis(self) -> str: | |
| return str(self.raw.get("axis", "")) | |
| def answer_type(self) -> str: | |
| return str(self.raw.get("answer_type", "")) | |
| def question(self) -> str: | |
| return self.raw["question"] | |
| def grounding(self) -> str: | |
| return str(self.raw.get("grounding", "record")) | |
| def evidence_visits(self) -> list[int]: | |
| return list(self.raw.get("evidence_visits", [])) | |
| def load_questions(questions_file, sessions: list[dict]) -> list[Question]: | |
| """Load a patient's questions, each with its checkpoint visit and date.""" | |
| records = read_jsonl(questions_file) | |
| date_by_visit = {s["visit_number"]: s["date"] for s in sessions} | |
| questions, problems = [], [] | |
| for i, r in enumerate(records): | |
| ck = r.get("checkpoint_visit") | |
| if ck is None: | |
| problems.append(f"qid {r.get('qid')}: no checkpoint_visit") | |
| continue | |
| ck = int(ck) | |
| if ck not in date_by_visit: | |
| problems.append(f"qid {r.get('qid')}: checkpoint_visit v{ck} not in dialogues") | |
| continue | |
| stated = r.get("checkpoint_date") | |
| if stated and str(stated) != date_by_visit[ck]: | |
| problems.append(f"qid {r.get('qid')}: checkpoint_date {stated} != v{ck} date {date_by_visit[ck]}") | |
| continue | |
| questions.append(Question(qid=i, raw=r, ckpt_visit=ck, | |
| ckpt_date=str(stated or date_by_visit[ck]))) | |
| if problems: | |
| raise SystemExit("questions with checkpoints failed validation:\n " + "\n ".join(problems)) | |
| return questions | |
| QUESTIONS_FILE = "questions.jsonl" | |
| class Patient: | |
| """One patient: give a `dir` and let `resolve()` find the files inside it, or set the paths explicitly.""" | |
| name: str | |
| patient_id: str | |
| dialogues: str = "" | |
| questions_dir: str = "" | |
| dir: str = "" | |
| questions_file: str = "" | |
| def resolve(self) -> bool: | |
| """Fill unset paths from `dir`; return whether the questions file exists.""" | |
| d = Path(self.dir) if self.dir else None | |
| if d and not self.dialogues and (d / "dialogues.jsonl").exists(): | |
| self.dialogues = str(d / "dialogues.jsonl") | |
| if d and not self.questions_dir: | |
| self.questions_dir = str(d) | |
| qfile = Path(self.questions_dir) / QUESTIONS_FILE if self.questions_dir else None | |
| if qfile is None or not qfile.exists(): | |
| return False | |
| self.questions_file = str(qfile) | |
| return True | |
| def load(self): | |
| """Return (sessions, questions) for this patient.""" | |
| found = self.resolve() | |
| if not self.dialogues: | |
| raise SystemExit(f"patient '{self.name}': no dialogues file found (dir={self.dir})") | |
| if not found: | |
| raise SystemExit(f"patient '{self.name}': no questions file in {self.questions_dir} " | |
| f"(looked for {QUESTIONS_FILE})") | |
| sessions = load_sessions(self.dialogues) | |
| questions = load_questions(self.questions_file, sessions) | |
| if not questions: | |
| raise SystemExit(f"patient '{self.name}': no questions found in {self.questions_file}") | |
| return sessions, questions | |
| def load_patients(config_path, skipped: list | None = None) -> dict[str, Patient]: | |
| """Build the patient registry from a YAML file. | |
| `patients` lists explicit entries; `patient_globs` lists globs of patient directories, each | |
| registered under its directory name. Directories without a questions file are left out and, | |
| when `skipped` is given, appended to it as (name, reason). | |
| """ | |
| import glob as _glob | |
| import yaml | |
| cfg = yaml.safe_load(open(config_path, encoding="utf-8")) or {} | |
| out: dict[str, Patient] = {} | |
| for p in cfg.get("patients") or []: | |
| out[p["name"]] = Patient( | |
| name=p["name"], patient_id=str(p["patient_id"]), dialogues=p.get("dialogues", ""), | |
| questions_dir=p.get("questions_dir", ""), dir=p.get("dir", "")) | |
| for entry in cfg.get("patient_globs") or []: | |
| pattern, prefix = (entry, "") if isinstance(entry, str) else (entry["path"], entry.get("prefix", "")) | |
| for path in sorted(_glob.glob(str(pattern))): | |
| d = Path(path) | |
| if not d.is_dir(): | |
| continue | |
| pid = d.name | |
| name = f"{prefix}{pid}" | |
| if name in out: | |
| continue | |
| pat = Patient(name=name, patient_id=pid, dir=str(d)) | |
| if not pat.resolve(): | |
| if skipped is not None: | |
| skipped.append((name, f"no {QUESTIONS_FILE} in {d}")) | |
| continue | |
| out[name] = pat | |
| return out | |