| """Pydantic schemas for the credit scoring API. |
| |
| PredictionRequest exposes every raw application_train column the model was |
| trained on (excluding the TARGET label). Drift monitoring needs the full |
| input space — so we collect all 120 features + SK_ID_CURR rather than a |
| top-N subset. |
| |
| Field ranges are business-driven (e.g. DAYS_BIRTH ∈ [-25550, -6570] = ages |
| 18 to 70) rather than blindly fitted on training mins/maxes — this catches |
| nonsensical inputs at the API boundary. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from typing import Literal |
|
|
| from pydantic import BaseModel, ConfigDict, Field |
|
|
| |
| |
| |
| |
| |
| ContractType = Literal["Cash loans", "Revolving loans"] |
| GenderCode = Literal["F", "M"] |
| YN = Literal["Y", "N"] |
| TypeSuite = Literal[ |
| "Children", |
| "Family", |
| "Group of people", |
| "Other_A", |
| "Other_B", |
| "Spouse, partner", |
| "Unaccompanied", |
| ] |
| IncomeType = Literal[ |
| "Businessman", |
| "Commercial associate", |
| "Maternity leave", |
| "Pensioner", |
| "State servant", |
| "Student", |
| "Unemployed", |
| "Working", |
| ] |
| EducationType = Literal[ |
| "Academic degree", |
| "Higher education", |
| "Incomplete higher", |
| "Lower secondary", |
| "Secondary / secondary special", |
| ] |
| FamilyStatus = Literal[ |
| "Civil marriage", |
| "Married", |
| "Separated", |
| "Single / not married", |
| "Unknown", |
| "Widow", |
| ] |
| HousingType = Literal[ |
| "Co-op apartment", |
| "House / apartment", |
| "Municipal apartment", |
| "Office apartment", |
| "Rented apartment", |
| "With parents", |
| ] |
| OccupationType = Literal[ |
| "Accountants", |
| "Cleaning staff", |
| "Cooking staff", |
| "Core staff", |
| "Drivers", |
| "HR staff", |
| "High skill tech staff", |
| "IT staff", |
| "Laborers", |
| "Low-skill Laborers", |
| "Managers", |
| "Medicine staff", |
| "Private service staff", |
| "Realty agents", |
| "Sales staff", |
| "Secretaries", |
| "Security staff", |
| "Waiters/barmen staff", |
| ] |
| WeekDay = Literal[ |
| "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" |
| ] |
| OrganizationType = Literal[ |
| "Advertising", |
| "Agriculture", |
| "Bank", |
| "Business Entity Type 1", |
| "Business Entity Type 2", |
| "Business Entity Type 3", |
| "Cleaning", |
| "Construction", |
| "Culture", |
| "Electricity", |
| "Emergency", |
| "Government", |
| "Hotel", |
| "Housing", |
| "Industry: type 1", |
| "Industry: type 10", |
| "Industry: type 11", |
| "Industry: type 12", |
| "Industry: type 13", |
| "Industry: type 2", |
| "Industry: type 3", |
| "Industry: type 4", |
| "Industry: type 5", |
| "Industry: type 6", |
| "Industry: type 7", |
| "Industry: type 8", |
| "Industry: type 9", |
| "Insurance", |
| "Kindergarten", |
| "Legal Services", |
| "Medicine", |
| "Military", |
| "Mobile", |
| "Other", |
| "Police", |
| "Postal", |
| "Realtor", |
| "Religion", |
| "Restaurant", |
| "School", |
| "Security", |
| "Security Ministries", |
| "Self-employed", |
| "Services", |
| "Telecom", |
| "Trade: type 1", |
| "Trade: type 2", |
| "Trade: type 3", |
| "Trade: type 4", |
| "Trade: type 5", |
| "Trade: type 6", |
| "Trade: type 7", |
| "Transport: type 1", |
| "Transport: type 2", |
| "Transport: type 3", |
| "Transport: type 4", |
| "University", |
| "XNA", |
| ] |
| FondKapremontMode = Literal[ |
| "not specified", "org spec account", "reg oper account", "reg oper spec account" |
| ] |
| HouseTypeMode = Literal["block of flats", "specific housing", "terraced house"] |
| WallsMaterialMode = Literal[ |
| "Block", "Mixed", "Monolithic", "Others", "Panel", "Stone, brick", "Wooden" |
| ] |
| EmergencyStateMode = Literal["No", "Yes"] |
|
|
|
|
| |
| |
| |
| class PredictionRequest(BaseModel): |
| """Raw application_train inputs for one client.""" |
|
|
| model_config = ConfigDict(extra="forbid") |
|
|
| |
| SK_ID_CURR: int = Field(ge=100_000, le=999_999_999, description="Client id") |
|
|
| |
| NAME_CONTRACT_TYPE: ContractType |
| CODE_GENDER: GenderCode |
| FLAG_OWN_CAR: YN |
| FLAG_OWN_REALTY: YN |
| CNT_CHILDREN: int = Field(ge=0, le=20) |
| AMT_INCOME_TOTAL: float = Field(gt=0) |
| AMT_CREDIT: float = Field(gt=0) |
| AMT_ANNUITY: float | None = Field(default=None, gt=0) |
| AMT_GOODS_PRICE: float | None = Field(default=None, gt=0) |
| NAME_TYPE_SUITE: TypeSuite | None = None |
| NAME_INCOME_TYPE: IncomeType |
| NAME_EDUCATION_TYPE: EducationType |
| NAME_FAMILY_STATUS: FamilyStatus |
| NAME_HOUSING_TYPE: HousingType |
| REGION_POPULATION_RELATIVE: float = Field(ge=0, le=1) |
| DAYS_BIRTH: int = Field(le=-6570, ge=-25550, description="Negative days from now") |
| DAYS_EMPLOYED: int = Field( |
| ge=-25000, |
| le=365_243, |
| description="Negative days; 365243 sentinel = not employed", |
| ) |
| DAYS_REGISTRATION: float = Field(le=0, ge=-25000) |
| DAYS_ID_PUBLISH: int = Field(le=0, ge=-10000) |
| OWN_CAR_AGE: float | None = Field(default=None, ge=0, le=100) |
| FLAG_MOBIL: int = Field(ge=0, le=1) |
| FLAG_EMP_PHONE: int = Field(ge=0, le=1) |
| FLAG_WORK_PHONE: int = Field(ge=0, le=1) |
| FLAG_CONT_MOBILE: int = Field(ge=0, le=1) |
| FLAG_PHONE: int = Field(ge=0, le=1) |
| FLAG_EMAIL: int = Field(ge=0, le=1) |
| OCCUPATION_TYPE: OccupationType | None = None |
| CNT_FAM_MEMBERS: float = Field(ge=1, le=20) |
| REGION_RATING_CLIENT: int = Field(ge=1, le=3) |
| REGION_RATING_CLIENT_W_CITY: int = Field(ge=1, le=3) |
| WEEKDAY_APPR_PROCESS_START: WeekDay |
| HOUR_APPR_PROCESS_START: int = Field(ge=0, le=23) |
| REG_REGION_NOT_LIVE_REGION: int = Field(ge=0, le=1) |
| REG_REGION_NOT_WORK_REGION: int = Field(ge=0, le=1) |
| LIVE_REGION_NOT_WORK_REGION: int = Field(ge=0, le=1) |
| REG_CITY_NOT_LIVE_CITY: int = Field(ge=0, le=1) |
| REG_CITY_NOT_WORK_CITY: int = Field(ge=0, le=1) |
| LIVE_CITY_NOT_WORK_CITY: int = Field(ge=0, le=1) |
| ORGANIZATION_TYPE: OrganizationType |
|
|
| |
| EXT_SOURCE_1: float | None = Field(default=None, ge=0, le=1) |
| EXT_SOURCE_2: float | None = Field(default=None, ge=0, le=1) |
| EXT_SOURCE_3: float | None = Field(default=None, ge=0, le=1) |
|
|
| |
| APARTMENTS_AVG: float | None = Field(default=None, ge=0, le=1) |
| BASEMENTAREA_AVG: float | None = Field(default=None, ge=0, le=1) |
| YEARS_BEGINEXPLUATATION_AVG: float | None = Field(default=None, ge=0, le=1) |
| YEARS_BUILD_AVG: float | None = Field(default=None, ge=0, le=1) |
| COMMONAREA_AVG: float | None = Field(default=None, ge=0, le=1) |
| ELEVATORS_AVG: float | None = Field(default=None, ge=0, le=1) |
| ENTRANCES_AVG: float | None = Field(default=None, ge=0, le=1) |
| FLOORSMAX_AVG: float | None = Field(default=None, ge=0, le=1) |
| FLOORSMIN_AVG: float | None = Field(default=None, ge=0, le=1) |
| LANDAREA_AVG: float | None = Field(default=None, ge=0, le=1) |
| LIVINGAPARTMENTS_AVG: float | None = Field(default=None, ge=0, le=1) |
| LIVINGAREA_AVG: float | None = Field(default=None, ge=0, le=1) |
| NONLIVINGAPARTMENTS_AVG: float | None = Field(default=None, ge=0, le=1) |
| NONLIVINGAREA_AVG: float | None = Field(default=None, ge=0, le=1) |
| APARTMENTS_MODE: float | None = Field(default=None, ge=0, le=1) |
| BASEMENTAREA_MODE: float | None = Field(default=None, ge=0, le=1) |
| YEARS_BEGINEXPLUATATION_MODE: float | None = Field(default=None, ge=0, le=1) |
| YEARS_BUILD_MODE: float | None = Field(default=None, ge=0, le=1) |
| COMMONAREA_MODE: float | None = Field(default=None, ge=0, le=1) |
| ELEVATORS_MODE: float | None = Field(default=None, ge=0, le=1) |
| ENTRANCES_MODE: float | None = Field(default=None, ge=0, le=1) |
| FLOORSMAX_MODE: float | None = Field(default=None, ge=0, le=1) |
| FLOORSMIN_MODE: float | None = Field(default=None, ge=0, le=1) |
| LANDAREA_MODE: float | None = Field(default=None, ge=0, le=1) |
| LIVINGAPARTMENTS_MODE: float | None = Field(default=None, ge=0, le=1) |
| LIVINGAREA_MODE: float | None = Field(default=None, ge=0, le=1) |
| NONLIVINGAPARTMENTS_MODE: float | None = Field(default=None, ge=0, le=1) |
| NONLIVINGAREA_MODE: float | None = Field(default=None, ge=0, le=1) |
| APARTMENTS_MEDI: float | None = Field(default=None, ge=0, le=1) |
| BASEMENTAREA_MEDI: float | None = Field(default=None, ge=0, le=1) |
| YEARS_BEGINEXPLUATATION_MEDI: float | None = Field(default=None, ge=0, le=1) |
| YEARS_BUILD_MEDI: float | None = Field(default=None, ge=0, le=1) |
| COMMONAREA_MEDI: float | None = Field(default=None, ge=0, le=1) |
| ELEVATORS_MEDI: float | None = Field(default=None, ge=0, le=1) |
| ENTRANCES_MEDI: float | None = Field(default=None, ge=0, le=1) |
| FLOORSMAX_MEDI: float | None = Field(default=None, ge=0, le=1) |
| FLOORSMIN_MEDI: float | None = Field(default=None, ge=0, le=1) |
| LANDAREA_MEDI: float | None = Field(default=None, ge=0, le=1) |
| LIVINGAPARTMENTS_MEDI: float | None = Field(default=None, ge=0, le=1) |
| LIVINGAREA_MEDI: float | None = Field(default=None, ge=0, le=1) |
| NONLIVINGAPARTMENTS_MEDI: float | None = Field(default=None, ge=0, le=1) |
| NONLIVINGAREA_MEDI: float | None = Field(default=None, ge=0, le=1) |
| FONDKAPREMONT_MODE: FondKapremontMode | None = None |
| HOUSETYPE_MODE: HouseTypeMode | None = None |
| TOTALAREA_MODE: float | None = Field(default=None, ge=0, le=1) |
| WALLSMATERIAL_MODE: WallsMaterialMode | None = None |
| EMERGENCYSTATE_MODE: EmergencyStateMode | None = None |
|
|
| |
| OBS_30_CNT_SOCIAL_CIRCLE: float | None = Field(default=None, ge=0, le=500) |
| DEF_30_CNT_SOCIAL_CIRCLE: float | None = Field(default=None, ge=0, le=500) |
| OBS_60_CNT_SOCIAL_CIRCLE: float | None = Field(default=None, ge=0, le=500) |
| DEF_60_CNT_SOCIAL_CIRCLE: float | None = Field(default=None, ge=0, le=500) |
|
|
| DAYS_LAST_PHONE_CHANGE: float = Field(le=0, ge=-15000) |
|
|
| |
| FLAG_DOCUMENT_2: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_3: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_4: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_5: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_6: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_7: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_8: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_9: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_10: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_11: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_12: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_13: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_14: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_15: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_16: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_17: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_18: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_19: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_20: int = Field(ge=0, le=1) |
| FLAG_DOCUMENT_21: int = Field(ge=0, le=1) |
|
|
| |
| AMT_REQ_CREDIT_BUREAU_HOUR: float | None = Field(default=None, ge=0, le=500) |
| AMT_REQ_CREDIT_BUREAU_DAY: float | None = Field(default=None, ge=0, le=500) |
| AMT_REQ_CREDIT_BUREAU_WEEK: float | None = Field(default=None, ge=0, le=500) |
| AMT_REQ_CREDIT_BUREAU_MON: float | None = Field(default=None, ge=0, le=500) |
| AMT_REQ_CREDIT_BUREAU_QRT: float | None = Field(default=None, ge=0, le=500) |
| AMT_REQ_CREDIT_BUREAU_YEAR: float | None = Field(default=None, ge=0, le=500) |
|
|
|
|
| |
| |
| |
| Decision = Literal["GRANTED", "REFUSED"] |
|
|
|
|
| class PredictionResponse(BaseModel): |
| sk_id_curr: int |
| probability_default: float = Field(ge=0, le=1) |
| decision: Decision |
| threshold: float = Field(ge=0, le=1) |
| model_version: str |
| client_known: bool = Field( |
| description="True if SK_ID_CURR found in feature store; False otherwise." |
| ) |
|
|
|
|
| class HealthResponse(BaseModel): |
| status: Literal["ok"] |
| model_version: str |
|
|
|
|
| class ModelInfoResponse(BaseModel): |
| model_name: str |
| version: str |
| threshold: float |
| n_features_expected: int |
| metrics: dict |
|
|