Spaces:
Running on Zero
Running on Zero
| """ | |
| Build the frontend's call_data.json from transcript_data.json: | |
| - groups words into speaker turns (agent/customer) | |
| - chapters the call via the LiteLLM router | |
| - emits turns + chapters with numeric seconds, ready for the React player | |
| Output: frontend/src/call_data.json | |
| { call, duration, turns:[{i,speaker,text,start,end}], | |
| chapters:[{index,label,summary,start,end,start_turn}] } | |
| """ | |
| import os, json, time | |
| import paths | |
| from env_util import load_env | |
| from segment import SYSTEM, SKELETON, numbered_transcript | |
| from router import chat_json_routed | |
| from extract import strip_fences | |
| load_env() | |
| FRONTEND = str(paths.FRONTEND_ROOT) | |
| TRANSCRIPT = os.path.join(FRONTEND, "transcript_data.json") | |
| OUT = os.path.join(FRONTEND, "src", "call_data.json") | |
| def build_turns(data): | |
| """Merge agent+customer words into time-ordered speaker turns.""" | |
| merged = [] | |
| for speaker, key in (("AGENT", "agent"), ("CUSTOMER", "customer")): | |
| for w in data.get(key, []): | |
| if w.get("start") is None: | |
| continue | |
| merged.append((w["start"], w.get("end", w["start"]), | |
| speaker, w["word"])) | |
| merged.sort(key=lambda x: x[0]) | |
| turns = [] | |
| cur = None | |
| for start, end, sp, word in merged: | |
| if cur is None or sp != cur["speaker"]: | |
| if cur: | |
| turns.append(cur) | |
| cur = {"speaker": sp, "text": word, "start": start, "end": end} | |
| else: | |
| cur["text"] += " " + word | |
| cur["end"] = end | |
| if cur: | |
| turns.append(cur) | |
| for i, t in enumerate(turns): | |
| t["i"] = i | |
| t["start"] = round(t["start"], 2) | |
| t["end"] = round(t["end"], 2) | |
| return turns | |
| def repair_chapters(seeds, turns, duration): | |
| n = len(turns) | |
| cleaned, seen = [], set() | |
| for s in seeds: | |
| try: | |
| st = max(0, min(int(s["start_turn"]), n - 1)) | |
| except (KeyError, ValueError, TypeError): | |
| continue | |
| if st in seen: | |
| continue | |
| seen.add(st) | |
| cleaned.append({"start_turn": st, | |
| "label": str(s.get("label", "Untitled")).strip(), | |
| "summary": str(s.get("summary", "")).strip()}) | |
| cleaned.sort(key=lambda x: x["start_turn"]) | |
| if not cleaned: | |
| cleaned = [{"start_turn": 0, "label": "Full Call", "summary": ""}] | |
| if cleaned[0]["start_turn"] != 0: | |
| cleaned[0]["start_turn"] = 0 | |
| chapters = [] | |
| for i, c in enumerate(cleaned): | |
| st = c["start_turn"] | |
| start = turns[st]["start"] | |
| end = (turns[cleaned[i + 1]["start_turn"]]["start"] | |
| if i + 1 < len(cleaned) else duration) | |
| chapters.append({"index": i + 1, "label": c["label"], | |
| "summary": c["summary"], "start": round(start, 2), | |
| "end": round(end, 2), "start_turn": st}) | |
| return chapters | |
| def main(): | |
| with open(TRANSCRIPT, encoding="utf-8") as f: | |
| data = json.load(f) | |
| turns = build_turns(data) | |
| duration = round(max(t["end"] for t in turns), 2) | |
| print(f"call={data.get('call')} | {len(turns)} turns | {duration:.0f}s") | |
| user = f"{numbered_transcript(turns)}\n\n{SKELETON}" | |
| t0 = time.time() | |
| raw, served = chat_json_routed(SYSTEM, user, max_tokens=1500, return_meta=True) | |
| seeds = json.loads(strip_fences(raw)).get("chapters", []) | |
| chapters = repair_chapters(seeds, turns, duration) | |
| print(f"chaptered by {served} in {time.time()-t0:.1f}s -> {len(chapters)} chapters") | |
| for c in chapters: | |
| print(f" [{c['index']}] {c['start']:6.1f}-{c['end']:6.1f} {c['label']}") | |
| out = {"call": data.get("call", "call_1"), | |
| "model": data.get("model", ""), | |
| "duration": duration, "turns": turns, "chapters": chapters} | |
| os.makedirs(os.path.dirname(OUT), exist_ok=True) | |
| with open(OUT, "w", encoding="utf-8") as f: | |
| json.dump(out, f, indent=2) | |
| print(f"\nSaved -> {OUT}") | |
| if __name__ == "__main__": | |
| main() | |