Download core/life_state.py from s-b3/LifeStack: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/spaces/s-b3/LifeStack/resolve/main/core/life_state.py
- Command line
-
hf download hf://spaces/s-b3/LifeStack/core/life_state.py
-
curl -L -o life_state.py https://huggingface.co/spaces/s-b3/LifeStack/resolve/main/core/life_state.py
10.6 kB
| from dataclasses import dataclass, field | |
| import copy | |
| # Cascade dampening factor — grounded in Starcke & Brand (2012) | |
| # Stress effects attenuate ~40% per cognitive/behavioral hop. | |
| # A disruption propagates at full strength to immediate neighbors, | |
| # 60% strength to second-order nodes, 36% to third-order, etc. | |
| CASCADE_DAMPENING_DEFAULT = 0.6 | |
| METRIC_FLOOR = 10.0 | |
| class CareerMetrics: | |
| satisfaction: float = 70.0 | |
| workload: float = 70.0 | |
| stability: float = 70.0 | |
| growth_trajectory: float = 70.0 | |
| class FinanceMetrics: | |
| liquidity: float = 70.0 | |
| debt_pressure: float = 70.0 | |
| monthly_runway: float = 70.0 | |
| long_term_health: float = 70.0 | |
| class RelationshipMetrics: | |
| romantic: float = 70.0 | |
| family: float = 70.0 | |
| social: float = 70.0 | |
| professional_network: float = 70.0 | |
| class PhysicalHealthMetrics: | |
| energy: float = 70.0 | |
| fitness: float = 70.0 | |
| sleep_quality: float = 70.0 | |
| nutrition: float = 70.0 | |
| class MentalWellbeingMetrics: | |
| stress_level: float = 70.0 | |
| clarity: float = 70.0 | |
| motivation: float = 70.0 | |
| emotional_stability: float = 70.0 | |
| class TimeMetrics: | |
| free_hours_per_week: float = 70.0 | |
| commute_burden: float = 70.0 | |
| admin_overhead: float = 70.0 | |
| class LifeMetrics: | |
| career: CareerMetrics = field(default_factory=CareerMetrics) | |
| finances: FinanceMetrics = field(default_factory=FinanceMetrics) | |
| relationships: RelationshipMetrics = field(default_factory=RelationshipMetrics) | |
| physical_health: PhysicalHealthMetrics = field(default_factory=PhysicalHealthMetrics) | |
| mental_wellbeing: MentalWellbeingMetrics = field(default_factory=MentalWellbeingMetrics) | |
| time: TimeMetrics = field(default_factory=TimeMetrics) | |
| def flatten(self) -> dict: | |
| """Returns a flat dictionary mapping 'domain.submetric' to value.""" | |
| flat = {} | |
| for domain_name in self.__dataclass_fields__: | |
| domain = getattr(self, domain_name) | |
| for sub_name in domain.__dataclass_fields__: | |
| flat[f"{domain_name}.{sub_name}"] = getattr(domain, sub_name) | |
| return flat | |
| class ResourceBudget: | |
| time_hours: float = 20.0 | |
| money_dollars: float = 500.0 | |
| energy_units: float = 100.0 | |
| def deduct(self, time: float = 0.0, money: float = 0.0, energy: float = 0.0) -> bool: | |
| """Returns False if any resource would go negative, otherwise deducts and returns True.""" | |
| if (self.time_hours < time or | |
| self.money_dollars < money or | |
| self.energy_units < energy): | |
| return False | |
| self.time_hours -= time | |
| self.money_dollars -= money | |
| self.energy_units = min(100.0, self.energy_units - energy) # cap at 100 | |
| return True | |
| class DependencyGraph: | |
| def __init__(self): | |
| # source_node -> [(target_node, weight)] | |
| self.edges = { | |
| "career.workload": [ | |
| ("mental_wellbeing.stress_level", 0.70), | |
| ("time.free_hours_per_week", -0.80) | |
| ], | |
| "finances.liquidity": [ | |
| ("mental_wellbeing.stress_level", -0.60), | |
| ("finances.monthly_runway", 0.90) | |
| ], | |
| "mental_wellbeing.stress_level": [ | |
| ("physical_health.sleep_quality", -0.55), | |
| ("mental_wellbeing.emotional_stability", -0.50), | |
| ("mental_wellbeing.motivation", -0.40), | |
| ("career.satisfaction", -0.35) | |
| ], | |
| "physical_health.sleep_quality": [ | |
| ("mental_wellbeing.clarity", 0.60), | |
| ("physical_health.energy", 0.50) | |
| ], | |
| "relationships.romantic": [ | |
| ("mental_wellbeing.emotional_stability", 0.50) | |
| ], | |
| "time.free_hours_per_week": [ | |
| ("relationships.social", 0.45), | |
| ("mental_wellbeing.stress_level", -0.30) | |
| ], | |
| "physical_health.energy": [ | |
| ("mental_wellbeing.motivation", 0.40), | |
| ("physical_health.fitness", 0.30) | |
| ], | |
| "career.satisfaction": [ | |
| ("mental_wellbeing.motivation", 0.50) | |
| ], | |
| "finances.debt_pressure": [ | |
| ("mental_wellbeing.stress_level", 0.65) | |
| ], | |
| "physical_health.nutrition": [ | |
| ("physical_health.energy", 0.35) | |
| ], | |
| "physical_health.fitness": [ | |
| ("physical_health.energy", 0.40) | |
| ], | |
| "time.commute_burden": [ | |
| ("physical_health.energy", -0.30), | |
| ("mental_wellbeing.stress_level", 0.25) | |
| ], | |
| "relationships.social": [ | |
| ("mental_wellbeing.emotional_stability", 0.30) | |
| ], | |
| "mental_wellbeing.clarity": [ | |
| ("career.growth_trajectory", 0.45) | |
| ], | |
| "finances.long_term_health": [ | |
| ("mental_wellbeing.stress_level", -0.40) | |
| ], | |
| "time.admin_overhead": [ | |
| ("mental_wellbeing.stress_level", 0.25) | |
| ], | |
| "career.stability": [ | |
| ("mental_wellbeing.stress_level", -0.35) | |
| ], | |
| "career.growth_trajectory": [ | |
| ("career.satisfaction", 0.40) | |
| ], | |
| "mental_wellbeing.motivation": [ | |
| ("career.growth_trajectory", 0.30) | |
| ], | |
| "relationships.professional_network": [ | |
| ("career.stability", 0.35) | |
| ] | |
| } | |
| def _get_val(self, metrics: LifeMetrics, path: str) -> float: | |
| if '.' not in path: | |
| return 0.0 | |
| domain, sub = path.split('.', 1) | |
| d = getattr(metrics, domain, None) | |
| return getattr(d, sub, 0.0) if d else 0.0 | |
| def _set_val(self, metrics: LifeMetrics, path: str, val: float, is_cascade: bool = False): | |
| if '.' not in path: | |
| return | |
| domain_name, sub_name = path.split('.', 1) | |
| domain = getattr(metrics, domain_name, None) | |
| if domain is None or not hasattr(domain, sub_name): | |
| return | |
| # Ensure values stay within bounds | |
| floor = METRIC_FLOOR if is_cascade else 0.0 | |
| clamped_val = max(floor, min(100.0, val)) | |
| setattr(domain, sub_name, clamped_val) | |
| def cascade(self, metrics: LifeMetrics, primary_disruption: dict, dampening: float = CASCADE_DAMPENING_DEFAULT, per_step_cascade_cap: int = 3) -> LifeMetrics: | |
| """Applies disruption and propagates effects through the dependency graph. | |
| The dampening factor (default 0.6) is grounded in three complementary | |
| research findings: | |
| 1. **Starcke & Brand (2012)** — Stress effects on decision-making | |
| attenuate approximately 40% per cognitive/behavioral hop. A workload | |
| spike directly raises stress at full magnitude, but the downstream | |
| effect on sleep quality is only ~60% of that, and the tertiary effect | |
| on mental clarity is ~36%. The 0.6 multiplier captures this empirical | |
| attenuation rate. | |
| 2. **General Systems Theory** — Perturbations in coupled systems lose | |
| energy as they propagate through interconnected nodes. Each transfer | |
| across an edge dissipates a fraction of the original signal, preventing | |
| unbounded cascades in finite systems. | |
| 3. **Empirical stress research** — Second-order life effects (e.g. | |
| work stress → poor sleep → relationship strain) are consistently | |
| reported as less severe than first-order effects in longitudinal | |
| psychological studies, supporting a sub-unity propagation coefficient. | |
| Args: | |
| metrics: Current LifeMetrics state. | |
| primary_disruption: Dict mapping 'domain.submetric' to delta float. | |
| dampening: Propagation decay per hop (default CASCADE_DAMPENING_DEFAULT = 0.6). | |
| per_step_cascade_cap: Max nodes allowed to be affected in one step. | |
| Returns: | |
| LifeMetrics: New state with disruption and cascade effects applied. | |
| """ | |
| new_metrics = copy.deepcopy(metrics) | |
| queue = [] | |
| for path, amount in primary_disruption.items(): | |
| if '.' not in path: # skip malformed keys from LLM | |
| continue | |
| old_val = self._get_val(new_metrics, path) | |
| self._set_val(new_metrics, path, old_val + amount, is_cascade=False) | |
| queue.append((path, amount)) | |
| cascaded_metrics = set() | |
| while queue: | |
| source_path, source_magnitude = queue.pop(0) | |
| if source_path in self.edges: | |
| for target_path, weight in self.edges[source_path]: | |
| if target_path not in cascaded_metrics and len(cascaded_metrics) >= per_step_cascade_cap: | |
| continue # Cap at max per_step_cascade_cap metrics affected | |
| impact = source_magnitude * weight * dampening | |
| if abs(impact) >= 0.05: | |
| old_target_val = self._get_val(new_metrics, target_path) | |
| self._set_val(new_metrics, target_path, old_target_val + impact, is_cascade=True) | |
| cascaded_metrics.add(target_path) | |
| queue.append((target_path, impact)) | |
| return new_metrics | |
| def main(): | |
| # Create LifeMetrics with default values (all at 70) | |
| metrics = LifeMetrics() | |
| # Create DependencyGraph | |
| graph = DependencyGraph() | |
| # Define test disruption | |
| disruption = { | |
| "career.workload": 30.0, | |
| "finances.liquidity": -40.0 | |
| } | |
| print("--- LIFE STACK INITIAL STATE (All defaults at 70) ---") | |
| before = metrics.flatten() | |
| for k, v in before.items(): | |
| print(f"{k:35} : {v:.2f}") | |
| # Run the cascade simulation | |
| after_metrics = graph.cascade(metrics, disruption) | |
| after = after_metrics.flatten() | |
| print("\n--- LIFE STACK AFTER DISRUPTION & CASCADE ---") | |
| print(f"Disruption Applied: {disruption}\n") | |
| for k in sorted(before.keys()): | |
| val_before = before[k] | |
| val_after = after[k] | |
| diff = val_after - val_before | |
| if abs(diff) > 0.001: | |
| status = f"-> {val_after:6.2f} ({'+' if diff > 0 else ''}{diff:6.2f}) [CHANGED]" | |
| else: | |
| status = f" {val_after:6.2f} ( unchanged )" | |
| print(f"{k:35} : {val_before:6.2f} {status}") | |
| if __name__ == "__main__": | |
| main() | |