arthrod commited on
Commit
39bdd21
·
verified ·
1 Parent(s): 35cbb09

fix(d1): decouple image capture from D1 push (PR #45) — unfreeze live feed

Browse files
Files changed (3) hide show
  1. README.md +10 -8
  2. sec_listener/db.py +5 -1
  3. sec_listener/worker.py +11 -4
README.md CHANGED
@@ -18,23 +18,25 @@ rebuilds) and caches responses at the edge (CDN + stale-while-revalidate).
18
 
19
  ## What runs here
20
 
21
- One process (`python -m sec_listener.worker`) = SEC listener + Markdown backfill
22
- + FastAPI API on port **7860**, which Hugging Face exposes at the Space URL.
23
 
24
- ## Required Space secret
25
 
26
  | Secret | Purpose |
27
  |--------|---------|
28
  | `SEC_API_KEY` | Key clients must send as the `X-API-Key` header. Set it, and use the same value in the frontend's `SEC_API_KEY`. |
 
29
 
30
  ## Endpoints
31
 
32
- `/health` (open), `/api/ex10?page=&page_size=`, `/api/ex10/since?seconds=`,
33
- `/api/ex10/{id}`, `/api/stats`, `/api/search?q=`.
34
 
35
  ## Notes
36
 
37
- - Ships with a seed database for instant content; new filings stream in
38
- continuously. Enable persistent storage (mount `/data`, set
39
- `SEC_DB_PATH=/data/ex10_listener.db`) to retain data across restarts.
 
40
  - Public SEC filing data only. Not legal/investment advice. Not affiliated with the SEC.
 
18
 
19
  ## What runs here
20
 
21
+ One process (`python -m sec_listener.worker`) = SEC listener + Markdown / filing-header /
22
+ image backfill + FastAPI API on port **7860**, which Hugging Face exposes at the Space URL.
23
 
24
+ ## Space secrets
25
 
26
  | Secret | Purpose |
27
  |--------|---------|
28
  | `SEC_API_KEY` | Key clients must send as the `X-API-Key` header. Set it, and use the same value in the frontend's `SEC_API_KEY`. |
29
+ | `HF_TOKEN` | Enables the parallel HF-dataset mirror, cold-start boot-restore, and scanned-exhibit image capture. |
30
 
31
  ## Endpoints
32
 
33
+ `/health` (open), `/api/ex10?page=&page_size=&form=&cik=&filer=&sort=`,
34
+ `/api/ex10/since?seconds=`, `/api/ex10/{id}`, `/api/facets`, `/api/stats`, `/api/search?q=`.
35
 
36
  ## Notes
37
 
38
+ - Ships with a seed database for instant content; new filings stream in continuously.
39
+ - HF Spaces have **no persistent disk**, so durability comes from the HF dataset mirror:
40
+ on boot the worker restores SQLite from `arthrod/sec-ex10-exhibits`
41
+ (`python -m sec_listener.boot_restore`) and re-mirrors every `SEC_HF_SYNC_INTERVAL` seconds.
42
  - Public SEC filing data only. Not legal/investment advice. Not affiliated with the SEC.
sec_listener/db.py CHANGED
@@ -200,9 +200,13 @@ class Database:
200
  conn.commit()
201
 
202
  def update_image_urls(self, exhibit_id: int, urls: list[str]) -> None:
 
 
 
 
203
  with self.connect() as conn:
204
  conn.execute(
205
- "UPDATE ex10_exhibits SET image_urls = ? WHERE id = ?",
206
  (json.dumps(urls), exhibit_id),
207
  )
208
  conn.commit()
 
200
  conn.commit()
201
 
202
  def update_image_urls(self, exhibit_id: int, urls: list[str]) -> None:
203
+ # Reset mirrored so a row that was already pushed to D1 on markdown+metadata
204
+ # (image capture is decoupled from the push) is re-queued for a follow-up push,
205
+ # propagating the freshly captured image_urls via the ingest upsert. A row not
206
+ # yet mirrored is unaffected (mirrored stays 0).
207
  with self.connect() as conn:
208
  conn.execute(
209
+ "UPDATE ex10_exhibits SET image_urls = ?, mirrored = 0 WHERE id = ?",
210
  (json.dumps(urls), exhibit_id),
211
  )
212
  conn.commit()
sec_listener/worker.py CHANGED
@@ -199,12 +199,16 @@ class BackfillWorker:
199
 
200
 
201
  async def _d1_push_loop(db: Database, *, url: str, key: str, interval: float,
202
- require_images: bool = True, batch: int = 100):
203
  """Periodically push *finalized* SQLite rows to D1 via the ingest route.
204
 
205
  SQLite stays the working buffer; D1 is authoritative. Failures here never
206
  disturb the listener/backfill — rows stay unmirrored and retry next cycle.
207
- When image capture is off (no HF_TOKEN) rows finalize without image_urls.
 
 
 
 
208
  """
209
  from .d1_sync import push_finalized
210
 
@@ -236,11 +240,14 @@ async def _run_all(config: Config):
236
 
237
  # Push finalized rows to D1 (authoritative store) via the ingest route —
238
  # gated on the shared SEC_API_KEY. Without it, SQLite is the sole store.
 
 
 
 
239
  if config.api_key:
240
  tasks.append(asyncio.create_task(
241
  _d1_push_loop(db, url=config.d1_ingest_url, key=config.api_key,
242
- interval=float(os.environ.get("SEC_D1_PUSH_INTERVAL", "60")),
243
- require_images=bool(os.environ.get("HF_TOKEN"))),
244
  name="d1-push",
245
  ))
246
 
 
199
 
200
 
201
  async def _d1_push_loop(db: Database, *, url: str, key: str, interval: float,
202
+ require_images: bool = False, batch: int = 100):
203
  """Periodically push *finalized* SQLite rows to D1 via the ingest route.
204
 
205
  SQLite stays the working buffer; D1 is authoritative. Failures here never
206
  disturb the listener/backfill — rows stay unmirrored and retry next cycle.
207
+
208
+ Image capture is **decoupled** from the push: a row finalizes (and pushes) on
209
+ markdown + metadata alone, so a slow/stalled image backfill never freezes the
210
+ live feed. When images are captured later, ``update_image_urls`` re-queues the
211
+ row so a follow-up push propagates them to D1 via the ingest upsert.
212
  """
213
  from .d1_sync import push_finalized
214
 
 
240
 
241
  # Push finalized rows to D1 (authoritative store) via the ingest route —
242
  # gated on the shared SEC_API_KEY. Without it, SQLite is the sole store.
243
+ # Image capture is decoupled from the push (require_images=False default): rows
244
+ # reach D1 on markdown+metadata, and re-push once images arrive (see
245
+ # update_image_urls / _d1_push_loop). This keeps the live feed flowing even when
246
+ # the image backfill is slow or stalled.
247
  if config.api_key:
248
  tasks.append(asyncio.create_task(
249
  _d1_push_loop(db, url=config.d1_ingest_url, key=config.api_key,
250
+ interval=float(os.environ.get("SEC_D1_PUSH_INTERVAL", "60"))),
 
251
  name="d1-push",
252
  ))
253