philverify-api / api /schemas.py
Ryan Christian D. Deniega
Fix: Overhaul Facebook post detection to filtered comments using [role='feed'] strategy and improve button positioning
7e55328
"""
PhilVerify β€” Pydantic Request / Response Schemas
Matches the structured JSON output format from the system spec.
"""
from __future__ import annotations
from enum import Enum
from typing import Optional
from pydantic import BaseModel, HttpUrl, Field
# ── Enums ─────────────────────────────────────────────────────────────────────
class Verdict(str, Enum):
CREDIBLE = "Credible"
UNVERIFIED = "Unverified"
LIKELY_FAKE = "Likely Fake"
class Stance(str, Enum):
SUPPORTS = "Supports"
REFUTES = "Refutes"
NOT_ENOUGH_INFO = "Not Enough Info"
class Language(str, Enum):
TAGALOG = "Tagalog"
ENGLISH = "English"
TAGLISH = "Taglish"
UNKNOWN = "Unknown"
class Sentiment(str, Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"
HIGH_POSITIVE = "high positive"
HIGH_NEGATIVE = "high negative"
class DomainTier(int, Enum):
CREDIBLE = 1
SATIRE_OPINION = 2
SUSPICIOUS = 3
KNOWN_FAKE = 4
# ── Request Models ─────────────────────────────────────────────────────────────
class TextVerifyRequest(BaseModel):
text: str = Field(..., min_length=10, max_length=10_000, description="Raw text to verify")
class URLVerifyRequest(BaseModel):
url: HttpUrl = Field(..., description="URL of the news article or social media post")
# ── Nested Response Models ────────────────────────────────────────────────────
class EntitiesResult(BaseModel):
persons: list[str] = []
organizations: list[str] = []
locations: list[str] = []
dates: list[str] = []
class Layer1Result(BaseModel):
verdict: Verdict
confidence: float = Field(..., ge=0.0, le=100.0, description="Confidence % from ML classifier")
triggered_features: list[str] = Field(
default_factory=list,
description="Human-readable list of suspicious features detected",
)
class EvidenceSource(BaseModel):
title: str
url: str
similarity: float = Field(..., ge=0.0, le=1.0, description="Cosine similarity to input claim")
stance: Stance
domain_tier: DomainTier
published_at: Optional[str] = None
source_name: Optional[str] = None
class Layer2Result(BaseModel):
verdict: Verdict
evidence_score: float = Field(..., ge=0.0, le=100.0)
sources: list[EvidenceSource] = []
claim_used: Optional[str] = Field(None, description="Extracted claim sent to evidence search")
# ── Main Response ─────────────────────────────────────────────────────────────
class VerificationResponse(BaseModel):
verdict: Verdict
confidence: float = Field(..., ge=0.0, le=100.0)
final_score: float = Field(..., ge=0.0, le=100.0)
layer1: Layer1Result
layer2: Layer2Result
entities: EntitiesResult
sentiment: str
emotion: str
language: Language
domain_credibility: Optional[DomainTier] = None
input_type: str = "text"
processing_time_ms: Optional[float] = None
extracted_text: Optional[str] = Field(None, description="Raw text extracted from the URL / image / video for transparency")
# ── History / Trends ──────────────────────────────────────────────────────────
class HistoryEntry(BaseModel):
id: str
timestamp: str
input_type: str
text_preview: str
verdict: Verdict
confidence: float
final_score: float
class HistoryResponse(BaseModel):
total: int
entries: list[HistoryEntry]
class TrendingEntity(BaseModel):
entity: str
entity_type: str # person | org | location
count: int
fake_count: int
fake_ratio: float
class TrendingTopic(BaseModel):
topic: str
count: int
dominant_verdict: Verdict
class VerdictDayPoint(BaseModel):
date: str # YYYY-MM-DD
credible: int = 0
unverified: int = 0
fake: int = 0
class TrendsResponse(BaseModel):
top_entities: list[TrendingEntity]
top_topics: list[TrendingTopic]
verdict_distribution: dict[str, int] = Field(
default_factory=dict,
description="Counts per verdict: Credible, Unverified, Likely Fake",
)
verdict_by_day: list[VerdictDayPoint] = Field(
default_factory=list,
description="Day-by-day verdict counts for the area chart (last N days)",
)
# ── Error ─────────────────────────────────────────────────────────────────────
class ErrorResponse(BaseModel):
error: str
detail: Optional[str] = None
code: Optional[str] = None