mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-25 08:18:05 +00:00
180 lines
5.8 KiB
Python
180 lines
5.8 KiB
Python
"""PostgreSQL repository for persisted video transcript jobs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from server.db.pg_database import get_pg_pool
|
|
|
|
|
|
def _coerce_list(value: Any) -> list[str]:
|
|
if isinstance(value, list):
|
|
return [str(item) for item in value if str(item).strip()]
|
|
if isinstance(value, str) and value.strip():
|
|
return [value]
|
|
return []
|
|
|
|
|
|
def _coerce_datetime(value: Any) -> Any:
|
|
if isinstance(value, str) and value.strip():
|
|
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
return value
|
|
|
|
|
|
def _normalise_row(row: Any) -> dict[str, Any]:
|
|
payload = dict(row)
|
|
payload["keywords"] = _coerce_list(payload.pop("keywords_json", []))
|
|
payload["topics"] = _coerce_list(payload.pop("topics_json", []))
|
|
if payload.get("created_at") is not None:
|
|
payload["created_at"] = payload["created_at"].isoformat()
|
|
if payload.get("completed_at") is not None:
|
|
payload["completed_at"] = payload["completed_at"].isoformat()
|
|
return payload
|
|
|
|
|
|
def _normalise_updates(fields: dict[str, Any]) -> dict[str, Any]:
|
|
alias_map = {"keywords": "keywords_json", "topics": "topics_json"}
|
|
allowed = {
|
|
"source_url",
|
|
"source_type",
|
|
"status",
|
|
"progress",
|
|
"error",
|
|
"title",
|
|
"duration_sec",
|
|
"language",
|
|
"transcript_text",
|
|
"summary",
|
|
"keywords_json",
|
|
"topics_json",
|
|
"sentiment",
|
|
"intent",
|
|
"completed_at",
|
|
}
|
|
normalised: dict[str, Any] = {}
|
|
for key, value in fields.items():
|
|
mapped_key = alias_map.get(key, key)
|
|
if mapped_key not in allowed:
|
|
continue
|
|
if mapped_key in {"keywords_json", "topics_json"}:
|
|
normalised[mapped_key] = _coerce_list(value)
|
|
elif mapped_key in {"completed_at"}:
|
|
normalised[mapped_key] = _coerce_datetime(value)
|
|
else:
|
|
normalised[mapped_key] = value
|
|
return normalised
|
|
|
|
|
|
async def pg_add_video_job(job_id: str, url: str, source_type: str) -> dict[str, Any] | None:
|
|
pool = await get_pg_pool()
|
|
if not pool:
|
|
return None
|
|
async with pool.acquire() as conn:
|
|
row = await conn.fetchrow(
|
|
"""
|
|
INSERT INTO video_jobs (job_id, source_url, source_type, status, progress)
|
|
VALUES ($1, $2, $3, 'queued', 0)
|
|
RETURNING *
|
|
""",
|
|
job_id,
|
|
url,
|
|
source_type,
|
|
)
|
|
return _normalise_row(row) if row else None
|
|
|
|
|
|
async def pg_update_video_job(job_id: str, **fields: Any) -> dict[str, Any] | None:
|
|
pool = await get_pg_pool()
|
|
if not pool:
|
|
return None
|
|
updates = _normalise_updates(fields)
|
|
async with pool.acquire() as conn:
|
|
if updates:
|
|
set_clause = ", ".join(f"{column} = ${index + 2}" for index, column in enumerate(updates))
|
|
values = [job_id] + list(updates.values())
|
|
row = await conn.fetchrow(
|
|
f"UPDATE video_jobs SET {set_clause} WHERE job_id = $1 RETURNING *", # noqa: S608
|
|
*values,
|
|
)
|
|
return _normalise_row(row) if row else None
|
|
row = await conn.fetchrow("SELECT * FROM video_jobs WHERE job_id = $1", job_id)
|
|
return _normalise_row(row) if row else None
|
|
|
|
|
|
async def pg_get_video_job(job_id: str) -> dict[str, Any] | None:
|
|
pool = await get_pg_pool()
|
|
if not pool:
|
|
return None
|
|
async with pool.acquire() as conn:
|
|
row = await conn.fetchrow("SELECT * FROM video_jobs WHERE job_id = $1", job_id)
|
|
return _normalise_row(row) if row else None
|
|
|
|
|
|
async def pg_list_video_jobs(limit: int = 50) -> list[dict[str, Any]]:
|
|
pool = await get_pg_pool()
|
|
if not pool:
|
|
return []
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch(
|
|
"""
|
|
SELECT
|
|
job_id, source_url, source_type, status, progress, error, title,
|
|
duration_sec, language, created_at, completed_at
|
|
FROM video_jobs
|
|
ORDER BY created_at DESC
|
|
LIMIT $1
|
|
""",
|
|
limit,
|
|
)
|
|
return [_normalise_row(row) for row in rows]
|
|
|
|
|
|
async def pg_search_videos(query: str, limit: int = 20) -> list[dict[str, Any]]:
|
|
pool = await get_pg_pool()
|
|
if not pool:
|
|
return []
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch(
|
|
"""
|
|
SELECT
|
|
job_id,
|
|
title,
|
|
ts_headline(
|
|
'simple',
|
|
coalesce(transcript_text, ''),
|
|
plainto_tsquery('simple', $1),
|
|
'StartSel=[,StopSel=],MaxFragments=1,MaxWords=18,MinWords=6'
|
|
) AS snippet,
|
|
ts_rank_cd(
|
|
to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(transcript_text, '')),
|
|
plainto_tsquery('simple', $1)
|
|
) AS rank
|
|
FROM video_jobs
|
|
WHERE to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(transcript_text, ''))
|
|
@@ plainto_tsquery('simple', $1)
|
|
ORDER BY rank DESC, created_at DESC
|
|
LIMIT $2
|
|
""",
|
|
query,
|
|
limit,
|
|
)
|
|
return [
|
|
{
|
|
"job_id": row["job_id"],
|
|
"title": row["title"],
|
|
"snippet": row["snippet"] or "",
|
|
"rank": float(row["rank"] or 0.0),
|
|
}
|
|
for row in rows
|
|
]
|
|
|
|
|
|
async def pg_delete_video_job(job_id: str) -> bool:
|
|
pool = await get_pg_pool()
|
|
if not pool:
|
|
return False
|
|
async with pool.acquire() as conn:
|
|
result = await conn.execute("DELETE FROM video_jobs WHERE job_id = $1", job_id)
|
|
return result == "DELETE 1"
|