Clarify baseline presets and extend income slider
Browse files- app.py +14 -8
- 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 |
-
|
| 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 |
-
"
|
| 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.
|
| 122 |
"Minimum monthly income, UAH",
|
| 123 |
-
|
| 124 |
-
max_value=INCOME_SLIDER_MAX_UAH,
|
| 125 |
value=30_000,
|
| 126 |
-
|
| 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 |
-
"
|
| 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
|
|
|
|
| 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
|
| 28 |
-
"value":
|
| 29 |
-
"note": "
|
| 30 |
},
|
| 31 |
}
|
| 32 |
|
|
@@ -36,7 +36,30 @@ SALARY_ANCHORS_UAH = {
|
|
| 36 |
"workua_current_average": 28_600,
|
| 37 |
}
|
| 38 |
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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",
|