feat: migrate advisor investment goals

This commit is contained in:
Windows
2026-09-11 13:11:58 +08:00
parent acb9175e31
commit 60c4a49b9b
14 changed files with 903 additions and 27 deletions
+75
View File
@@ -0,0 +1,75 @@
"""Contracts for structured investment-goal collection."""
from decimal import Decimal
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
LiquidityRequirement = Literal[
"daily",
"within_7_days",
"within_30_days",
"over_30_days",
]
class InvestmentGoalCreate(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
customer_id: int | None = Field(default=None, gt=0)
annualized_return_lower_pct: Decimal = Field(ge=0, le=100, max_digits=7, decimal_places=4)
annualized_return_upper_pct: Decimal = Field(ge=0, le=100, max_digits=7, decimal_places=4)
max_drawdown_pct: Decimal = Field(ge=0, le=100, max_digits=7, decimal_places=4)
liquidity_requirement: LiquidityRequirement
investment_horizon_months: int = Field(ge=1, le=600)
benchmark_name: str = Field(min_length=1, max_length=128)
notes: str | None = Field(default=None, max_length=1000)
@field_validator("benchmark_name", "notes")
@classmethod
def normalize_text(cls, value: str | None) -> str | None:
if value is None:
return None
normalized = value.strip()
if not normalized:
raise ValueError("text must not be blank")
if any(ord(char) < 32 and char not in "\n\t" for char in normalized):
raise ValueError("text must not contain control characters")
return normalized
@model_validator(mode="after")
def validate_return_range(self) -> "InvestmentGoalCreate":
if self.annualized_return_lower_pct > self.annualized_return_upper_pct:
raise ValueError("annualized return lower bound must not exceed upper bound")
return self
class InvestmentGoalConfirmation(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
confirmed: Literal[True]
class InvestmentGoalBookReview(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
decision: Literal["approved", "rejected"]
comment: str | None = Field(default=None, max_length=1000)
@field_validator("comment")
@classmethod
def normalize_comment(cls, value: str | None) -> str | None:
if value is None:
return None
normalized = value.strip()
return normalized or None
class InvestmentGoalBookPublish(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
publish: Literal[True]
class InvestmentGoalQuery(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)