DEVALIDATION OF VALIDATION

This commit is contained in:
NoeBerdoz
2025-04-13 08:45:47 +02:00
parent 790cbb43ab
commit 54e794d670
5 changed files with 120 additions and 147 deletions

View File

@ -1,43 +1,56 @@
from typing import Literal, Optional, Self
from pydantic import BaseModel, ConfigDict, EmailStr, Field, model_validator
from pydantic_extra_types.phone_numbers import PhoneNumber
from typing import Optional # Removed Literal, Self
from pydantic import BaseModel, ConfigDict # Removed Field, model_validator
# Removed PhoneNumber import as it's not used and types are simplified
class FromAccount(BaseModel):
"""
Fields which can be extracted from account.pdf
Fields which can be extracted from account.pdf - All fields optional and simplified to string.
Validators removed for testing purposes.
"""
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True)
# Details of the Account and Client
account_name: str = Field(min_length=1)
account_holder_name: str = Field(min_length=1)
account_holder_surname: str = Field(min_length=1)
account_name: Optional[str] = None
account_holder_name: Optional[str] = None
account_holder_surname: Optional[str] = None
@model_validator(mode='after')
def check_account_name_is_name_surname(self) -> Self:
combined = f"{self.account_holder_name} {self.account_holder_surname}"
if combined != self.account_name:
raise ValueError(f'Account name is not name + surname: {self.account_name} != {combined}')
return self
# --- Updated Validator 1 ---
# @model_validator(mode='after')
# def check_account_name_is_name_surname(self) -> Self:
# # Only validate if all relevant fields are present
# if self.account_holder_name is not None and \
# self.account_holder_surname is not None and \
# self.account_name is not None:
# combined = f"{self.account_holder_name} {self.account_holder_surname}"
# if combined != self.account_name:
# raise ValueError(f'Account name is not name + surname: {self.account_name} != {combined}')
# return self
passport_number: str = Field(min_length=5)
passport_number: Optional[str] = None # Removed Field constraint
reference_currency: Literal["CHF", "EUR", "USD", "Other"]
reference_currency: Optional[str] = None # Simplified from Literal
other_currency: Optional[str] = None
# Delivery of Communication
building_number: str = Field(min_length=1)
street_name: str = Field(min_length=1)
postal_code: str = Field(min_length=1)
city: str = Field(min_length=1)
country: str = Field(min_length=1)
building_number: Optional[str] = None
street_name: Optional[str] = None
postal_code: Optional[str] = None
city: Optional[str] = None
country: Optional[str] = None
# Application for e-banking
ebanking_name: str = Field(min_length=1)
@model_validator(mode='after')
def check_account_name_ebanking_name(self) -> Self:
if self.ebanking_name != self.account_name:
raise ValueError(f'Ebanking name is different from account name')
return self
phone_number: str = Field(..., min_length=8)
email: str = Field(min_length=5)
ebanking_name: Optional[str] = None
# --- Updated Validator 2 ---
# @model_validator(mode='after')
# def check_account_name_ebanking_name(self) -> Self:
# # Only validate if both fields are present
# if self.ebanking_name is not None and self.account_name is not None:
# if self.ebanking_name != self.account_name:
# raise ValueError(f'Ebanking name ({self.ebanking_name}) is different from account name ({self.account_name})')
# return self
# Kept as Optional[str], but you might consider Optional[PhoneNumber]
# if you want specific phone number validation using pydantic-extra-types
phone_number: Optional[str] = None # Removed Field constraint
email: Optional[str] = None # Removed Field constraint

View File

