TEZv commited on
Commit
177b9af
·
1 Parent(s): ac4e07f

Add expanded scenario filters

Browse files
Files changed (3) hide show
  1. app.py +130 -59
  2. src/assumptions.py +71 -0
  3. src/model_pool.py +27 -0
app.py CHANGED
@@ -39,64 +39,125 @@ st.info(
39
 
40
  with st.sidebar:
41
  st.header("Scenario")
42
- base_population_text = st.text_input(
43
- "Baseline population",
44
- value=format_count(BASELINE.total_reference_population),
45
- help=f"Reference population before filters, formatted with commas. Demo default: {format_count(BASELINE.total_reference_population)}.",
46
- )
47
- try:
48
- base_population = parse_count(base_population_text, BASELINE.total_reference_population)
49
- except ValueError:
50
- st.warning("Use digits with optional commas, for example 10,000,000.")
51
- base_population = BASELINE.total_reference_population
52
- if not 10_000 <= base_population <= 50_000_000:
53
- st.warning("Baseline population should stay between 10,000 and 50,000,000 for this demo.")
54
- base_population = max(10_000, min(base_population, 50_000_000))
55
-
56
- target_population = st.selectbox(
57
- "Target population",
58
- ["all_adults", "women", "men"],
59
- format_func=title_label,
60
- help="Applies a demo sex-share coefficient before the other filters. Women: 53%, Men: 47%, All adults: 100%.",
61
- )
62
- age_min, age_max = st.slider(
63
- "Age range",
64
- 18,
65
- 70,
66
- (28, 42),
67
- help="Narrows the pool by the selected age-band overlap.",
68
- )
69
- region_scope = st.selectbox(
70
- "Region scope",
71
- ["all_ukraine", "large_cities", "kyiv_region", "western_regions"],
72
- format_func=title_label,
73
- help="Applies the selected regional scope coefficient.",
74
- )
75
- relationship_status = st.selectbox(
76
- "Relationship status",
77
- ["any", "not_married", "single_or_divorced"],
78
- format_func=title_label,
79
- help="Demo availability proxy. Official marital status is not the same as real availability.",
80
- )
81
- min_height = st.slider(
82
- "Minimum height, cm",
83
- 150,
84
- 205,
85
- 175,
86
- help="Interpolates a demo height-distribution coefficient.",
87
- )
88
- income_level = st.selectbox(
89
- "Income threshold",
90
- ["any", "above_median", "top_25", "top_10"],
91
- format_func=title_label,
92
- help="Applies an estimated income threshold coefficient.",
93
- )
94
- education_level = st.selectbox(
95
- "Education filter",
96
- ["any", "higher_education", "graduate_plus"],
97
- format_func=title_label,
98
- help="Applies an estimated education-level coefficient.",
99
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  criteria = Criteria(
102
  base_population=base_population,
@@ -108,6 +169,15 @@ criteria = Criteria(
108
  min_height_cm=min_height,
109
  income_level=income_level,
110
  education_level=education_level,
 
 
 
 
 
 
 
 
 
111
  )
112
 
113
  estimate = estimate_pool(criteria)
@@ -149,5 +219,6 @@ for note in DATA_QUALITY_NOTES:
149
  st.subheader("Interpretation guardrails")
150
  st.write(
151
  "This model estimates a demographic scenario, not compatibility, attraction, safety, or relationship success. "
152
- "A stricter filter can make a pool smaller, but it does not define a person's real-life chances."
 
153
  )
 
39
 
40
  with st.sidebar:
41
  st.header("Scenario")
42
+ with st.expander("Core demographics", expanded=True):
43
+ base_population_text = st.text_input(
44
+ "Baseline population",
45
+ value=format_count(BASELINE.total_reference_population),
46
+ help=f"Reference population before filters, formatted with commas. Demo default: {format_count(BASELINE.total_reference_population)}.",
47
+ )
48
+ try:
49
+ base_population = parse_count(base_population_text, BASELINE.total_reference_population)
50
+ except ValueError:
51
+ st.warning("Use digits with optional commas, for example 10,000,000.")
52
+ base_population = BASELINE.total_reference_population
53
+ if not 10_000 <= base_population <= 50_000_000:
54
+ st.warning("Baseline population should stay between 10,000 and 50,000,000 for this demo.")
55
+ base_population = max(10_000, min(base_population, 50_000_000))
56
+
57
+ target_population = st.selectbox(
58
+ "Target population",
59
+ ["all_adults", "women", "men"],
60
+ format_func=title_label,
61
+ help="Applies a demo sex-share coefficient before the other filters. Women: 53%, Men: 47%, All adults: 100%.",
62
+ )
63
+ age_min, age_max = st.slider(
64
+ "Age range",
65
+ 18,
66
+ 70,
67
+ (28, 42),
68
+ help="Narrows the pool by the selected age-band overlap.",
69
+ )
70
+ region_scope = st.selectbox(
71
+ "Region scope",
72
+ ["all_ukraine", "large_cities", "kyiv_region", "western_regions"],
73
+ format_func=title_label,
74
+ help="Applies the selected regional scope coefficient.",
75
+ )
76
+ relationship_status = st.selectbox(
77
+ "Relationship status",
78
+ ["any", "not_married", "single_or_divorced"],
79
+ format_func=title_label,
80
+ help="Demo availability proxy. Official marital status is not the same as real availability.",
81
+ )
82
+ min_height = st.slider(
83
+ "Minimum height, cm",
84
+ 150,
85
+ 205,
86
+ 175,
87
+ help="Interpolates a demo height-distribution coefficient.",
88
+ )
89
+ income_level = st.selectbox(
90
+ "Income threshold",
91
+ ["any", "above_median", "top_25", "top_10"],
92
+ format_func=title_label,
93
+ help="Applies an estimated income threshold coefficient.",
94
+ )
95
+ education_level = st.selectbox(
96
+ "Education filter",
97
+ ["any", "higher_education", "graduate_plus"],
98
+ format_func=title_label,
99
+ help="Applies an estimated education-level coefficient.",
100
+ )
101
+
102
+ with st.expander("Family context"):
103
+ children_status = st.selectbox(
104
+ "Children status",
105
+ ["any", "no_children", "has_children", "co_parenting_ready"],
106
+ format_func=title_label,
107
+ help="Scenario preference around existing children. These are demo assumptions, not value judgments.",
108
+ )
109
+ future_children = st.selectbox(
110
+ "Future children",
111
+ ["any", "wants_children", "does_not_want_children", "open_or_undecided"],
112
+ format_func=title_label,
113
+ help="Scenario preference around future children.",
114
+ )
115
+
116
+ with st.expander("War and mobility"):
117
+ military_status = st.selectbox(
118
+ "Military status",
119
+ ["any", "civilian_or_not_serving", "active_service", "veteran_or_service_history"],
120
+ format_func=title_label,
121
+ help="War-related scenario filter. Active service and veteran/service-history shares are placeholders until sourced.",
122
+ )
123
+ relocation = st.selectbox(
124
+ "Relocation",
125
+ ["any", "same_city_only", "open_to_relocation", "remote_or_long_distance_ok"],
126
+ format_func=title_label,
127
+ help="Mobility and distance preference filter.",
128
+ )
129
+
130
+ with st.expander("Lifestyle and compatibility"):
131
+ housing = st.selectbox(
132
+ "Housing",
133
+ ["any", "independent_living", "own_or_stable_housing"],
134
+ format_func=title_label,
135
+ help="Scenario proxy for independent or stable living setup.",
136
+ )
137
+ smoking = st.selectbox(
138
+ "Smoking",
139
+ ["any", "non_smoker", "ok_with_smoking"],
140
+ format_func=title_label,
141
+ help="Lifestyle preference around smoking.",
142
+ )
143
+ alcohol = st.selectbox(
144
+ "Alcohol",
145
+ ["any", "rare_or_none", "moderate_ok"],
146
+ format_func=title_label,
147
+ help="Lifestyle preference around alcohol use.",
148
+ )
149
+ language = st.selectbox(
150
+ "Language comfort",
151
+ ["any", "ukrainian_comfortable", "english_comfortable", "ukrainian_and_english"],
152
+ format_func=title_label,
153
+ help="Communication comfort filter.",
154
+ )
155
+ pets = st.selectbox(
156
+ "Pets",
157
+ ["any", "pet_friendly", "no_pets_preferred"],
158
+ format_func=title_label,
159
+ help="Household compatibility preference around pets.",
160
+ )
161
 
162
  criteria = Criteria(
163
  base_population=base_population,
 
169
  min_height_cm=min_height,
170
  income_level=income_level,
171
  education_level=education_level,
172
+ children_status=children_status,
173
+ future_children=future_children,
174
+ military_status=military_status,
175
+ relocation=relocation,
176
+ housing=housing,
177
+ smoking=smoking,
178
+ alcohol=alcohol,
179
+ language=language,
180
+ pets=pets,
181
  )
182
 
183
  estimate = estimate_pool(criteria)
 
219
  st.subheader("Interpretation guardrails")
220
  st.write(
221
  "This model estimates a demographic scenario, not compatibility, attraction, safety, or relationship success. "
222
+ "A stricter filter can make a pool smaller, but it does not define a person's real-life chances. "
223
+ "War, children, housing, and lifestyle filters are sensitive context variables; treat them as transparent assumptions."
224
  )
src/assumptions.py CHANGED
@@ -62,6 +62,65 @@ EDUCATION_FACTORS = {
62
  "graduate_plus": 0.16,
63
  }
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  DATA_QUALITY_NOTES = [
66
  {
67
  "label": "Population",
@@ -79,4 +138,16 @@ DATA_QUALITY_NOTES = [
79
  "label": "Height",
80
  "note": "Height currently requires proxy distribution unless a Ukraine-specific source is validated.",
81
  },
 
 
 
 
 
 
 
 
 
 
 
 
82
  ]
 
62
  "graduate_plus": 0.16,
63
  }
64
 
65
+ CHILDREN_STATUS_FACTORS = {
66
+ "any": 1.0,
67
+ "no_children": 0.62,
68
+ "has_children": 0.31,
69
+ "co_parenting_ready": 0.18,
70
+ }
71
+
72
+ FUTURE_CHILDREN_FACTORS = {
73
+ "any": 1.0,
74
+ "wants_children": 0.48,
75
+ "does_not_want_children": 0.22,
76
+ "open_or_undecided": 0.58,
77
+ }
78
+
79
+ MILITARY_STATUS_FACTORS = {
80
+ "any": 1.0,
81
+ "civilian_or_not_serving": 0.91,
82
+ "active_service": 0.07,
83
+ "veteran_or_service_history": 0.15,
84
+ }
85
+
86
+ RELOCATION_FACTORS = {
87
+ "any": 1.0,
88
+ "same_city_only": 0.22,
89
+ "open_to_relocation": 0.36,
90
+ "remote_or_long_distance_ok": 0.44,
91
+ }
92
+
93
+ HOUSING_FACTORS = {
94
+ "any": 1.0,
95
+ "independent_living": 0.48,
96
+ "own_or_stable_housing": 0.29,
97
+ }
98
+
99
+ SMOKING_FACTORS = {
100
+ "any": 1.0,
101
+ "non_smoker": 0.72,
102
+ "ok_with_smoking": 1.0,
103
+ }
104
+
105
+ ALCOHOL_FACTORS = {
106
+ "any": 1.0,
107
+ "rare_or_none": 0.46,
108
+ "moderate_ok": 0.76,
109
+ }
110
+
111
+ LANGUAGE_FACTORS = {
112
+ "any": 1.0,
113
+ "ukrainian_comfortable": 0.82,
114
+ "english_comfortable": 0.38,
115
+ "ukrainian_and_english": 0.31,
116
+ }
117
+
118
+ PETS_FACTORS = {
119
+ "any": 1.0,
120
+ "pet_friendly": 0.54,
121
+ "no_pets_preferred": 0.42,
122
+ }
123
+
124
  DATA_QUALITY_NOTES = [
125
  {
126
  "label": "Population",
 
138
  "label": "Height",
139
  "note": "Height currently requires proxy distribution unless a Ukraine-specific source is validated.",
140
  },
141
+ {
142
+ "label": "Military status",
143
+ "note": "War-related filters are sensitive, time-changing, and should stay scenario-only until sourced.",
144
+ },
145
+ {
146
+ "label": "Children",
147
+ "note": "Children and co-parenting filters are preference-context assumptions, not value judgments.",
148
+ },
149
+ {
150
+ "label": "Independence",
151
+ "note": "Multiplying many filters assumes independence; use results as a stress test, not a factual census.",
152
+ },
153
  ]
src/model_pool.py CHANGED
@@ -4,12 +4,21 @@ from dataclasses import dataclass
4
 
5
  from .assumptions import (
6
  AGE_BAND_FACTORS,
 
7
  BASELINE,
 
8
  EDUCATION_FACTORS,
 
9
  HEIGHT_FACTORS,
 
10
  INCOME_FACTORS,
 
 
 
11
  REGION_FACTORS,
12
  RELATIONSHIP_STATUS_FACTORS,
 
 
13
  TARGET_POPULATION_FACTORS,
14
  )
15
 
@@ -25,6 +34,15 @@ class Criteria:
25
  min_height_cm: int
26
  income_level: str
27
  education_level: str
 
 
 
 
 
 
 
 
 
28
 
29
 
30
  @dataclass(frozen=True)
@@ -78,6 +96,15 @@ def model_factors(criteria: Criteria) -> list[tuple[str, float]]:
78
  ("Minimum height", height_factor(criteria.min_height_cm)),
79
  ("Income threshold", INCOME_FACTORS[criteria.income_level]),
80
  ("Education filter", EDUCATION_FACTORS[criteria.education_level]),
 
 
 
 
 
 
 
 
 
81
  ]
82
 
83
 
 
4
 
5
  from .assumptions import (
6
  AGE_BAND_FACTORS,
7
+ ALCOHOL_FACTORS,
8
  BASELINE,
9
+ CHILDREN_STATUS_FACTORS,
10
  EDUCATION_FACTORS,
11
+ FUTURE_CHILDREN_FACTORS,
12
  HEIGHT_FACTORS,
13
+ HOUSING_FACTORS,
14
  INCOME_FACTORS,
15
+ LANGUAGE_FACTORS,
16
+ MILITARY_STATUS_FACTORS,
17
+ PETS_FACTORS,
18
  REGION_FACTORS,
19
  RELATIONSHIP_STATUS_FACTORS,
20
+ RELOCATION_FACTORS,
21
+ SMOKING_FACTORS,
22
  TARGET_POPULATION_FACTORS,
23
  )
24
 
 
34
  min_height_cm: int
35
  income_level: str
36
  education_level: str
37
+ children_status: str
38
+ future_children: str
39
+ military_status: str
40
+ relocation: str
41
+ housing: str
42
+ smoking: str
43
+ alcohol: str
44
+ language: str
45
+ pets: str
46
 
47
 
48
  @dataclass(frozen=True)
 
96
  ("Minimum height", height_factor(criteria.min_height_cm)),
97
  ("Income threshold", INCOME_FACTORS[criteria.income_level]),
98
  ("Education filter", EDUCATION_FACTORS[criteria.education_level]),
99
+ ("Children status", CHILDREN_STATUS_FACTORS[criteria.children_status]),
100
+ ("Future children", FUTURE_CHILDREN_FACTORS[criteria.future_children]),
101
+ ("Military status", MILITARY_STATUS_FACTORS[criteria.military_status]),
102
+ ("Relocation", RELOCATION_FACTORS[criteria.relocation]),
103
+ ("Housing", HOUSING_FACTORS[criteria.housing]),
104
+ ("Smoking", SMOKING_FACTORS[criteria.smoking]),
105
+ ("Alcohol", ALCOHOL_FACTORS[criteria.alcohol]),
106
+ ("Language", LANGUAGE_FACTORS[criteria.language]),
107
+ ("Pets", PETS_FACTORS[criteria.pets]),
108
  ]
109
 
110