#!/opt/homebrew/bin/python3.11 """ Rebuild parent/story chunks (280 × 15 children) + LLM summaries + Qdrant momentry_dev_stories. """ import json, sys, time, psycopg2 from collections import Counter from urllib.request import Request, urlopen UUID = "aeed71342a899fe4b4c57b7d41bcb692" DB_URL = "postgresql://accusys@localhost:5432/momentry?host=/tmp" QDRANT_URL = "http://localhost:6333" LLM_URL = "http://localhost:8082/v1/chat/completions" EMBED_URL = "http://localhost:11436/v1/embeddings" FPS = 25.0 FILE_ID = 242 CHILDREN_PER_PARENT = 15 print("=== Step 1: Load sentence chunks sorted by time ===") conn = psycopg2.connect(DB_URL) cur = conn.cursor() cur.execute(""" SELECT chunk_index, chunk_id, start_time, end_time, text_content, metadata->>'speaker_name', file_uuid FROM dev.chunks WHERE file_uuid=%s AND chunk_type='sentence' ORDER BY start_time, chunk_index """, (UUID,)) children = cur.fetchall() print(f"Loaded {len(children)} sentence chunks") # Group into parents of 15 parents = [] for i in range(0, len(children), CHILDREN_PER_PARENT): group = children[i:i+CHILDREN_PER_PARENT] if not group: continue p_start = group[0][2] p_end = group[-1][3] child_ids = [c[1] for c in group] # Speaker breakdown spk_counter = Counter(c[4] for c in group) # Actually count speaker names spk_names = Counter(c[5] for c in group) primary = spk_names.most_common(1)[0][0] if spk_names else "Unknown" parents.append({ "start": p_start, "end": p_end, "child_ids": child_ids, "child_indices": [c[0] for c in group], "speakers": dict(spk_names.most_common()), "primary": primary, "texts": [c[4] for c in group], }) print(f"Parent chunks: {len(parents)}") print(f"Speakers per parent: {[len(p['speakers']) for p in parents[:5]]}") # Delete old story chunks cur.execute("DELETE FROM dev.chunks WHERE file_uuid=%s AND chunk_type='story'", (UUID,)) print(f"Deleted old story chunks: {cur.rowcount}") # Insert parent chunks print("\n=== Step 2: Insert parent chunks ===") parent_records = [] for pi, p in enumerate(parents): pid = f"{UUID}_story_{pi}" dialogue = " ".join([t or "" for t in p["texts"]]) sf = int(p["start"] * FPS) ef = int(p["end"] * FPS) fc = ef - sf metadata = json.dumps({ "method": "fixed_15", "seg_count": len(p["child_ids"]), "speakers": p["speakers"], "speaker_count": len(p["speakers"]), "primary_speaker": p["primary"], "words": len(dialogue.split()), }) parent_records.append(( UUID, pid, pi, "story", p["start"], p["end"], json.dumps({"type": "story_parent"}), dialogue, FPS, sf, ef, fc, FILE_ID, pid, metadata, p["child_ids"], [], None, )) cur.executemany(""" INSERT INTO dev.chunks (file_uuid, chunk_id, chunk_index, chunk_type, start_time, end_time, content, text_content, fps, start_frame, end_frame, frame_count, file_id, old_chunk_id, metadata, child_chunk_ids, pre_chunk_ids, summary_text) VALUES (%s,%s,%s,%s,%s,%s,%s::jsonb,%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s,%s,%s) """, parent_records) conn.commit() print(f"Inserted {len(parent_records)} parent chunks") # Update sentence chunks with parent_chunk_id for pi, p in enumerate(parents): pid = f"{UUID}_story_{pi}" for cid in p["child_ids"]: cur.execute("UPDATE dev.chunks SET parent_chunk_id=%s WHERE chunk_id=%s", (pid, cid)) conn.commit() print("Updated child parent references") print("\n=== Step 3: Generate LLM summaries ===") def call_llm(prompt): body = json.dumps({"model": "google_gemma-4-26B-A4B-it-Q5_K_M.gguf", "messages": [{"role": "user", "content": prompt}], "temperature": 0.1, "max_tokens": 100}).encode() req = Request(LLM_URL, data=body, headers={"Content-Type": "application/json"}) resp = urlopen(req, timeout=120) return json.loads(resp.read())["choices"][0]["message"]["content"].strip() def call_embed(text): body = json.dumps({"input": text}).encode() req = Request(EMBED_URL, data=body, headers={"Content-Type": "application/json"}) resp = urlopen(req, timeout=30) return json.loads(resp.read())["data"][0]["embedding"] t0 = time.time() summaries = [] for pi, p in enumerate(parents): dialogue = " ".join([t or "" for t in p["texts"]]) if len(dialogue) < 10: summary = "[no dialogue]" embedding = [0.0] * 768 else: try: prompt = f"Act as a film scene analyst. Summarize this dialogue in 50 words:\n{dialogue[:3000]}" summary = call_llm(prompt) time.sleep(0.2) embedding = call_embed(summary) except Exception as e: print(f" P{pi} ERROR: {e}") summary = "[error]" embedding = [0.0] * 768 cur.execute("UPDATE dev.chunks SET summary_text=%s, updated_at=NOW() WHERE chunk_id=%s", (summary, f"{UUID}_story_{pi}")) summaries.append({"index": pi, "chunk_id": f"{UUID}_story_{pi}", "summary": summary, "start": p["start"], "end": p["end"], "embedding": embedding}) if (pi + 1) % 20 == 0: print(f" [{pi+1}/{len(parents)}] [{time.time()-t0:.0f}s]") conn.commit() print(f"Summaries: {len(summaries)}") print("\n=== Step 4: Update Qdrant momentry_dev_stories ===") # Delete old req = Request(f"{QDRANT_URL}/collections/momentry_dev_stories", method="DELETE") try: urlopen(req); time.sleep(0.5) except: pass # Create req = Request(f"{QDRANT_URL}/collections/momentry_dev_stories", data=json.dumps({"vectors": {"size": 768, "distance": "Cosine"}}).encode(), headers={"Content-Type": "application/json"}, method="PUT") urlopen(req) time.sleep(0.5) # Upload dialogue + summary points (dialogue=0..279, summary=280..559) points = [] for pi, p in enumerate(parents): # Dialogue point (zero vector) points.append({ "id": pi + 1, "vector": [0.0] * 768, "payload": {"chunk_id": f"{UUID}_story_{pi}", "file_uuid": UUID, "start_time": p["start"], "end_time": p["end"], "type": "story_dialogue", "text": " ".join(p["texts"])[:500]}, }) # Summary point s = summaries[pi] points.append({ "id": pi + 1 + len(parents), "vector": s["embedding"], "payload": {"chunk_id": s["chunk_id"], "file_uuid": UUID, "start_time": s["start"], "end_time": s["end"], "type": "story_summary", "summary": s["summary"]}, }) batch_size = 100 for start in range(0, len(points), batch_size): batch = points[start:start+batch_size] req = Request(f"{QDRANT_URL}/collections/momentry_dev_stories/points?wait=true", data=json.dumps({"points": batch}).encode(), headers={"Content-Type": "application/json"}, method="PUT") try: urlopen(req) except Exception as e: print(f" batch {start}: {e}") print(f"Uploaded {len(points)} points") # Verify resp = json.loads(urlopen(f"{QDRANT_URL}/collections/momentry_dev_stories").read()) info = resp["result"] print(f"Verifed: {info['points_count']} pts, {info['config']['params']['vectors'].get('size','?')}D") conn.close() print(f"\n=== Done [{time.time()-t0:.0f}s] ===")