Spaces:
Sleeping
Sleeping
| """ | |
| Deterministic DYNAMICS-to-behaviour mappings. | |
| These rules derive observable behavioural attributes from the eight DYNAMICS | |
| personality dimensions. All mappings are grounded in published validation | |
| literature (Ashton & Lee, 2009; de Vries et al., 2009) and contain no | |
| proprietary content. | |
| Dimension key: | |
| D = Discipline, Y = Yielding, N = Novelty, A = Acuity, | |
| M = Mercuriality, I = Impulsivity, C = Candour, S = Sociability | |
| """ | |
| from __future__ import annotations | |
| def income_band(C: float, D: float) -> str: | |
| """Predict income band from Candour and Discipline. | |
| Empirical rationale: financial ethics (Candour) and self-regulation | |
| (Discipline) are the strongest DYNAMICS predictors of stable income | |
| (de Vries et al., 2009). | |
| """ | |
| score = C * 0.4 + D * 0.6 | |
| if score > 0.75: | |
| return "wealthy" | |
| if score > 0.55: | |
| return "high" | |
| if score > 0.35: | |
| return "mid" | |
| return "low" | |
| def financial_anxiety(M: float, balance: float = 0.0, income: float = 1.0) -> str: | |
| """Derive financial anxiety level from Mercuriality and balance-to-income ratio. | |
| Higher Mercuriality amplifies emotional reactivity to financial pressure. | |
| A low balance-to-income ratio compounds the effect. | |
| """ | |
| ratio = balance / income if income > 0 else 0.0 | |
| base = M * 0.7 + (1.0 - min(ratio, 1.0)) * 0.3 | |
| if base > 0.65: | |
| return "high" | |
| if base > 0.35: | |
| return "moderate" | |
| return "low" | |
| def risk_tolerance(M: float, I: float) -> str: | |
| """Derive risk tolerance from Mercuriality (inverse) and Impulsivity. | |
| High Mercuriality suppresses risk-taking (anxiety dampens speculation). | |
| High Impulsivity promotes it (spontaneous engagement with uncertainty). | |
| """ | |
| score = (1.0 - M) * 0.5 + I * 0.5 | |
| if score > 0.65: | |
| return "speculative" | |
| if score > 0.35: | |
| return "balanced" | |
| return "conservative" | |
| def spending_keywords(S: float, I: float, N: float) -> list[str]: | |
| """Generate spending-style keywords from Sociability, Impulsivity, and Novelty.""" | |
| keywords: list[str] = [] | |
| if I > 0.6: | |
| keywords.append("impulsive") | |
| elif I < 0.4: | |
| keywords.append("deliberate") | |
| if S > 0.6: | |
| keywords.append("social-buyer") | |
| if N > 0.6: | |
| keywords.append("novelty-seeking") | |
| elif N < 0.4: | |
| keywords.append("conservative") | |
| return keywords or ["balanced"] | |
| # --------------------------------------------------------------------------- | |
| # Income-band lookup tables | |
| # --------------------------------------------------------------------------- | |
| _INCOME_BAND_RANGES: dict[str, tuple[float, float]] = { | |
| "low": (1200.0, 1800.0), | |
| "mid": (2000.0, 3200.0), | |
| "high": (3500.0, 5500.0), | |
| "wealthy": (6000.0, 12000.0), | |
| } | |
| def default_income_for_band(band: str) -> float: | |
| """Return the midpoint monthly income for a given income band.""" | |
| lo, hi = _INCOME_BAND_RANGES.get(band, (2000.0, 3200.0)) | |
| return (lo + hi) / 2.0 | |
| def derive_attributes( | |
| dynamics: dict[str, float], | |
| balance: float | None = None, | |
| income: float | None = None, | |
| ) -> dict: | |
| """Compute all derived behavioural attributes from a DYNAMICS vector. | |
| Parameters | |
| ---------- | |
| dynamics : dict | |
| Keys D, Y, N, A, M, I, C, S with float values in [0, 1]. | |
| balance : float or None | |
| Current account balance in GBP. If None, defaults to half of | |
| the derived monthly income. | |
| income : float or None | |
| Monthly income in GBP. If None, derived from income band. | |
| Returns | |
| ------- | |
| dict with keys: income_band, monthly_income, current_balance, | |
| financial_anxiety, risk_tolerance, spending_keywords | |
| """ | |
| D = dynamics.get("D", 0.5) | |
| Y = dynamics.get("Y", 0.5) | |
| N = dynamics.get("N", 0.5) | |
| A = dynamics.get("A", 0.5) | |
| M = dynamics.get("M", 0.5) | |
| I = dynamics.get("I", 0.5) | |
| C = dynamics.get("C", 0.5) | |
| S = dynamics.get("S", 0.5) | |
| band = income_band(C, D) | |
| monthly = income if income is not None else default_income_for_band(band) | |
| bal = balance if balance is not None else monthly * 0.5 | |
| return { | |
| "income_band": band, | |
| "monthly_income": round(monthly, 2), | |
| "current_balance": round(bal, 2), | |
| "financial_anxiety": financial_anxiety(M, bal, monthly), | |
| "risk_tolerance": risk_tolerance(M, I), | |
| "spending_keywords": spending_keywords(S, I, N), | |
| } | |