import uuid from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user from app.database import get_db from app.models.user import User from app.models.brand import Brand from app.models.schema_suggestion import SchemaSuggestion from app.schemas.schema_suggestion import ( SchemaAdviseRequest, SchemaSuggestionResponse, SchemaSuggestionList, SchemaValidationResult, SchemaStatusUpdateRequest, ) from app.services.schema.schema_advisor_service import SchemaAdvisorService from app.services.scoring.scoring_service import ScoringService router = APIRouter() async def _get_brand_with_access( brand_id: uuid.UUID, db: AsyncSession, current_user: User, ) -> Brand: stmt = select(Brand).where( Brand.id == brand_id, Brand.user_id == current_user.id, ) result = await db.execute(stmt) brand = result.scalar_one_or_none() if not brand: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="品牌不存在", ) return brand async def _get_brand_diagnosis_data( db: AsyncSession, user_id: uuid.UUID, brand: Brand, ) -> dict: from app.models.query import Query as QueryModel from app.models.citation_record import CitationRecord from app.models.competitor import Competitor from app.schemas.scoring import CitationResult from app.services.analysis.sentiment_service import get_sentiment_service queries_stmt = select(QueryModel).where( QueryModel.user_id == user_id, QueryModel.target_brand == brand.name, ) queries_result = await db.execute(queries_stmt) queries = list(queries_result.scalars().all()) if not queries: scoring_service = ScoringService() empty_result = scoring_service.calculate_v2( mentioned_count=0, total_queries=0, positions=[], sentiment_counts={"positive": 0, "neutral": 0, "negative": 0}, citations=[], brand_mentions=0, competitor_mentions={}, ) return empty_result.to_dict() query_ids = [q.id for q in queries] citations_stmt = select(CitationRecord).where( CitationRecord.query_id.in_(query_ids), ) citations_result = await db.execute(citations_stmt) all_citations = list(citations_result.scalars().all()) total_queries = len(all_citations) brand_citations = [c for c in all_citations if c.cited] competitor_stmt = select(Competitor).where(Competitor.brand_id == brand.id) competitor_result = await db.execute(competitor_stmt) competitors = list(competitor_result.scalars().all()) competitor_names = [c.name for c in competitors] competitor_mentions: dict[str, int] = {} for comp_name in competitor_names: count = sum( 1 for c in all_citations if c.cited and c.competitor_brands and comp_name in c.competitor_brands ) if count > 0: competitor_mentions[comp_name] = count sentiment_service = get_sentiment_service() sentiment_counts = {"positive": 0, "neutral": 0, "negative": 0} for citation in brand_citations: if citation.sentiment and citation.sentiment in ("positive", "neutral", "negative"): sentiment_counts[citation.sentiment] += 1 else: content = citation.raw_response or citation.citation_text or "" if content.strip(): try: result = await sentiment_service.analyze( brand_name=brand.name, content=content, ) sentiment_counts[result.sentiment] += 1 except Exception: sentiment_counts["neutral"] += 1 else: sentiment_counts["neutral"] += 1 citation_results = [ CitationResult( cited=c.cited, position=c.citation_position, citation_text=c.citation_text, sentiment="neutral", confidence=c.confidence or 0.0, ) for c in brand_citations ] positions = [c.citation_position for c in brand_citations if c.cited] scoring_service = ScoringService() v2_result = scoring_service.calculate_v2( mentioned_count=len(brand_citations), total_queries=total_queries, positions=positions, sentiment_counts=sentiment_counts, citations=citation_results, brand_mentions=len(brand_citations), competitor_mentions=competitor_mentions, ) return v2_result.to_dict() @router.post("/advise", response_model=SchemaSuggestionList) async def generate_schema_advise( request: SchemaAdviseRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): brand = await _get_brand_with_access(request.brand_id, db, current_user) diagnosis_data = await _get_brand_diagnosis_data(db, current_user.id, brand) brand_info = { "name": brand.name, "website": brand.website or "", "industry": brand.industry or "", } service = SchemaAdvisorService() suggestions = await service.generate_suggestions( db=db, brand_id=brand.id, diagnosis_data=diagnosis_data, brand_info=brand_info, target_url=request.target_url, focus_dimensions=request.focus_dimensions, ) return SchemaSuggestionList( suggestions=[SchemaSuggestionResponse.model_validate(s) for s in suggestions], total=len(suggestions), ) @router.get("/brand/{brand_id}", response_model=SchemaSuggestionList) async def get_brand_schema_suggestions( brand_id: uuid.UUID, status_filter: str | None = Query(None, alias="status", description="按状态筛选"), schema_type: str | None = Query(None, description="按Schema类型筛选"), skip: int = Query(0, ge=0), limit: int = Query(20, ge=1, le=100), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): await _get_brand_with_access(brand_id, db, current_user) service = SchemaAdvisorService() suggestions, total = await service.get_suggestions( db=db, brand_id=brand_id, status_filter=status_filter, schema_type=schema_type, skip=skip, limit=limit, ) return SchemaSuggestionList( suggestions=[SchemaSuggestionResponse.model_validate(s) for s in suggestions], total=total, ) @router.get("/{suggestion_id}", response_model=SchemaSuggestionResponse) async def get_schema_suggestion_detail( suggestion_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): service = SchemaAdvisorService() suggestion = await service.get_suggestion_by_id(db, suggestion_id) if not suggestion: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="建议不存在", ) brand = await _get_brand_with_access(suggestion.brand_id, db, current_user) return SchemaSuggestionResponse.model_validate(suggestion) @router.put("/{suggestion_id}/status", response_model=SchemaSuggestionResponse) async def update_schema_suggestion_status( suggestion_id: uuid.UUID, status_update: SchemaStatusUpdateRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): valid_statuses = {"pending", "applied", "dismissed"} if status_update.status not in valid_statuses: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"无效的状态值,支持: {', '.join(valid_statuses)}", ) service = SchemaAdvisorService() suggestion = await service.get_suggestion_by_id(db, suggestion_id) if not suggestion: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="建议不存在", ) await _get_brand_with_access(suggestion.brand_id, db, current_user) updated = await service.update_status(db, suggestion_id, status_update.status) return SchemaSuggestionResponse.model_validate(updated)