Spaces:
Paused
Paused
File size: 5,729 Bytes
82eac1b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | """
Database Layer β PostgreSQL (asyncpg)
ββββββββββββββββββββββββββββββββββββββ
ΰΉΰΈΰΉΰΈ conversation history ΰΉΰΈ₯ΰΈ° sentiment results
"""
import logging
import asyncpg
from datetime import datetime, timezone
from app.config import settings
logger = logging.getLogger(__name__)
CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS conversations (
id SERIAL PRIMARY KEY,
conv_id TEXT NOT NULL UNIQUE,
user_id TEXT NOT NULL,
message TEXT NOT NULL,
source_type TEXT DEFAULT 'user',
timestamp BIGINT NOT NULL, -- LINE timestamp (ms)
created_at TIMESTAMPTZ DEFAULT NOW(),
-- Sentiment fields (nullable ΰΈΰΉΰΈΰΈ analyze ΰΉΰΈͺΰΈ£ΰΉΰΈ)
sentiment_label TEXT, -- positive | neutral | negative
sentiment_score FLOAT, -- confidence score 0β1
raw_scores JSONB -- {"positive": 0.8, ...}
);
CREATE INDEX IF NOT EXISTS idx_conv_user ON conversations(user_id);
CREATE INDEX IF NOT EXISTS idx_conv_created ON conversations(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conv_sentiment ON conversations(sentiment_label);
"""
class Database:
def __init__(self):
self._pool: asyncpg.Pool | None = None
async def connect(self):
self._pool = await asyncpg.create_pool(
dsn=settings.DATABASE_URL,
min_size=2,
max_size=10,
)
logger.info("β
Database pool created")
async def disconnect(self):
if self._pool:
await self._pool.close()
async def _pool_ok(self) -> asyncpg.Pool:
if not self._pool:
await self.connect()
return self._pool
# ββ Write ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async def save_conversation(
self,
conv_id: str,
user_id: str,
message: str,
timestamp: int,
source_type: str = "user",
) -> dict:
pool = await self._pool_ok()
async with pool.acquire() as conn:
row = await conn.fetchrow(
"""
INSERT INTO conversations (conv_id, user_id, message, source_type, timestamp)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (conv_id) DO NOTHING
RETURNING *
""",
conv_id, user_id, message, source_type, timestamp,
)
return dict(row) if row else {}
async def update_sentiment(
self,
conv_id: str,
label: str,
score: float,
raw_scores: dict,
) -> None:
import json
pool = await self._pool_ok()
async with pool.acquire() as conn:
await conn.execute(
"""
UPDATE conversations
SET sentiment_label = $1,
sentiment_score = $2,
raw_scores = $3
WHERE conv_id = $4
""",
label, score, json.dumps(raw_scores), conv_id,
)
# ββ Read βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async def get_conversations(self, limit: int = 50, offset: int = 0) -> list[dict]:
pool = await self._pool_ok()
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT conv_id, user_id, message, sentiment_label,
sentiment_score, raw_scores, created_at
FROM conversations
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
""",
limit, offset,
)
return [dict(r) for r in rows]
async def get_sentiment_summary(self) -> dict:
"""ΰΈͺΰΈ£ΰΈΈΰΈΰΈ’ΰΈΰΈΰΈ£ΰΈ§ΰΈ‘ sentiment ΰΈͺΰΈ³ΰΈ«ΰΈ£ΰΈ±ΰΈ stat cards"""
pool = await self._pool_ok()
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT
COUNT(*) FILTER (WHERE sentiment_label = 'positive') AS positive,
COUNT(*) FILTER (WHERE sentiment_label = 'neutral') AS neutral,
COUNT(*) FILTER (WHERE sentiment_label = 'negative') AS negative,
COUNT(*) AS total
FROM conversations
WHERE created_at >= NOW() - INTERVAL '7 days'
"""
)
row = dict(rows[0])
total = row["total"] or 1 # ΰΈΰΉΰΈΰΈΰΈΰΈ±ΰΈ div/0
return {
"positive": {"count": row["positive"], "pct": round(row["positive"] / total * 100, 1)},
"neutral": {"count": row["neutral"], "pct": round(row["neutral"] / total * 100, 1)},
"negative": {"count": row["negative"], "pct": round(row["negative"] / total * 100, 1)},
"total": row["total"],
"period": "last 7 days",
}
async def init_db():
"""ΰΈͺΰΈ£ΰΉΰΈ²ΰΈ table ΰΈΰΉΰΈ²ΰΈ’ΰΈ±ΰΈΰΉΰΈ‘ΰΉΰΈ‘ΰΈ΅ (ΰΈ£ΰΈ±ΰΈΰΈΰΈΰΈ startup)"""
pool = await asyncpg.create_pool(dsn=settings.DATABASE_URL)
async with pool.acquire() as conn:
await conn.execute(CREATE_TABLE_SQL)
await pool.close()
logger.info("β
Database initialized")
# Singleton
db = Database()
|