fix(portfolio): default new workspaces to 100 oz
This commit is contained in:
@@ -9,6 +9,12 @@ from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
_DEFAULT_GOLD_VALUE = 215_000.0
|
||||
_DEFAULT_ENTRY_PRICE = 2_150.0
|
||||
_LEGACY_DEFAULT_ENTRY_PRICE = 215.0
|
||||
_DEFAULT_GOLD_OUNCES = 100.0
|
||||
_LEGACY_DEFAULT_GOLD_OUNCES = 1_000.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LombardPortfolio:
|
||||
@@ -79,7 +85,7 @@ class PortfolioConfig:
|
||||
"""
|
||||
|
||||
gold_value: float | None = None
|
||||
entry_price: float | None = 215.0
|
||||
entry_price: float | None = _DEFAULT_ENTRY_PRICE
|
||||
gold_ounces: float | None = None
|
||||
entry_basis_mode: str = "value_price"
|
||||
loan_amount: float = 145000.0
|
||||
@@ -116,7 +122,7 @@ class PortfolioConfig:
|
||||
raise ValueError("Gold weight must be positive")
|
||||
|
||||
if self.gold_value is None and self.gold_ounces is None:
|
||||
self.gold_value = 215000.0
|
||||
self.gold_value = _DEFAULT_GOLD_VALUE
|
||||
self.gold_ounces = self.gold_value / self.entry_price
|
||||
return
|
||||
|
||||
@@ -322,7 +328,9 @@ class PortfolioRepository:
|
||||
if not isinstance(portfolio, dict):
|
||||
raise TypeError("portfolio payload must be an object")
|
||||
cls._validate_portfolio_fields(portfolio)
|
||||
return PortfolioConfig.from_dict(cls._deserialize_portfolio_payload(portfolio))
|
||||
deserialized = cls._deserialize_portfolio_payload(portfolio)
|
||||
upgraded = cls._upgrade_legacy_default_workspace(deserialized)
|
||||
return PortfolioConfig.from_dict(upgraded)
|
||||
|
||||
@classmethod
|
||||
def _validate_portfolio_fields(cls, payload: dict[str, Any]) -> None:
|
||||
@@ -341,6 +349,39 @@ class PortfolioRepository:
|
||||
def _deserialize_portfolio_payload(cls, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
return {key: cls._deserialize_value(key, value) for key, value in payload.items()}
|
||||
|
||||
@classmethod
|
||||
def _upgrade_legacy_default_workspace(cls, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
if not cls._looks_like_legacy_default_workspace(payload):
|
||||
return payload
|
||||
upgraded = dict(payload)
|
||||
upgraded["entry_price"] = _DEFAULT_ENTRY_PRICE
|
||||
upgraded["gold_ounces"] = _DEFAULT_GOLD_OUNCES
|
||||
upgraded["gold_value"] = _DEFAULT_GOLD_VALUE
|
||||
return upgraded
|
||||
|
||||
@staticmethod
|
||||
def _looks_like_legacy_default_workspace(payload: dict[str, Any]) -> bool:
|
||||
def _close(key: str, expected: float) -> bool:
|
||||
value = payload.get(key)
|
||||
return isinstance(value, (int, float)) and abs(float(value) - expected) <= 1e-9
|
||||
|
||||
return (
|
||||
_close("gold_value", _DEFAULT_GOLD_VALUE)
|
||||
and _close("entry_price", _LEGACY_DEFAULT_ENTRY_PRICE)
|
||||
and _close("gold_ounces", _LEGACY_DEFAULT_GOLD_OUNCES)
|
||||
and payload.get("entry_basis_mode") == "value_price"
|
||||
and _close("loan_amount", 145_000.0)
|
||||
and _close("margin_threshold", 0.75)
|
||||
and _close("monthly_budget", 8_000.0)
|
||||
and _close("ltv_warning", 0.70)
|
||||
and payload.get("primary_source") == "yfinance"
|
||||
and payload.get("fallback_source") == "yfinance"
|
||||
and payload.get("refresh_interval") == 5
|
||||
and _close("volatility_spike", 0.25)
|
||||
and _close("spot_drawdown", 7.5)
|
||||
and payload.get("email_alerts") is False
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _serialize_value(cls, key: str, value: Any) -> Any:
|
||||
if key in cls._MONEY_FIELDS:
|
||||
|
||||
Reference in New Issue
Block a user