from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, field_validator RiskObservationStatus = Literal[ "pending_review", "confirmed", "false_positive", "ignored", "resolved", ] RiskObservationFeedbackType = Literal[ "confirm", "false_positive", "ignore", "resolve", "comment", ] class RiskObservationFeedbackRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: str observation_id: str feedback_type: str action: str actor: str comment: str | None payload_json: dict[str, Any] decision: str = "" candidate_rule_source: str = "" confidence_score: float = 0.0 escalation_target: str = "" supplement_required: bool = False created_at: datetime class RiskObservationRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: str observation_key: str subject_type: str subject_key: str subject_label: str claim_id: str | None claim_no: str run_id: str | None execution_log_id: str | None risk_type: str risk_signal: str title: str description: str risk_score: int risk_level: str confidence_score: float control_stage: str control_mode: str automation_mode: str source: str algorithm_version: str status: str feedback_status: str contribution_scores_json: dict[str, Any] baseline_json: dict[str, Any] evidence_json: list[Any] graph_node_keys_json: list[Any] graph_edge_keys_json: list[Any] policy_refs_json: list[Any] similar_case_claim_ids_json: list[Any] ontology_json: dict[str, Any] decision_trace_json: dict[str, Any] sampling_strategy: dict[str, Any] = Field(default_factory=dict) evaluation_case_id: str = "" ontology_parse_id: str = "" ontology_version: str = "" domain: str = "" scenario: str = "" intent: str = "" ontology_entities_json: list[Any] = Field(default_factory=list) risk_signals_json: list[Any] = Field(default_factory=list) canonical_subject_key: str = "" created_at: datetime updated_at: datetime feedback_items: list[RiskObservationFeedbackRead] = Field(default_factory=list) class RiskObservationListRead(BaseModel): items: list[RiskObservationRead] total: int limit: int offset: int class RiskObservationFeedbackCreate(BaseModel): feedback_type: RiskObservationFeedbackType action: str | None = Field(default=None, max_length=50) actor: str | None = Field(default=None, max_length=100) comment: str | None = Field(default=None, max_length=1000) payload_json: dict[str, Any] = Field(default_factory=dict) @field_validator("action", "actor", "comment", mode="before") @classmethod def normalize_text(cls, value: Any) -> Any: if value is None: return None normalized = str(value).strip() return normalized or None class RiskObservationDashboardRead(BaseModel): window_days: int total_observations: int pending_count: int risk_clue_count: int = 0 high_or_above_count: int confirmed_count: int false_positive_count: int feedback_sample_count: int = 0 total_amount: float = 0.0 average_score: float level_distribution: dict[str, int] = Field(default_factory=dict) status_distribution: dict[str, int] = Field(default_factory=dict) signal_distribution: dict[str, int] = Field(default_factory=dict) source_distribution: dict[str, int] = Field(default_factory=dict) automation_distribution: dict[str, int] = Field(default_factory=dict) department_distribution: dict[str, int] = Field(default_factory=dict) expense_type_distribution: dict[str, int] = Field(default_factory=dict) risk_type_distribution: dict[str, int] = Field(default_factory=dict) supplier_distribution: dict[str, int] = Field(default_factory=dict) employee_grade_distribution: dict[str, int] = Field(default_factory=dict) daily_trend: list[dict[str, Any]] = Field(default_factory=list) top_risk_signals: list[dict[str, Any]] = Field(default_factory=list) top_departments: list[dict[str, Any]] = Field(default_factory=list) top_employees: list[dict[str, Any]] = Field(default_factory=list) top_suppliers: list[dict[str, Any]] = Field(default_factory=list) top_expense_types: list[dict[str, Any]] = Field(default_factory=list) top_rules: list[dict[str, Any]] = Field(default_factory=list) candidate_rule_count: int = 0 confirmation_rate: float false_positive_rate: float recent_high_observations: list[RiskObservationRead] = Field(default_factory=list)