|
@@ -264,10 +264,13 @@ class PostgresAcquisitionRepository:
|
|
|
job_id: UUID | None = None,
|
|
job_id: UUID | None = None,
|
|
|
query_id: UUID | None = None,
|
|
query_id: UUID | None = None,
|
|
|
platform_item_id: str | None = None,
|
|
platform_item_id: str | None = None,
|
|
|
|
|
+ unique_key: str | None = None,
|
|
|
canonical_url: str | None = None,
|
|
canonical_url: str | None = None,
|
|
|
content_type: str | None = None,
|
|
content_type: str | None = None,
|
|
|
|
|
+ content_mode: str | None = None,
|
|
|
title: str | None = None,
|
|
title: str | None = None,
|
|
|
author_name: str | None = None,
|
|
author_name: str | None = None,
|
|
|
|
|
+ body_text: str | None = None,
|
|
|
raw_summary: str | None = None,
|
|
raw_summary: str | None = None,
|
|
|
status: str = "candidate",
|
|
status: str = "candidate",
|
|
|
source_payload: dict[str, Any] | None = None,
|
|
source_payload: dict[str, Any] | None = None,
|
|
@@ -275,31 +278,46 @@ class PostgresAcquisitionRepository:
|
|
|
error_message: str | None = None,
|
|
error_message: str | None = None,
|
|
|
) -> CandidateItem:
|
|
) -> CandidateItem:
|
|
|
existing_id = None
|
|
existing_id = None
|
|
|
- if platform_item_id:
|
|
|
|
|
|
|
+ if unique_key:
|
|
|
row = self._one_or_none(
|
|
row = self._one_or_none(
|
|
|
"""
|
|
"""
|
|
|
SELECT id FROM candidate_items
|
|
SELECT id FROM candidate_items
|
|
|
- WHERE platform = %s AND platform_item_id = %s
|
|
|
|
|
|
|
+ WHERE unique_key = %s
|
|
|
ORDER BY created_at DESC
|
|
ORDER BY created_at DESC
|
|
|
LIMIT 1
|
|
LIMIT 1
|
|
|
""",
|
|
""",
|
|
|
- (platform, platform_item_id),
|
|
|
|
|
|
|
+ (unique_key,),
|
|
|
)
|
|
)
|
|
|
existing_id = row["id"] if row else None
|
|
existing_id = row["id"] if row else None
|
|
|
|
|
|
|
|
|
|
+ if platform_item_id:
|
|
|
|
|
+ if existing_id is None:
|
|
|
|
|
+ row = self._one_or_none(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT id FROM candidate_items
|
|
|
|
|
+ WHERE platform = %s AND platform_item_id = %s
|
|
|
|
|
+ ORDER BY created_at DESC
|
|
|
|
|
+ LIMIT 1
|
|
|
|
|
+ """,
|
|
|
|
|
+ (platform, platform_item_id),
|
|
|
|
|
+ )
|
|
|
|
|
+ existing_id = row["id"] if row else None
|
|
|
|
|
+
|
|
|
if existing_id:
|
|
if existing_id:
|
|
|
row = self._one(
|
|
row = self._one(
|
|
|
"""
|
|
"""
|
|
|
UPDATE candidate_items SET
|
|
UPDATE candidate_items SET
|
|
|
job_id = %s,
|
|
job_id = %s,
|
|
|
query_id = %s,
|
|
query_id = %s,
|
|
|
|
|
+ unique_key = COALESCE(%s, unique_key),
|
|
|
canonical_url = %s,
|
|
canonical_url = %s,
|
|
|
content_type = %s,
|
|
content_type = %s,
|
|
|
|
|
+ content_mode = %s,
|
|
|
title = %s,
|
|
title = %s,
|
|
|
author_name = %s,
|
|
author_name = %s,
|
|
|
|
|
+ body_text = %s,
|
|
|
raw_summary = %s,
|
|
raw_summary = %s,
|
|
|
status = %s,
|
|
status = %s,
|
|
|
- source_payload = %s,
|
|
|
|
|
metadata = %s,
|
|
metadata = %s,
|
|
|
error_message = %s
|
|
error_message = %s
|
|
|
WHERE id = %s
|
|
WHERE id = %s
|
|
@@ -308,13 +326,15 @@ class PostgresAcquisitionRepository:
|
|
|
(
|
|
(
|
|
|
job_id,
|
|
job_id,
|
|
|
query_id,
|
|
query_id,
|
|
|
|
|
+ unique_key,
|
|
|
canonical_url,
|
|
canonical_url,
|
|
|
content_type,
|
|
content_type,
|
|
|
|
|
+ content_mode,
|
|
|
title,
|
|
title,
|
|
|
author_name,
|
|
author_name,
|
|
|
|
|
+ body_text,
|
|
|
raw_summary,
|
|
raw_summary,
|
|
|
status,
|
|
status,
|
|
|
- Json(source_payload or {}),
|
|
|
|
|
Json(metadata or {}),
|
|
Json(metadata or {}),
|
|
|
error_message,
|
|
error_message,
|
|
|
existing_id,
|
|
existing_id,
|
|
@@ -324,11 +344,11 @@ class PostgresAcquisitionRepository:
|
|
|
row = self._one(
|
|
row = self._one(
|
|
|
"""
|
|
"""
|
|
|
INSERT INTO candidate_items(
|
|
INSERT INTO candidate_items(
|
|
|
- job_id, query_id, platform, platform_item_id, canonical_url,
|
|
|
|
|
- content_type, title, author_name, raw_summary, status,
|
|
|
|
|
|
|
+ job_id, query_id, platform, platform_item_id, unique_key, canonical_url,
|
|
|
|
|
+ content_type, content_mode, title, author_name, body_text, raw_summary, status,
|
|
|
source_payload, metadata, error_message
|
|
source_payload, metadata, error_message
|
|
|
)
|
|
)
|
|
|
- VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
|
|
|
|
|
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
|
RETURNING *
|
|
RETURNING *
|
|
|
""",
|
|
""",
|
|
|
(
|
|
(
|
|
@@ -336,10 +356,13 @@ class PostgresAcquisitionRepository:
|
|
|
query_id,
|
|
query_id,
|
|
|
platform,
|
|
platform,
|
|
|
platform_item_id,
|
|
platform_item_id,
|
|
|
|
|
+ unique_key,
|
|
|
canonical_url,
|
|
canonical_url,
|
|
|
content_type,
|
|
content_type,
|
|
|
|
|
+ content_mode,
|
|
|
title,
|
|
title,
|
|
|
author_name,
|
|
author_name,
|
|
|
|
|
+ body_text,
|
|
|
raw_summary,
|
|
raw_summary,
|
|
|
status,
|
|
status,
|
|
|
Json(source_payload or {}),
|
|
Json(source_payload or {}),
|
|
@@ -355,6 +378,44 @@ class PostgresAcquisitionRepository:
|
|
|
row = cur.fetchone()
|
|
row = cur.fetchone()
|
|
|
return dict(row) if row else None
|
|
return dict(row) if row else None
|
|
|
|
|
|
|
|
|
|
+ def get_candidate_item_by_unique_key(self, unique_key: str) -> CandidateItem | None:
|
|
|
|
|
+ row = self._one_or_none(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT * FROM candidate_items
|
|
|
|
|
+ WHERE unique_key = %s
|
|
|
|
|
+ ORDER BY created_at DESC
|
|
|
|
|
+ LIMIT 1
|
|
|
|
|
+ """,
|
|
|
|
|
+ (unique_key,),
|
|
|
|
|
+ )
|
|
|
|
|
+ return CandidateItem.model_validate(row) if row else None
|
|
|
|
|
+
|
|
|
|
|
+ def attach_existing_candidate_item(
|
|
|
|
|
+ self,
|
|
|
|
|
+ item_id: UUID,
|
|
|
|
|
+ *,
|
|
|
|
|
+ job_id: UUID,
|
|
|
|
|
+ query_id: UUID,
|
|
|
|
|
+ metadata: dict[str, Any] | None = None,
|
|
|
|
|
+ ) -> CandidateItem:
|
|
|
|
|
+ row = self._one(
|
|
|
|
|
+ """
|
|
|
|
|
+ UPDATE candidate_items SET
|
|
|
|
|
+ job_id = %s,
|
|
|
|
|
+ query_id = %s,
|
|
|
|
|
+ metadata = metadata || %s
|
|
|
|
|
+ WHERE id = %s
|
|
|
|
|
+ RETURNING *
|
|
|
|
|
+ """,
|
|
|
|
|
+ (
|
|
|
|
|
+ job_id,
|
|
|
|
|
+ query_id,
|
|
|
|
|
+ Json(metadata or {}),
|
|
|
|
|
+ item_id,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ return CandidateItem.model_validate(row)
|
|
|
|
|
+
|
|
|
def add_media_asset(
|
|
def add_media_asset(
|
|
|
self,
|
|
self,
|
|
|
*,
|
|
*,
|
|
@@ -509,6 +570,7 @@ class PostgresAcquisitionRepository:
|
|
|
item_ids = [row["id"] for row in items]
|
|
item_ids = [row["id"] for row in items]
|
|
|
media: list[dict[str, Any]] = []
|
|
media: list[dict[str, Any]] = []
|
|
|
classifications: list[dict[str, Any]] = []
|
|
classifications: list[dict[str, Any]] = []
|
|
|
|
|
+ decode_summaries: list[dict[str, Any]] = []
|
|
|
if item_ids:
|
|
if item_ids:
|
|
|
media = self._all(
|
|
media = self._all(
|
|
|
"""
|
|
"""
|
|
@@ -526,23 +588,107 @@ class PostgresAcquisitionRepository:
|
|
|
""",
|
|
""",
|
|
|
(item_ids,),
|
|
(item_ids,),
|
|
|
)
|
|
)
|
|
|
|
|
+ decode_summaries = self._all(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT DISTINCT ON (dr.item_id)
|
|
|
|
|
+ dr.item_id,
|
|
|
|
|
+ dr.status AS decode_status,
|
|
|
|
|
+ COUNT(DISTINCT pd.id)::int AS payload_count
|
|
|
|
|
+ FROM decode_results dr
|
|
|
|
|
+ LEFT JOIN payload_drafts pd ON pd.item_id = dr.item_id
|
|
|
|
|
+ WHERE dr.item_id = ANY(%s)
|
|
|
|
|
+ GROUP BY dr.item_id, dr.status, dr.created_at
|
|
|
|
|
+ ORDER BY dr.item_id, dr.created_at DESC
|
|
|
|
|
+ """,
|
|
|
|
|
+ (item_ids,),
|
|
|
|
|
+ )
|
|
|
return {
|
|
return {
|
|
|
"query": query,
|
|
"query": query,
|
|
|
"jobs": jobs,
|
|
"jobs": jobs,
|
|
|
"items": items,
|
|
"items": items,
|
|
|
"media_assets": media,
|
|
"media_assets": media,
|
|
|
"classifications": classifications,
|
|
"classifications": classifications,
|
|
|
|
|
+ "decode_summaries": decode_summaries,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def get_query_detail_for_batch(self, *, batch_id: UUID, query_id: UUID) -> dict[str, Any]:
|
|
|
|
|
+ query = self._one(
|
|
|
|
|
+ "SELECT * FROM queries WHERE id = %s AND batch_id = %s",
|
|
|
|
|
+ (query_id, batch_id),
|
|
|
|
|
+ )
|
|
|
|
|
+ jobs = self._all(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT aj.* FROM acquisition_jobs aj
|
|
|
|
|
+ JOIN acquisition_runs ar ON ar.id = aj.run_id
|
|
|
|
|
+ WHERE ar.batch_id = %s AND aj.query_id = %s
|
|
|
|
|
+ ORDER BY aj.created_at, aj.platform
|
|
|
|
|
+ """,
|
|
|
|
|
+ (batch_id, query_id),
|
|
|
|
|
+ )
|
|
|
|
|
+ items = self._all(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT ci.* FROM candidate_items ci
|
|
|
|
|
+ JOIN acquisition_jobs aj ON aj.id = ci.job_id
|
|
|
|
|
+ JOIN acquisition_runs ar ON ar.id = aj.run_id
|
|
|
|
|
+ WHERE ar.batch_id = %s AND ci.query_id = %s
|
|
|
|
|
+ ORDER BY ci.platform, ci.created_at
|
|
|
|
|
+ """,
|
|
|
|
|
+ (batch_id, query_id),
|
|
|
|
|
+ )
|
|
|
|
|
+ item_ids = [row["id"] for row in items]
|
|
|
|
|
+ media: list[dict[str, Any]] = []
|
|
|
|
|
+ classifications: list[dict[str, Any]] = []
|
|
|
|
|
+ decode_summaries: list[dict[str, Any]] = []
|
|
|
|
|
+ if item_ids:
|
|
|
|
|
+ media = self._all(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT * FROM media_assets
|
|
|
|
|
+ WHERE item_id = ANY(%s)
|
|
|
|
|
+ ORDER BY item_id, position
|
|
|
|
|
+ """,
|
|
|
|
|
+ (item_ids,),
|
|
|
|
|
+ )
|
|
|
|
|
+ classifications = self._all(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT * FROM item_classifications
|
|
|
|
|
+ WHERE item_id = ANY(%s)
|
|
|
|
|
+ ORDER BY created_at DESC
|
|
|
|
|
+ """,
|
|
|
|
|
+ (item_ids,),
|
|
|
|
|
+ )
|
|
|
|
|
+ decode_summaries = self._all(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT DISTINCT ON (dr.item_id)
|
|
|
|
|
+ dr.item_id,
|
|
|
|
|
+ dr.status AS decode_status,
|
|
|
|
|
+ COUNT(DISTINCT pd.id)::int AS payload_count
|
|
|
|
|
+ FROM decode_results dr
|
|
|
|
|
+ LEFT JOIN payload_drafts pd ON pd.item_id = dr.item_id
|
|
|
|
|
+ WHERE dr.item_id = ANY(%s)
|
|
|
|
|
+ GROUP BY dr.item_id, dr.status, dr.created_at
|
|
|
|
|
+ ORDER BY dr.item_id, dr.created_at DESC
|
|
|
|
|
+ """,
|
|
|
|
|
+ (item_ids,),
|
|
|
|
|
+ )
|
|
|
|
|
+ return {
|
|
|
|
|
+ "query": query,
|
|
|
|
|
+ "jobs": jobs,
|
|
|
|
|
+ "items": items,
|
|
|
|
|
+ "media_assets": media,
|
|
|
|
|
+ "classifications": classifications,
|
|
|
|
|
+ "decode_summaries": decode_summaries,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
def get_latest_singleton_overview(self) -> dict[str, Any]:
|
|
def get_latest_singleton_overview(self) -> dict[str, Any]:
|
|
|
batch = self._one_or_none(
|
|
batch = self._one_or_none(
|
|
|
"""
|
|
"""
|
|
|
- SELECT * FROM query_batches
|
|
|
|
|
- WHERE generation_method = %s
|
|
|
|
|
- ORDER BY created_at DESC
|
|
|
|
|
|
|
+ SELECT qb.* FROM query_batches qb
|
|
|
|
|
+ JOIN acquisition_runs ar ON ar.batch_id = qb.id
|
|
|
|
|
+ GROUP BY qb.id
|
|
|
|
|
+ ORDER BY MAX(ar.created_at) DESC
|
|
|
LIMIT 1
|
|
LIMIT 1
|
|
|
""",
|
|
""",
|
|
|
- ("creation_singleton_v1",),
|
|
|
|
|
|
|
+ (),
|
|
|
)
|
|
)
|
|
|
if batch is None:
|
|
if batch is None:
|
|
|
return {"batch": None, "run": None, "queries": [], "decoded_items": []}
|
|
return {"batch": None, "run": None, "queries": [], "decoded_items": []}
|
|
@@ -550,11 +696,11 @@ class PostgresAcquisitionRepository:
|
|
|
run = self._one_or_none(
|
|
run = self._one_or_none(
|
|
|
"""
|
|
"""
|
|
|
SELECT * FROM acquisition_runs
|
|
SELECT * FROM acquisition_runs
|
|
|
- WHERE batch_id = %s AND run_key LIKE %s
|
|
|
|
|
|
|
+ WHERE batch_id = %s
|
|
|
ORDER BY created_at DESC
|
|
ORDER BY created_at DESC
|
|
|
LIMIT 1
|
|
LIMIT 1
|
|
|
""",
|
|
""",
|
|
|
- (batch["id"], "singleton-acquisition:%"),
|
|
|
|
|
|
|
+ (batch["id"],),
|
|
|
)
|
|
)
|
|
|
if run is None:
|
|
if run is None:
|
|
|
queries = self._all(
|
|
queries = self._all(
|
|
@@ -584,16 +730,21 @@ class PostgresAcquisitionRepository:
|
|
|
COUNT(DISTINCT ci.id)::int AS candidate_count,
|
|
COUNT(DISTINCT ci.id)::int AS candidate_count,
|
|
|
COUNT(DISTINCT ic.id) FILTER (
|
|
COUNT(DISTINCT ic.id) FILTER (
|
|
|
WHERE ic.is_creation_knowledge IS TRUE
|
|
WHERE ic.is_creation_knowledge IS TRUE
|
|
|
- )::int AS creation_hit_count
|
|
|
|
|
|
|
+ )::int AS creation_hit_count,
|
|
|
|
|
+ COUNT(DISTINCT dr.id)::int AS decoded_count,
|
|
|
|
|
+ COUNT(DISTINCT pd.id)::int AS payload_count
|
|
|
FROM queries q
|
|
FROM queries q
|
|
|
- LEFT JOIN acquisition_jobs aj ON aj.query_id = q.id AND aj.run_id = %s
|
|
|
|
|
|
|
+ LEFT JOIN acquisition_jobs aj ON aj.query_id = q.id
|
|
|
|
|
+ LEFT JOIN acquisition_runs ar ON ar.id = aj.run_id AND ar.batch_id = q.batch_id
|
|
|
LEFT JOIN candidate_items ci ON ci.job_id = aj.id
|
|
LEFT JOIN candidate_items ci ON ci.job_id = aj.id
|
|
|
LEFT JOIN item_classifications ic ON ic.item_id = ci.id
|
|
LEFT JOIN item_classifications ic ON ic.item_id = ci.id
|
|
|
|
|
+ LEFT JOIN decode_results dr ON dr.item_id = ci.id
|
|
|
|
|
+ LEFT JOIN payload_drafts pd ON pd.item_id = ci.id
|
|
|
WHERE q.batch_id = %s
|
|
WHERE q.batch_id = %s
|
|
|
GROUP BY q.id, q.query_text, q.metadata, q.sort_order
|
|
GROUP BY q.id, q.query_text, q.metadata, q.sort_order
|
|
|
ORDER BY q.sort_order, q.created_at
|
|
ORDER BY q.sort_order, q.created_at
|
|
|
""",
|
|
""",
|
|
|
- (run["id"], batch["id"]),
|
|
|
|
|
|
|
+ (batch["id"],),
|
|
|
)
|
|
)
|
|
|
decoded_items = self._all(
|
|
decoded_items = self._all(
|
|
|
"""
|
|
"""
|
|
@@ -606,13 +757,14 @@ class PostgresAcquisitionRepository:
|
|
|
COUNT(DISTINCT pd.id)::int AS payload_count
|
|
COUNT(DISTINCT pd.id)::int AS payload_count
|
|
|
FROM candidate_items ci
|
|
FROM candidate_items ci
|
|
|
JOIN acquisition_jobs aj ON aj.id = ci.job_id
|
|
JOIN acquisition_jobs aj ON aj.id = ci.job_id
|
|
|
|
|
+ JOIN acquisition_runs ar ON ar.id = aj.run_id
|
|
|
JOIN decode_results dr ON dr.item_id = ci.id
|
|
JOIN decode_results dr ON dr.item_id = ci.id
|
|
|
LEFT JOIN payload_drafts pd ON pd.item_id = ci.id
|
|
LEFT JOIN payload_drafts pd ON pd.item_id = ci.id
|
|
|
- WHERE aj.run_id = %s
|
|
|
|
|
|
|
+ WHERE ar.batch_id = %s
|
|
|
GROUP BY ci.id, ci.query_id, ci.title, ci.platform, dr.status, dr.created_at
|
|
GROUP BY ci.id, ci.query_id, ci.title, ci.platform, dr.status, dr.created_at
|
|
|
ORDER BY dr.created_at DESC
|
|
ORDER BY dr.created_at DESC
|
|
|
""",
|
|
""",
|
|
|
- (run["id"],),
|
|
|
|
|
|
|
+ (batch["id"],),
|
|
|
)
|
|
)
|
|
|
return {
|
|
return {
|
|
|
"batch": batch,
|
|
"batch": batch,
|