Viney Claude Opus 5 (1M context) commited on
Commit
1e5f13b
·
1 Parent(s): 44c5fa8

docs: rule on the Task 1 provenance gap

Browse files

holdings gains retrieved_at: as_of is the position date printed in the fund's
report, not the date we read it, so nothing distinguished a holding scraped
this morning from one scraped months ago. signals gain nothing — observed_at
and url already are the retrieval timestamp and the source. Spec prose amended
to name the actual per-table fields instead of contradicting its own model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016XzekHxqiXutBW4gw3Arsx

docs/superpowers/plans/2026-08-29-prospection-buyside.md CHANGED
@@ -223,6 +223,7 @@ CREATE TABLE IF NOT EXISTS holdings (
223
  as_of TEXT,
224
  source_url TEXT NOT NULL,
225
  confidence TEXT NOT NULL,
 
226
  confirmed_at TEXT,
227
  UNIQUE (account_id, fund_name, raw_label)
228
  );
@@ -397,9 +398,10 @@ def active_signals(account_id: int, today: date) -> list[dict]:
397
 
398
  def upsert_holding(row: dict) -> int:
399
  cols = ["account_id", "fund_name", "raw_label", "ticker", "weight_pct",
400
- "as_of", "source_url", "confidence"]
401
  updates = ", ".join(f"{c}=excluded.{c}" for c in ["ticker", "weight_pct", "as_of",
402
- "source_url", "confidence"])
 
403
  with connect() as conn:
404
  conn.execute(
405
  f"INSERT INTO holdings ({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))}) "
@@ -2362,6 +2364,7 @@ validateur de rédaction ne voit jamais une ligne non confirmée.
2362
  from __future__ import annotations
2363
 
2364
  import re
 
2365
  from pathlib import Path
2366
 
2367
  from prospection.sources.ticker_aliases import ALIASES
@@ -2415,6 +2418,7 @@ def extract_holdings(pdf_path: Path, source_url: str) -> list[dict]:
2415
  "as_of": None,
2416
  "source_url": source_url,
2417
  "confidence": "probable",
 
2418
  "confirmed_at": None,
2419
  })
2420
  return rows
 
223
  as_of TEXT,
224
  source_url TEXT NOT NULL,
225
  confidence TEXT NOT NULL,
226
+ retrieved_at TEXT NOT NULL,
227
  confirmed_at TEXT,
228
  UNIQUE (account_id, fund_name, raw_label)
229
  );
 
398
 
399
  def upsert_holding(row: dict) -> int:
400
  cols = ["account_id", "fund_name", "raw_label", "ticker", "weight_pct",
401
+ "as_of", "source_url", "confidence", "retrieved_at"]
402
  updates = ", ".join(f"{c}=excluded.{c}" for c in ["ticker", "weight_pct", "as_of",
403
+ "source_url", "confidence",
404
+ "retrieved_at"])
405
  with connect() as conn:
406
  conn.execute(
407
  f"INSERT INTO holdings ({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))}) "
 
2364
  from __future__ import annotations
2365
 
2366
  import re
2367
+ from datetime import datetime
2368
  from pathlib import Path
2369
 
2370
  from prospection.sources.ticker_aliases import ALIASES
 
2418
  "as_of": None,
2419
  "source_url": source_url,
2420
  "confidence": "probable",
2421
+ "retrieved_at": datetime.utcnow().isoformat(timespec="seconds"),
2422
  "confirmed_at": None,
2423
  })
2424
  return rows
docs/superpowers/specs/2026-08-29-prospection-buyside-design.md CHANGED
@@ -19,8 +19,10 @@ d'implémentation.
19
 
20
  **Le dossier prospection applique le même contrat d'évidence que le produit.**
21
 
22
- Chaque ligne de contact, de position et de signal porte `source`, `confidence`
23
- et `retrieved_at`. La rédaction est *fail-closed* : aucune affirmation de
 
 
24
  personnalisation n'entre dans un email si elle n'est pas adossée à une ligne
25
  stockée portant une URL source. Un cold email qui invente une position détruit
26
  la crédibilité auprès d'un acheteur régulé — et c'est aussi l'exigence RGPD de
 
19
 
20
  **Le dossier prospection applique le même contrat d'évidence que le produit.**
21
 
22
+ Chaque ligne de contact, de position et de signal porte sa provenance et sa
23
+ date de collecte : `source` / `confidence` / `retrieved_at` pour un contact,
24
+ `source_url` / `confidence` / `retrieved_at` pour une position, `url` /
25
+ `weight` / `observed_at` pour un signal. La rédaction est *fail-closed* : aucune affirmation de
26
  personnalisation n'entre dans un email si elle n'est pas adossée à une ligne
27
  stockée portant une URL source. Un cold email qui invente une position détruit
28
  la crédibilité auprès d'un acheteur régulé — et c'est aussi l'exigence RGPD de