"""Read-only exploration provenance for reports, archives and source-aware video.""" import json from artemis.exploration.store import explorations from artemis.exploration.demonstrations import Demonstrations from apps.admin_console.services.evidence_service import evidence_file, task_root def exploration_metadata(run_id: str) -> dict | None: run = explorations.run(run_id) if not run: return None return { "id": run["exploration_id"], "kind": run["kind"], "case_version": run.get("case_version"), } def demonstration_sources(run_id: str) -> list[dict]: root = task_root(run_id) directory = root / "evidence" / "demonstrations" from apps.admin_console.services.media_service import MediaService sources = [] for manifest in directory.glob("*/demonstration.json"): if ( not evidence_file(root, manifest.relative_to(root).as_posix()) or manifest.stat().st_size > 4 * 1024 * 1024 ): continue try: record = json.loads(manifest.read_text(encoding="utf-8")) for track in record["media"]: segments = [] for item in track["segments"]: path = evidence_file(root, item["file"]) if path and path.suffix.lower() == ".mp4" and item["duration_ms"] > 0: segments.append( { **item, "start": item["offset_ms"] / 1000, "duration": item["duration_ms"] / 1000, "url": MediaService.path_to_video_url(path), } ) if segments: sources.append( { **track, "segments": segments, "label": "人工示范 · " + ("安卓" if track["kind"] == "android" else "电脑"), "status": "ready", "message": "人工操作录像,不等于自动复跑通过", "demonstration_id": record["id"], } ) except (OSError, ValueError, KeyError, TypeError): continue return sources def demonstration_files(run_id: str) -> list[tuple]: root = task_root(run_id) directory = root / "evidence" / "demonstrations" files = [] for path in directory.rglob("*"): if path.suffix.lower() not in {".mp4", ".mkv", ".webm"}: continue checked = evidence_file(root, path.relative_to(root).as_posix()) if checked: files.append((checked, "任务录屏/人工示范/" + path.relative_to(directory).as_posix())) return files def exploration_index(run_id: str, archive_paths: dict | None = None) -> dict | None: run = explorations.run(run_id) if not run: return None sid = run["exploration_id"] session = explorations.get(sid) messages = [] after = 0 while page := explorations.messages(sid, after=after): messages.extend(page) after = page[-1]["sequence"] demos = Demonstrations(explorations).list(sid) root = task_root(run_id) for demo in demos: for track in demo.get("media", []): for segment in track.get("segments", []): relative = segment.pop("file", "") segment["trace_file"] = relative segment["file"] = ( (archive_paths or {}).get((root / relative).resolve()) if demo["run_id"] == run_id else None ) return { "session": session, "messages": messages, "runs": explorations.runs(sid), "cases": [ explorations.case(sid, version) for version in range(1, session["case_version"] + 1) ], "demonstrations": demos, "note": "此导出保留会话、用例版本和各次运行的关联;只有当前任务的媒体文件在此目录中。", }