@ -1,35 +1,32 @@
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional # Removed Literal
from pydantic import BaseModel, ConfigDict # Removed Field
class FromDescription(BaseModel):
"""
Fields which can be extracted from description.txt
Fields which can be extracted from description.txt - All fields optional and simplified to string.
"""
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True)
full_name: str = Field(..., min_length=1)
age: int = Field(..., ge=0, le=120)
nationality: str = Field(..., min_length=1)
full_name: Optional[str] = None
age: Optional[str] = None # Simplified from int
nationality: Optional[str] = None
marital_status: Optional[str] = None # Simplified from Literal
has_children: Optional[str] = None # Simplified from bool
marital_status: Literal["single", "married", "divorced", "widowed"]
has_children: bool
secondary_education_school: Optional[str] = None
secondary_education_year: Optional[str] = None # Simplified from int
university_name: Optional[str] = None
university_graduation_year: Optional[str] = None # Simplified from int
secondary_education_school: str
secondary_education_year: int = Field(..., ge=1900, le=2100)
university_name: str
university_graduation_year: int = Field(..., ge=1900, le=2100)
occupation_title: Optional[str] = None
employer: Optional[str] = None
start_year: Optional[str] = None # Simplified from int
annual_salary_eur: Optional[str] = None # Simplified from float
occupation_title: str
employer: str
start_year: int = Field(..., ge=1900, le=2100)
annual_salary_eur: float = Field(..., ge=0)
total_savings_eur: float = Field(..., ge=0)
has_properties: bool
inheritance_amount_eur: float = Field(..., ge=0)
inheritance_year: int = Field(..., ge=1900, le=2100)
inheritance_source: str
total_savings_eur: Optional[str] = None # Simplified from float
has_properties: Optional[str] = None # Simplified from bool
inheritance_amount_eur: Optional[str] = None # Simplified from float
inheritance_year: Optional[str] = None # Simplified from int
inheritance_source: Optional[str] = None

View File

