"""Hybrid Grant Intelligence System — admin dashboard, rekomendacje, research.""" from __future__ import annotations import logging import os from typing import Any, Dict, List, Optional from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field from core.subscription.db import SessionLocal logger = logging.getLogger(__name__) def get_db(): db = SessionLocal() try: yield db finally: db.close() router = APIRouter(prefix="/intelligence", tags=["grant-intelligence"]) class CompanyProfileRequest(BaseModel): name: Optional[str] = "" size: str = "mikro" region: Optional[str] = None voivodeship: Optional[str] = None pkd: List[str] = Field(default_factory=list) pkd_codes: List[str] = Field(default_factory=list) entity_type: str = "przedsiębiorca" industry: Optional[str] = None class HybridSearchRequest(BaseModel): query: str = "" filters: Dict[str, Any] = Field(default_factory=dict) limit: int = 50 trusted_only: bool = False class GrantResearchRequest(BaseModel): grant_id: str company_profile: Optional[CompanyProfileRequest] = None changes: List[Dict[str, Any]] = Field(default_factory=list) class ResolveVerificationRequest(BaseModel): resolution_note: str = "" status: str = "approved" @router.get("/dashboard") async def intelligence_dashboard(db=Depends(get_db)): """Dashboard administracyjny: research, kompletność, kolejka weryfikacji.""" from core.grants.catalog_service import get_catalog_stats from core.grants.human_verification import hvq_stats, list_pending_verifications from core.grants.live_research import research_source_state from core.grants.versioning import get_recent_versions return { "status": "ok", "layers": { "curated_kb": get_catalog_stats(db), "live_research": research_source_state(db), "human_verification": hvq_stats(db), "recent_changes": get_recent_versions(db, limit=15), "pending_verifications": list_pending_verifications(db, limit=20), }, "feature_flags": { "ENABLE_LIVE_RESEARCH": os.environ.get("ENABLE_LIVE_RESEARCH", "true"), "ENABLE_GRANT_RESEARCH_AGENT": os.environ.get("ENABLE_GRANT_RESEARCH_AGENT", "true"), "ENABLE_HYBRID_SEARCH": os.environ.get("ENABLE_HYBRID_SEARCH", "true"), "ENABLE_PINECONE_SEMANTIC": os.environ.get("ENABLE_PINECONE_SEMANTIC", "true"), "HUMAN_VERIF_QUEUE": os.environ.get("HUMAN_VERIF_QUEUE", "true"), }, "hybrid_search_status": __import__( "core.grants.hybrid_search_status", fromlist=["get_hybrid_search_status"] ).get_hybrid_search_status(), } @router.post("/recommendations") async def company_recommendations(request: CompanyProfileRequest, db=Depends(get_db)): """Rekomendacje dla firmy z relevance score i wyjaśnieniem.""" from core.grants.recommendations import recommend_grants_for_company company = request.model_dump() if company.get("voivodeship") and not company.get("region"): company["region"] = company["voivodeship"] if company.get("pkd_codes") and not company.get("pkd"): company["pkd"] = company["pkd_codes"] return recommend_grants_for_company(db, company, limit=15) @router.post("/hybrid-search") async def hybrid_search_endpoint(request: HybridSearchRequest, db=Depends(get_db)): """Hybrydowe wyszukiwanie PostgreSQL FTS + Pinecone semantic.""" from core.grants.hybrid_search import hybrid_search_catalog results = hybrid_search_catalog( db, query=request.query, filters=request.filters, limit=request.limit, trusted_only=request.trusted_only, ) return {"status": "ok", "count": len(results), "grants": results} @router.post("/research/run-cycle") async def run_research_cycle(db=Depends(get_db)): """Uruchamia cykl Live Research (detection + job queue).""" from core.grants.live_research import run_live_research_cycle return {"status": "ok", **run_live_research_cycle(db)} @router.get("/research/jobs") async def list_jobs( status: Optional[str] = Query(None), limit: int = Query(50, ge=1, le=200), db=Depends(get_db), ): from core.grants.live_research import list_research_jobs return {"status": "ok", "jobs": list_research_jobs(db, status=status, limit=limit)} @router.post("/agent/run") async def run_research_agent(request: GrantResearchRequest, db=Depends(get_db)): """Ręczne uruchomienie grant_research_agent LangGraph.""" from agents.grant_research_agent import run_grant_research_agent from core.grants.completeness import grant_dict_from_row from core.grants.models import Grant row = db.query(Grant).filter(Grant.source_id == request.grant_id).first() if not row: return {"status": "error", "detail": "grant_not_found"} grant_data = grant_dict_from_row(row) company = request.company_profile.model_dump() if request.company_profile else {} result = run_grant_research_agent( grant_data, changes=request.changes, company_profile=company, ) return {"status": "ok", "result": result} @router.get("/verification/queue") async def verification_queue( status: str = Query("pending"), limit: int = Query(50, ge=1, le=200), db=Depends(get_db), ): from core.grants.human_verification import list_pending_verifications return { "status": "ok", "items": list_pending_verifications(db, status=status, limit=limit), } @router.post("/verification/{item_id}/resolve") async def resolve_verification_item( item_id: str, request: ResolveVerificationRequest, db=Depends(get_db), ): from core.grants.human_verification import resolve_verification return resolve_verification( db, item_id, resolution_note=request.resolution_note, new_status=request.status, ) @router.get("/versions") async def grant_versions( grant_id: Optional[str] = Query(None), limit: int = Query(50, ge=1, le=200), db=Depends(get_db), ): from core.grants.versioning import get_recent_versions return { "status": "ok", "versions": get_recent_versions(db, grant_source_id=grant_id, limit=limit), } def register_intelligence_routes(app) -> None: """Montuje router na aplikacji FastAPI.""" app.include_router(router) logger.info("[GrantIntelligence] endpoints registered at /api/grants/intelligence")