File size: 5,173 Bytes
6c9b8f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e55328
6c9b8f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1c84b5
 
 
 
 
 
 
6c9b8f1
 
 
b1c84b5
 
 
 
 
 
 
 
6c9b8f1
 
 
 
 
 
 
 
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
157
158
159
160
161
162
163
164
165
166
167
168
"""
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