@ -10,61 +10,22 @@ class FromPassport(BaseModel):
"""
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True)
country: str = Field(
...,
min_length=3,
max_length=3,
description="The country issuing the passport, as a 3-letter ISO 3166-1 alpha-3 code (e.g., 'CHE' for Switzerland)."
)
country: Optional[str] = None
passport_number: Optional[str] = None
surname: Optional[str] = None
given_names: Optional[str] = None
birth_date: Optional[str] = None # Simplified from date
citizenship: Optional[str] = None
sex: Optional[str] = None # Simplified from Literal
issue_date: Optional[str] = None # Simplified from date
expiry_date: Optional[str] = None # Simplified from date
passport_number: str = Field(
...,
min_length=9,
max_length=9,
pattern=r"^[A-Z0-9]{9}$",
description="The passport number, exactly 9 alphanumeric uppercase characters."
)
surname: str = Field(
...,
min_length=1,
description="The surname (family name) of the passport holder."
)
given_names: str = Field(
...,
min_length=1,
description="The given names (first and middle names) of the passport holder."
)
@model_validator(mode='after')
def check_expiry_date_after_issue_date(self) -> Self:
if self.issue_date >= self.expiry_date:
raise ValueError(f'Expiry date is not after issue date')
return self
birth_date: date = Field(
...,
description="Date of birth of the passport holder in ISO format (YYYY-MM-DD)."
)
citizenship: str = Field(
...,
min_length=2,
description="The nationality or citizenship of the passport holder, preferably as a country name or ISO code."
)
sex: Literal["M", "F"] = Field(
...,
description="Sex of the passport holder: 'M' for male, 'F' for female."
)
issue_date: date = Field(
...,
description="Date when the passport was issued, in ISO format (YYYY-MM-DD)."
)
expiry_date: date = Field(
...,
description="Date when the passport expires, in ISO format (YYYY-MM-DD)."
)
# --- Updated Validator ---
# @model_validator(mode='after')
# def check_expiry_date_after_issue_date(self) -> Self:
# # Only validate if both dates are present
# if self.issue_date is not None and self.expiry_date is not None:
# if self.issue_date >= self.expiry_date:
# # Raise error only if both dates are present and invalid
# raise ValueError(f'Expiry date ({self.expiry_date}) must be after issue date ({self.issue_date})')
# return self

View File

@ -1,65 +1,67 @@
from datetime import date
from typing import List, Literal, Optional
from pydantic import BaseModel, ConfigDict, EmailStr, Field
from typing import Optional # Keep Optional, remove others
from pydantic import BaseModel, ConfigDict # Removed Field
class FromProfile(BaseModel):
"""
Fields which can be extracted from description.txt
Fields which can be extracted from description.txt - All fields optional and simplified to string where possible.
"""
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True)
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) # Keep config if needed
first_name: str = Field(..., min_length=1)
last_name: str = Field(..., min_length=1)
date_of_birth: date
nationality: str
country_of_domicile: str
gender: Literal["Female", "Male"]
first_name: Optional[str] = None
last_name: Optional[str] = None
date_of_birth: Optional[str] = None # Simplified from date
nationality: Optional[str] = None
country_of_domicile: Optional[str] = None
gender: Optional[str] = None # Simplified from Literal
# ID information
passport_number: str = Field(..., min_length=9, max_length=9, pattern=r"^[A-Z0-9]{9}$")
id_type: Literal["passport"]
id_issue_date: date
id_expiry_date: date
passport_number: Optional[str] = None # Simplified, removed Field constraints
id_type: Optional[str] = None # Simplified from Literal
id_issue_date: Optional[str] = None # Simplified from date
id_expiry_date: Optional[str] = None # Simplified from date
# Contact
phone: str = Field(..., min_length=8)
email: str = Field(min_length=5)
address: str
phone: Optional[str] = None # Simplified, removed Field constraints
email: Optional[str] = None # Simplified, removed Field constraints
address: Optional[str] = None
# Personal info
politically_exposed_person: bool
marital_status: Literal["Single", "Married", "Divorced", "Widowed"]
highest_education: Literal["Tertiary", "Secondary", "Primary", "None"]
politically_exposed_person: Optional[str] = None # Simplified from bool
marital_status: Optional[str] = None # Simplified from Literal
highest_education: Optional[str] = None # Simplified from Literal
education_history: Optional[str] = None
# Employment
employment_status: Literal["Employee", "Self-Employed", "Unemployed", "Retired", "Student", "Diplomat", "Military", "Homemaker", "Other"]
employment_since: Optional[int] = None
employment_status: Optional[str] = None # Simplified from Literal
employment_since: Optional[str] = None # Simplified from int
employer: Optional[str] = None
position: Optional[str] = None
annual_salary_eur: Optional[float] = None
annual_salary_eur: Optional[str] = None # Simplified from float
# Wealth background
total_wealth_range: Literal["<1.5m", "1.5m-5m", "5m-10m", "10m-20m", "20m-50m", ">50m"]
origin_of_wealth: List[Literal["Employment", "Inheritance", "Business", "Investments", "Sale of real estate", "Retirement package", "Other"]]
total_wealth_range: Optional[str] = None # Simplified from Literal
# List types are often handled differently; simplifying to a single string might lose info.
# Keeping as Optional[str] based on request, but consider if Optional[List[str]] = None is better long-term.
origin_of_wealth: Optional[str] = None # Simplified from List[Literal]
inheritance_details: Optional[str] = None
# Assets
business_assets_eur: float = Field(..., ge=0)
business_assets_eur: Optional[str] = None # Simplified from float, removed Field constraint
# Income
estimated_annual_income: Literal["<250k", "250k-500k", "500k-1m", ">1m"]
income_country: str
estimated_annual_income: Optional[str] = None # Was already Optional[str]
income_country: Optional[str] = None
# Account preferences
commercial_account: bool
investment_risk_profile: Literal["Low", "Moderate", "Considerable", "High"]
mandate_type: Literal["Advisory", "Discretionary"]
investment_experience: Literal["Inexperienced", "Experienced", "Expert"]
investment_horizon: Literal["Short", "Medium", "Long-Term"]
preferred_markets: List[str]
commercial_account: Optional[str] = None # Simplified from bool
investment_risk_profile: Optional[str] = None # Simplified from Literal
mandate_type: Optional[str] = None # Simplified from Literal
investment_experience: Optional[str] = None # Simplified from Literal
investment_horizon: Optional[str] = None # Simplified from Literal
# Keeping as Optional[str] based on request, but consider if Optional[List[str]] = None is better long-term.
preferred_markets: Optional[str] = None # Simplified from List[str]
# Assets under management
total_aum: float
aum_to_transfer: float
total_aum: Optional[str] = None # Simplified from float
aum_to_transfer: Optional[str] = None # Simplified from float