WhoIsLuwah commited on
Commit
a927b9c
·
1 Parent(s): f39e6ee

feat(backend): store and query reel embeddings in the data layer

Browse files

Claude-Session: https://claude.ai/code/session_01MV2z14HLUeGpAd4apqTpCh

Files changed (2) hide show
  1. db.py +26 -8
  2. test_pipeline.py +69 -0
db.py CHANGED
@@ -35,13 +35,17 @@ def _chunked(seq: list, size: int):
35
  yield seq[i:i + size]
36
 
37
 
38
- def _select_newest_first(columns: str, limit: Optional[int] = None) -> list:
39
- """Select rows newest-first, paging past PostgREST's per-request row cap."""
 
 
40
  rows = []
41
  while limit is None or len(rows) < limit:
42
  last = len(rows) + 999 if limit is None else limit - 1
43
- page = (get_client().table(TABLE).select(columns)
44
- .order("created_at", desc=True).order("id")
 
 
45
  .range(len(rows), last).execute().data or [])
46
  if not page:
47
  break
@@ -78,7 +82,7 @@ def get_reel_by_url(url: str) -> Optional[dict]:
78
 
79
 
80
  def insert_reel(url, title, raw_transcript, post_caption, extracted_json,
81
- status="done", source=None) -> dict:
82
  row = {
83
  "id": str(uuid.uuid4()),
84
  "url": url,
@@ -90,6 +94,8 @@ def insert_reel(url, title, raw_transcript, post_caption, extracted_json,
90
  "status": status,
91
  "source": source,
92
  }
 
 
93
  get_client().table(TABLE).insert(row).execute()
94
  return row_to_record(row)
95
 
@@ -139,6 +145,15 @@ def reels_for_clustering() -> list:
139
  return _select_newest_first("id, extracted_json")
140
 
141
 
 
 
 
 
 
 
 
 
 
142
  def set_cluster(reel_ids: list, cluster: Optional[str]) -> None:
143
  for chunk in _chunked(reel_ids, 100):
144
  get_client().table(TABLE).update({"cluster": cluster}).in_("id", chunk).execute()
@@ -179,9 +194,9 @@ def claim_next_pending() -> Optional[dict]:
179
 
180
 
181
  def update_reel_result(reel_id: str, title: str, raw_transcript, post_caption,
182
- extracted_json, status: str = "done") -> None:
183
  """Write pipeline results back onto an existing (claimed) row."""
184
- get_client().table(TABLE).update({
185
  "title": title,
186
  "raw_transcript": raw_transcript,
187
  "post_caption": post_caption,
@@ -190,7 +205,10 @@ def update_reel_result(reel_id: str, title: str, raw_transcript, post_caption,
190
  "error": None,
191
  "processing_started_at": None,
192
  "next_attempt_at": None,
193
- }).eq("id", reel_id).execute()
 
 
 
194
 
195
 
196
  def mark_failed(reel_id: str, error, status: str = "failed") -> None:
 
35
  yield seq[i:i + size]
36
 
37
 
38
+ def _select_newest_first(columns: str, limit: Optional[int] = None, refine=None) -> list:
39
+ """Select rows newest-first, paging past PostgREST's per-request row cap.
40
+
41
+ refine, if given, adds filters to each page's query."""
42
  rows = []
43
  while limit is None or len(rows) < limit:
44
  last = len(rows) + 999 if limit is None else limit - 1
45
+ query = get_client().table(TABLE).select(columns)
46
+ if refine:
47
+ query = refine(query)
48
+ page = (query.order("created_at", desc=True).order("id")
49
  .range(len(rows), last).execute().data or [])
50
  if not page:
51
  break
 
82
 
83
 
84
  def insert_reel(url, title, raw_transcript, post_caption, extracted_json,
85
+ status="done", source=None, embedding=None) -> dict:
86
  row = {
87
  "id": str(uuid.uuid4()),
88
  "url": url,
 
94
  "status": status,
95
  "source": source,
96
  }
97
+ if embedding is not None:
98
+ row["embedding"] = embedding # list -> pgvector
99
  get_client().table(TABLE).insert(row).execute()
100
  return row_to_record(row)
101
 
 
145
  return _select_newest_first("id, extracted_json")
146
 
147
 
148
+ def reels_missing_embedding() -> list:
149
+ """Rows with no search embedding yet, as [{id, extracted_json}]."""
150
+ return _select_newest_first("id, extracted_json", refine=lambda q: q.is_("embedding", "null"))
151
+
152
+
153
+ def set_embedding(reel_id: str, embedding: list) -> None:
154
+ get_client().table(TABLE).update({"embedding": embedding}).eq("id", reel_id).execute()
155
+
156
+
157
  def set_cluster(reel_ids: list, cluster: Optional[str]) -> None:
158
  for chunk in _chunked(reel_ids, 100):
159
  get_client().table(TABLE).update({"cluster": cluster}).in_("id", chunk).execute()
 
194
 
195
 
196
  def update_reel_result(reel_id: str, title: str, raw_transcript, post_caption,
197
+ extracted_json, status: str = "done", embedding=None) -> None:
198
  """Write pipeline results back onto an existing (claimed) row."""
199
+ payload = {
200
  "title": title,
201
  "raw_transcript": raw_transcript,
202
  "post_caption": post_caption,
 
205
  "error": None,
206
  "processing_started_at": None,
207
  "next_attempt_at": None,
208
+ }
209
+ if embedding is not None:
210
+ payload["embedding"] = embedding
211
+ get_client().table(TABLE).update(payload).eq("id", reel_id).execute()
212
 
213
 
214
  def mark_failed(reel_id: str, error, status: str = "failed") -> None:
test_pipeline.py CHANGED
@@ -65,6 +65,45 @@ class FakeTable:
65
  return type("Result", (), {"data": self.data})()
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  @pytest.fixture
69
  def pipeline(monkeypatch):
70
  """Mock download, transcription and extraction so the pipeline runs offline."""
@@ -498,3 +537,33 @@ def test_db_queue_metadata_contract(monkeypatch):
498
  assert db.recover_stale_processing(datetime.now(timezone.utc)) == 1
499
  assert ("eq", ("status", "processing")) in query.calls
500
  assert any(kind == "lt" and args[0] == "processing_started_at" for kind, args in query.calls)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  return type("Result", (), {"data": self.data})()
66
 
67
 
68
+ class RecordingTable:
69
+ """Stand-in for a Supabase table query that records payloads and filters."""
70
+ def __init__(self, rows=None):
71
+ self.rows = rows or []
72
+ self.payloads = []
73
+ self.filters = []
74
+ self.window = (0, 999)
75
+
76
+ def select(self, *args, **kwargs):
77
+ return self
78
+
79
+ def insert(self, payload):
80
+ self.payloads.append(payload)
81
+ return self
82
+
83
+ def update(self, payload):
84
+ self.payloads.append(payload)
85
+ return self
86
+
87
+ def eq(self, *args):
88
+ self.filters.append(("eq", args))
89
+ return self
90
+
91
+ def is_(self, *args):
92
+ self.filters.append(("is", args))
93
+ return self
94
+
95
+ def order(self, *args, **kwargs):
96
+ return self
97
+
98
+ def range(self, start, end):
99
+ self.window = (start, end)
100
+ return self
101
+
102
+ def execute(self):
103
+ start, end = self.window
104
+ return type("Result", (), {"data": self.rows[start:end + 1]})()
105
+
106
+
107
  @pytest.fixture
108
  def pipeline(monkeypatch):
109
  """Mock download, transcription and extraction so the pipeline runs offline."""
 
537
  assert db.recover_stale_processing(datetime.now(timezone.utc)) == 1
538
  assert ("eq", ("status", "processing")) in query.calls
539
  assert any(kind == "lt" and args[0] == "processing_started_at" for kind, args in query.calls)
540
+
541
+
542
+ def test_insert_reel_stores_embedding_only_when_given(monkeypatch):
543
+ table = RecordingTable()
544
+ monkeypatch.setattr(db, "get_client", fake_client(table))
545
+ record = db.insert_reel("u", "t", "tr", "cap", {"core_topic": "x"}, embedding=[0.1, 0.2])
546
+ db.insert_reel("u2", "t", "tr", "cap", {"core_topic": "x"})
547
+ assert table.payloads[0]["embedding"] == [0.1, 0.2]
548
+ assert "embedding" not in table.payloads[1]
549
+ assert "embedding" not in record # never leaks into the API record
550
+
551
+
552
+ def test_update_reel_result_stores_embedding_only_when_given(monkeypatch):
553
+ table = RecordingTable()
554
+ monkeypatch.setattr(db, "get_client", fake_client(table))
555
+ db.update_reel_result("r1", "t", "tr", "cap", {"core_topic": "x"}, embedding=[0.3])
556
+ db.update_reel_result("r2", "t", "tr", "cap", {"core_topic": "x"})
557
+ assert table.payloads[0]["embedding"] == [0.3]
558
+ assert "embedding" not in table.payloads[1]
559
+
560
+
561
+ def test_embedding_queue_helpers(monkeypatch):
562
+ table = RecordingTable([{"id": "a", "extracted_json": {}}, {"id": "b", "extracted_json": {}}])
563
+ monkeypatch.setattr(db, "get_client", fake_client(table))
564
+ assert [row["id"] for row in db.reels_missing_embedding()] == ["a", "b"]
565
+ assert ("is", ("embedding", "null")) in table.filters
566
+
567
+ db.set_embedding("a", [0.5])
568
+ assert table.payloads == [{"embedding": [0.5]}]
569
+ assert ("eq", ("id", "a")) in table.filters