File size: 5,239 Bytes
3c0e2ec 177b9af 3c0e2ec 177b9af 3c0e2ec 177b9af 3c0e2ec 177b9af 82c668e 177b9af 3c0e2ec 177b9af ac4e07f 3c0e2ec ac4e07f 3c0e2ec 82c668e 3c0e2ec 177b9af 3c0e2ec 82c668e 3c0e2ec ac4e07f 3c0e2ec 82c668e 3c0e2ec 177b9af 3c0e2ec ecf207c 3c0e2ec ecf207c 3c0e2ec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | from __future__ import annotations
from dataclasses import dataclass
from .assumptions import (
AGE_BAND_FACTORS,
ALCOHOL_FACTORS,
BASELINE,
CHILDREN_STATUS_FACTORS,
EDUCATION_FACTORS,
FUTURE_CHILDREN_FACTORS,
HEIGHT_FACTORS,
HOUSING_FACTORS,
INCOME_CURVE_POINTS_UAH,
LANGUAGE_FACTORS,
MILITARY_STATUS_FACTORS,
PETS_FACTORS,
REGION_FACTORS,
RELATIONSHIP_STATUS_FACTORS,
RELOCATION_FACTORS,
SMOKING_FACTORS,
TARGET_POPULATION_FACTORS,
)
@dataclass(frozen=True)
class Criteria:
base_population: int
target_population: str
age_min: int
age_max: int
region_scope: str
relationship_status: str
min_height_cm: int
income_min_uah: int
education_level: str
children_status: str
future_children: str
military_status: str
relocation: str
housing: str
smoking: str
alcohol: str
language: str
pets: str
@dataclass(frozen=True)
class PoolEstimate:
conservative: float
central: float
optimistic: float
def age_factor(age_min: int, age_max: int) -> float:
bands = {
"18-24": (18, 24),
"25-34": (25, 34),
"35-44": (35, 44),
"45-54": (45, 54),
"55-70": (55, 70),
}
selected = 0.0
for label, (band_min, band_max) in bands.items():
overlap_min = max(age_min, band_min)
overlap_max = min(age_max, band_max)
if overlap_min <= overlap_max:
band_width = band_max - band_min + 1
overlap_width = overlap_max - overlap_min + 1
selected += AGE_BAND_FACTORS[label] * (overlap_width / band_width)
return max(0.01, min(selected, 1.0))
def height_factor(min_height_cm: int) -> float:
thresholds = sorted(HEIGHT_FACTORS)
if min_height_cm <= thresholds[0]:
return HEIGHT_FACTORS[thresholds[0]]
if min_height_cm >= thresholds[-1]:
return HEIGHT_FACTORS[thresholds[-1]]
lower = max(threshold for threshold in thresholds if threshold <= min_height_cm)
upper = min(threshold for threshold in thresholds if threshold >= min_height_cm)
if lower == upper:
return HEIGHT_FACTORS[lower]
ratio = (min_height_cm - lower) / (upper - lower)
return HEIGHT_FACTORS[lower] + ratio * (HEIGHT_FACTORS[upper] - HEIGHT_FACTORS[lower])
def income_factor(income_min_uah: int) -> float:
points = sorted(INCOME_CURVE_POINTS_UAH)
if income_min_uah <= points[0][0]:
return points[0][1]
if income_min_uah >= points[-1][0]:
return points[-1][1]
lower = max(point for point in points if point[0] <= income_min_uah)
upper = min(point for point in points if point[0] >= income_min_uah)
if lower == upper:
return lower[1]
ratio = (income_min_uah - lower[0]) / (upper[0] - lower[0])
return lower[1] + ratio * (upper[1] - lower[1])
def model_factors(criteria: Criteria) -> list[tuple[str, float]]:
return [
("Target population", TARGET_POPULATION_FACTORS[criteria.target_population]),
("Age range", age_factor(criteria.age_min, criteria.age_max)),
("Region scope", REGION_FACTORS[criteria.region_scope]),
("Relationship status", RELATIONSHIP_STATUS_FACTORS[criteria.relationship_status]),
("Minimum height", height_factor(criteria.min_height_cm)),
("Minimum income", income_factor(criteria.income_min_uah)),
("Education filter", EDUCATION_FACTORS[criteria.education_level]),
("Children status", CHILDREN_STATUS_FACTORS[criteria.children_status]),
("Future children", FUTURE_CHILDREN_FACTORS[criteria.future_children]),
("Military status", MILITARY_STATUS_FACTORS[criteria.military_status]),
("Relocation", RELOCATION_FACTORS[criteria.relocation]),
("Housing", HOUSING_FACTORS[criteria.housing]),
("Smoking", SMOKING_FACTORS[criteria.smoking]),
("Alcohol", ALCOHOL_FACTORS[criteria.alcohol]),
("Language", LANGUAGE_FACTORS[criteria.language]),
("Pets", PETS_FACTORS[criteria.pets]),
]
def central_estimate(criteria: Criteria) -> float:
value = float(criteria.base_population)
for _, factor in model_factors(criteria):
value *= factor
return value
def estimate_pool(criteria: Criteria) -> PoolEstimate:
central = central_estimate(criteria)
return PoolEstimate(
conservative=central * BASELINE.uncertainty_low,
central=central,
optimistic=central * BASELINE.uncertainty_high,
)
def sensitivity_table(criteria: Criteria) -> list[dict[str, float | str]]:
remaining = float(criteria.base_population)
rows: list[dict[str, float | str]] = [
{
"factor": "Baseline",
"coefficient": 1.0,
"remaining": remaining,
"percent_of_baseline": 100.0,
}
]
for label, coefficient in model_factors(criteria):
remaining *= coefficient
rows.append(
{
"factor": label,
"coefficient": round(coefficient, 4),
"remaining": round(remaining, 2),
"percent_of_baseline": round((remaining / criteria.base_population) * 100, 6),
}
)
return rows
|