""" UndertriAI — Pydantic Models Defines all Action and Observation types for the bail assessment environment. """ from __future__ import annotations from typing import Any, Dict, List, Literal, Optional, Union from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # OpenEnv base stubs (avoids hard import dependency when running locally) # --------------------------------------------------------------------------- try: from openenv.core.models import Action, Observation, State, StepResult # type: ignore except ImportError: class Action(BaseModel): pass class Observation(BaseModel): pass class State(BaseModel): episode_id: str = "" step_count: int = 0 class StepResult(BaseModel): observation: Any reward: float = 0.0 done: bool = False info: Dict[str, Any] = Field(default_factory=dict) # --------------------------------------------------------------------------- # ACTIONS — tool calls the agent can make before submitting the final memo # --------------------------------------------------------------------------- class RequestDocumentAction(Action): """Request a missing document (surety affidavit, prior judgment, etc.)""" tool_name: Literal["request_document"] = "request_document" document_type: str = Field( ..., description="Type of document to request: surety_affidavit | prior_judgment | fir_copy | medical_report | employment_proof", ) justification: str = Field(..., description="Why this document is needed") class FlagInconsistencyAction(Action): """Flag a legal inconsistency in the charge or prosecution argument.""" tool_name: Literal["flag_inconsistency"] = "flag_inconsistency" inconsistency: str = Field(..., description="Description of the inconsistency found") severity: Literal["minor", "major", "fatal"] = Field( ..., description="Severity: minor=procedural, major=affects merits, fatal=vitiates proceedings" ) location: str = Field(..., description="Where in the record the inconsistency appears") class CrossReferencePrecedentAction(Action): """Retrieve and cite a relevant precedent from the case database.""" tool_name: Literal["cross_reference_precedent"] = "cross_reference_precedent" query: str = Field(..., description="Legal principle or scenario to search for") jurisdiction: Optional[str] = Field(None, description="Preferred jurisdiction (e.g., 'Supreme Court', 'Delhi HC')") crime_category: Optional[str] = Field(None, description="Narrow search by crime category") class ComputeStatutoryEligibilityAction(Action): """Check if accused has served half the maximum sentence (default bail eligibility).""" tool_name: Literal["compute_statutory_eligibility"] = "compute_statutory_eligibility" sections_invoked: List[str] = Field(..., description="IPC/BNSS sections charged under") max_sentence_years: float = Field(..., description="Maximum sentence for the most serious charge in years") custody_months: float = Field(..., description="Months in custody to date") special_law_applicable: bool = Field(False, description="Whether NDPS/UAPA/PMLA or similar special law applies") class AssessSuretyAction(Action): """Evaluate financial viability of the proposed surety.""" tool_name: Literal["assess_surety"] = "assess_surety" proposed_amount: int = Field(..., description="Proposed surety amount in INR") accused_occupation: str = Field(..., description="Occupation of accused") income_estimate: Optional[int] = Field(None, description="Estimated monthly income in INR") surety_relation: Optional[str] = Field(None, description="Relation of surety to accused") class ClassifyBailTypeAction(Action): """Determine whether grounds support conditional bail, absolute bail, or denial.""" tool_name: Literal["classify_bail_type"] = "classify_bail_type" grounds_for: List[str] = Field(..., description="List of grounds supporting bail") grounds_against: List[str] = Field(..., description="List of grounds opposing bail") accused_category: Optional[str] = Field(None, description="First-time offender | repeat | undertrial | convict") class ReadSubmissionsAction(Action): """Read and summarise prosecution or defence submissions on record.""" tool_name: Literal["read_submissions"] = "read_submissions" party: Literal["prosecution", "defence", "both"] = Field( ..., description="Which party's submissions to read" ) focus: Optional[str] = Field( None, description="Specific legal issue to focus on (e.g. 'flight risk', 'BNSS 479')" ) class AssessFlightRiskAction(Action): """Systematically assess the accused's flight risk based on case factors.""" tool_name: Literal["assess_flight_risk"] = "assess_flight_risk" roots_in_community: Optional[str] = Field( None, description="Evidence of local ties: family, employment, property" ) prior_absconding: bool = Field(False, description="Has the accused ever absconded before?") passport_status: Optional[str] = Field( None, description="surrendered | impounded | at-large | unknown" ) severity_of_offence: Literal["minor", "moderate", "serious", "heinous"] = Field( ..., description="Gravity of the offence (determines flight incentive)" ) class CheckCaseFactorsAction(Action): """Examine specific case factors relevant to bail determination.""" tool_name: Literal["check_case_factors"] = "check_case_factors" factors_to_check: List[str] = Field( ..., description="Factors to examine, e.g.: 'nature_of_offence', 'victim_vulnerability', " "'evidence_tampering_risk', 'co_accused_bail_status', 'recovery_of_property'" ) class ApplyProportionalityAction(Action): """Apply proportionality principle: custody duration vs. maximum sentence vs. trial timeline.""" tool_name: Literal["apply_proportionality"] = "apply_proportionality" custody_months: float = Field(..., description="Months in custody to date") max_sentence_years: float = Field(..., description="Maximum sentence for the most serious charge") expected_trial_months: Optional[float] = Field( None, description="Estimated months until trial completion (if known)" ) class PullCriminalHistoryAction(Action): """Pull the accused's prior criminal record, bail history, and conviction status.""" tool_name: Literal["pull_criminal_history"] = "pull_criminal_history" include_bail_history: bool = Field( default=True, description="Whether to include prior bail applications and outcomes" ) class IssueOrderAction(Action): """ TERMINAL ACTION — Issue a bail order (Block 4.3 spec alias for submit_memo). Maps order_type to recommended_outcome: grant → Bail Granted deny → Bail Denied conditional → Bail Granted (conditions must be provided) This is the short-form action compatible with the OpenEnv compliance checklist spec (`issue_order(grant | deny | conditional)`). Use submit_memo for the full structured memo form. """ tool_name: Literal["issue_order"] = "issue_order" order_type: Literal["grant", "deny", "conditional"] = Field( ..., description="Type of bail order: grant | deny | conditional" ) flight_risk: Literal["Low", "Medium", "High"] = Field( ..., description="Flight risk classification" ) flight_risk_justification: str = Field( ..., description="Justification for flight risk assessment referencing case facts" ) statutory_eligible: bool = Field( ..., description="Whether accused qualifies for default bail under statute" ) statutory_computation: str = Field( ..., description="Computation: section → max sentence → threshold → custody served" ) grounds_for_bail: List[str] = Field(..., description="Grounds supporting bail") grounds_against_bail: List[str] = Field(..., description="Grounds opposing bail") recommended_conditions: Optional[List[str]] = Field( None, description="Bail conditions (required when order_type='conditional')" ) confidence: Literal["High", "Medium", "Low"] = "Medium" class SubmitMemoAction(Action): """ TERMINAL ACTION — Submit the structured bail assessment memo. This triggers reward computation against the ground truth. """ tool_name: Literal["submit_memo"] = "submit_memo" # Flight risk assessment flight_risk: Literal["Low", "Medium", "High"] = Field( ..., description="Flight risk classification" ) flight_risk_justification: str = Field( ..., description="Justification for flight risk score with reference to case facts" ) # Statutory eligibility statutory_eligible: bool = Field( ..., description="Whether accused is eligible for bail under the statute" ) statutory_computation: str = Field( ..., description="Show the computation: sections, max sentence, time served, threshold" ) # Balanced assessment grounds_for_bail: List[str] = Field( ..., description="Specific grounds from case facts supporting bail" ) grounds_against_bail: List[str] = Field( ..., description="Specific grounds from prosecution / case facts opposing bail" ) # Recommendation recommended_outcome: Literal["Bail Granted", "Bail Denied"] = Field( ..., description="Final recommendation: Bail Granted | Bail Denied" ) recommended_conditions: Optional[List[str]] = Field( None, description="Conditions if bail granted: surety amount, travel restrictions, reporting, etc." ) # Confidence confidence: Literal["High", "Medium", "Low"] = Field( "Medium", description="Confidence in the recommendation" ) # Union of all valid agent actions BailAction = Union[ RequestDocumentAction, FlagInconsistencyAction, CrossReferencePrecedentAction, ComputeStatutoryEligibilityAction, AssessSuretyAction, ClassifyBailTypeAction, ReadSubmissionsAction, AssessFlightRiskAction, CheckCaseFactorsAction, ApplyProportionalityAction, PullCriminalHistoryAction, IssueOrderAction, # Block 4.3: spec-compliant alias for submit_memo SubmitMemoAction, ] # --------------------------------------------------------------------------- # OBSERVATION — what the agent sees at each step # --------------------------------------------------------------------------- class AccusedProfile(BaseModel): name: str gender: str occupation: Optional[str] = None region: Optional[str] = None prior_cases: Optional[str] = None bail_type: Optional[str] = None class CaseObservation(Observation): """Full state the agent observes at each step of an episode.""" case_id: str case_title: str # Case materials charge_sheet: str = Field(..., description="Facts and FIR summary") ipc_sections: List[str] = Field(..., description="Sections invoked (IPC or BNSS)") crime_type: str court: str date: str # Accused accused_profile: AccusedProfile # Arguments prosecution_arguments: List[str] defence_arguments: List[str] legal_issues: List[str] # Context cited_precedents: List[str] = Field(default_factory=list) documents_available: List[str] = Field(default_factory=list) # Episode state action_result: Optional[str] = None action_history: List[str] = Field( default_factory=list, description="Ordered log of all tool results seen so far this episode", ) flags_raised: List[str] = Field(default_factory=list) precedents_retrieved: List[str] = Field(default_factory=list) memo_submitted: bool = False step_count: int = 0 # Schema drift indicator (Patronus AI bonus track) schema_variant: str = "standard" # "standard" | "bnss" | "regional_" # --------------------------------------------------------------------------- # REWARD BREAKDOWN — returned in StepResult.info when memo is submitted # --------------------------------------------------------------------------- class RewardBreakdown(BaseModel): outcome_match: float # 0.0 – 1.0 flight_risk_accuracy: float # 0.0 – 1.0 statutory_accuracy: float # 0.0 – 1.0 condition_appropriateness: float # 0.0 – 1.0 bias_penalty: float # 0.0 – 1.0 (subtracted) total_reward: float # final R ground_truth_outcome: str agent_outcome: str explanation: str # --------------------------------------------------------------------------- # Public API # --------------------------------------------------------------------------- __all__ = [ # Base types "Action", "Observation", "State", "StepResult", # Actions (12 tool types + 1 terminal alias) "RequestDocumentAction", "FlagInconsistencyAction", "CrossReferencePrecedentAction", "ComputeStatutoryEligibilityAction", "AssessSuretyAction", "ClassifyBailTypeAction", "ReadSubmissionsAction", "AssessFlightRiskAction", "CheckCaseFactorsAction", "ApplyProportionalityAction", "PullCriminalHistoryAction", "IssueOrderAction", "SubmitMemoAction", # Union type "BailAction", # Observation / state "AccusedProfile", "CaseObservation", "RewardBreakdown", ]