TEZv commited on
Commit
363723c
·
1 Parent(s): 82c668e

Clarify baseline presets and extend income slider

Browse files
Files changed (2) hide show
  1. app.py +14 -8
  2. src/assumptions.py +41 -6
app.py CHANGED
@@ -8,7 +8,7 @@ from src.assumptions import (
8
  BASELINE,
9
  BASELINE_REFERENCE_OPTIONS,
10
  DATA_QUALITY_NOTES,
11
- INCOME_SLIDER_MAX_UAH,
12
  SALARY_ANCHORS_UAH,
13
  SOURCE_LINKS,
14
  )
@@ -30,6 +30,12 @@ def title_label(value: str) -> str:
30
  return value.replace("_", " ").title()
31
 
32
 
 
 
 
 
 
 
33
  def format_percent(value: int | float) -> str:
34
  if value == 0:
35
  return "0%"
@@ -74,7 +80,7 @@ with st.sidebar:
74
  value=format_count(preset["value"]),
75
  help=(
76
  f"{preset['note']} Formatted with commas. "
77
- "Example: 10,000,000 means a synthetic reference pool, not Ukraine's total population."
78
  ),
79
  )
80
  try:
@@ -118,17 +124,16 @@ with st.sidebar:
118
  175,
119
  help="Interpolates a demo height-distribution coefficient.",
120
  )
121
- income_min_uah = st.slider(
122
  "Minimum monthly income, UAH",
123
- min_value=0,
124
- max_value=INCOME_SLIDER_MAX_UAH,
125
  value=30_000,
126
- step=5_000,
127
  help=(
128
  "Scenario salary threshold. 0 means no income filter. Salary anchors: Work.ua current benchmark is about "
129
  f"{format_count(SALARY_ANCHORS_UAH['workua_current_average'])} UAH/month; "
130
  f"KSE cites Work.ua January 2026 median at {format_count(SALARY_ANCHORS_UAH['kse_workua_jan_2026_median'])} UAH/month. "
131
- "Very high thresholds such as 200,000 or 500,000 UAH/month use demo tail assumptions, not official percentiles."
132
  ),
133
  )
134
  st.caption(
@@ -271,7 +276,8 @@ st.write(
271
  )
272
  st.write(
273
  f"The income slider uses {format_count(SALARY_ANCHORS_UAH['workua_current_average'])} UAH/month as the current public job-market benchmark. "
274
- "High values such as 200,000 or 500,000 UAH/month are supported as scenario stress-test cutoffs, not official salary percentiles."
 
275
  )
276
 
277
  st.subheader("Data quality notes")
 
8
  BASELINE,
9
  BASELINE_REFERENCE_OPTIONS,
10
  DATA_QUALITY_NOTES,
11
+ INCOME_THRESHOLD_OPTIONS_UAH,
12
  SALARY_ANCHORS_UAH,
13
  SOURCE_LINKS,
14
  )
 
30
  return value.replace("_", " ").title()
31
 
32
 
33
+ def income_threshold_label(value: int) -> str:
34
+ if value == 0:
35
+ return "Any income"
36
+ return f"{format_count(value)} UAH"
37
+
38
+
39
  def format_percent(value: int | float) -> str:
40
  if value == 0:
41
  return "0%"
 
80
  value=format_count(preset["value"]),
81
  help=(
82
  f"{preset['note']} Formatted with commas. "
83
+ "Demo is a fixed synthetic example. Custom means you choose your own starting audience."
84
  ),
85
  )
86
  try:
 
124
  175,
125
  help="Interpolates a demo height-distribution coefficient.",
126
  )
127
+ income_min_uah = st.select_slider(
128
  "Minimum monthly income, UAH",
129
+ options=INCOME_THRESHOLD_OPTIONS_UAH,
 
130
  value=30_000,
131
+ format_func=income_threshold_label,
132
  help=(
133
  "Scenario salary threshold. 0 means no income filter. Salary anchors: Work.ua current benchmark is about "
134
  f"{format_count(SALARY_ANCHORS_UAH['workua_current_average'])} UAH/month; "
135
  f"KSE cites Work.ua January 2026 median at {format_count(SALARY_ANCHORS_UAH['kse_workua_jan_2026_median'])} UAH/month. "
136
+ "High thresholds up to 1,000,000 UAH/month are scenario stress-test cutoffs, not official maximum salary data."
137
  ),
138
  )
139
  st.caption(
 
276
  )
277
  st.write(
278
  f"The income slider uses {format_count(SALARY_ANCHORS_UAH['workua_current_average'])} UAH/month as the current public job-market benchmark. "
279
+ "High values such as 200,000, 500,000, or 1,000,000 UAH/month are supported as scenario stress-test cutoffs. "
280
+ "They are not official salary percentiles or a claimed real maximum."
281
  )
282
 
283
  st.subheader("Data quality notes")
src/assumptions.py CHANGED
@@ -14,7 +14,7 @@ BASELINE = BaselineAssumptions()
14
 
