"""종목 검색 / 메타 API.""" from __future__ import annotations from fastapi import APIRouter, HTTPException, Query from sqlalchemy import text from app.db.connection import get_engine router = APIRouter(prefix="/api/symbols", tags=["symbols"]) @router.get("/search") def search_symbols( q: str = Query(..., min_length=1, max_length=40, description="종목명 또는 코드 prefix/부분 일치"), limit: int = Query(default=20, ge=1, le=100), seed_only: bool = Query(default=False, description="true 면 학습/배치 대상 10종목만"), ) -> dict: """이름은 trigram + ILIKE, 코드는 prefix 매치. 우선순위: 1) 코드가 정확히 같으면 가장 위 2) 이름 prefix 매치 3) 이름 부분 매치 (trigram similarity) """ q_norm = q.strip() if not q_norm: raise HTTPException(status_code=400, detail="empty query") eng = get_engine() where_seed = "AND is_seed = TRUE" if seed_only else "" sql = text( f""" WITH ranked AS ( SELECT code, name, market, sector, is_seed, CASE WHEN code = :q THEN 0 WHEN code LIKE :prefix THEN 1 WHEN name LIKE :prefix THEN 2 WHEN name ILIKE :contains THEN 3 ELSE 4 END AS rank, similarity(name, :q) AS sim FROM symbols WHERE active = TRUE {where_seed} AND (code LIKE :prefix OR name ILIKE :contains OR similarity(name, :q) > 0.2) ) SELECT code, name, market, sector, is_seed FROM ranked ORDER BY rank ASC, sim DESC, name ASC LIMIT :lim """ ) with eng.connect() as conn: rows = conn.execute( sql, { "q": q_norm, "prefix": f"{q_norm}%", "contains": f"%{q_norm}%", "lim": limit, }, ).all() return { "q": q_norm, "count": len(rows), "items": [ { "code": r[0], "name": r[1], "market": r[2], "sector": r[3], "is_seed": bool(r[4]), } for r in rows ], } @router.get("/{code}") def get_symbol(code: str) -> dict: eng = get_engine() with eng.connect() as conn: row = conn.execute( text( "SELECT code, name, market, sector, is_seed, active, created_at " "FROM symbols WHERE code = :c" ), {"c": code}, ).first() if not row: raise HTTPException(status_code=404, detail=f"unknown code: {code}") return { "code": row[0], "name": row[1], "market": row[2], "sector": row[3], "is_seed": bool(row[4]), "active": bool(row[5]), "created_at": str(row[6]) if row[6] else None, }