proofread-demo / logbook.py
dev-strender's picture
deploy: v35 document-level chunking (DP split + parallel chunks), progress view, Supabase logbook
8debb9c verified
Raw
History Blame Contribute Delete
3.2 kB
"""์‚ฌ์šฉ์ž ์ž…๋ ฅยท๊ต์—ด ๊ฒฐ๊ณผ ๊ธฐ๋ก โ€” Supabase(PostgREST) ์— ์ง์ ‘ REST ํ˜ธ์ถœ.
์˜› gradio ๋ฐ๋ชจ(`services/prj-chosun-gradio/blindtest/db.py`)์˜ `articles` ยท `pipeline_runs` ๋‘ ํ…Œ์ด๋ธ”์„
๊ทธ๋Œ€๋กœ ์“ด๋‹ค(์Šคํ‚ค๋งˆ๋Š” `schema.sql`). SDK ๋Œ€์‹  httpx ๋กœ REST ๋ฅผ ๋ถ€๋ฅด๋Š” ๊ฒƒ๋„ ๊ทธ๋•Œ์™€ ๊ฐ™๋‹ค โ€” `supabase`
ํŒจํ‚ค์ง€๊ฐ€ ๋Œ๊ณ  ์˜ค๋Š” `realtime`/`websockets` ํ•€์ด ๋‹ค๋ฅธ ์˜์กด์„ฑ๊ณผ ์ถฉ๋Œํ–ˆ๋˜ ์ „๋ ฅ ๋•Œ๋ฌธ.
ํ™˜๊ฒฝ ๋ณ€์ˆ˜:
SUPABASE_URL โ€” ํ”„๋กœ์ ํŠธ URL (https://xxx.supabase.co)
SUPABASE_KEY (๋˜๋Š” SUPABASE_ANON_KEY) โ€” anon ํ‚ค (RLS ์ •์ฑ…์€ schema.sql)
๋‘˜ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์—†์œผ๋ฉด ๋ชจ๋“  ์“ฐ๊ธฐ๋Š” no-op ์ด๊ณ  `is_configured()` ๊ฐ€ False ๋‹ค. ๊ธฐ๋ก ์‹คํŒจ๋Š” ์ ˆ๋Œ€ ๋ฐ๋ชจ๋ฅผ
๋ฉˆ์ถ”์ง€ ์•Š๋Š”๋‹ค โ€” `last_error()` ์— ํ•œ ์ค„ ๋‚จ๊ธฐ๊ณ  None ์„ ๋Œ๋ ค์ค€๋‹ค.
"""
from __future__ import annotations
import os
import httpx
TIMEOUT_S = 10.0
_last_error: str | None = None
def _url() -> str:
return os.environ.get("SUPABASE_URL", "").strip().rstrip("/")
def _key() -> str:
# ์˜› gradio ๋ฐ๋ชจ๋Š” SUPABASE_KEY, ์ตœ๊ทผ .env ๋Š” SUPABASE_ANON_KEY ๋ฅผ ์“ด๋‹ค -- ๋‘˜ ๋‹ค ๋ฐ›๋Š”๋‹ค
return (os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or "").strip()
def is_configured() -> bool:
return bool(_url() and _key())
def last_error() -> str | None:
return _last_error
def _post(table: str, row: dict) -> list[dict]:
headers = {
"apikey": _key(),
"Authorization": f"Bearer {_key()}",
"Content-Type": "application/json",
"Prefer": "return=representation",
}
with httpx.Client(timeout=TIMEOUT_S) as client:
resp = client.post(f"{_url()}/rest/v1/{table}", headers=headers, json=row)
if resp.status_code >= 300:
raise RuntimeError(f"{resp.status_code} {resp.text[:200]}")
return resp.json() if resp.content else []
def _insert(table: str, row: dict) -> int | None:
global _last_error
if not is_configured():
return None
try:
data = _post(table, row)
_last_error = None
return data[0]["id"] if data else None
except Exception as exc: # noqa: BLE001 โ€” ๊ธฐ๋ก์€ ๋ฐ๋ชจ์˜ ๋ถ€์ˆ˜ ๊ธฐ๋Šฅ, ์–ด๋–ค ์‹คํŒจ๋„ ์‚ผ์ผœ์„œ ํ•œ ์ค„๋งŒ ๋‚จ๊ธด๋‹ค
_last_error = f"{table}: {type(exc).__name__}: {exc}"
return None
def save_article(source_text: str) -> int | None:
"""์‚ฌ์šฉ์ž ์ž…๋ ฅ ์›๋ฌธ 1๊ฑด. ๊ต์—ด ์‹คํ–‰ **์ „**์— ๋ถ€๋ฅธ๋‹ค โ€” ์‹คํ–‰์ด ์‹คํŒจํ•ด๋„ ์ž…๋ ฅ์€ ๋‚จ๊ฒŒ."""
return _insert("articles", {"source_text": source_text})
def save_run(
article_id: int | None,
*,
pipeline_key: str,
prompt_key: str,
model: str,
output: str,
processing_time_s: float,
) -> int | None:
"""๊ต์—ด ๊ฒฐ๊ณผ 1๊ฑด. `article_id` ๊ฐ€ ์—†์œผ๋ฉด(์ž…๋ ฅ ๊ธฐ๋ก ์‹คํŒจ) ๊ฒฐ๊ณผ๋„ ๊ธฐ๋กํ•˜์ง€ ์•Š๋Š”๋‹ค."""
if article_id is None:
return None
return _insert(
"pipeline_runs",
{
"article_id": article_id,
"pipeline_key": pipeline_key,
"prompt_key": prompt_key,
"model": model,
"output": output,
"processing_time_s": round(processing_time_s, 2),
},
)