15
  BASELINE_REFERENCE_OPTIONS = {
16
  "demo_reference_pool": {
17
- "label": "Demo working pool",
18
  "value": 10_000_000,
19
  "note": "Synthetic starting universe for scenario testing; not the full Ukraine population.",
20
  },
@@ -24,9 +24,9 @@ BASELINE_REFERENCE_OPTIONS = {
24
  "note": "Official pre-full-scale-invasion total population estimate cited by ACAPS.",
25
  },
26
  "custom": {
27
- "label": "Custom baseline",
28
- "value": 10_000_000,
29
- "note": "Use when modeling a narrower adult, regional, platform, or pre-filtered pool.",
30
  },
31
  }
32
 
@@ -36,7 +36,30 @@ SALARY_ANCHORS_UAH = {
36
  "workua_current_average": 28_600,
37
  }
38
 
39
- INCOME_SLIDER_MAX_UAH = 500_000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  INCOME_CURVE_POINTS_UAH = [
42
  (0, 1.0),
@@ -47,6 +70,8 @@ INCOME_CURVE_POINTS_UAH = [
47
  (100_000, 0.055),
48
  (200_000, 0.015),
49
  (500_000, 0.002),
 
 
50
  ]
51
 
52
  SOURCE_LINKS = [
@@ -65,6 +90,16 @@ SOURCE_LINKS = [
65
  "url": "https://www.pfu.gov.ua/2170600-pokaznyk-serednoyi-zarobitnoyi-platy-za-2025-rik/",
66
  "note": "Official average wage indicator used for pension calculations; annual 2025 value is UAH 20,653.55.",
67
  },
 
 
 
 
 
 
 
 
 
 
68
  {
69
  "label": "ACAPS Ukraine population data sources report",
70
  "url": "https://www.acaps.org/fileadmin/Data_Product/Main_media/20230818_ACAPS_Thematic_report_Ukraine_estimates_and_sources_of_population_data.pdf",
@@ -190,7 +225,7 @@ DATA_QUALITY_NOTES = [
190
  },
191
  {
192
  "label": "Income",
193
- "note": "Income and salary filters are sensitive to self-employment and informal earnings.",
194
  },
195
  {
196
  "label": "Height",
 
14
 
15
  BASELINE_REFERENCE_OPTIONS = {
16
  "demo_reference_pool": {
17
+ "label": "Demo working pool (10M)",
18
  "value": 10_000_000,
19
  "note": "Synthetic starting universe for scenario testing; not the full Ukraine population.",
20
  },
 
24
  "note": "Official pre-full-scale-invasion total population estimate cited by ACAPS.",
25
  },
26
  "custom": {
27
+ "label": "Custom starting pool",
28
+ "value": 1_000_000,
29
+ "note": "Editable starting universe for a narrower adult, regional, platform, or pre-filtered pool.",
30
  },
31
  }
32
 
 
36
  "workua_current_average": 28_600,
37
  }
38
 
39
+ INCOME_THRESHOLD_OPTIONS_UAH = [
40
+ 0,
41
+ 20_000,
42
+ 25_000,
43
+ 30_000,
44
+ 35_000,
45
+ 40_000,
46
+ 45_000,
47
+ 50_000,
48
+ 60_000,
49
+ 70_000,
50
+ 80_000,
51
+ 90_000,
52
+ 100_000,
53
+ 125_000,
54
+ 150_000,
55
+ 200_000,
56
+ 250_000,
57
+ 300_000,
58
+ 400_000,
59
+ 500_000,
60
+ 750_000,
61
+ 1_000_000,
62
+ ]
63
 
64
  INCOME_CURVE_POINTS_UAH = [
65
  (0, 1.0),
 
70
  (100_000, 0.055),
71
  (200_000, 0.015),
72
  (500_000, 0.002),
73
+ (750_000, 0.001),
74
+ (1_000_000, 0.0005),
75
  ]
76
 
77
  SOURCE_LINKS = [
 
90
  "url": "https://www.pfu.gov.ua/2170600-pokaznyk-serednoyi-zarobitnoyi-platy-za-2025-rik/",
91
  "note": "Official average wage indicator used for pension calculations; annual 2025 value is UAH 20,653.55.",
92
  },
93
+ {
94
+ "label": "DOU developer salary statistics",
95
+ "url": "https://jobs.dou.ua/salaries/?switch_lang=en",
96
+ "note": "IT salary survey and CSV-backed salary analytics for Ukrainian tech roles.",
97
+ },
98
+ {
99
+ "label": "Djinni salary guide",
100
+ "url": "https://guide.djinni.co/salaries",
101
+ "note": "Tech salary benchmarks for Ukraine-focused hiring; useful for high-income scenario context.",
102
+ },
103
  {
104
  "label": "ACAPS Ukraine population data sources report",
105
  "url": "https://www.acaps.org/fileadmin/Data_Product/Main_media/20230818_ACAPS_Thematic_report_Ukraine_estimates_and_sources_of_population_data.pdf",
 
225
  },
226
  {
227
  "label": "Income",
228
+ "note": "Income thresholds above public medians are scenario cutoffs. Open sources do not provide a universal real maximum salary.",
229
  },
230
  {
231
  "label": "Height",