File size: 2,113 Bytes
4eefabb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Pydantic request / response schemas — the contract between FE and BE."""
from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field

TerrainType = Literal["Valley", "Slope", "Flat", "Peak", "Unknown"]
RiskLevel   = Literal["Safe", "Caution", "Warning", "Danger"]
ActivityType = Literal["hiker", "driver", "construction", "general"]


class PredictionRequest(BaseModel):
    latitude:  float = Field(..., ge=-90.0,  le=90.0,  description="WGS84 latitude")
    longitude: float = Field(..., ge=-180.0, le=180.0, description="WGS84 longitude")
    activity:  ActivityType = "general"


class VetoTrigger(BaseModel):
    rule:    str
    value:   float | None
    message_en: str
    message_zh: str


class InferenceStep(BaseModel):
    """One line of the XAI (explainable AI) inference log."""
    kind: Literal["info", "ml", "rule", "veto", "score", "hazard", "table", "activity"]
    text_en: str
    text_zh: str


class HazardSubscores(BaseModel):
    """Per-category risk score 0-100. Matches the four hazard types
    enumerated in the D5 proposal §3.7 (P4.3)."""
    rainfall:     int = Field(..., ge=0, le=100)
    fog:          int = Field(..., ge=0, le=100)
    wind_gust:    int = Field(..., ge=0, le=100)
    thunderstorm: int = Field(..., ge=0, le=100)


class DecisionTableMatch(BaseModel):
    """A row of D5 §3.7.2 / Table 4.2 that has fired for this request."""
    rule:           str           # 'R1' | 'R2' | 'R3' | 'R4'
    description:    str
    conclusion_en:  str
    conclusion_zh:  str


class PredictionResponse(BaseModel):
    latitude:  float
    longitude: float
    elevation_m: float
    terrain: TerrainType

    ml_rain_probability: float = Field(..., ge=0.0, le=1.0)

    hazard_subscores: HazardSubscores
    decision_table_matches: list[DecisionTableMatch]
    activity: ActivityType

    risk_score: int = Field(..., ge=0, le=100)
    risk_level: RiskLevel

    veto_triggers: list[VetoTrigger]
    inference_log: list[InferenceStep]

    advice_en: str
    advice_zh: str

    cached:    bool = False
    cache_ttl: int  = 0