diff --git a/transmonee_dashboard/src/transmonee_dashboard/index.py b/transmonee_dashboard/src/transmonee_dashboard/index.py
index 7f1871a7..9eb010d1 100755
--- a/transmonee_dashboard/src/transmonee_dashboard/index.py
+++ b/transmonee_dashboard/src/transmonee_dashboard/index.py
@@ -3,48 +3,32 @@
from .app import app
from .utils import DashRouter, DashNavBar
from .pages import (
- child_education,
- child_protection,
- child_health,
- child_poverty,
- child_rights,
- child_participation,
home,
- overview,
- resources,
- country_profiles,
- data_query,
- adolescent,
- disability,
- gender,
- ecd,
- risks,
- climate,
+ child_education
)
from .components import fa
-
# Ordered iterable of routes: tuples of (route, layout), where 'route' is a
# string corresponding to path of the route (will be prefixed with Dash's
# 'routes_pathname_prefix' and 'layout' is a Dash Component.
urls = (
("", home.get_layout),
- ("overview", overview.get_layout),
+ # ("overview", overview.get_layout),
("child-education", child_education.get_layout),
- ("child-protection", child_protection.get_layout),
- ("child-health", child_health.get_layout),
- ("child-poverty", child_poverty.get_layout),
- ("child-rights", child_rights.get_layout),
- ("child-participation", child_participation.get_layout),
- ("profiles", country_profiles.get_layout),
- ("data_query", data_query.get_layout),
- ("resources", resources.get_layout),
- ("adolescent", adolescent.get_layout),
- ("disability", disability.get_layout),
- ("gender", gender.get_layout),
- ("ecd", ecd.get_layout),
- ("risks", risks.get_layout),
- ("climate", climate.get_layout),
+ # ("child-protection", child_protection.get_layout),
+ # ("child-health", child_health.get_layout),
+ # ("child-poverty", child_poverty.get_layout),
+ # ("child-rights", child_rights.get_layout),
+ # ("child-participation", child_participation.get_layout),
+ # ("profiles", country_profiles.get_layout),
+ # ("data_query", data_query.get_layout),
+ # ("resources", resources.get_layout),
+ # ("adolescent", adolescent.get_layout),
+ # ("disability", disability.get_layout),
+ # ("gender", gender.get_layout),
+ # ("ecd", ecd.get_layout),
+ # ("risks", risks.get_layout),
+ # ("climate", climate.get_layout),
)
# Ordered iterable of navbar items: tuples of `(route, display)`, where `route`
@@ -53,57 +37,64 @@
# keyword argument for a Dash component (ie a Dash Component or a string).
nav_items = (
("", html.Div([fa("fas fa-home"), "Home"]), []),
- ("overview", html.Div([fa("fas fa-info-circle"), "Overview"]), []),
(
"sectors",
- "Domains",
- [
- ("child-education", html.Div([fa("fas fa-book"), "Education"])),
- (
- "child-protection",
- html.Div([fa("fas fa-child"), "Family Environment and Protection"]),
- ),
- (
- "child-health",
- html.Div([fa("fas fa-heartbeat"), "Health and Nutrition"]),
- ),
- (
- "child-poverty",
- html.Div([fa("fas fa-hand-holding-usd"), "Poverty"]),
- ),
- (
- "child-rights",
- html.Div(
- [
- fa("fas fa-balance-scale"),
- "Child Rights Landscape",
- ]
- ),
- ),
- (
- "child-participation",
- html.Div([fa("fas fa-users"), "Participation"]),
- ),
- ],
- ),
- (
- "cross-sectors",
- "Cross-Sectoral Issues",
- [
- ("adolescent", html.Div([fa("fas fa-user-friends"), "Adolescent"])),
- ("disability", html.Div([fa("fas fa-blind"), "Disability"])),
- ("gender", html.Div([fa("fas fa-venus-mars"), "Gender"])),
- ("ecd", html.Div([fa("fas fa-baby"), "ECD"])),
- (
- "risks",
- html.Div([fa("fas fa-exclamation-triangle"), "Risks and Humanitarian"]),
- ),
- ("climate", html.Div([fa("fas fa-sun"), "Climate Change"])),
- ],
- ),
- ("profiles", html.Div([fa("fas fa-globe"), "Country Snapshots"]), []),
- ("data_query", html.Div([fa("fas fa-table"), "Query Data"]), []),
- ("resources", html.Div([fa("fas fa-database"), "Resources"]), []),
+ "Domains", [
+ ("child-education", html.Div([fa("fas fa-book"), "Education"]))
+ ]
+ )
+
+ # ("overview", html.Div([fa("fas fa-info-circle"), "Overview"]), []),
+ # (
+ # "sectors",
+ # "Domains",
+ # [
+ # ("child-education", html.Div([fa("fas fa-book"), "Education"])),
+ # (
+ # "child-protection",
+ # html.Div([fa("fas fa-child"), "Family Environment and Protection"]),
+ # ),
+ # (
+ # "child-health",
+ # html.Div([fa("fas fa-heartbeat"), "Health and Nutrition"]),
+ # ),
+ # (
+ # "child-poverty",
+ # html.Div([fa("fas fa-hand-holding-usd"), "Poverty"]),
+ # ),
+ # (
+ # "child-rights",
+ # html.Div(
+ # [
+ # fa("fas fa-balance-scale"),
+ # "Child Rights Landscape",
+ # ]
+ # ),
+ # ),
+ # (
+ # "child-participation",
+ # html.Div([fa("fas fa-users"), "Participation"]),
+ # ),
+ # ],
+ # ),
+ # (
+ # "cross-sectors",
+ # "Cross-Sectoral Issues",
+ # [
+ # ("adolescent", html.Div([fa("fas fa-user-friends"), "Adolescent"])),
+ # ("disability", html.Div([fa("fas fa-blind"), "Disability"])),
+ # ("gender", html.Div([fa("fas fa-venus-mars"), "Gender"])),
+ # ("ecd", html.Div([fa("fas fa-baby"), "ECD"])),
+ # (
+ # "risks",
+ # html.Div([fa("fas fa-exclamation-triangle"), "Risks and Humanitarian"]),
+ # ),
+ # ("climate", html.Div([fa("fas fa-sun"), "Climate Change"])),
+ # ],
+ # ),
+ # ("profiles", html.Div([fa("fas fa-globe"), "Country Snapshots"]), []),
+ # ("data_query", html.Div([fa("fas fa-table"), "Query Data"]), []),
+ # ("resources", html.Div([fa("fas fa-database"), "Resources"]), []),
)
nav_items_full_names = {
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/__init__.py b/transmonee_dashboard/src/transmonee_dashboard/pages/__init__.py
old mode 100755
new mode 100644
index 46cd8726..2276412c
--- a/transmonee_dashboard/src/transmonee_dashboard/pages/__init__.py
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/__init__.py
@@ -1,1074 +1,25 @@
-import json
-import logging
-import pathlib
-import collections
-from io import BytesIO
-from re import L
-import urllib
-
import dash_html_components as html
-import numpy as np
+import datetime
+import collections
import pandas as pd
-import requests
-from requests.exceptions import HTTPError
-
-import pandasdmx as sdmx
-
-
-# TODO: Move all of these to env/setting vars from production
-sdmx_url = "https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/data/ECARO,TRANSMONEE,1.0/.{}....?format=csv&startPeriod={}&endPeriod={}"
-
-geo_json_file = (
- pathlib.Path(__file__).parent.parent.absolute() / "assets/countries.geo.json"
-)
-with open(geo_json_file) as shapes_file:
- geo_json_countries = json.load(shapes_file)
-
-with open(
- pathlib.Path(__file__).parent.parent.absolute() / "assets/indicator_config.json"
-) as config_file:
- indicators_config = json.load(config_file)
-
-unicef = sdmx.Request("UNICEF")
-
-metadata = unicef.dataflow("TRANSMONEE", provider="ECARO", version="1.0")
-dsd = metadata.structure["DSD_ECARO_TRANSMONEE"]
-
-indicator_names = {
- code.id: code.name.en
- for code in dsd.dimensions.get("INDICATOR").local_representation.enumerated
-}
-# lbassil: get the age groups code list as it is not in the DSD
-cl_age = unicef.codelist("CL_AGE", version="1.0")
-age_groups = sdmx.to_pandas(cl_age)
-dict_age_groups = age_groups["codelist"]["CL_AGE"].reset_index()
-age_groups_names = {
- age["CL_AGE"]: age["name"]
- for index, age in dict_age_groups.iterrows()
- if age["CL_AGE"] != "_T"
-}
-
-units_names = {
- unit.id: str(unit.name)
- for unit in dsd.attributes.get("UNIT_MEASURE").local_representation.enumerated
-}
-
-# lbassil: get the names of the residence dimensions
-residence_names = {
- residence.id: str(residence.name)
- for residence in dsd.dimensions.get("RESIDENCE").local_representation.enumerated
-}
-
-# lbassil: get the names of the wealth quintiles dimensions
-wealth_names = {
- wealth.id: str(wealth.name)
- for wealth in dsd.dimensions.get("WEALTH_QUINTILE").local_representation.enumerated
-}
-
-gender_names = {"F": "Female", "M": "Male", "_T": "Total"}
-
-dimension_names = {
- "SEX": "Sex_name",
- "AGE": "Age_name",
- "RESIDENCE": "Residence_name",
- "WEALTH_QUINTILE": "Wealth_name",
-}
-
-adolescent_codes = [
- "DM_ASYL_FRST",
- "DM_ASYL_UASC",
- "HT_ADOL_UNMETMED_FEAR",
- "HT_ADOL_UNMETMED_HOPING",
- "HT_ADOL_UNMETMED_NOKNOW",
- "HT_ADOL_UNMETMED_NOTIME",
- "HT_ADOL_UNMETMED_NOUNMET",
- "HT_ADOL_UNMETMED_TOOEFW",
- "HT_ADOL_UNMETMED_TOOEXP",
- "HT_ADOL_UNMETMED_TOOFAR",
- "HT_ADOL_UNMETMED_WAITING",
- "HT_ADOL_UNMETMED_OTH",
- "HT_CDRT_SELF_HARM",
- "HT_SH_HIV_INCD",
- "HVA_EPI_LHIV_0-19",
- "HVA_EPI_LHIV_15-24",
- "JJ_PRISIONERS_RT",
- "MNCH_CSEC",
- "MNCH_PNCMOM",
- "PT_ADLT_PS_NEC",
- "PT_CHLD_1-14_PS-PSY-V_CGVR",
- "PT_CHLD_5-17_LBR_ECON",
- "PT_CHLD_5-17_LBR_ECON-HC",
- "PT_CHLD_CARED_BY_FOSTER",
- "PT_CHLD_ENTEREDFOSTER",
- "PT_CHLD_INRESIDENTIAL",
- "PT_F_15-49_W-BTNG",
- "PT_F_GE15_PS-SX-EM_V_PTNR_12MNTH",
- "PT_M_15-49_W-BTNG",
- "PT_ST_13-15_BUL_30-DYS",
- "PV_AROPE",
- "PV_AROPRT",
- "PV_SD_MDP_MUHC",
- "PV_SEV_MAT_DPRT",
- "PV_SI_POV_EMP1",
-]
-
-adolescent_age_groups = [
- "_T",
- "Y14T17",
- "Y0T13",
- "Y1T4",
- "Y0",
- "Y0T14",
- "Y14T15",
- "Y16T17",
- "Y16T19",
- "Y16T24",
- "Y10T14",
- "Y15T19",
- "Y15T24",
- "Y10T19",
- "Y0T17",
- "Y15T49",
- "Y20T24",
- "Y18T49",
- "Y18T29",
- "Y0T24",
- "Y25T39",
- "Y40T59",
- "Y_GE50",
- "Y_GE15",
- "Y1T14",
- "Y2T14",
- "Y5T14",
- "Y5T17",
- "Y7T17",
- "Y10T17",
- "Y13T15",
- "Y15",
- "Y11T15",
- "Y12T17",
- "Y0T15",
- "Y0T4",
- "M0",
-]
-
-# Add all indicators found in the data dictionary to get their data to the query data page
-data_query_codes = [
- "DM_BRTS",
- # "DM_POP_TOT",
- # "DM_AVG_POP_TOT",
- # "DM_POP_PROP",
- "DM_DPR_AGE",
- "DM_DPR_CHD",
- "DM_DPR_OLD",
- # "DM_IMG",
- # "DM_EMG",
- # "DM_NEXTRT_MG",
- "DM_MRG_AGE",
- "DM_DIV",
- "DM_CRDIVRT",
- "DM_CHLD_DIV",
- "DM_CHLDRT_DIV",
- "DM_POP_TOT_AGE",
- "FT_WHS_PBR",
- "MT_SP_DYN_CDRT_IN",
- "DM_LIFE_EXP",
- "HT_SH_HAP_HBSAG",
- "HT_SH_TBS_INCD",
- "HT_SH_SUD_ALCOL",
- "HT_DIST79DTP3_P",
- "IM_MCV2",
- "HT_DIST79MCV2_P",
- "HT_SDG_PM25",
- "ECD_CHLD_36-59M_ADLT_SRC",
- "HT_SH_SUD_TREAT",
- "EDUNF_STU_L01_TOT",
- "EDU_SDG_GER_L01",
- "EDUNF_STU_L02_TOT",
- "EDUNF_FEP_L02",
- "EDUNF_NARA_L1_UNDER1",
- "EDUNF_FEP_L1",
- "EDUNF_FEP_L2",
- "EDUNF_FEP_L3",
- "EDUNF_STU_L3_GEN",
- "EDUNF_STU_L3_VOC",
- "EDUNF_STU_L3_GEN_PUB",
- "EDUNF_STU_L3_GEN_PRV",
- "EDUNF_STU_L3_VOC_PUB",
- "EDUNF_STU_L3_VOC_PRV",
- "EDUNF_GER_L1AND2",
- "EDU_TIMSS_MAT4",
- "EDU_TIMSS_SCI4",
- "EDU_TIMSS_MAT8",
- "EDU_TIMSS_SCI8",
- "EDU_PIRLS_REA",
- "EDUNF_REPP_L1",
- "EDUNF_REPP_L2",
- "EDUNF_FRP_L1",
- "EDUNF_SR_L1",
- "EDUNF_SR_L2",
- "EDUNF_STU_L4_TOT",
- "EDUNF_STU_L4_PUB",
- "EDUNF_STU_L4_PRV",
- "EDUNF_PRP_L4",
- "EDUNF_FEP_L4",
- "EDUNF_STU_L5T8_TOT",
- "EDUNF_STU_L5T8_PUB",
- "EDUNF_STU_L5T8_PRV",
- "EDUNF_PRP_L5T8",
- "EDUNF_FEP_L5T8",
- "EDUNF_GER_GPI_L02",
- "EDUNF_GER_GPI_L1",
- "EDUNF_GER_GPI_L2",
- "EDUNF_GER_GPI_L3",
- "EDUNF_GER_GPI_L2AND3",
- "EDUNF_PTR_L1",
- "EDUNF_PTR_L2",
- "EDUNF_PTR_L2AND3",
- "EDUNF_PTR_L3",
- "EDU_SDG_PTTR_L02",
- "EDU_SDG_PTTR_L1",
- "EDU_SDG_PTTR_L2",
- "EDU_SDG_PTTR_L3",
- "EDU_SDG_PQTR_L02",
- "EDU_SDG_PQTR_L1",
- "EDU_SDG_PQTR_L2",
- "EDU_SDG_PQTR_L3",
- "ECD_CHLD_U5_BKS-HM",
- "ECD_CHLD_U5_PLYTH-HM",
- "EDUNF_EA_L2T8",
- "EDU_PISA_MAT2",
- "EDU_PISA_MAT3",
- "EDU_PISA_MAT4",
- "EDU_PISA_MAT5",
- "EDU_PISA_MAT6",
- "EDU_PISA_REA2",
- "EDU_PISA_REA3",
- "EDU_PISA_REA4",
- "EDU_PISA_REA5",
- "EDU_PISA_REA6",
- "EDU_PISA_SCI2",
- "EDU_PISA_SCI3",
- "EDU_PISA_SCI4",
- "EDU_PISA_SCI5",
- "EDU_PISA_SCI6",
- "EDU_CHLD_DISAB_L02",
- "EDU_CHLD_DISAB_L1",
- "EDU_CHLD_DISAB_L2",
- "EDU_CHLD_DISAB_L3",
- "EDUNF_SAP_L02",
- "EDUNF_SAP_L1",
- "EDUNF_SAP_L2",
- "EDUNF_SAP_L3",
- "EDUNF_SAP_L2_GLAST",
- "EDUNF_FRP_L2AND3",
- "EDUNF_GER_GPI_L01",
- "EDUNF_OFST_L1T3",
- "EDUNF_PRP_L2AND3",
- "EDUNF_STU_L2AND3_PRV",
- "EDUNF_COMP_YR",
- "EDUNF_COMP_YR_L02T3",
- "EDUNF_PTR_L02",
- "EDUNF_GECER_L01",
- "EDUNF_GECER_L02",
- "ED_ANAR_L1",
- "ED_ANAR_L2",
- "ED_ANAR_L3",
- "EDU_SE_ACS_INTNT_L1",
- "EDU_SE_ACS_INTNT_L2",
- "EDU_SE_ACS_INTNT_L3",
- "EDU_SE_ACS_CMPTR_L1",
- "EDU_SE_ACS_CMPTR_L2",
- "EDU_SE_ACS_CMPTR_L3",
- "EDU_SE_ACS_ELECT_L1",
- "EDU_SE_ACS_ELECT_L2",
- "EDU_SE_ACS_ELECT_L3",
- "EDU_SE_TOT_GPI_L1_REA",
- "EDU_SE_TOT_GPI_L2_REA",
- "EDU_SE_TOT_GPI_L1_MAT",
- "EDU_SE_TOT_GPI_L2_MAT",
- "EDU_SE_TOT_GPI_FS_LIT",
- "EDU_SE_TOT_GPI_FS_NUM",
- "EDU_SE_AGP_CPRA_L1",
- "EDU_SE_AGP_CPRA_L2",
- "EDU_SE_AGP_CPRA_L3",
- "EDU_SE_GPI_PART",
- "EDU_SE_GPI_PTNPRE",
- "EDU_SE_GPI_TCAQ_L02",
- "EDU_SE_GPI_TCAQ_L1",
- "EDU_SE_GPI_TCAQ_L2",
- "EDU_SE_GPI_TCAQ_L3",
- "EDU_SE_NAP_ACHI_L1_REA",
- "EDU_SE_NAP_ACHI_L2_REA",
- "EDU_SE_NAP_ACHI_L1_MAT",
- "EDU_SE_NAP_ACHI_L2_MAT",
- "EDU_SE_IMP_FPOF_LIT",
- "EDU_SE_IMP_FPOF_NUM",
- "EDU_SE_LGP_ACHI_L1_REA",
- "EDU_SE_LGP_ACHI_L2_REA",
- "EDU_SE_LGP_ACHI_L1_MAT",
- "EDU_SE_LGP_ACHI_L2_MAT",
- "EDU_SE_ALP_CPLR_L1",
- "EDU_SE_ALP_CPLR_L2",
- "EDU_SE_ALP_CPLR_L3",
- "EDU_SE_TOT_SESPI_L1_REA",
- "EDU_SE_TOT_SESPI_L2_REA",
- "EDU_SE_TOT_SESPI_L1_MAT",
- "EDU_SE_TOT_SESPI_L2_MAT",
- "EDU_SE_TOT_SESPI_FS_LIT",
- "EDU_SE_TOT_SESPI_FS_NUM",
- "EDU_SE_TOT_RUPI_L1_REA",
- "EDU_SE_TOT_RUPI_L2_REA",
- "EDU_SE_TOT_RUPI_L1_MAT",
- "EDU_SE_TOT_RUPI_L2_MAT",
- "EDU_SE_AWP_CPRA_L1",
- "EDU_SE_AWP_CPRA_L2",
- "EDU_SE_AWP_CPRA_L3",
- "EDU_SE_GPI_ICTS_ATCH",
- "EDU_SE_GPI_ICTS_CPT",
- "EDU_SE_GPI_ICTS_CDV",
- "EDU_SE_GPI_ICTS_SSHT",
- "EDU_SE_GPI_ICTS_PRGM",
- "EDU_SE_GPI_ICTS_PST",
- "EDU_SE_GPI_ICTS_SFWR",
- "EDU_SE_GPI_ICTS_TRFF",
- "EDU_SE_GPI_ICTS_CMFL",
- "EDUNF_STEM_GRAD_RT",
- "DM_TOT_POP_PROSP",
- "DM_SP_POP_BRTH_MF",
- "DM_ADOL_YOUTH_POP",
- "DM_REPD_AGE_POP",
- "GN_MTNTY_LV_BNFTS",
- "GN_PTNTY_LV_BNFTS",
- "EC_GDI",
- "EC_HCI_OVRL",
- "EC_MIN_WAGE",
- "EC_IQ_CPA_GNDR_XQ",
- "EC_SIGI",
- "EC_YOUTH_UNE_RT",
- "EC_EAP_RT",
- "EC_GNI_PCAP_PPP",
- "EC_FB_BNK_ACCSS",
- "SL_DOM_TSPD",
- "SG_GEN_PARL",
- "CR_VC_VOV_GDSD",
- "PT_ADLS_10-14_LBR_HC",
- "ECD_CHLD_U5_LFT-ALN",
- "MNCH_MATERNAL_DEATHS",
- "MNCH_SH_MMR_RISK",
- "MNCH_SH_MMR_RISK_ZS",
- "MNCH_INSTDEL",
- "MNCH_BIRTH18",
- "HT_NCD_BMI_18A",
- "HVA_EPI_INF_ANN_15-24",
- "CR_CCRI_VUL_HT",
- "CR_CCRI_VUL_EDU",
- "CR_CCRI_VUL_WASH",
- "CR_CCRI_VUL_SP",
- "CR_CCRI_VUL_ES",
- "CR_CCRI",
- "CR_CCRI_EXP_WS",
- "CR_CCRI_EXP_RF",
- "CR_CCRI_EXP_CF",
- "CR_CCRI_EXP_TC",
- "CR_CCRI_EXP_VBD",
- "CR_CCRI_EXP_HEAT",
- "CR_CCRI_EXP_AP",
- "CR_CCRI_EXP_SWP",
- "CR_CCRI_EXP_CESS",
- "CR_UN_CHLD_RIGHTS",
- "CR_UN_CHLD_SALE",
- "CR_UN_RIGHTS_DISAB",
-]
-
-years = list(range(2010, 2022))
-
-# a key:value dictionary of countries where the 'key' is the country name as displayed in the selection
-# tree whereas the 'value' is the country name as returned by the sdmx list: https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/codelist/UNICEF/CL_COUNTRY/1.0
-countries_iso3_dict = {
- "Albania": "ALB",
- "Andorra": "AND",
- "Armenia": "ARM",
- "Austria": "AUT",
- "Azerbaijan": "AZE",
- "Belarus": "BLR",
- "Belgium": "BEL",
- "Bosnia and Herzegovina": "BIH",
- "Bulgaria": "BGR",
- "Croatia": "HRV",
- "Cyprus": "CYP",
- "Czech Republic": "CZE",
- "Denmark": "DNK",
- "Estonia": "EST",
- "Finland": "FIN",
- "France": "FRA",
- "Georgia": "GEO",
- "Germany": "DEU",
- "Greece": "GRC",
- "Holy See": "VAT",
- "Hungary": "HUN",
- "Iceland": "ISL",
- "Ireland": "IRL",
- "Italy": "ITA",
- "Kazakhstan": "KAZ",
- "Kosovo (UN SC resolution 1244)": "XKX", # UNDP defines it as KOS
- "Kyrgyzstan": "KGZ",
- "Latvia": "LVA",
- "Liechtenstein": "LIE",
- "Lithuania": "LTU",
- "Luxembourg": "LUX",
- "Malta": "MLT",
- "Monaco": "MCO",
- "Montenegro": "MNE",
- "Netherlands": "NLD",
- "North Macedonia": "MKD",
- "Norway": "NOR",
- "Poland": "POL",
- "Portugal": "PRT",
- "Republic of Moldova": "MDA",
- "Romania": "ROU",
- "Russian Federation": "RUS",
- "San Marino": "SMR",
- "Serbia": "SRB",
- "Slovakia": "SVK",
- "Slovenia": "SVN",
- "Spain": "ESP",
- "Sweden": "SWE",
- "Switzerland": "CHE",
- "Tajikistan": "TJK",
- "Turkey": "TUR",
- "Turkmenistan": "TKM",
- "Ukraine": "UKR",
- "United Kingdom": "GBR",
- "Uzbekistan": "UZB",
-}
-
-# create a list of country names in the same order as the countries_iso3_dict
-countries = list(countries_iso3_dict.keys())
-
-unicef_country_prog = [
- "Albania",
- "Armenia",
- "Azerbaijan",
- "Belarus",
- "Bosnia and Herzegovina",
- "Bulgaria",
- "Croatia",
- "Georgia",
- "Greece",
- "Kazakhstan",
- "Kosovo (UN SC resolution 1244)",
- "Kyrgyzstan",
- "Montenegro",
- "North Macedonia",
- "Republic of Moldova",
- "Romania",
- "Serbia",
- "Tajikistan",
- "Turkey",
- "Turkmenistan",
- "Ukraine",
- "Uzbekistan",
-]
-
-country_selections = [
- {
- "label": "Eastern Europe and Central Asia",
- "value": [
- {"label": "Caucasus", "value": ["Armenia", "Azerbaijan", "Georgia"]},
- {
- "label": "Western Balkans",
- "value": [
- "Albania",
- "Bosnia and Herzegovina",
- "Croatia",
- "Kosovo (UN SC resolution 1244)",
- "North Macedonia",
- "Montenegro",
- "Serbia",
- ],
- },
- {
- "label": "Central Asia",
- "value": [
- "Kazakhstan",
- "Kyrgyzstan",
- "Tajikistan",
- "Turkmenistan",
- "Uzbekistan",
- ],
- },
- {
- "label": "Eastern Europe",
- "value": [
- "Bulgaria",
- "Belarus",
- "Republic of Moldova",
- "Romania",
- "Russian Federation",
- "Turkey",
- "Ukraine",
- ],
- },
- ],
- },
- {
- "label": "Western Europe",
- "value": [
- "Andorra",
- "Austria",
- "Belgium",
- "Cyprus",
- "Czech Republic",
- "Denmark",
- "Estonia",
- "Finland",
- "France",
- "Germany",
- "Greece",
- "Holy See",
- "Hungary",
- "Iceland",
- "Ireland",
- "Italy",
- "Latvia",
- "Liechtenstein",
- "Lithuania",
- "Luxembourg",
- "Malta",
- "Monaco",
- "Netherlands",
- "Norway",
- "Poland",
- "Portugal",
- "San Marino",
- "Slovakia",
- "Slovenia",
- "Spain",
- "Sweden",
- "Switzerland",
- "United Kingdom",
- ],
- },
- {
- "label": "By EU Engagement",
- "value": [
- {
- "label": "Central Asia",
- "value": [
- "Kazakhstan",
- "Kyrgyzstan",
- "Tajikistan",
- "Turkmenistan",
- "Uzbekistan",
- ],
- },
- {
- "label": "Eastern Partnership",
- "value": [
- "Armenia",
- "Azerbaijan",
- "Belarus",
- "Georgia",
- "Republic of Moldova",
- "Ukraine",
- ],
- },
- {
- "label": "EFTA",
- "value": ["Iceland", "Liechtenstein", "Norway", "Switzerland"],
- },
- {
- "label": "EU Member States",
- "value": [
- "Andorra",
- "Austria",
- "Belgium",
- "Bulgaria",
- "Croatia",
- "Cyprus",
- "Czech Republic",
- "Denmark",
- "Estonia",
- "Finland",
- "France",
- "Germany",
- "Greece",
- "Hungary",
- "Ireland",
- "Italy",
- "Latvia",
- "Lithuania",
- "Luxembourg",
- "Malta",
- "Netherlands",
- "Poland",
- "Portugal",
- "Romania",
- "Slovakia",
- "Slovenia",
- "Spain",
- "Sweden",
- ],
- },
- {
- "label": "Other",
- "value": [
- "Andorra",
- "Monaco",
- "Holy See",
- "San Marino",
- ],
- },
- {
- "label": "Pre-accession countries",
- "value": [
- "Albania",
- "Bosnia and Herzegovina",
- "Kosovo (UN SC resolution 1244)",
- "North Macedonia",
- "Montenegro",
- "Serbia",
- "Turkey",
- ],
- },
- {
- "label": "Russian Federation",
- "value": ["Russian Federation"],
- },
- {
- "label": "United Kingdom (left EU on January 31, 2020)",
- "value": ["United Kingdom"],
- },
- ],
- },
-]
-
-data_sources = {
- "CDDEM": "CountDown 2030",
- "CCRI": "Children's Climate Risk Index",
- "UN Treaties": "UN Treaties",
- "ESTAT": "Euro Stat",
- "Helix": " Health Entrepreneurship and LIfestyle Xchange",
- "ILO": "International Labour Organization",
- "WHO": "World Health Organization",
- "Immunization Monitoring (WHO)": "Immunization Monitoring (WHO)",
- "WB": "World Bank",
- "OECD": "Organisation for Economic Co-operation and Development",
- "SDG": "Sustainable Development Goals",
- "UIS": "UNESCO Institute for Statistics",
- "UNDP": "United Nations Development Programme",
- "TMEE": "Transformative Monitoring for Enhanced Equity",
-}
-
-topics_subtopics = {
- "All": ["All"],
- "Education, Leisure, and Culture": [
- {"Participation": "Education access and participation"},
- {"Quality": "Learning quality and skills"},
- {"System": "Education system"},
- ],
- "Family Environment and Protection": [
- {"Violence": "Violence against Children and Women"},
- {"Care": "Children without parental care"},
- {"Justice": "Justice for Children"},
- {"Marriage": "Child marriage and other harmful practices"},
- {"Labour": "Child labour and other forms of exploitation"},
- ],
- "Health and Nutrition": [
- {"HS": "Health System"},
- {"MNCH": "Maternal, newborn and child health"},
- {"Immunization": "Immunization"},
- {"Nutrition": "Nutrition"},
- {"Adolescent": "Adolescent physical, mental, and reproductive health"},
- {"HIVAIDS": "HIV/AIDS"},
- {"Wash": "Water, sanitation and hygiene"},
- ],
- "Poverty and Social Protection": [
- {"Poverty": "Child Poverty and Material Deprivation"},
- {"Protection": "Social protection system"},
- ],
- "Child Rights Landscape and Governance": [
- {"Demography": "Demographics"},
- {"Economy": "Political Economy"},
- {"Migration": "Migration and Displacement"},
- {"Access": "Access to Justice"},
- {"Data": "Data on Children"},
- {"Spending": "Public spending on Children"},
- ],
- "Participation and Civil Rights": [
- {"Registration": "Birth registration and identity"},
- {"Information": "Information, Internet and Protection of privacy"},
- {"Leisure": "Education, Leisure, and Culture"},
- ],
-}
-
-dict_topics_subtopics = {
- "Education, Leisure, and Culture": [
- "Education access and participation",
- "Learning quality and skills",
- "Education System",
- ],
- "Family Environment and Protection": [
- "Violence against Children and Women",
- "Children without parental care",
- "Justice for Children",
- "Child marriage and other harmful practices",
- "Child labour and other forms of exploitation",
- ],
- "Health and Nutrition": [
- "Health System",
- "Maternal, newborn and child health",
- "Immunization",
- "Nutrition",
- "Adolescent physical, mental, and reproductive health",
- "HIV/AIDS",
- "Water, sanitation and hygiene",
- ],
- "Poverty and Social Protection": [
- "Child Poverty and Material Deprivation",
- "Social protection system",
- ],
- "Child Rights Landscape and Governance": [
- "Demographics",
- "Political Economy",
- "Migration and Displacement",
- "Access to Justice",
- "Data on Children",
- "Public spending on Children",
- ],
- "Participation and Civil Rights": [
- "Birth registration and identity",
- "Information, Internet and Protection of privacy",
- "Education, Leisure, and Culture",
- ],
-}
-
-
-def get_search_countries(add_all):
- all_countries = {"label": "All", "value": "All"}
- countries_list = [
- {
- "label": key,
- "value": countries_iso3_dict[key],
- }
- for key in countries_iso3_dict.keys()
- ]
- if add_all:
- countries_list.insert(0, all_countries)
- return countries_list
-
-
-def get_sector(subtopic):
- for key in dict_topics_subtopics.keys():
- if subtopic.strip() in dict_topics_subtopics.get(key):
- return key
- return ""
-
-
-# function to check if the config of a certain indicator are only about its dtype
-def only_dtype(config):
- return list(config.keys()) == ["DTYPE"]
-
-
-def get_filtered_dataset(
- indicators: list,
- years: list,
- country_codes: list,
- breakdown: str = "TOTAL", # send default breakdown as Total
- dimensions: dict = {},
- latest_data: bool = True,
-) -> pd.DataFrame:
-
- # TODO: This is temporary, need to move to config
- # Add all dimensions by default to the keys
- keys = {
- "REF_AREA": country_codes,
- "INDICATOR": indicators,
- "SEX": [],
- "AGE": [],
- "RESIDENCE": [],
- "WEALTH_QUINTILE": [],
- }
-
- # get the first indicator of the list... we have more than one indicator in the cards
- indicator_config = (
- indicators_config[indicators[0]] if indicators[0] in indicators_config else {}
- )
- # check if the indicator has special config, update the keys from the config
- if indicator_config and not only_dtype(indicator_config):
- # TODO: need to confirm that a TOTAL is always available when a config is available for the indicator
- card_keys = indicator_config[breakdown]
- if (
- dimensions
- ): # if we are sending cards related filters, update the keys with the set values
- card_keys.update(dimensions)
- keys.update(card_keys) # update the keys with the sent values
-
- try:
- data = unicef.data(
- "TRANSMONEE",
- provider="ECARO",
- key=keys,
- params=dict(
- startPeriod=years[0],
- endPeriod=years[-1],
- lastNObservations=1 if latest_data else 0,
- ),
- dsd=dsd,
- )
- logging.debug(f"URL: {data.response.url} CACHED: {data.response.from_cache}")
- except HTTPError as e:
- logging.exception(f"URL: {e.response}", e)
- # TODO: Maybe do something better here
- return pd.DataFrame()
-
- # lbassil: add sorting by Year to display the years in proper order on the x-axis
- dtype = (
- eval(indicator_config["DTYPE"]) if "DTYPE" in indicator_config else np.float64
- )
- data = (
- data.to_pandas(attributes="o", rtype="rows", dtype=dtype)
- .sort_values(by=["TIME_PERIOD"])
- .reset_index()
- )
- data.rename(columns={"value": "OBS_VALUE", "INDICATOR": "CODE"}, inplace=True)
- # replace Yes by 1 and No by 0
- data.OBS_VALUE.replace({"Yes": "1", "No": "0", "<": "", ">": ""}, inplace=True)
-
- # convert to numeric and round
- data["OBS_VALUE"] = pd.to_numeric(data.OBS_VALUE, errors="coerce")
- data.dropna(subset=["OBS_VALUE"], inplace=True)
- data = data.round({"OBS_VALUE": 2})
- # converting TIME_PERIOD to numeric: we should get integers by default
- data["TIME_PERIOD"] = pd.to_numeric(data.TIME_PERIOD)
-
- # lbassil: add the code to fill the country names
- countries_val_list = list(countries_iso3_dict.values())
-
- def create_labels(row):
- row["Country_name"] = countries[countries_val_list.index(row["REF_AREA"])]
- row["Unit_name"] = str(units_names.get(str(row["UNIT_MEASURE"]), ""))
- row["Sex_name"] = str(gender_names.get(str(row["SEX"]), ""))
- row["Residence_name"] = str(residence_names.get(str(row["RESIDENCE"]), ""))
- row["Wealth_name"] = str(wealth_names.get(str(row["WEALTH_QUINTILE"]), ""))
- row["Age_name"] = str(age_groups_names.get(str(row["AGE"]), ""))
- return row
-
- data = data.apply(create_labels, axis="columns")
-
- return data
-
-
-# create two dicts, one for display tree and one with the index of all possible selections
-selection_index = collections.OrderedDict({"0": countries})
-selection_tree = dict(title="Select All", key="0", children=[])
-for num1, group in enumerate(country_selections):
- parent = dict(title=group["label"], key=f"0-{num1}", children=[])
- group_countries = []
-
- for num2, region in enumerate(group["value"]):
- child_region = dict(
- title=region["label"] if "label" in region else region,
- key=f"0-{num1}-{num2}",
- children=[],
- )
- parent.get("children").append(child_region)
- if "value" in region:
- selection_index[f"0-{num1}-{num2}"] = (
- region["value"]
- if isinstance(region["value"], list)
- else [region["value"]]
- )
- for num3, country in enumerate(region["value"]):
- child_country = dict(title=country, key=f"0-{num1}-{num2}-{num3}")
- if len(region["value"]) > 1:
- # only create child nodes for more then one child
- child_region.get("children").append(child_country)
- selection_index[f"0-{num1}-{num2}-{num3}"] = [country]
- group_countries.append(country)
- else:
- selection_index[f"0-{num1}-{num2}"] = [region]
- group_countries.append(region)
-
- selection_index[f"0-{num1}"] = group_countries
- selection_tree.get("children").append(parent)
-
-programme_country_indexes = [
- next(
- key
- for key, value in selection_index.items()
- if value[0] == item and len(value) == 1
- )
- for item in unicef_country_prog
-]
-
-data = pd.DataFrame()
-data_query_inds = set(data_query_codes)
-
-# column data types coerced
-col_types = {
- "COVERAGE_TIME": str,
- "OBS_FOOTNOTE": str,
- "OBS_VALUE": str,
- "Frequency": str,
- "Unit multiplier": str,
- "OBS_STATUS": str,
- "Observation Status": str,
- "TIME_PERIOD": int,
-}
-
-# avoid a loop to query SDMX
-try:
- data_query_sdmx = pd.read_csv(
- sdmx_url.format("+".join(data_query_inds), years[0], years[-1]),
- dtype=col_types,
- storage_options={"Accept-Encoding": "gzip"},
- low_memory=False,
- )
-except urllib.error.HTTPError as e:
- raise e
-
-data = data.append(data_query_sdmx)
-# no need to create column CODE, just rename indicator
-data.rename(columns={"INDICATOR": "CODE"}, inplace=True)
-
-# replace Yes by 1 and No by 0
-data.OBS_VALUE.replace({"Yes": "1", "No": "0"}, inplace=True)
-
-
-# check and drop non-numeric observations, eg: SDMX accepts > 95 as an OBS_VALUE
-filter_non_num = pd.to_numeric(data.OBS_VALUE, errors="coerce").isnull()
-if filter_non_num.any():
- not_num_code_val = data[["CODE", "OBS_VALUE"]][filter_non_num]
- f"Non-numeric observations in {not_num_code_val.CODE.unique()}\ndiscarded: {not_num_code_val.OBS_VALUE.unique()}"
- data.drop(data[filter_non_num].index, inplace=True)
-
-# convert to numeric
-data["OBS_VALUE"] = pd.to_numeric(data.OBS_VALUE)
-data = data.round({"OBS_VALUE": 2})
-# print(data.shape)
-
-# TODO: calculations for children age population
-indicators = data["Indicator"].unique()
-
-# extract the indicators that have gender/sex disaggregation
-gender_indicators = data.groupby("CODE").agg({"SEX": "nunique"}).reset_index()
-# Keep only indicators with gender/sex disaggregation
-gender_indicators = gender_indicators[gender_indicators["SEX"] > 1]
-
-# path to excel data dictionary in repo
-github_url = "https://github.com/UNICEFECAR/data-etl/raw/proto_API/tmee/data_in/data_dictionary/indicator_dictionary_TM_v8.xlsx"
-data_dict_content = requests.get(github_url).content
-# Reading the downloaded content and turning it into a pandas dataframe and read Snapshot sheet from excel data-dictionary
-snapshot_df = pd.read_excel(BytesIO(data_dict_content), sheet_name="Snapshot")
-snapshot_df.dropna(subset=["Source_name"], inplace=True)
-snapshot_df["Source"] = snapshot_df["Source_name"].apply(lambda x: x.split(":")[0])
-# read indicators table from excel data-dictionary
-df_topics_subtopics = pd.read_excel(BytesIO(data_dict_content), sheet_name="Indicator")
-df_topics_subtopics.dropna(subset=["Issue"], inplace=True)
-df_sources = pd.merge(df_topics_subtopics, snapshot_df, how="outer", on=["Code"])
-# assign source = TMEE to all indicators without a source since they all come from excel data collection files
-df_sources.fillna("TMEE", inplace=True)
-# Concatenate sectors/subtopics dictionary value lists (mapping str lower)
-sitan_subtopics = list(map(str.lower, sum(dict_topics_subtopics.values(), [])))
+import urllib
+from ..sdmx_utils import utils
-df_sources.rename(
- columns={
- "Name_x": "Indicator",
- "Issue": "Subdomain",
- },
- inplace=True,
-)
-# filter the sources to keep only sitan related sectors and sub-topics
-df_sources["Subdomain"] = df_sources["Subdomain"].str.strip()
-df_sources["Domain"] = df_sources["Subdomain"].apply(
- lambda x: get_sector(x) if not pd.isna(x) else ""
-)
-df_sources["Source_Full"] = df_sources["Source"].apply(
- lambda x: data_sources[x] if not pd.isna(x) else ""
-)
+years = list(range(2010, datetime.datetime.now().year))
-df_sources = df_sources[df_sources["Subdomain"].str.lower().isin(sitan_subtopics)]
-# read source table from excel data-dictionary and merge
-source_table_df = pd.read_excel(BytesIO(data_dict_content), sheet_name="Source")
-df_sources = df_sources.merge(
- source_table_df[["Source_Id", "Source_Link"]],
- on="Source_Id",
- how="left",
- sort=False,
-)
-# assign source link for TMEE, url: UNICEF_RDM/indicator_code
-tmee_source_link = df_sources.Source_Link.isnull()
-unicef_rdm_url = "https://data.unicef.org/indicator-profile/{helix_code}/"
-df_sources.loc[tmee_source_link, "Source_Link"] = df_sources[
- tmee_source_link
-].Code.apply(lambda x: unicef_rdm_url.format(helix_code=x))
-df_sources_groups = df_sources.groupby("Source")
-df_sources_summary_groups = df_sources.groupby("Source_Full")
-# Extract the indicators' potential unique disaggregations.
-# Group by indicator code and keep only unique aggregations for the 4 possible dimensions:
-# Sex, Age, Residence and Wealth.
-indicators_disagg = (
- data.groupby("CODE")
- .agg(
- {
- "AGE": "nunique",
- "SEX": "nunique",
- "RESIDENCE": "nunique",
- "WEALTH_QUINTILE": "nunique",
- }
- )
- .reset_index()
-)
-# Filter the dimensions with count greater than 1 which means Total is there (default) in addition to other possible values.
-indicators_disagg_no_total = indicators_disagg[
- (indicators_disagg["AGE"] > 1)
- | (indicators_disagg["SEX"] > 1)
- | (indicators_disagg["RESIDENCE"] > 1)
- | (indicators_disagg["WEALTH_QUINTILE"] > 1)
-]
+sdmx_base_url = "https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/"
+sdmx_cl_url = sdmx_base_url + "codelist/{}/{}/{}/"
-# include the indicators with Total only to show in the data query
-indicators_disagg_with_total = indicators_disagg[
- (indicators_disagg["AGE"] >= 1)
- | (indicators_disagg["SEX"] >= 1)
- | (indicators_disagg["RESIDENCE"] >= 1)
- | (indicators_disagg["WEALTH_QUINTILE"] >= 1)
-]
-# Get the data for all the indicators having disaggregated data by any of the 4 dimensions.
-indicators_disagg_details = data[
- data["CODE"].isin(indicators_disagg_with_total["CODE"])
-]
+url_ref_areas = sdmx_cl_url.format("BRAZIL_CO", "CL_BRAZIL_REF_AREAS", "latest")
+cl_ref_areas = utils.get_codelist(sdmx_cl_url.format("BRAZIL_CO", "CL_BRAZIL_REF_AREAS", "latest"))
-# Filter the dataframe to be used in the data query to keep indicators code and the possible disaggregations.
-indicators_disagg_details = indicators_disagg_details[
- ["CODE", "Age", "Sex", "Residence", "Wealth Quintile"]
-]
-indicators_disagg_details = indicators_disagg_details.drop_duplicates()
-# extract the indicators that have gender/sex disaggregation
-age_indicators_counts = data.groupby("CODE").agg({"AGE": "nunique"}).reset_index()
-# Keep only indicators with gender/sex disaggregation
-age_indicators_counts = age_indicators_counts[age_indicators_counts["AGE"] > 1]
-# age_indicators_counts.to_csv("age_indicators_counts.csv", index=True)
-age_indicators = pd.merge(data, age_indicators_counts, on=["CODE"])
-age_indicators = age_indicators[["CODE", "Indicator", "Age"]]
-age_indicators = age_indicators.drop_duplicates()
-age_indicators = age_indicators.sort_values(by=["CODE", "Age"])
-# age_indicators.to_csv("age_indicators.csv", index=False)
+sel_ref_areas = [x["id"] for x in cl_ref_areas]
+selection_tree = dict(title="Select All", key="0", children=[{"title":x["name"], "key":x["id"]} for x in cl_ref_areas])
-# extract the indicators that have gender/sex disaggregation
-age_indicators_counts = data.groupby("CODE").agg({"AGE": "nunique"}).reset_index()
-# Keep only indicators with gender/sex disaggregation
-age_indicators_counts = age_indicators_counts[age_indicators_counts["AGE"] > 1]
-# age_indicators_counts.to_csv("age_indicators_counts.csv", index=True)
-age_indicators = pd.merge(data, age_indicators_counts, on=["CODE"])
-age_indicators = age_indicators[["CODE", "Indicator", "Age"]]
-age_indicators = age_indicators.drop_duplicates()
-age_indicators = age_indicators.sort_values(by=["CODE", "Age"])
-# age_indicators.to_csv("age_indicators.csv", index=False)
+# data = pd.DataFrame()
+# data_query_inds = set(["REPROV_EFFINAL_MUNICIPAL"])
def page_not_found(pathname):
- return html.P("No page '{}'".format(pathname))
+ return html.P("No page '{}'".format(pathname))
\ No newline at end of file
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/__init__OLD.py b/transmonee_dashboard/src/transmonee_dashboard/pages/__init__OLD.py
new file mode 100644
index 00000000..49bf0b40
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/__init__OLD.py
@@ -0,0 +1,123 @@
+import dash_html_components as html
+import datetime
+import collections
+import pandas as pd
+import urllib
+from ..sdmx_utils import utils
+
+years = list(range(2010, datetime.datetime.now().year))
+
+sdmx_base_url = "https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/"
+sdmx_cl_url = sdmx_base_url + "codelist/{}/{}/{}/"
+
+url_countries = sdmx_cl_url.format("BRAZIL_CO", "CL_BRAZIL_REF_AREAS", "latest")
+cl_countries = utils.get_codelist(sdmx_cl_url.format("BRAZIL_CO", "CL_BRAZIL_REF_AREAS", "latest"))
+
+# do we need this? Can we reshape the dictionary in get_search_countries?
+countries_iso3_dict = {c["name"]: c["id"] for c in cl_countries}
+
+
+def get_search_countries(add_all):
+ all_countries = {"label": "All", "value": "All"}
+ countries_list = [
+ {
+ "label": key,
+ "value": countries_iso3_dict[key],
+ }
+ for key in countries_iso3_dict.keys()
+ ]
+ if add_all:
+ countries_list.insert(0, all_countries)
+ return countries_list
+
+
+# create a list of country names in the same order as the countries_iso3_dict
+countries = list(countries_iso3_dict.keys())
+
+# Hierarchical cl?
+# country_selections = [
+# {
+# "label": "Eastern Europe and Central Asia",
+# "value": [x["name"] for x in cl_countries]
+# }
+# ]
+
+# print(country_selections)
+
+selection_index = collections.OrderedDict({"0": countries})
+selection_tree = dict(title="Select All", key="0", children=[{"title":x["name"], "key":x["id"]} for x in cl_countries])
+
+# {"title": "Subchild1", "key": "0-0-1"},
+
+#max 3 levels? Can we make it recursive?
+# for num1, group in enumerate(country_selections):
+# parent = dict(title=group["label"], key=f"0-{num1}", children=[])
+# group_countries = []
+#
+# for num2, region in enumerate(group["value"]):
+# child_region = dict(
+# title=region["label"] if "label" in region else region,
+# key=f"0-{num1}-{num2}",
+# children=[],
+# )
+# parent.get("children").append(child_region)
+# if "value" in region:
+# selection_index[f"0-{num1}-{num2}"] = (
+# region["value"]
+# if isinstance(region["value"], list)
+# else [region["value"]]
+# )
+# for num3, country in enumerate(region["value"]):
+# child_country = dict(title=country, key=f"0-{num1}-{num2}-{num3}")
+# if len(region["value"]) > 1:
+# # only create child nodes for more then one child
+# child_region.get("children").append(child_country)
+# selection_index[f"0-{num1}-{num2}-{num3}"] = [country]
+# group_countries.append(country)
+# else:
+# selection_index[f"0-{num1}-{num2}"] = [region]
+# group_countries.append(region)
+#
+# selection_index[f"0-{num1}"] = group_countries
+# selection_tree.get("children").append(parent)
+
+data = pd.DataFrame()
+data_query_inds = set(["REPROV_EFFINAL_MUNICIPAL"])
+
+# column data types coerced
+'''
+col_types = {
+ "COVERAGE_TIME": str,
+ "OBS_FOOTNOTE": str,
+ "OBS_VALUE": str,
+ "Frequency": str,
+ "Unit multiplier": str,
+ "OBS_STATUS": str,
+ "Observation Status": str,
+ "TIME_PERIOD": int,
+}
+'''
+
+# avoid a loop to query SDMX
+sdmx_url = "https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/data/BRAZIL_CO,BRAZIL_CO,1.0/.{}..?format=csv&startPeriod={}&endPeriod={}"
+to_call = sdmx_url.format("+".join(data_query_inds), years[0], years[-1])
+
+try:
+ data_query_sdmx = pd.read_csv(to_call,
+ # dtype=col_types,
+ storage_options={"Accept-Encoding": "gzip"},
+ low_memory=False,
+ )
+except urllib.error.HTTPError as e:
+ raise e
+
+data = data.append(data_query_sdmx)
+# no need to create column CODE, just rename indicator
+data.rename(columns={"INDICATOR": "CODE"}, inplace=True)
+
+print(data.head())
+
+indicators = data["Indicator"].unique()
+
+def page_not_found(pathname):
+ return html.P("No page '{}'".format(pathname))
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/base_page.py b/transmonee_dashboard/src/transmonee_dashboard/pages/base_page.py
index 4f7d9bfc..c7e4b492 100644
--- a/transmonee_dashboard/src/transmonee_dashboard/pages/base_page.py
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/base_page.py
@@ -1,5 +1,3 @@
-import textwrap
-
import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
@@ -14,21 +12,12 @@
from ..app import app
from ..components import fa
+
from . import (
- countries,
- countries_iso3_dict,
- df_sources,
- dimension_names,
- geo_json_countries,
- get_filtered_dataset,
- indicator_names,
- indicators_config,
- programme_country_indexes,
- selection_index,
- selection_tree,
- unicef_country_prog,
years,
- get_search_countries,
+ cl_ref_areas,
+ sel_ref_areas,
+ selection_tree
)
# set defaults
@@ -47,14 +36,6 @@
"danger",
]
AREA_KEYS = ["MAIN", "AREA_1", "AREA_2", "AREA_3", "AREA_4", "AREA_5", "AREA_6"]
-DEFAULT_LABELS = {
- "Country_name": "Country",
- "TIME_PERIOD": "Year",
- "Sex_name": "Sex",
- "Residence_name": "Residence",
- "Age_name": "Age",
- "Wealth_name": "Wealth Quintile",
-}
CARD_TEXT_STYLE = {"textAlign": "center", "color": "#0074D9"}
EMPTY_CHART = {
@@ -74,121 +55,13 @@
}
-def make_area(area_name):
- area_id = {"type": "area", "index": area_name}
- popover_id = {"type": "area_sources", "index": area_name}
- historical_data_style = {"display": "none"}
- exclude_outliers_style = {"paddingLeft": 20, "display": "block"}
- breakdowns_style = {"display": "block"}
-
- # lbassil: still differentiating main area id from other areas ids because the call backs are still not unified
- if area_name == "MAIN":
- area_id = f"{area_name.lower()}_area"
- popover_id = f"{area_name.lower()}_area_sources"
- historical_data_style = {"display": "block"}
- exclude_outliers_style = {"display": "none"}
- breakdowns_style = {"display": "none"}
-
- # lbassil: unifying both main and figure area generations by tweaking the ids and styles
- area = dbc.Card(
- [
- dbc.CardHeader(
- id={"type": "area_title", "index": area_name},
- style={"fontWeight": "bold"},
- ),
- dbc.CardBody(
- [
- dcc.Dropdown(
- id={"type": "area_options", "index": area_name},
- className="dcc_control",
- ),
- html.Br(),
- dbc.Checklist(
- options=[
- {
- "label": "Show historical data",
- "value": 1,
- }
- ],
- value=[],
- id={
- "type": "historical_data_toggle",
- "index": area_name,
- },
- switch=True,
- style=historical_data_style,
- ),
- html.Br(),
- dbc.RadioItems(
- id={"type": "area_types", "index": area_name},
- inline=True,
- ),
- dcc.Loading([dcc.Graph(id=area_id)]),
- dbc.Checklist(
- options=[
- {
- "label": "Exclude outliers ",
- "value": 1,
- }
- ],
- value=[1],
- id={
- "type": "exclude_outliers_toggle",
- "index": area_name,
- },
- switch=True,
- style=exclude_outliers_style,
- ),
- html.Br(),
- dbc.RadioItems(
- id={"type": "area_breakdowns", "index": area_name},
- inline=True,
- style=breakdowns_style,
- ),
- html.Div(
- fa("fas fa-info-circle"),
- id=f"{area_name.lower()}_area_info",
- className="float-right",
- ),
- dbc.Popover(
- [
- dbc.PopoverHeader("Sources"),
- dbc.PopoverBody(id=popover_id),
- ],
- id="hover",
- target=f"{area_name.lower()}_area_info",
- trigger="hover",
- ),
- ]
- ),
- ],
- id={"type": "area_parent", "index": area_name},
- )
- return area
-
-
def get_base_layout(**kwargs):
indicators_dict = kwargs.get("indicators")
main_title = kwargs.get("main_title")
- is_country_profile = kwargs.get("is_country_profile")
- country_dropdown_style = {"display": "none"}
+
themes_row_style = {"verticalAlign": "center", "display": "flex"}
+ country_dropdown_style = {"display": "none"}
countries_filter_style = {"display": "block"}
- programme_toggle_style = {"display": "block"}
- main_area_style = {"display": "block"}
-
- if is_country_profile:
- country_dropdown_style = {
- "minWidth": 400,
- "maxWidth": 600,
- "paddingRight": 4,
- "verticalAlign": "center",
- "display": "visible",
- }
- themes_row_style = {"display": "none"}
- countries_filter_style = {"display": "none"}
- programme_toggle_style = {"display": "none"}
- main_area_style = {"display": "none"}
return html.Div(
[
@@ -217,880 +90,222 @@ def get_base_layout(**kwargs):
],
),
],
- )
- ],
- ),
- dbc.Row(
- children=[
- dbc.Col(
- [
- dbc.Row(
- [
- dbc.ButtonGroup(
- id="themes",
- ),
- ],
- id="theme-row",
- # width=4,
- className="my-2",
- # no_gutters=True,
- justify="center",
- style=themes_row_style,
- ),
- dbc.Row(
+ ),
+
+ dbc.Row(
+ children=[
+ dbc.Col(
[
- dbc.DropdownMenu(
- label=f"Years: {years[0]} - {years[-1]}",
- id="collapse-years-button",
- className="m-2",
- color="info",
- # block=True,
- children=[
- dbc.Card(
- dcc.RangeSlider(
- id="year_slider",
- min=0,
- max=len(years) - 1,
- step=None,
- marks={
- index: str(year)
- for index, year in enumerate(
- years
- )
- },
- value=[0, len(years) - 1],
- ),
- style={
- "maxHeight": "250px",
- "minWidth": "500px",
- },
- className="overflow-auto",
- body=True,
- ),
- ],
- ),
- dcc.Dropdown(
- id="country_profile_selector",
- options=get_search_countries(False),
- value="ALB",
- multi=False,
- placeholder="Select a country...",
- className="m-2",
- style=country_dropdown_style,
- ),
- dbc.DropdownMenu(
- label=f"Countries: {len(countries)}",
- id="collapse-countries-button",
- className="m-2",
- color="info",
- style=countries_filter_style,
- children=[
- dbc.Card(
- dash_treeview_antd.TreeView(
- id="country_selector",
- multiple=True,
- checkable=True,
- checked=["0"],
- # selected=[],
- expanded=["0"],
- data=selection_tree,
- ),
- style={
- "maxHeight": "250px",
- # "maxWidth": "300px",
- },
- className="overflow-auto",
- body=True,
+ dbc.Row(
+ [
+ dbc.ButtonGroup(
+ id="themes",
),
],
+ id="theme-row",
+ # width=4,
+ className="my-2",
+ # no_gutters=True,
+ justify="center",
+ style=themes_row_style,
),
- dbc.FormGroup(
+ dbc.Row(
[
- dbc.Checkbox(
- id="programme-toggle",
- className="custom-control-input",
+ dbc.DropdownMenu(
+ label=f"Years: {years[0]} - {years[-1]}",
+ id="collapse-years-button",
+ className="m-2",
+ color="info",
+ # block=True,
+ children=[
+ dbc.Card(
+ dcc.RangeSlider(
+ id="year_slider",
+ min=0,
+ max=len(years) - 1,
+ step=None,
+ marks={
+ index: str(year)
+ for index, year in enumerate(
+ years
+ )
+ },
+ value=[0, len(years) - 1],
+ ),
+ style={
+ "maxHeight": "250px",
+ "minWidth": "500px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
),
- dbc.Label(
- "UNICEF Country Programmes",
- html_for="programme-toggle",
- className="custom-control-label",
- color="primary",
+
+ # dcc.Dropdown(
+ # id="country_profile_selector",
+ # options=get_search_countries(False),
+ # value="ALB",
+ # multi=False,
+ # placeholder="Select a country...",
+ # className="m-2",
+ # style=country_dropdown_style,
+ # ),
+ dbc.DropdownMenu(
+ label=f"Countries: {len(sel_ref_areas)}",
+ id="collapse-countries-button",
+ className="m-2",
+ color="info",
+ style=countries_filter_style,
+ children=[
+ dbc.Card(
+ dash_treeview_antd.TreeView(
+ id="country_selector",
+ multiple=True,
+ checkable=True,
+ checked=["0"],
+ # selected=[],
+ expanded=["0"],
+ data=selection_tree,
+ ),
+ style={
+ "maxHeight": "250px",
+ # "maxWidth": "300px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
),
+ # dbc.FormGroup(
+ # [
+ # dbc.Checkbox(
+ # id="programme-toggle",
+ # className="custom-control-input",
+ # ),
+ # dbc.Label(
+ # "UNICEF Country Programmes",
+ # html_for="programme-toggle",
+ # className="custom-control-label",
+ # color="primary",
+ # ),
+ # ],
+ # className="custom-control custom-switch m-2",
+ # check=True,
+ # inline=True,
+ # style=programme_toggle_style,
+ # ),
],
- className="custom-control custom-switch m-2",
- check=True,
- inline=True,
- style=programme_toggle_style,
+ id="filter-row",
+ no_gutters=True,
+ justify="center",
),
- ],
- id="filter-row",
- no_gutters=True,
- justify="center",
+ ]
),
- ]
- ),
- ],
- # sticky="top",
- className="sticky-top bg-light",
- ),
- dbc.Row(
- [
- dbc.CardDeck(
- id="cards_row",
- className="mt-3",
- ),
- ],
- justify="center",
- ),
- html.Br(),
- dbc.CardDeck(
- [make_area(area) for area in ["MAIN"]],
- style=main_area_style,
- ),
- html.Br(),
- dbc.CardDeck(
- [make_area(area) for area in ["AREA_1", "AREA_2"]],
- ),
- html.Br(),
- dbc.CardDeck(
- [make_area(area) for area in ["AREA_3", "AREA_4"]],
- ),
- html.Br(),
- dbc.CardDeck(
- [make_area(area) for area in ["AREA_5", "AREA_6"]],
- ),
- html.Br(),
- ],
- )
-
-
-def make_card(
- card_id,
- name,
- suffix,
- indicator_sources,
- source_link,
- indicator_header,
- numerator_pairs,
-):
- card = dbc.Card(
- [
- dbc.CardBody(
- [
- html.H1(
- indicator_header,
- className="display-4",
- style={
- # "fontSize": 50,
- "textAlign": "center",
- "color": "#1cabe2",
- },
- ),
- html.H4(suffix, className="card-title"),
- html.P(name, className="lead"),
- html.Div(
- fa("fas fa-info-circle"),
- id=f"{card_id}_info",
- # className="float-right",
- style={
- "position": "absolute",
- "bottom": "10px",
- "right": "10px",
- },
- ),
- ],
- style={
- # "fontSize": 50,
- "textAlign": "center",
- },
- ),
- dbc.Popover(
- [
- dbc.PopoverHeader(
- html.A(
- html.P(f"Sources: {indicator_sources}"),
- href=source_link,
- target="_blank",
- )
+ ],
+ # sticky="top",
+ className="sticky-top bg-light",
),
- dbc.PopoverBody(
- dcc.Markdown(get_card_popover_body(numerator_pairs))
+ dbc.Row(
+ [
+ dbc.CardDeck(
+ id="cards_row",
+ className="mt-3",
+ ),
+ ],
+ justify="center",
),
+ html.Br(),
],
- id="hover",
- target=f"{card_id}_info",
- trigger="hover",
),
- ],
- color="primary",
- outline=True,
- id=card_id,
+ ]
)
- return card
-
-def get_card_popover_body(sources):
- """This function is used to generate the list of countries that are part of the card's
- displayed result; it displays the countries as a list, each on a separate line
-
- Args:
- sources (_type_): _description_
-
- Returns:
- _type_: _description_
- """
- countries = []
- # lbassil: added this condition to stop the exception when sources is empty
- if len(sources) > 0:
- for index, source_info in sources.sort_values(by="OBS_VALUE").iterrows():
- countries.append(f"- {index[0]}, {source_info[0]} ({index[1]})")
- card_countries = "\n".join(countries)
- return card_countries
- else:
- return "NA"
-
-
-# TODO: Move to client side call back
@app.callback(
- Output("collapse-years", "is_open"),
- Output("collapse-countries", "is_open"),
- Output("collapse-engagements", "is_open"),
- [
- Input("collapse-years-button", "n_clicks"),
- Input("collapse-countries-button", "n_clicks"),
- Input("collapse-engagements-button", "n_clicks"),
- ],
- [
- State("collapse-years-button", "is_open"),
- State("collapse-countries-button", "is_open"),
- State("collapse-engagements-button", "is_open"),
- ],
-)
-def toggle_collapse(n1, n2, n3, is_open1, is_open2, is_open3):
- ctx = dash.callback_context
-
- if not ctx.triggered:
- return False, False, False
- else:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
-
- if button_id == "collapse-years-button" and n1:
- return not is_open1, False, False
- elif button_id == "collapse-countries-button" and n2:
- return False, not is_open2, False
- elif button_id == "collapse-engagements-button" and n3:
- return False, False, not is_open3
- return False, False, False
-
-
-@app.callback(
- Output({"type": "area_parent", "index": MATCH}, "hidden"),
- Input("theme", "hash"),
- [
- State("indicators", "data"),
- State({"type": "area_parent", "index": MATCH}, "id"),
- ],
+ Output()
)
-def display_areas(theme, indicators_dict, id):
- area = id["index"]
- theme = theme[1:].upper() if theme else next(iter(indicators_dict.keys()))
- return area not in indicators_dict[theme]
@app.callback(
Output("store", "data"),
Output("country_selector", "checked"),
- Output("programme-toggle", "checked"),
Output("collapse-years-button", "label"),
Output("collapse-countries-button", "label"),
[
Input("theme", "hash"),
Input("year_slider", "value"),
Input("country_selector", "checked"),
- Input("programme-toggle", "checked"),
- Input("country_profile_selector", "value"),
],
State("indicators", "data"),
)
def apply_filters(
- theme,
- years_slider,
- country_selector,
- programme_toggle,
- selected_country,
- indicators,
+ theme,
+ years_slider,
+ country_selector,
+ indicators,
):
ctx = dash.callback_context
selected = ctx.triggered[0]["prop_id"].split(".")[0]
- countries_selected = set()
+ print("selected")
+ print(selected)
+ print("selected end")
+ # countries_selected = set()
current_theme = theme[1:].upper() if theme else next(iter(indicators.keys()))
- # check if it is the country profile page
- is_country_profile = current_theme == "COUNTRYPROFILE"
- # check if the user clicked on the generate button in the country profile page
- if is_country_profile:
- key_list = list(countries_iso3_dict.keys())
- val_list = list(countries_iso3_dict.values())
- # get the name of the selected country in the dropdown to filter the data accordingly
- countries_selected = (
- [key_list[val_list.index(selected_country)]] if selected_country else []
- )
- elif programme_toggle and selected == "programme-toggle":
- countries_selected = unicef_country_prog
- country_selector = programme_country_indexes
- # Add the condition to know when the user unchecks the UNICEF country programs!
- elif not country_selector or (
- not programme_toggle and selected == "programme-toggle"
- ):
- countries_selected = countries
- # Add this to check all the items in the selection tree
- country_selector = ["0"]
- else:
- for index in country_selector:
- countries_selected.update(selection_index[index])
- if countries_selected == countries:
- # if all countries are all selected then stop
- break
- countries_selected = list(countries_selected)
- country_text = f"{len(countries_selected)} Selected"
+
+ # if selected=="country_selector":
+ # if
+
+ print("country_selector")
+ print(country_selector)
+ print("country_selector END")
+
+ # for index in country_selector:
+ #
+ # # print(index)
+ # print(index)
+ # print(country_selector[index])
+ # countries_selected.update(selection_index[index])
+ # if countries_selected == countries:
+ # if all countries are all selected then stop
+ # break
+
+
+ # countries_selected = list(countries_selected)
+ country_text = f"{len(country_selector)} Selected"
# need to include the last selected year as it was exluded in the previous method
- selected_years = years[years_slider[0] : years_slider[1] + 1]
+ selected_years = years[years_slider[0]: years_slider[1] + 1]
# selected_years = years[slice(*years_slider)]
# Use the dictionary to return the values of the selected countries based on the SDMX ISO3 codes
- countries_selected_codes = [
- countries_iso3_dict[country] for country in countries_selected
- ]
+ # countries_selected_codes = [
+ # countries_iso3_dict[country] for country in countries_selected
+ # ]
+
+ # print("countries_selected")
+ # print(countries_selected)
+ print("country_text")
+ print(country_text)
+ print("selected_years")
+ print(selected_years)
+ print("countries_selected_codes")
+ # print(countries_selected_codes)
selections = dict(
theme=current_theme,
indicators_dict=indicators,
years=selected_years,
- countries=countries_selected_codes,
- is_adolescent=("ADOLESCENT" in indicators),
+ # countries=countries_selected_codes,
+ countries=[]
+ # is_adolescent=("ADOLESCENT" in indicators),
)
+ print("SELECTION")
+ print(selections)
+
return (
selections,
country_selector,
- countries_selected == unicef_country_prog,
+ # countries_selected == unicef_country_prog,
f"Years: {selected_years[0]} - {selected_years[-1]}",
"Countries: {}".format(country_text),
)
-
-
-def indicator_card(
- selections,
- card_id,
- name,
- numerator,
- suffix,
- denominator=None,
- absolute=False,
- average=False,
- min_max=False,
- sex_code=None,
- age_group=None,
-):
- indicators = numerator.split(",")
-
- # TODO: Change to use albertos config
- # lbassil: had to change this to cater for 2 dimensions set to the indicator card like age and sex
- breakdown = "TOTAL"
- # define the empty dimensions dict to be filled based on the card data filters
- dimensions = {}
- if age_group is not None:
- dimensions["AGE"] = [age_group]
- if sex_code is not None:
- dimensions["SEX"] = [sex_code]
-
- filtered_data = get_filtered_dataset(
- indicators,
- selections["years"],
- selections["countries"],
- breakdown,
- dimensions,
- latest_data=True,
- )
-
- df_indicator_sources = df_sources[df_sources["Code"].isin(indicators)]
- unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
- indicator_sources = (
- "; ".join(list(unique_indicator_sources))
- if len(unique_indicator_sources) > 0
- else ""
- )
- source_link = (
- df_indicator_sources["Source_Link"].unique()[0]
- if len(unique_indicator_sources) > 0
- else ""
- )
- # lbassil: add this check because we are getting an exception where there is no data; i.e. no totals for all dimensions mostly age for the selected indicator
- if filtered_data.empty:
- indicator_header = "No data"
- indicator_sources = "NA"
- numerator_pairs = []
- return make_card(
- card_id,
- name,
- indicator_header,
- indicator_sources,
- source_link,
- numerator_pairs,
- )
-
- # select last value for each country
- indicator_values = (
- filtered_data.groupby(
- [
- "Country_name",
- "TIME_PERIOD",
- ]
- ).agg({"OBS_VALUE": "sum", "CODE": "count"})
- ).reset_index()
-
- numerator_pairs = (
- indicator_values[indicator_values.CODE == len(indicators)]
- .groupby("Country_name", as_index=False)
- .last()
- .set_index(["Country_name", "TIME_PERIOD"])
- )
-
- if suffix.lower() == "countries":
- # this is a hack to accomodate small cases (to discuss with James)
- if "FREE" in numerator:
- # trick to filter number of years of free education
- indicator_sum = (numerator_pairs.OBS_VALUE >= 1).to_numpy().sum()
- sources = numerator_pairs.index.tolist()
- numerator_pairs = numerator_pairs[numerator_pairs.OBS_VALUE >= 1]
- else:
- # trick to accomodate cards for admin exams (AND for boolean indicators)
- # filter exams according to number of indicators
- indicator_sum = (
- (numerator_pairs.OBS_VALUE == len(indicators)).to_numpy().sum()
- )
- sources = numerator_pairs.index.tolist()
-
- else:
- indicator_sum = numerator_pairs["OBS_VALUE"].to_numpy().sum()
- sources = numerator_pairs.index.tolist()
- if average and len(sources) > 1:
- indicator_sum = indicator_sum / len(sources)
-
- # define indicator header text: the resultant number except for the min-max range
- if min_max and len(sources) > 1:
- indicator_min = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].min())
- indicator_max = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].max())
- indicator_header = f"[{indicator_min} - {indicator_max}]"
- else:
- indicator_header = "{:,.0f}".format(indicator_sum)
-
- return make_card(
- card_id,
- name,
- suffix,
- indicator_sources,
- source_link,
- indicator_header,
- numerator_pairs,
- )
-
-
-@app.callback(
- Output("cards_row", "children"),
- [
- Input("store", "data"),
- ],
- [State("cards_row", "children"), State("indicators", "data")],
-)
-def show_cards(selections, current_cards, indicators_dict):
- cards = [
- indicator_card(
- selections,
- f"card-{num}",
- card["name"],
- card["indicator"],
- card["suffix"],
- card.get("denominator"),
- card.get("absolute"),
- card.get("average"),
- card.get("min_max"),
- card.get("sex"),
- card.get("age"),
- )
- for num, card in enumerate(indicators_dict[selections["theme"]]["CARDS"])
- ]
- return cards
-
-
-@app.callback(
- Output("subtitle", "children"),
- Output("themes", "children"),
- [
- Input("store", "data"),
- Input("country_profile_selector", "value"),
- ],
- State("indicators", "data"),
-)
-def show_themes(selections, selected_country, indicators_dict):
- # check if it is the country profile page
- is_country_profile = selections["theme"] == "COUNTRYPROFILE"
- ctx = dash.callback_context
- ctrl_id = ctx.triggered[0]["prop_id"].split(".")[0]
- # check if the countries dropdown is causing this callback in order to set the sub-title to the country's name
- if is_country_profile or ctrl_id == "countries":
- key_list = list(countries_iso3_dict.keys())
- val_list = list(countries_iso3_dict.values())
- subtitle = (
- key_list[val_list.index(selected_country)]
- if selected_country
- else "Country Name"
- )
- return subtitle, []
-
- subtitle = indicators_dict[selections["theme"]].get("NAME")
- url_hash = "#{}".format((next(iter(selections.items())))[1].lower())
- # hide the buttons when only one option is available
- if len(indicators_dict.items()) == 1:
- return subtitle, []
- buttons = [
- dbc.Button(
- value["NAME"],
- id=key,
- color=colours[num],
- className="theme mx-1",
- href=f"#{key.lower()}",
- active=url_hash == f"#{key.lower()}",
- )
- for num, (key, value) in enumerate(indicators_dict.items())
- ]
- return subtitle, buttons
-
-
-@app.callback(
- Output({"type": "area_title", "index": MATCH}, "children"),
- Output({"type": "area_options", "index": MATCH}, "options"),
- Output({"type": "area_types", "index": MATCH}, "options"),
- Output({"type": "area_options", "index": MATCH}, "value"),
- Output({"type": "area_types", "index": MATCH}, "value"),
- Input("store", "data"),
- [
- State("indicators", "data"),
- State({"type": "area_options", "index": MATCH}, "id"),
- ],
-)
-def set_options(theme, indicators_dict, id):
-
- area = id["index"]
-
- area_options = area_types = []
- if area in indicators_dict[theme["theme"]]:
- indicators = indicators_dict[theme["theme"]][area].get("indicators")
- area_indicators = indicators.keys() if indicators is dict else indicators
- area_options = [
- {
- "label": indicator_names[code],
- "value": code,
- }
- for code in area_indicators
- ]
-
- area_types = [
- {
- "label": name.capitalize(),
- "value": name,
- }
- for name in indicators_dict[theme["theme"]][area].get("graphs", {}).keys()
- ]
-
- name = (
- indicators_dict[theme["theme"]][area].get("name")
- if area in indicators_dict[theme["theme"]]
- else ""
- )
- default_option = (
- indicators_dict[theme["theme"]][area].get("default")
- if area in indicators_dict[theme["theme"]]
- else ""
- )
- default_graph = (
- indicators_dict[theme["theme"]][area].get("default_graph")
- if area in indicators_dict[theme["theme"]]
- else ""
- )
-
- return name, area_options, area_types, default_option, default_graph
-
-
-@app.callback(
- Output({"type": "area_breakdowns", "index": MATCH}, "options"),
- [
- Input({"type": "area_options", "index": MATCH}, "value"),
- Input({"type": "area_types", "index": MATCH}, "value"),
- ],
- [
- State({"type": "area_breakdowns", "index": MATCH}, "id"),
- ],
-)
-def breakdown_options(indicator, fig_type, id):
-
- options = [{"label": "Total", "value": "TOTAL"}]
- # lbassil: change the disaggregation to use the names of the dimensions instead of the codes
- all_breakdowns = [
- {"label": "Sex", "value": "SEX"},
- {"label": "Age", "value": "AGE"},
- {"label": "Residence", "value": "RESIDENCE"},
- {"label": "Wealth Quintile", "value": "WEALTH_QUINTILE"},
- ]
- dimensions = indicators_config.get(indicator, {}).keys()
- # keep only TOTAL for line charts
- if dimensions and fig_type != "line":
- for breakdown in all_breakdowns:
- if breakdown["value"] in dimensions:
- options.append(breakdown)
- return options
-
-
-@app.callback(
- Output({"type": "area_breakdowns", "index": MATCH}, "value"),
- [
- Input("store", "data"),
- Input({"type": "area_breakdowns", "index": MATCH}, "options"),
- Input({"type": "area_types", "index": MATCH}, "value"),
- ],
- [
- State("indicators", "data"),
- State({"type": "area_breakdowns", "index": MATCH}, "id"),
- ],
-)
-def set_default_compare(
- selections, compare_options, selected_type, indicators_dict, id
-):
- area = id["index"]
- # lbassil: add this condition to stop the exception for the main area
- if area in indicators_dict[selections["theme"]] and area != "MAIN":
- default = indicators_dict[selections["theme"]][area]["default_graph"]
- fig_type = selected_type if selected_type else default
- config = indicators_dict[selections["theme"]][area]["graphs"][fig_type]
- default_compare = config.get("compare")
-
- return (
- "TOTAL"
- if fig_type == "line" or default_compare is None
- else default_compare
- if default_compare in compare_options
- else compare_options[1]["value"]
- if len(compare_options) > 1
- else compare_options[0]["value"]
- )
- return "TOTAL"
-
-
-@app.callback(
- Output("main_area", "figure"),
- Output("main_area_sources", "children"),
- [
- Input({"type": "area_options", "index": "MAIN"}, "value"),
- Input({"type": "historical_data_toggle", "index": "MAIN"}, "value"),
- Input("store", "data"),
- ],
- [
- State("indicators", "data"),
- ],
-)
-def main_figure(indicator, show_historical_data, selections, indicators_dict):
- latest_data = not show_historical_data
- options = indicators_dict[selections["theme"]]["MAIN"]["options"]
-
- data = get_filtered_dataset(
- [indicator],
- selections["years"],
- selections["countries"],
- latest_data=latest_data,
- )
-
- # check if the dataframe is empty meaning no data to display as per the user's selection
- if data.empty:
- return EMPTY_CHART, ""
-
- # lbassil: replace UNIT_MEASURE by Unit_name to use the name of the unit instead of the code
- name = (
- data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()[0]
- if len(data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()) > 0
- else ""
- )
- df_indicator_sources = df_sources[df_sources["Code"] == indicator]
- unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
- source = (
- "; ".join(list(unique_indicator_sources))
- if len(unique_indicator_sources) > 0
- else ""
- )
- source_link = (
- df_indicator_sources["Source_Link"].unique()[0]
- if len(unique_indicator_sources) > 0
- else ""
- )
-
- options["labels"] = DEFAULT_LABELS.copy()
- options["labels"]["OBS_VALUE"] = name
- options["labels"]["text"] = "OBS_VALUE"
- options["geojson"] = geo_json_countries
- if latest_data:
- # remove the animation frame and show all countries at once
- options.pop("animation_frame")
- # add the year to show on hover
- options["hover_name"] = "TIME_PERIOD"
-
- main_figure = px.choropleth_mapbox(data, **options)
- main_figure.update_layout(margin={"r": 0, "t": 1, "l": 2, "b": 1})
-
- # check if this area's config has an animation frame and hence a slider
- if len(main_figure.layout["sliders"]) > 0:
- # set last frame as the active one; i.e. select the max year as the default displayed year
- main_figure.layout["sliders"][0]["active"] = len(main_figure.frames) - 1
- # assign the data of the last year to the map; without this line the data will show the first year;
- main_figure = go.Figure(
- data=main_figure["frames"][-1]["data"],
- frames=main_figure["frames"],
- layout=main_figure.layout,
- )
- return main_figure, html.A(html.P(source), href=source_link, target="_blank")
-
-
-@app.callback(
- Output({"type": "area", "index": MATCH}, "figure"),
- Output({"type": "area_sources", "index": MATCH}, "children"),
- [
- Input("store", "data"),
- Input({"type": "area_options", "index": MATCH}, "value"),
- Input({"type": "area_breakdowns", "index": MATCH}, "value"),
- Input({"type": "area_types", "index": MATCH}, "value"),
- Input({"type": "exclude_outliers_toggle", "index": MATCH}, "value"),
- ],
- [
- State("indicators", "data"),
- State({"type": "area_options", "index": MATCH}, "id"),
- ],
-)
-def area_figure(
- selections,
- indicator,
- compare,
- selected_type,
- exclude_outliers,
- indicators_dict,
- id,
-):
- # only run if indicator not empty
- if not indicator:
- return {}, {}
- # check if it is the country profile page
- is_country_profile = selections["theme"] == "COUNTRYPROFILE"
-
- area = id["index"]
- indicators = indicators_dict[selections["theme"]][area]["indicators"]
- default_graph = indicators_dict[selections["theme"]][area].get(
- "default_graph", "line"
- )
- fig_type = selected_type if selected_type else default_graph
- fig_config = indicators_dict[selections["theme"]][area]["graphs"][fig_type]
- options = fig_config.get("options")
- traces = fig_config.get("trace_options")
- dimension = False if fig_type == "line" or compare == "TOTAL" else compare
-
- indicator_name = str(indicator_names.get(indicator, ""))
- # do we need `indicator_settings` below?
- indicator_settings = (
- indicators.get(indicator, {}) if type(indicators) is dict else {}
- )
- data = get_filtered_dataset(
- [indicator],
- selections["years"],
- selections["countries"],
- compare,
- latest_data=False if fig_type == "line" or is_country_profile else True,
- ).sort_values("OBS_VALUE", ascending=False)
- # check if the dataframe is empty meaning no data to display as per the user's selection
- if data.empty:
- return EMPTY_CHART, ""
-
- # check if the exclude outliers checkbox is checked
- if exclude_outliers:
- # filter the data to the remove the outliers
- # (df < df.quantile(0.1)).any() (df > df.quantile(0.9)).any()
- data["z_scores"] = np.abs(zscore(data["OBS_VALUE"])) # calculate z-scores of df
- # filter the data entries to remove the outliers
- data = data[(data["z_scores"] < 3) | (data["z_scores"].isnull())]
-
- # lbassil: was UNIT_MEASURE
- name = (
- data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()[0]
- if len(data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()) > 0
- else ""
- )
- df_indicator_sources = df_sources[df_sources["Code"] == indicator]
- unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
- source = (
- "; ".join(list(unique_indicator_sources))
- if len(unique_indicator_sources) > 0
- else ""
- )
- source_link = (
- df_indicator_sources["Source_Link"].unique()[0]
- if len(unique_indicator_sources) > 0
- else ""
- )
-
- options["labels"] = DEFAULT_LABELS.copy()
- options["labels"]["OBS_VALUE"] = name
-
- # set the chart title, wrap the text when the indicator name is too long
- chart_title = textwrap.wrap(
- indicator_name,
- width=74,
- )
- chart_title = "
".join(chart_title)
-
- # set the layout to center the chart title and change its font size and color
- layout = go.Layout(
- title=chart_title,
- title_x=0.5,
- font=dict(family="Arial", size=12),
- legend=dict(x=0.9, y=0.5),
- )
-
- # Add this code to avoid having decimal year on the x-axis for time series charts
- if fig_type == "line" or is_country_profile:
- data.sort_values(by=["TIME_PERIOD"], inplace=True)
- layout["xaxis"] = dict(
- tickmode="linear",
- tick0=selections["years"][0],
- dtick=1,
- categoryorder="total ascending",
- )
-
- if dimension:
- # lbassil: use the dimension name instead of the code
- dimension_name = str(dimension_names.get(dimension, ""))
- options["color"] = dimension_name
- if compare == "WEALTH_QUINTILE":
- wealth_dict = {
- "Lowest": 0,
- "Second": 1,
- "Middle": 2,
- "Fourth": 3,
- "Highest": 4,
- }
- data.sort_values(
- by=[dimension], key=lambda x: x.map(wealth_dict), inplace=True
- )
- else:
- # sort by the compare value to have the legend in the right ascending order
- data.sort_values(by=[dimension], inplace=True)
-
- fig = getattr(px, fig_type)(data, **options)
- fig.update_layout(layout)
- if traces:
- fig.update_traces(**traces)
-
- return fig, html.A(html.P(source), href=source_link, target="_blank")
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/base_page_old.py b/transmonee_dashboard/src/transmonee_dashboard/pages/base_page_old.py
new file mode 100644
index 00000000..5611eb02
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/base_page_old.py
@@ -0,0 +1,1393 @@
+import textwrap
+
+import dash
+import dash_bootstrap_components as dbc
+import dash_core_components as dcc
+import dash_html_components as html
+import dash_treeview_antd
+import numpy as np
+import plotly.express as px
+import plotly.graph_objects as go
+import plotly.io as pio
+from dash.dependencies import MATCH, ClientsideFunction, Input, Output, State
+from scipy.stats import zscore
+
+from ..app import app
+from ..components import fa
+
+'''
+from . import (
+ countries,
+ countries_iso3_dict,
+ df_sources,
+ dimension_names,
+ geo_json_countries,
+ get_filtered_dataset,
+ indicator_names,
+ indicators_config,
+ programme_country_indexes,
+ selection_index,
+ selection_tree,
+ unicef_country_prog,
+ years,
+ get_search_countries,
+)
+'''
+
+from . import (
+ years,
+ countries,
+ get_search_countries,
+ selection_tree,
+ countries_iso3_dict
+)
+
+# set defaults
+pio.templates.default = "plotly_white"
+px.defaults.color_continuous_scale = px.colors.sequential.BuGn
+px.defaults.color_discrete_sequence = px.colors.qualitative.Dark24
+
+colours = [
+ "primary",
+ "success",
+ "warning",
+ "danger",
+ "secondary",
+ "info",
+ "success",
+ "danger",
+]
+AREA_KEYS = ["MAIN", "AREA_1", "AREA_2", "AREA_3", "AREA_4", "AREA_5", "AREA_6"]
+DEFAULT_LABELS = {
+ "Country_name": "Country",
+ "TIME_PERIOD": "Year",
+ "Sex_name": "Sex",
+ "Residence_name": "Residence",
+ "Age_name": "Age",
+ "Wealth_name": "Wealth Quintile",
+}
+CARD_TEXT_STYLE = {"textAlign": "center", "color": "#0074D9"}
+
+EMPTY_CHART = {
+ "layout": {
+ "xaxis": {"visible": False},
+ "yaxis": {"visible": False},
+ "annotations": [
+ {
+ "text": "No data is available for the selected filters",
+ "xref": "paper",
+ "yref": "paper",
+ "showarrow": False,
+ "font": {"size": 28},
+ }
+ ],
+ }
+}
+
+
+def make_area(area_name):
+ area_id = {"type": "area", "index": area_name}
+ popover_id = {"type": "area_sources", "index": area_name}
+ historical_data_style = {"display": "none"}
+ exclude_outliers_style = {"paddingLeft": 20, "display": "block"}
+ breakdowns_style = {"display": "block"}
+
+ # lbassil: still differentiating main area id from other areas ids because the call backs are still not unified
+ if area_name == "MAIN":
+ area_id = f"{area_name.lower()}_area"
+ popover_id = f"{area_name.lower()}_area_sources"
+ historical_data_style = {"display": "block"}
+ exclude_outliers_style = {"display": "none"}
+ breakdowns_style = {"display": "none"}
+
+ # lbassil: unifying both main and figure area generations by tweaking the ids and styles
+ area = dbc.Card(
+ [
+ dbc.CardHeader(
+ id={"type": "area_title", "index": area_name},
+ style={"fontWeight": "bold"},
+ ),
+ dbc.CardBody(
+ [
+ dcc.Dropdown(
+ id={"type": "area_options", "index": area_name},
+ className="dcc_control",
+ ),
+ html.Br(),
+ dbc.Checklist(
+ options=[
+ {
+ "label": "Show historical data",
+ "value": 1,
+ }
+ ],
+ value=[],
+ id={
+ "type": "historical_data_toggle",
+ "index": area_name,
+ },
+ switch=True,
+ style=historical_data_style,
+ ),
+ html.Br(),
+ dbc.RadioItems(
+ id={"type": "area_types", "index": area_name},
+ inline=True,
+ ),
+ dcc.Loading([dcc.Graph(id=area_id)]),
+ dbc.Checklist(
+ options=[
+ {
+ "label": "Exclude outliers ",
+ "value": 1,
+ }
+ ],
+ value=[1],
+ id={
+ "type": "exclude_outliers_toggle",
+ "index": area_name,
+ },
+ switch=True,
+ style=exclude_outliers_style,
+ ),
+ html.Br(),
+ dbc.RadioItems(
+ id={"type": "area_breakdowns", "index": area_name},
+ inline=True,
+ style=breakdowns_style,
+ ),
+ html.Div(
+ fa("fas fa-info-circle"),
+ id=f"{area_name.lower()}_area_info",
+ className="float-right",
+ ),
+ dbc.Popover(
+ [
+ dbc.PopoverHeader("Sources"),
+ dbc.PopoverBody(id=popover_id),
+ ],
+ id="hover",
+ target=f"{area_name.lower()}_area_info",
+ trigger="hover",
+ ),
+ ]
+ ),
+ ],
+ id={"type": "area_parent", "index": area_name},
+ )
+ return area
+
+
+def get_base_layout(**kwargs):
+ main_title = kwargs.get("main_title")
+ themes_row_style = {"verticalAlign": "center", "display": "flex"}
+ country_dropdown_style = {"display": "none"}
+ countries_filter_style = {"display": "block"}
+
+ return html.Div(
+ [
+ html.Div(
+ className="heading",
+ style={"padding": 36},
+ children=[
+ html.Div(
+ className="heading-content",
+ children=[
+ html.Div(
+ className="heading-panel",
+ style={"padding": 20},
+ children=[
+ html.H1(
+ main_title,
+ id="main_title",
+ className="heading-title",
+ ),
+ html.P(
+ id="subtitle",
+ className="heading-subtitle",
+ ),
+ ],
+ ),
+ ],
+ ),
+
+ dbc.Row(
+ children=[
+ dbc.Col(
+ [
+ dbc.Row(
+ [
+ dbc.ButtonGroup(
+ id="themes",
+ ),
+ ],
+ id="theme-row",
+ # width=4,
+ className="my-2",
+ # no_gutters=True,
+ justify="center",
+ style=themes_row_style,
+ ),
+ dbc.Row(
+ [
+ dbc.DropdownMenu(
+ label=f"Years: {years[0]} - {years[-1]}",
+ id="collapse-years-button",
+ className="m-2",
+ color="info",
+ # block=True,
+ children=[
+ dbc.Card(
+ dcc.RangeSlider(
+ id="year_slider",
+ min=0,
+ max=len(years) - 1,
+ step=None,
+ marks={
+ index: str(year)
+ for index, year in enumerate(
+ years
+ )
+ },
+ value=[0, len(years) - 1],
+ ),
+ style={
+ "maxHeight": "250px",
+ "minWidth": "500px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+
+ dcc.Dropdown(
+ id="country_profile_selector",
+ options=get_search_countries(False),
+ value="ALB",
+ multi=False,
+ placeholder="Select a country...",
+ className="m-2",
+ style=country_dropdown_style,
+ ),
+ dbc.DropdownMenu(
+ label=f"Countries: {len(countries)}",
+ id="collapse-countries-button",
+ className="m-2",
+ color="info",
+ style=countries_filter_style,
+ children=[
+ dbc.Card(
+ dash_treeview_antd.TreeView(
+ id="country_selector",
+ multiple=True,
+ checkable=True,
+ checked=["0"],
+ # selected=[],
+ expanded=["0"],
+ data=selection_tree,
+ ),
+ style={
+ "maxHeight": "250px",
+ # "maxWidth": "300px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+ # dbc.FormGroup(
+ # [
+ # dbc.Checkbox(
+ # id="programme-toggle",
+ # className="custom-control-input",
+ # ),
+ # dbc.Label(
+ # "UNICEF Country Programmes",
+ # html_for="programme-toggle",
+ # className="custom-control-label",
+ # color="primary",
+ # ),
+ # ],
+ # className="custom-control custom-switch m-2",
+ # check=True,
+ # inline=True,
+ # style=programme_toggle_style,
+ # ),
+ ],
+ id="filter-row",
+ no_gutters=True,
+ justify="center",
+ ),
+ ]
+ ),
+ ],
+ # sticky="top",
+ className="sticky-top bg-light",
+ ),
+ dbc.Row(
+ [
+ dbc.CardDeck(
+ id="cards_row",
+ className="mt-3",
+ ),
+ ],
+ justify="center",
+ ),
+ html.Br(),
+ ],
+ ),
+ ]
+ )
+
+
+def get_base_layout_old(**kwargs):
+ indicators_dict = kwargs.get("indicators")
+ main_title = kwargs.get("main_title")
+ is_country_profile = kwargs.get("is_country_profile")
+ country_dropdown_style = {"display": "none"}
+ themes_row_style = {"verticalAlign": "center", "display": "flex"}
+ countries_filter_style = {"display": "block"}
+ programme_toggle_style = {"display": "block"}
+ main_area_style = {"display": "block"}
+
+ if is_country_profile:
+ country_dropdown_style = {
+ "minWidth": 400,
+ "maxWidth": 600,
+ "paddingRight": 4,
+ "verticalAlign": "center",
+ "display": "visible",
+ }
+ themes_row_style = {"display": "none"}
+ countries_filter_style = {"display": "none"}
+ programme_toggle_style = {"display": "none"}
+ main_area_style = {"display": "none"}
+
+ return html.Div(
+ [
+ dcc.Store(id="indicators", data=indicators_dict),
+ dcc.Location(id="theme"),
+ html.Div(
+ className="heading",
+ style={"padding": 36},
+ children=[
+ html.Div(
+ className="heading-content",
+ children=[
+ html.Div(
+ className="heading-panel",
+ style={"padding": 20},
+ children=[
+ html.H1(
+ main_title,
+ id="main_title",
+ className="heading-title",
+ ),
+ html.P(
+ id="subtitle",
+ className="heading-subtitle",
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ ),
+ dbc.Row(
+ children=[
+ dbc.Col(
+ [
+ dbc.Row(
+ [
+ dbc.ButtonGroup(
+ id="themes",
+ ),
+ ],
+ id="theme-row",
+ # width=4,
+ className="my-2",
+ # no_gutters=True,
+ justify="center",
+ style=themes_row_style,
+ ),
+ dbc.Row(
+ [
+ dbc.DropdownMenu(
+ label=f"Years: {years[0]} - {years[-1]}",
+ id="collapse-years-button",
+ className="m-2",
+ color="info",
+ # block=True,
+ children=[
+ dbc.Card(
+ dcc.RangeSlider(
+ id="year_slider",
+ min=0,
+ max=len(years) - 1,
+ step=None,
+ marks={
+ index: str(year)
+ for index, year in enumerate(
+ years
+ )
+ },
+ value=[0, len(years) - 1],
+ ),
+ style={
+ "maxHeight": "250px",
+ "minWidth": "500px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+ dcc.Dropdown(
+ id="country_profile_selector",
+ options=get_search_countries(False),
+ value="ALB",
+ multi=False,
+ placeholder="Select a country...",
+ className="m-2",
+ style=country_dropdown_style,
+ ),
+ dbc.DropdownMenu(
+ label=f"Countries: {len(countries)}",
+ id="collapse-countries-button",
+ className="m-2",
+ color="info",
+ style=countries_filter_style,
+ children=[
+ dbc.Card(
+ dash_treeview_antd.TreeView(
+ id="country_selector",
+ multiple=True,
+ checkable=True,
+ checked=["0"],
+ # selected=[],
+ expanded=["0"],
+ data=selection_tree,
+ ),
+ style={
+ "maxHeight": "250px",
+ # "maxWidth": "300px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+ dbc.FormGroup(
+ [
+ dbc.Checkbox(
+ id="programme-toggle",
+ className="custom-control-input",
+ ),
+ dbc.Label(
+ "UNICEF Country Programmes",
+ html_for="programme-toggle",
+ className="custom-control-label",
+ color="primary",
+ ),
+ ],
+ className="custom-control custom-switch m-2",
+ check=True,
+ inline=True,
+ style=programme_toggle_style,
+ ),
+ ],
+ id="filter-row",
+ no_gutters=True,
+ justify="center",
+ ),
+ ]
+ ),
+ ],
+ # sticky="top",
+ className="sticky-top bg-light",
+ ),
+ dbc.Row(
+ [
+ dbc.CardDeck(
+ id="cards_row",
+ className="mt-3",
+ ),
+ ],
+ justify="center",
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["MAIN"]],
+ style=main_area_style,
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["AREA_1", "AREA_2"]],
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["AREA_3", "AREA_4"]],
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["AREA_5", "AREA_6"]],
+ ),
+ html.Br(),
+ ],
+ )
+
+
+def make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+):
+ card = dbc.Card(
+ [
+ dbc.CardBody(
+ [
+ html.H1(
+ indicator_header,
+ className="display-4",
+ style={
+ # "fontSize": 50,
+ "textAlign": "center",
+ "color": "#1cabe2",
+ },
+ ),
+ html.H4(suffix, className="card-title"),
+ html.P(name, className="lead"),
+ html.Div(
+ fa("fas fa-info-circle"),
+ id=f"{card_id}_info",
+ # className="float-right",
+ style={
+ "position": "absolute",
+ "bottom": "10px",
+ "right": "10px",
+ },
+ ),
+ ],
+ style={
+ # "fontSize": 50,
+ "textAlign": "center",
+ },
+ ),
+ dbc.Popover(
+ [
+ dbc.PopoverHeader(
+ html.A(
+ html.P(f"Sources: {indicator_sources}"),
+ href=source_link,
+ target="_blank",
+ )
+ ),
+ dbc.PopoverBody(
+ dcc.Markdown(get_card_popover_body(numerator_pairs))
+ ),
+ ],
+ id="hover",
+ target=f"{card_id}_info",
+ trigger="hover",
+ ),
+ ],
+ color="primary",
+ outline=True,
+ id=card_id,
+ )
+ return card
+
+
+def indicator_card(
+ selections,
+ card_id,
+ name,
+ numerator,
+ suffix,
+ denominator=None,
+ absolute=False,
+ average=False,
+ min_max=False,
+ sex_code=None,
+ age_group=None,
+):
+ indicators = numerator.split(",")
+
+ # TODO: Change to use albertos config
+ # lbassil: had to change this to cater for 2 dimensions set to the indicator card like age and sex
+ breakdown = "TOTAL"
+ # define the empty dimensions dict to be filled based on the card data filters
+ dimensions = {}
+ if age_group is not None:
+ dimensions["AGE"] = [age_group]
+ if sex_code is not None:
+ dimensions["SEX"] = [sex_code]
+
+ filtered_data = get_filtered_dataset(
+ indicators,
+ selections["years"],
+ selections["countries"],
+ breakdown,
+ dimensions,
+ latest_data=True,
+ )
+
+ df_indicator_sources = df_sources[df_sources["Code"].isin(indicators)]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ indicator_sources = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ # lbassil: add this check because we are getting an exception where there is no data; i.e. no totals for all dimensions mostly age for the selected indicator
+ if filtered_data.empty:
+ indicator_header = "No data"
+ indicator_sources = "NA"
+ numerator_pairs = []
+ return make_card(
+ card_id,
+ name,
+ indicator_header,
+ indicator_sources,
+ source_link,
+ numerator_pairs,
+ )
+
+ # select last value for each country
+ indicator_values = (
+ filtered_data.groupby(
+ [
+ "Country_name",
+ "TIME_PERIOD",
+ ]
+ ).agg({"OBS_VALUE": "sum", "CODE": "count"})
+ ).reset_index()
+
+ numerator_pairs = (
+ indicator_values[indicator_values.CODE == len(indicators)]
+ .groupby("Country_name", as_index=False)
+ .last()
+ .set_index(["Country_name", "TIME_PERIOD"])
+ )
+
+ if suffix.lower() == "countries":
+ # this is a hack to accomodate small cases (to discuss with James)
+ if "FREE" in numerator:
+ # trick to filter number of years of free education
+ indicator_sum = (numerator_pairs.OBS_VALUE >= 1).to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ numerator_pairs = numerator_pairs[numerator_pairs.OBS_VALUE >= 1]
+ else:
+ # trick to accomodate cards for admin exams (AND for boolean indicators)
+ # filter exams according to number of indicators
+ indicator_sum = (
+ (numerator_pairs.OBS_VALUE == len(indicators)).to_numpy().sum()
+ )
+ sources = numerator_pairs.index.tolist()
+
+ else:
+ indicator_sum = numerator_pairs["OBS_VALUE"].to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ if average and len(sources) > 1:
+ indicator_sum = indicator_sum / len(sources)
+
+ # define indicator header text: the resultant number except for the min-max range
+ if min_max and len(sources) > 1:
+ indicator_min = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].min())
+ indicator_max = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].max())
+ indicator_header = f"[{indicator_min} - {indicator_max}]"
+ else:
+ indicator_header = "{:,.0f}".format(indicator_sum)
+
+ return make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+ )
+
+
+
+def get_card_popover_body(sources):
+ """This function is used to generate the list of countries that are part of the card's
+ displayed result; it displays the countries as a list, each on a separate line
+
+ Args:
+ sources (_type_): _description_
+
+ Returns:
+ _type_: _description_
+ """
+ countries = []
+ # lbassil: added this condition to stop the exception when sources is empty
+ if len(sources) > 0:
+ for index, source_info in sources.sort_values(by="OBS_VALUE").iterrows():
+ countries.append(f"- {index[0]}, {source_info[0]} ({index[1]})")
+ card_countries = "\n".join(countries)
+ return card_countries
+ else:
+ return "NA"
+
+
+
+
+
+
+
+# TODO: Move to client side call back
+@app.callback(
+ Output("collapse-years", "is_open"),
+ Output("collapse-countries", "is_open"),
+ Output("collapse-engagements", "is_open"),
+ [
+ Input("collapse-years-button", "n_clicks"),
+ Input("collapse-countries-button", "n_clicks"),
+ Input("collapse-engagements-button", "n_clicks"),
+ ],
+ [
+ State("collapse-years-button", "is_open"),
+ State("collapse-countries-button", "is_open"),
+ State("collapse-engagements-button", "is_open"),
+ ],
+)
+def toggle_collapse(n1, n2, n3, is_open1, is_open2, is_open3):
+ ctx = dash.callback_context
+
+ if not ctx.triggered:
+ return False, False, False
+ else:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+
+ if button_id == "collapse-years-button" and n1:
+ return not is_open1, False, False
+ elif button_id == "collapse-countries-button" and n2:
+ return False, not is_open2, False
+ elif button_id == "collapse-engagements-button" and n3:
+ return False, False, not is_open3
+ return False, False, False
+
+'''
+@app.callback(
+ Output({"type": "area_parent", "index": MATCH}, "hidden"),
+ Input("theme", "hash"),
+ [
+ State("indicators", "data"),
+ State({"type": "area_parent", "index": MATCH}, "id"),
+ ],
+)
+def display_areas(theme, indicators_dict, id):
+ area = id["index"]
+ theme = theme[1:].upper() if theme else next(iter(indicators_dict.keys()))
+ return area not in indicators_dict[theme]
+
+
+@app.callback(
+ Output("store", "data"),
+ Output("country_selector", "checked"),
+ Output("programme-toggle", "checked"),
+ Output("collapse-years-button", "label"),
+ Output("collapse-countries-button", "label"),
+ [
+ Input("theme", "hash"),
+ Input("year_slider", "value"),
+ Input("country_selector", "checked"),
+ Input("programme-toggle", "checked"),
+ Input("country_profile_selector", "value"),
+ ],
+ State("indicators", "data"),
+)
+def apply_filters(
+ theme,
+ years_slider,
+ country_selector,
+ programme_toggle,
+ selected_country,
+ indicators,
+):
+ ctx = dash.callback_context
+ selected = ctx.triggered[0]["prop_id"].split(".")[0]
+ countries_selected = set()
+ current_theme = theme[1:].upper() if theme else next(iter(indicators.keys()))
+ # check if it is the country profile page
+ is_country_profile = current_theme == "COUNTRYPROFILE"
+ # check if the user clicked on the generate button in the country profile page
+ if is_country_profile:
+ key_list = list(countries_iso3_dict.keys())
+ val_list = list(countries_iso3_dict.values())
+ # get the name of the selected country in the dropdown to filter the data accordingly
+ countries_selected = (
+ [key_list[val_list.index(selected_country)]] if selected_country else []
+ )
+ elif programme_toggle and selected == "programme-toggle":
+ countries_selected = unicef_country_prog
+ country_selector = programme_country_indexes
+ # Add the condition to know when the user unchecks the UNICEF country programs!
+ elif not country_selector or (
+ not programme_toggle and selected == "programme-toggle"
+ ):
+ countries_selected = countries
+ # Add this to check all the items in the selection tree
+ country_selector = ["0"]
+ else:
+ for index in country_selector:
+ countries_selected.update(selection_index[index])
+ if countries_selected == countries:
+ # if all countries are all selected then stop
+ break
+
+ countries_selected = list(countries_selected)
+ country_text = f"{len(countries_selected)} Selected"
+ # need to include the last selected year as it was exluded in the previous method
+ selected_years = years[years_slider[0]: years_slider[1] + 1]
+ # selected_years = years[slice(*years_slider)]
+
+ # Use the dictionary to return the values of the selected countries based on the SDMX ISO3 codes
+ countries_selected_codes = [
+ countries_iso3_dict[country] for country in countries_selected
+ ]
+ selections = dict(
+ theme=current_theme,
+ indicators_dict=indicators,
+ years=selected_years,
+ countries=countries_selected_codes,
+ is_adolescent=("ADOLESCENT" in indicators),
+ )
+
+ return (
+ selections,
+ country_selector,
+ countries_selected == unicef_country_prog,
+ f"Years: {selected_years[0]} - {selected_years[-1]}",
+ "Countries: {}".format(country_text),
+ )
+
+
+def indicator_card(
+ selections,
+ card_id,
+ name,
+ numerator,
+ suffix,
+ denominator=None,
+ absolute=False,
+ average=False,
+ min_max=False,
+ sex_code=None,
+ age_group=None,
+):
+ indicators = numerator.split(",")
+
+ # TODO: Change to use albertos config
+ # lbassil: had to change this to cater for 2 dimensions set to the indicator card like age and sex
+ breakdown = "TOTAL"
+ # define the empty dimensions dict to be filled based on the card data filters
+ dimensions = {}
+ if age_group is not None:
+ dimensions["AGE"] = [age_group]
+ if sex_code is not None:
+ dimensions["SEX"] = [sex_code]
+
+ filtered_data = get_filtered_dataset(
+ indicators,
+ selections["years"],
+ selections["countries"],
+ breakdown,
+ dimensions,
+ latest_data=True,
+ )
+
+ df_indicator_sources = df_sources[df_sources["Code"].isin(indicators)]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ indicator_sources = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ # lbassil: add this check because we are getting an exception where there is no data; i.e. no totals for all dimensions mostly age for the selected indicator
+ if filtered_data.empty:
+ indicator_header = "No data"
+ indicator_sources = "NA"
+ numerator_pairs = []
+ return make_card(
+ card_id,
+ name,
+ indicator_header,
+ indicator_sources,
+ source_link,
+ numerator_pairs,
+ )
+
+ # select last value for each country
+ indicator_values = (
+ filtered_data.groupby(
+ [
+ "Country_name",
+ "TIME_PERIOD",
+ ]
+ ).agg({"OBS_VALUE": "sum", "CODE": "count"})
+ ).reset_index()
+
+ numerator_pairs = (
+ indicator_values[indicator_values.CODE == len(indicators)]
+ .groupby("Country_name", as_index=False)
+ .last()
+ .set_index(["Country_name", "TIME_PERIOD"])
+ )
+
+ if suffix.lower() == "countries":
+ # this is a hack to accomodate small cases (to discuss with James)
+ if "FREE" in numerator:
+ # trick to filter number of years of free education
+ indicator_sum = (numerator_pairs.OBS_VALUE >= 1).to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ numerator_pairs = numerator_pairs[numerator_pairs.OBS_VALUE >= 1]
+ else:
+ # trick to accomodate cards for admin exams (AND for boolean indicators)
+ # filter exams according to number of indicators
+ indicator_sum = (
+ (numerator_pairs.OBS_VALUE == len(indicators)).to_numpy().sum()
+ )
+ sources = numerator_pairs.index.tolist()
+
+ else:
+ indicator_sum = numerator_pairs["OBS_VALUE"].to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ if average and len(sources) > 1:
+ indicator_sum = indicator_sum / len(sources)
+
+ # define indicator header text: the resultant number except for the min-max range
+ if min_max and len(sources) > 1:
+ indicator_min = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].min())
+ indicator_max = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].max())
+ indicator_header = f"[{indicator_min} - {indicator_max}]"
+ else:
+ indicator_header = "{:,.0f}".format(indicator_sum)
+
+ return make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+ )
+'''
+
+@app.callback(
+ Output("cards_row", "children"),
+ [
+ Input("store", "data"),
+ ],
+ [State("cards_row", "children"), State("indicators", "data")],
+)
+def show_cards(selections, current_cards, indicators_dict):
+ cards = [
+ indicator_card(
+ selections,
+ f"card-{num}",
+ card["name"],
+ card["indicator"],
+ card["suffix"],
+ card.get("denominator"),
+ card.get("absolute"),
+ card.get("average"),
+ card.get("min_max"),
+ card.get("sex"),
+ card.get("age"),
+ )
+ for num, card in enumerate(indicators_dict[selections["theme"]]["CARDS"])
+ ]
+ return cards
+
+
+@app.callback(
+ Output("subtitle", "children"),
+ Output("themes", "children"),
+ [
+ Input("store", "data"),
+ Input("country_profile_selector", "value"),
+ ],
+ State("indicators", "data"),
+)
+'''
+def show_themes(selections, selected_country, indicators_dict):
+ # check if it is the country profile page
+ is_country_profile = selections["theme"] == "COUNTRYPROFILE"
+ ctx = dash.callback_context
+ ctrl_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ # check if the countries dropdown is causing this callback in order to set the sub-title to the country's name
+ if is_country_profile or ctrl_id == "countries":
+ key_list = list(countries_iso3_dict.keys())
+ val_list = list(countries_iso3_dict.values())
+ subtitle = (
+ key_list[val_list.index(selected_country)]
+ if selected_country
+ else "Country Name"
+ )
+ return subtitle, []
+
+ subtitle = indicators_dict[selections["theme"]].get("NAME")
+ url_hash = "#{}".format((next(iter(selections.items())))[1].lower())
+ # hide the buttons when only one option is available
+ if len(indicators_dict.items()) == 1:
+ return subtitle, []
+ buttons = [
+ dbc.Button(
+ value["NAME"],
+ id=key,
+ color=colours[num],
+ className="theme mx-1",
+ href=f"#{key.lower()}",
+ active=url_hash == f"#{key.lower()}",
+ )
+ for num, (key, value) in enumerate(indicators_dict.items())
+ ]
+ return subtitle, buttons
+
+
+@app.callback(
+ Output({"type": "area_title", "index": MATCH}, "children"),
+ Output({"type": "area_options", "index": MATCH}, "options"),
+ Output({"type": "area_types", "index": MATCH}, "options"),
+ Output({"type": "area_options", "index": MATCH}, "value"),
+ Output({"type": "area_types", "index": MATCH}, "value"),
+ Input("store", "data"),
+ [
+ State("indicators", "data"),
+ State({"type": "area_options", "index": MATCH}, "id"),
+ ],
+)
+def set_options(theme, indicators_dict, id):
+ area = id["index"]
+
+ area_options = area_types = []
+ if area in indicators_dict[theme["theme"]]:
+ indicators = indicators_dict[theme["theme"]][area].get("indicators")
+ area_indicators = indicators.keys() if indicators is dict else indicators
+ area_options = [
+ {
+ "label": indicator_names[code],
+ "value": code,
+ }
+ for code in area_indicators
+ ]
+
+ area_types = [
+ {
+ "label": name.capitalize(),
+ "value": name,
+ }
+ for name in indicators_dict[theme["theme"]][area].get("graphs", {}).keys()
+ ]
+
+ name = (
+ indicators_dict[theme["theme"]][area].get("name")
+ if area in indicators_dict[theme["theme"]]
+ else ""
+ )
+ default_option = (
+ indicators_dict[theme["theme"]][area].get("default")
+ if area in indicators_dict[theme["theme"]]
+ else ""
+ )
+ default_graph = (
+ indicators_dict[theme["theme"]][area].get("default_graph")
+ if area in indicators_dict[theme["theme"]]
+ else ""
+ )
+
+ return name, area_options, area_types, default_option, default_graph
+
+
+@app.callback(
+ Output({"type": "area_breakdowns", "index": MATCH}, "options"),
+ [
+ Input({"type": "area_options", "index": MATCH}, "value"),
+ Input({"type": "area_types", "index": MATCH}, "value"),
+ ],
+ [
+ State({"type": "area_breakdowns", "index": MATCH}, "id"),
+ ],
+)
+def breakdown_options(indicator, fig_type, id):
+ options = [{"label": "Total", "value": "TOTAL"}]
+ # lbassil: change the disaggregation to use the names of the dimensions instead of the codes
+ all_breakdowns = [
+ {"label": "Sex", "value": "SEX"},
+ {"label": "Age", "value": "AGE"},
+ {"label": "Residence", "value": "RESIDENCE"},
+ {"label": "Wealth Quintile", "value": "WEALTH_QUINTILE"},
+ ]
+ dimensions = indicators_config.get(indicator, {}).keys()
+ # keep only TOTAL for line charts
+ if dimensions and fig_type != "line":
+ for breakdown in all_breakdowns:
+ if breakdown["value"] in dimensions:
+ options.append(breakdown)
+ return options
+
+
+@app.callback(
+ Output({"type": "area_breakdowns", "index": MATCH}, "value"),
+ [
+ Input("store", "data"),
+ Input({"type": "area_breakdowns", "index": MATCH}, "options"),
+ Input({"type": "area_types", "index": MATCH}, "value"),
+ ],
+ [
+ State("indicators", "data"),
+ State({"type": "area_breakdowns", "index": MATCH}, "id"),
+ ],
+)
+def set_default_compare(
+ selections, compare_options, selected_type, indicators_dict, id
+):
+ area = id["index"]
+ # lbassil: add this condition to stop the exception for the main area
+ if area in indicators_dict[selections["theme"]] and area != "MAIN":
+ default = indicators_dict[selections["theme"]][area]["default_graph"]
+ fig_type = selected_type if selected_type else default
+ config = indicators_dict[selections["theme"]][area]["graphs"][fig_type]
+ default_compare = config.get("compare")
+
+ return (
+ "TOTAL"
+ if fig_type == "line" or default_compare is None
+ else default_compare
+ if default_compare in compare_options
+ else compare_options[1]["value"]
+ if len(compare_options) > 1
+ else compare_options[0]["value"]
+ )
+ return "TOTAL"
+
+
+@app.callback(
+ Output("main_area", "figure"),
+ Output("main_area_sources", "children"),
+ [
+ Input({"type": "area_options", "index": "MAIN"}, "value"),
+ Input({"type": "historical_data_toggle", "index": "MAIN"}, "value"),
+ Input("store", "data"),
+ ],
+ [
+ State("indicators", "data"),
+ ],
+)
+def main_figure(indicator, show_historical_data, selections, indicators_dict):
+ latest_data = not show_historical_data
+ options = indicators_dict[selections["theme"]]["MAIN"]["options"]
+
+ data = get_filtered_dataset(
+ [indicator],
+ selections["years"],
+ selections["countries"],
+ latest_data=latest_data,
+ )
+
+ # check if the dataframe is empty meaning no data to display as per the user's selection
+ if data.empty:
+ return EMPTY_CHART, ""
+
+ # lbassil: replace UNIT_MEASURE by Unit_name to use the name of the unit instead of the code
+ name = (
+ data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()[0]
+ if len(data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()) > 0
+ else ""
+ )
+ df_indicator_sources = df_sources[df_sources["Code"] == indicator]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ source = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+
+ options["labels"] = DEFAULT_LABELS.copy()
+ options["labels"]["OBS_VALUE"] = name
+ options["labels"]["text"] = "OBS_VALUE"
+ options["geojson"] = geo_json_countries
+ if latest_data:
+ # remove the animation frame and show all countries at once
+ options.pop("animation_frame")
+ # add the year to show on hover
+ options["hover_name"] = "TIME_PERIOD"
+
+ main_figure = px.choropleth_mapbox(data, **options)
+ main_figure.update_layout(margin={"r": 0, "t": 1, "l": 2, "b": 1})
+
+ # check if this area's config has an animation frame and hence a slider
+ if len(main_figure.layout["sliders"]) > 0:
+ # set last frame as the active one; i.e. select the max year as the default displayed year
+ main_figure.layout["sliders"][0]["active"] = len(main_figure.frames) - 1
+ # assign the data of the last year to the map; without this line the data will show the first year;
+ main_figure = go.Figure(
+ data=main_figure["frames"][-1]["data"],
+ frames=main_figure["frames"],
+ layout=main_figure.layout,
+ )
+ return main_figure, html.A(html.P(source), href=source_link, target="_blank")
+
+
+@app.callback(
+ Output({"type": "area", "index": MATCH}, "figure"),
+ Output({"type": "area_sources", "index": MATCH}, "children"),
+ [
+ Input("store", "data"),
+ Input({"type": "area_options", "index": MATCH}, "value"),
+ Input({"type": "area_breakdowns", "index": MATCH}, "value"),
+ Input({"type": "area_types", "index": MATCH}, "value"),
+ Input({"type": "exclude_outliers_toggle", "index": MATCH}, "value"),
+ ],
+ [
+ State("indicators", "data"),
+ State({"type": "area_options", "index": MATCH}, "id"),
+ ],
+)
+def area_figure(
+ selections,
+ indicator,
+ compare,
+ selected_type,
+ exclude_outliers,
+ indicators_dict,
+ id,
+):
+ # only run if indicator not empty
+ if not indicator:
+ return {}, {}
+ # check if it is the country profile page
+ is_country_profile = selections["theme"] == "COUNTRYPROFILE"
+
+ area = id["index"]
+ indicators = indicators_dict[selections["theme"]][area]["indicators"]
+ default_graph = indicators_dict[selections["theme"]][area].get(
+ "default_graph", "line"
+ )
+ fig_type = selected_type if selected_type else default_graph
+ fig_config = indicators_dict[selections["theme"]][area]["graphs"][fig_type]
+ options = fig_config.get("options")
+ traces = fig_config.get("trace_options")
+ dimension = False if fig_type == "line" or compare == "TOTAL" else compare
+
+ indicator_name = str(indicator_names.get(indicator, ""))
+ # do we need `indicator_settings` below?
+ indicator_settings = (
+ indicators.get(indicator, {}) if type(indicators) is dict else {}
+ )
+ data = get_filtered_dataset(
+ [indicator],
+ selections["years"],
+ selections["countries"],
+ compare,
+ latest_data=False if fig_type == "line" or is_country_profile else True,
+ ).sort_values("OBS_VALUE", ascending=False)
+ # check if the dataframe is empty meaning no data to display as per the user's selection
+ if data.empty:
+ return EMPTY_CHART, ""
+
+ # check if the exclude outliers checkbox is checked
+ if exclude_outliers:
+ # filter the data to the remove the outliers
+ # (df < df.quantile(0.1)).any() (df > df.quantile(0.9)).any()
+ data["z_scores"] = np.abs(zscore(data["OBS_VALUE"])) # calculate z-scores of df
+ # filter the data entries to remove the outliers
+ data = data[(data["z_scores"] < 3) | (data["z_scores"].isnull())]
+
+ # lbassil: was UNIT_MEASURE
+ name = (
+ data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()[0]
+ if len(data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()) > 0
+ else ""
+ )
+ df_indicator_sources = df_sources[df_sources["Code"] == indicator]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ source = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+
+ options["labels"] = DEFAULT_LABELS.copy()
+ options["labels"]["OBS_VALUE"] = name
+
+ # set the chart title, wrap the text when the indicator name is too long
+ chart_title = textwrap.wrap(
+ indicator_name,
+ width=74,
+ )
+ chart_title = "
".join(chart_title)
+
+ # set the layout to center the chart title and change its font size and color
+ layout = go.Layout(
+ title=chart_title,
+ title_x=0.5,
+ font=dict(family="Arial", size=12),
+ legend=dict(x=0.9, y=0.5),
+ )
+
+ # Add this code to avoid having decimal year on the x-axis for time series charts
+ if fig_type == "line" or is_country_profile:
+ data.sort_values(by=["TIME_PERIOD"], inplace=True)
+ layout["xaxis"] = dict(
+ tickmode="linear",
+ tick0=selections["years"][0],
+ dtick=1,
+ categoryorder="total ascending",
+ )
+
+ if dimension:
+ # lbassil: use the dimension name instead of the code
+ dimension_name = str(dimension_names.get(dimension, ""))
+ options["color"] = dimension_name
+ if compare == "WEALTH_QUINTILE":
+ wealth_dict = {
+ "Lowest": 0,
+ "Second": 1,
+ "Middle": 2,
+ "Fourth": 3,
+ "Highest": 4,
+ }
+ data.sort_values(
+ by=[dimension], key=lambda x: x.map(wealth_dict), inplace=True
+ )
+ else:
+ # sort by the compare value to have the legend in the right ascending order
+ data.sort_values(by=[dimension], inplace=True)
+
+ fig = getattr(px, fig_type)(data, **options)
+ fig.update_layout(layout)
+ if traces:
+ fig.update_traces(**traces)
+
+ return fig, html.A(html.P(source), href=source_link, target="_blank")
+'''
\ No newline at end of file
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/base_page_old2.py b/transmonee_dashboard/src/transmonee_dashboard/pages/base_page_old2.py
new file mode 100644
index 00000000..08636c72
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/base_page_old2.py
@@ -0,0 +1,617 @@
+import textwrap
+
+import dash
+import dash_bootstrap_components as dbc
+import dash_core_components as dcc
+import dash_html_components as html
+import dash_treeview_antd
+import numpy as np
+import plotly.express as px
+import plotly.graph_objects as go
+import plotly.io as pio
+from dash.dependencies import MATCH, ClientsideFunction, Input, Output, State
+from scipy.stats import zscore
+
+from ..app import app
+from ..components import fa
+
+'''
+from . import (
+ countries,
+ countries_iso3_dict,
+ df_sources,
+ dimension_names,
+ geo_json_countries,
+ get_filtered_dataset,
+ indicator_names,
+ indicators_config,
+ programme_country_indexes,
+ selection_index,
+ selection_tree,
+ unicef_country_prog,
+ years,
+ get_search_countries,
+)
+'''
+
+from . import (
+ years,
+ countries,
+ get_search_countries,
+ selection_tree,
+ selection_index,
+ countries_iso3_dict
+)
+
+# set defaults
+pio.templates.default = "plotly_white"
+px.defaults.color_continuous_scale = px.colors.sequential.BuGn
+px.defaults.color_discrete_sequence = px.colors.qualitative.Dark24
+
+colours = [
+ "primary",
+ "success",
+ "warning",
+ "danger",
+ "secondary",
+ "info",
+ "success",
+ "danger",
+]
+AREA_KEYS = ["MAIN", "AREA_1", "AREA_2", "AREA_3", "AREA_4", "AREA_5", "AREA_6"]
+DEFAULT_LABELS = {
+ "Country_name": "Country",
+ "TIME_PERIOD": "Year",
+ "Sex_name": "Sex",
+ "Residence_name": "Residence",
+ "Age_name": "Age",
+ "Wealth_name": "Wealth Quintile",
+}
+CARD_TEXT_STYLE = {"textAlign": "center", "color": "#0074D9"}
+
+EMPTY_CHART = {
+ "layout": {
+ "xaxis": {"visible": False},
+ "yaxis": {"visible": False},
+ "annotations": [
+ {
+ "text": "No data is available for the selected filters",
+ "xref": "paper",
+ "yref": "paper",
+ "showarrow": False,
+ "font": {"size": 28},
+ }
+ ],
+ }
+}
+
+
+def get_base_layout(**kwargs):
+ indicators_dict = kwargs.get("indicators")
+ main_title = kwargs.get("main_title")
+ themes_row_style = {"verticalAlign": "center", "display": "flex"}
+ country_dropdown_style = {"display": "none"}
+ countries_filter_style = {"display": "block"}
+
+ return html.Div(
+ [
+ dcc.Store(id="indicators", data=indicators_dict),
+ dcc.Location(id="theme"),
+ html.Div(
+ className="heading",
+ style={"padding": 36},
+ children=[
+ html.Div(
+ className="heading-content",
+ children=[
+ html.Div(
+ className="heading-panel",
+ style={"padding": 20},
+ children=[
+ html.H1(
+ main_title,
+ id="main_title",
+ className="heading-title",
+ ),
+ html.P(
+ id="subtitle",
+ className="heading-subtitle",
+ ),
+ ],
+ ),
+ ],
+ ),
+
+ dbc.Row(
+ children=[
+ dbc.Col(
+ [
+ dbc.Row(
+ [
+ dbc.ButtonGroup(
+ id="themes",
+ ),
+ ],
+ id="theme-row",
+ # width=4,
+ className="my-2",
+ # no_gutters=True,
+ justify="center",
+ style=themes_row_style,
+ ),
+ dbc.Row(
+ [
+ dbc.DropdownMenu(
+ label=f"Years: {years[0]} - {years[-1]}",
+ id="collapse-years-button",
+ className="m-2",
+ color="info",
+ # block=True,
+ children=[
+ dbc.Card(
+ dcc.RangeSlider(
+ id="year_slider",
+ min=0,
+ max=len(years) - 1,
+ step=None,
+ marks={
+ index: str(year)
+ for index, year in enumerate(
+ years
+ )
+ },
+ value=[0, len(years) - 1],
+ ),
+ style={
+ "maxHeight": "250px",
+ "minWidth": "500px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+
+ dcc.Dropdown(
+ id="country_profile_selector",
+ options=get_search_countries(False),
+ value="ALB",
+ multi=False,
+ placeholder="Select a country...",
+ className="m-2",
+ style=country_dropdown_style,
+ ),
+ dbc.DropdownMenu(
+ label=f"Countries: {len(countries)}",
+ id="collapse-countries-button",
+ className="m-2",
+ color="info",
+ style=countries_filter_style,
+ children=[
+ dbc.Card(
+ dash_treeview_antd.TreeView(
+ id="country_selector",
+ multiple=True,
+ checkable=True,
+ checked=["0"],
+ # selected=[],
+ expanded=["0"],
+ data=selection_tree,
+ ),
+ style={
+ "maxHeight": "250px",
+ # "maxWidth": "300px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+ # dbc.FormGroup(
+ # [
+ # dbc.Checkbox(
+ # id="programme-toggle",
+ # className="custom-control-input",
+ # ),
+ # dbc.Label(
+ # "UNICEF Country Programmes",
+ # html_for="programme-toggle",
+ # className="custom-control-label",
+ # color="primary",
+ # ),
+ # ],
+ # className="custom-control custom-switch m-2",
+ # check=True,
+ # inline=True,
+ # style=programme_toggle_style,
+ # ),
+ ],
+ id="filter-row",
+ no_gutters=True,
+ justify="center",
+ ),
+ ]
+ ),
+ ],
+ # sticky="top",
+ className="sticky-top bg-light",
+ ),
+ dbc.Row(
+ [
+ dbc.CardDeck(
+ id="cards_row",
+ className="mt-3",
+ ),
+ ],
+ justify="center",
+ ),
+ html.Br(),
+ ],
+ ),
+ ]
+ )
+
+
+def get_card_popover_body(sources):
+ """This function is used to generate the list of countries that are part of the card's
+ displayed result; it displays the countries as a list, each on a separate line
+
+ Args:
+ sources (_type_): _description_
+
+ Returns:
+ _type_: _description_
+ """
+ countries = []
+ # lbassil: added this condition to stop the exception when sources is empty
+ if len(sources) > 0:
+ for index, source_info in sources.sort_values(by="OBS_VALUE").iterrows():
+ countries.append(f"- {index[0]}, {source_info[0]} ({index[1]})")
+ card_countries = "\n".join(countries)
+ return card_countries
+ else:
+ return "NA"
+
+
+def make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+):
+ card = dbc.Card(
+ [
+ dbc.CardBody(
+ [
+ html.H1(
+ indicator_header,
+ className="display-4",
+ style={
+ # "fontSize": 50,
+ "textAlign": "center",
+ "color": "#1cabe2",
+ },
+ ),
+ html.H4(suffix, className="card-title"),
+ html.P(name, className="lead"),
+ html.Div(
+ fa("fas fa-info-circle"),
+ id=f"{card_id}_info",
+ # className="float-right",
+ style={
+ "position": "absolute",
+ "bottom": "10px",
+ "right": "10px",
+ },
+ ),
+ ],
+ style={
+ # "fontSize": 50,
+ "textAlign": "center",
+ },
+ ),
+ dbc.Popover(
+ [
+ dbc.PopoverHeader(
+ html.A(
+ html.P(f"Sources: {indicator_sources}"),
+ href=source_link,
+ target="_blank",
+ )
+ ),
+ dbc.PopoverBody(
+ dcc.Markdown(get_card_popover_body(numerator_pairs))
+ ),
+ ],
+ id="hover",
+ target=f"{card_id}_info",
+ trigger="hover",
+ ),
+ ],
+ color="primary",
+ outline=True,
+ id=card_id,
+ )
+ return card
+
+
+@app.callback(
+ Output("store", "data"),
+ Output("country_selector", "checked"),
+ # Output("programme-toggle", "checked"),
+ Output("collapse-years-button", "label"),
+ Output("collapse-countries-button", "label"),
+ [
+ Input("theme", "hash"),
+ Input("year_slider", "value"),
+ Input("country_selector", "checked"),
+ # Input("programme-toggle", "checked"),
+ # Input("country_profile_selector", "value"),
+ ],
+ State("indicators", "data"),
+)
+def apply_filters(
+ theme,
+ years_slider,
+ country_selector,
+ # programme_toggle,
+ # selected_country,
+ indicators,
+):
+ print("apply_filters called")
+ ctx = dash.callback_context
+ selected = ctx.triggered[0]["prop_id"].split(".")[0]
+ countries_selected = set()
+ current_theme = theme[1:].upper() if theme else next(iter(indicators.keys()))
+ # check if it is the country profile page
+ is_country_profile = current_theme == "COUNTRYPROFILE"
+ # check if the user clicked on the generate button in the country profile page
+ '''
+ if is_country_profile:
+ key_list = list(countries_iso3_dict.keys())
+ val_list = list(countries_iso3_dict.values())
+ # get the name of the selected country in the dropdown to filter the data accordingly
+ countries_selected = (
+ [key_list[val_list.index(selected_country)]] if selected_country else []
+ )
+ elif programme_toggle and selected == "programme-toggle":
+ countries_selected = unicef_country_prog
+ country_selector = programme_country_indexes
+ # Add the condition to know when the user unchecks the UNICEF country programs!
+ elif not country_selector or (
+ not programme_toggle and selected == "programme-toggle"
+ ):
+ countries_selected = countries
+ # Add this to check all the items in the selection tree
+ country_selector = ["0"]
+ else:
+ for index in country_selector:
+ countries_selected.update(selection_index[index])
+ if countries_selected == countries:
+ # if all countries are all selected then stop
+ break
+ '''
+ for index in country_selector:
+ countries_selected.update(selection_index[index])
+ if countries_selected == countries:
+ # if all countries are all selected then stop
+ break
+ countries_selected = list(countries_selected)
+ country_text = f"{len(countries_selected)} Selected"
+ # need to include the last selected year as it was exluded in the previous method
+ selected_years = years[years_slider[0]: years_slider[1] + 1]
+ # selected_years = years[slice(*years_slider)]
+
+ # Use the dictionary to return the values of the selected countries based on the SDMX ISO3 codes
+ countries_selected_codes = [
+ countries_iso3_dict[country] for country in countries_selected
+ ]
+
+ print("countries_selected")
+ print(countries_selected)
+ print("country_text")
+ print(country_text)
+ print("selected_years")
+ print(selected_years)
+ print("countries_selected_codes")
+ print(countries_selected_codes)
+ selections = dict(
+ theme=current_theme,
+ indicators_dict=indicators,
+ years=selected_years,
+ countries=countries_selected_codes,
+ is_adolescent=("ADOLESCENT" in indicators),
+ )
+
+ print("SELECTION")
+ print(selections)
+
+ return (
+ selections,
+ country_selector,
+ # countries_selected == unicef_country_prog,
+ f"Years: {selected_years[0]} - {selected_years[-1]}",
+ "Countries: {}".format(country_text),
+ )
+
+
+# TODO: Move to client side call back
+@app.callback(
+ Output("collapse-years", "is_open"),
+ Output("collapse-countries", "is_open"),
+ Output("collapse-engagements", "is_open"),
+ [
+ Input("collapse-years-button", "n_clicks"),
+ Input("collapse-countries-button", "n_clicks"),
+ Input("collapse-engagements-button", "n_clicks"),
+ ],
+ [
+ State("collapse-years-button", "is_open"),
+ State("collapse-countries-button", "is_open"),
+ State("collapse-engagements-button", "is_open"),
+ ],
+)
+def toggle_collapse(n1, n2, n3, is_open1, is_open2, is_open3):
+ ctx = dash.callback_context
+
+ if not ctx.triggered:
+ return False, False, False
+ else:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+
+ if button_id == "collapse-years-button" and n1:
+ return not is_open1, False, False
+ elif button_id == "collapse-countries-button" and n2:
+ return False, not is_open2, False
+ elif button_id == "collapse-engagements-button" and n3:
+ return False, False, not is_open3
+ return False, False, False
+
+
+def indicator_card(
+ selections,
+ card_id,
+ name,
+ numerator,
+ suffix,
+ denominator=None,
+ absolute=False,
+ average=False,
+ min_max=False,
+ sex_code=None,
+ age_group=None,
+):
+ indicators = numerator.split(",")
+
+ # TODO: Change to use albertos config
+ # lbassil: had to change this to cater for 2 dimensions set to the indicator card like age and sex
+ breakdown = "TOTAL"
+ # define the empty dimensions dict to be filled based on the card data filters
+ dimensions = {}
+ if age_group is not None:
+ dimensions["AGE"] = [age_group]
+ if sex_code is not None:
+ dimensions["SEX"] = [sex_code]
+
+ filtered_data = get_filtered_dataset(
+ indicators,
+ selections["years"],
+ selections["countries"],
+ breakdown,
+ dimensions,
+ latest_data=True,
+ )
+
+ df_indicator_sources = df_sources[df_sources["Code"].isin(indicators)]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ indicator_sources = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ # lbassil: add this check because we are getting an exception where there is no data; i.e. no totals for all dimensions mostly age for the selected indicator
+ if filtered_data.empty:
+ indicator_header = "No data"
+ indicator_sources = "NA"
+ numerator_pairs = []
+ return make_card(
+ card_id,
+ name,
+ indicator_header,
+ indicator_sources,
+ source_link,
+ numerator_pairs,
+ )
+
+ # select last value for each country
+ indicator_values = (
+ filtered_data.groupby(
+ [
+ "Country_name",
+ "TIME_PERIOD",
+ ]
+ ).agg({"OBS_VALUE": "sum", "CODE": "count"})
+ ).reset_index()
+
+ numerator_pairs = (
+ indicator_values[indicator_values.CODE == len(indicators)]
+ .groupby("Country_name", as_index=False)
+ .last()
+ .set_index(["Country_name", "TIME_PERIOD"])
+ )
+
+ if suffix.lower() == "countries":
+ # this is a hack to accomodate small cases (to discuss with James)
+ if "FREE" in numerator:
+ # trick to filter number of years of free education
+ indicator_sum = (numerator_pairs.OBS_VALUE >= 1).to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ numerator_pairs = numerator_pairs[numerator_pairs.OBS_VALUE >= 1]
+ else:
+ # trick to accomodate cards for admin exams (AND for boolean indicators)
+ # filter exams according to number of indicators
+ indicator_sum = (
+ (numerator_pairs.OBS_VALUE == len(indicators)).to_numpy().sum()
+ )
+ sources = numerator_pairs.index.tolist()
+
+ else:
+ indicator_sum = numerator_pairs["OBS_VALUE"].to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ if average and len(sources) > 1:
+ indicator_sum = indicator_sum / len(sources)
+
+ # define indicator header text: the resultant number except for the min-max range
+ if min_max and len(sources) > 1:
+ indicator_min = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].min())
+ indicator_max = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].max())
+ indicator_header = f"[{indicator_min} - {indicator_max}]"
+ else:
+ indicator_header = "{:,.0f}".format(indicator_sum)
+
+ return make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+ )
+
+@app.callback(
+ Output("cards_row", "children"),
+ [
+ Input("store", "data"),
+ ],
+ [State("cards_row", "children"), State("indicators", "data")],
+)
+def show_cards(selections, current_cards, indicators_dict):
+ cards = []
+
+ cards = [
+ indicator_card(
+ selections,
+ f"card-{num}",
+ card["name"],
+ card["indicator"],
+ card["suffix"],
+ card.get("denominator"),
+ card.get("absolute"),
+ card.get("average"),
+ card.get("min_max"),
+ card.get("sex"),
+ card.get("age"),
+ )
+ for num, card in enumerate(indicators_dict[selections["theme"]]["CARDS"])
+ ]
+
+ return cards
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/child_education.py b/transmonee_dashboard/src/transmonee_dashboard/pages/child_education.py
index 0d6a627b..91abefbc 100644
--- a/transmonee_dashboard/src/transmonee_dashboard/pages/child_education.py
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/child_education.py
@@ -1,672 +1,27 @@
-from collections import defaultdict
import plotly.express as px
-from . import data, years
from .base_page import get_base_layout
+
indicators_dict = {
"PARTICIPATION": {
"NAME": "Education access and participation",
"CARDS": [
{
"name": "Who are Out-of-School",
- "indicator": "EDUNF_OFST_L1,EDUNF_OFST_L2,EDUNF_OFST_L3",
+ "indicator": "REPROV_EFFINAL_MUNICIPAL",
"suffix": "Primary to upper secondary aged Children and Adolescents",
- "age": "SCHOOL_AGE",
- },
- {
- "name": "Who are Out-of-School",
- "indicator": "EDUNF_OFST_L1,EDUNF_OFST_L2,EDUNF_OFST_L3",
- "suffix": "Primary to upper secondary aged Girls",
- "sex": "F",
- "age": "SCHOOL_AGE",
- },
- {
- "name": "Who are Out-of-School",
- "indicator": "EDUNF_OFST_L1_UNDER1",
- "suffix": "Children one year younger than the official primary entry age",
- "age": "UNDER1_SCHOOL_ENTRY",
- },
- ],
- "MAIN": {
- "name": "Out-of-School Children",
- "geo": "Country_name", # REF_AREA
- "options": dict(
- locations="REF_AREA",
- featureidkey="id",
- color="OBS_VALUE",
- color_continuous_scale=px.colors.sequential.GnBu,
- mapbox_style="carto-positron",
- zoom=2,
- center={"lat": 62.995158, "lon": 88.048713},
- opacity=0.5,
- labels={
- "OBS_VALUE": "Value",
- "REF_AREA": "ISO3 Code",
- "TIME_PERIOD": "Year",
- "Country_name": "Country",
- },
- hover_data={
- "OBS_VALUE": True,
- "REF_AREA": False,
- "Country_name": True,
- "TIME_PERIOD": True,
- },
- animation_frame="TIME_PERIOD",
- height=750,
- ),
- "indicators": [
- "EDUNF_ROFST_L1",
- "EDUNF_ROFST_L2",
- "EDUNF_ROFST_L3",
- "EDUNF_OFST_L1",
- "EDUNF_OFST_L2",
- "EDUNF_OFST_L3",
- "EDU_SDG_PRYA",
- "EDUNF_ROFST_L1_UNDER1",
- "EDUNF_ROFST_L1T3",
- ],
- "default": "EDUNF_ROFST_L1",
- },
- "AREA_1": {
- "name": "Education entry and transition",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDUNF_ROFST_L1",
- "EDUNF_ROFST_L2",
- "EDUNF_ROFST_L3",
- "EDUNF_ROFST_L1_UNDER1",
- "EDUNF_ROFST_L1T3",
- "EDUNF_STU_L1_TOT",
- "EDUNF_STU_L2_TOT",
- "EDUNF_STU_L3_TOT",
- "EDUNF_GER_L2_GEN",
- "EDUNF_GER_L2_VOC",
- "EDUNF_GER_L3",
- "EDUNF_GER_L3_GEN",
- "EDUNF_GER_L3_VOC",
- ],
- "default": "EDUNF_ROFST_L1",
- },
- "AREA_2": {
- "name": "Education entry and transition",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "line",
- "indicators": [
- "EDUNF_NER_L02",
- "EDUNF_NERA_L1_UNDER1",
- "EDUNF_NERA_L1",
- "EDUNF_NERA_L2",
- "EDUNF_GER_L1",
- "EDUNF_GER_L2",
- "EDUNF_GER_L3",
- "EDUNF_NIR_L1_ENTRYAGE",
- "EDUNF_TRANRA_L2",
- ],
- "default": "EDUNF_TRANRA_L2",
- },
- "AREA_3": {
- "name": "Safe and inclusive learning environments",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDU_SDG_SCH_L1",
- "EDU_SDG_SCH_L2",
- "EDU_SDG_SCH_L3",
- "WS_SCH_H-B",
- "WS_SCH_S-B",
- "WS_SCH_W-B",
- "EDU_CHLD_DISAB",
- "EDU_CHLD_DISAB_GENERAL",
- "EDU_CHLD_DISAB_SPECIAL",
- "EDU_CHLD_DISAB_GENERAL_BOARDING",
- "EDU_CHLD_DISAB_SPECIAL_BOARDING",
- "EDU_CHLD_DISAB_HOME",
- "EDU_SDG_PRYA",
- ],
- "default": "EDU_CHLD_DISAB",
- },
- "AREA_4": {
- "name": "Education completion",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDUNF_CR_L1",
- "EDUNF_CR_L2",
- "EDUNF_CR_L3",
- "EDUNF_DR_L1",
- "EDUNF_DR_L2",
- "EDUNF_GER_L2",
- ],
- "default": "EDUNF_CR_L1",
- },
- },
- "QUALITY": {
- "NAME": "Learning quality and skills",
- "CARDS": [
- {
- "name": "(enrolled in the same grade for a second or further year) in primary and lower secondary education",
- "indicator": "EDUNF_RPTR_L1,EDUNF_RPTR_L2",
- "suffix": "Children and adolescent repeaters",
- },
- {
- "name": "from primary education",
- "indicator": "EDUNF_ESL_L1",
- "suffix": "Early school leavers",
- },
- {
- "name": "administering nationally representative learning assessment in both reading and math at the end of primary education",
- "indicator": "EDUNF_ADMIN_L1_GLAST_REA,EDUNF_ADMIN_L1_GLAST_MAT",
- "suffix": "Countries",
- },
- {
- "name": "participating in the latest round of PISA",
- "indicator": "EDU_PISA_MAT,EDU_PISA_REA,EDU_PISA_SCI",
- "suffix": "Countries",
- "absolute": True,
- },
- ],
- "MAIN": {
- "name": "What students know and can do",
- "geo": "Country_name",
- "options": dict(
- locations="REF_AREA",
- featureidkey="id",
- color="OBS_VALUE",
- color_continuous_scale=px.colors.sequential.GnBu,
- mapbox_style="carto-positron",
- zoom=2,
- center={"lat": 62.995158, "lon": 88.048713},
- opacity=0.5,
- labels={
- "OBS_VALUE": "Value",
- "Country_name": "Country",
- "TIME_PERIOD": "Year",
- "REF_AREA": "ISO3 Code",
- },
- hover_data={
- "OBS_VALUE": True,
- "REF_AREA": False,
- "Country_name": True,
- "TIME_PERIOD": True,
- },
- animation_frame="TIME_PERIOD",
- height=750,
- ),
- "indicators": ["EDU_PISA_MAT", "EDU_PISA_REA", "EDU_PISA_SCI"],
- "default": "EDU_PISA_MAT",
- },
- "AREA_1": {
- "name": "Foundational skills",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "ECD_CHLD_36-59M_LMPSL",
- "EDU_SDG_STU_L2_GLAST_MAT",
- "EDU_SDG_STU_L2_GLAST_REA",
- "EDU_SDG_STU_L1_GLAST_MAT",
- "EDU_SDG_STU_L1_G2OR3_MAT",
- "EDU_SDG_STU_L1_GLAST_REA",
- "EDU_SDG_STU_L1_G2OR3_REA",
- "EDUNF_LR_YOUTH",
- "EDUNF_LR_ADULT",
- ],
- "default": "EDU_SDG_STU_L2_GLAST_MAT",
- },
- "AREA_2": {
- "name": "Foundational skills",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "line",
- "indicators": [
- "ECD_CHLD_36-59M_LMPSL",
- "EDU_SDG_STU_L2_GLAST_MAT",
- "EDU_SDG_STU_L2_GLAST_REA",
- "EDU_SDG_STU_L1_GLAST_MAT",
- "EDU_SDG_STU_L1_G2OR3_MAT",
- "EDU_SDG_STU_L1_GLAST_REA",
- "EDU_SDG_STU_L1_G2OR3_REA",
- "EDUNF_LR_YOUTH",
- "EDUNF_LR_ADULT",
- ],
- "default": "EDU_SDG_STU_L2_GLAST_MAT",
- },
- "AREA_3": {
- "name": "Trained and qualified teachers",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDU_SDG_TRTP_L02",
- "EDU_SDG_TRTP_L1",
- "EDU_SDG_TRTP_L2",
- "EDU_SDG_TRTP_L3",
- ],
- "default": "EDU_SDG_TRTP_L2",
- },
- "AREA_4": {
- "name": "Trained and qualified teachers",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDU_SDG_QUTP_L02",
- "EDU_SDG_QUTP_L1",
- "EDU_SDG_QUTP_L2",
- "EDU_SDG_QUTP_L3",
- ],
- "default": "EDU_SDG_QUTP_L2",
- },
- },
- "SYSTEM": {
- "NAME": "Education System",
- "CARDS": [
- {
- "name": "Guaranteeing at least one year of free pre-primary education in their legal frameworks",
- "indicator": "EDU_SDG_FREE_EDU_L02",
- "suffix": "Countries",
- },
- {
- "name": "Enrolled in private institutions (primary, lower secondary and upper secondary education)",
- "indicator": "EDUNF_STU_L1_PRV,EDUNF_STU_L2_PRV,EDUNF_STU_L3_PRV",
- "suffix": "Children and Adolescents",
- },
- {
- "name": "Total in primary, lower secondary and upper secondary education",
- "indicator": "EDUNF_TEACH_L1,EDUNF_TEACH_L2,EDUNF_TEACH_L3",
- "suffix": "Classroom Teachers",
+ "age": "_T",
},
],
- "MAIN": {
- "name": "Guaranteeing and paying for education", # "Education Expenditures and Legal Frameworks",
- "geo": "Country_name",
- "options": dict(
- locations="REF_AREA",
- featureidkey="id",
- color="OBS_VALUE",
- color_continuous_scale=px.colors.sequential.GnBu,
- mapbox_style="carto-positron",
- zoom=2,
- center={"lat": 62.995158, "lon": 88.048713},
- opacity=0.5,
- labels={
- "OBS_VALUE": "Value",
- "Country_name": "Country",
- "TIME_PERIOD": "Year",
- "REF_AREA": "ISO3 Code",
- },
- hover_data={
- "OBS_VALUE": True,
- "REF_AREA": False,
- "Country_name": True,
- "TIME_PERIOD": True,
- },
- animation_frame="TIME_PERIOD",
- height=750,
- ),
- "indicators": [
- "EDU_FIN_EXP_PT_GDP",
- "EDU_FIN_EXP_PT_TOT",
- "EDU_SDG_FREE_EDU_L02",
- "EDU_SDG_COMP_EDU_L02",
- "EDU_FIN_EXP_CONST_PPP",
- ],
- "default": "EDU_FIN_EXP_PT_GDP",
- },
- "AREA_1": {
- "name": "Public and private enrolments",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDUNF_PRP_L02",
- "EDUNF_PRP_L1",
- "EDUNF_PRP_L2",
- "EDUNF_PRP_L3",
- "EDUNF_STU_L01_PUB",
- "EDUNF_STU_L02_PUB",
- "EDUNF_STU_L1_PUB",
- "EDUNF_STU_L2_PUB",
- "EDUNF_STU_L3_PUB",
- "EDUNF_STU_L01_PRV",
- "EDUNF_STU_L02_PRV",
- "EDUNF_STU_L1_PRV",
- "EDUNF_STU_L2_PRV",
- "EDUNF_STU_L3_PRV",
- ],
- "default": "EDUNF_PRP_L1",
- },
- "AREA_2": {
- "name": "Public and private enrolments",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "line",
- "indicators": [
- "EDUNF_PRP_L02",
- "EDUNF_PRP_L1",
- "EDUNF_PRP_L2",
- "EDUNF_PRP_L3",
- "EDUNF_STU_L01_PUB",
- "EDUNF_STU_L02_PUB",
- "EDUNF_STU_L1_PUB",
- "EDUNF_STU_L2_PUB",
- "EDUNF_STU_L3_PUB",
- "EDUNF_STU_L01_PRV",
- "EDUNF_STU_L02_PRV",
- "EDUNF_STU_L1_PRV",
- "EDUNF_STU_L2_PRV",
- "EDUNF_STU_L3_PRV",
- ],
- "default": "EDUNF_PRP_L2",
- },
- "AREA_3": {
- "name": "Government education expenditure",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- # "compare": "Sex",
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": [
- "EDU_FIN_EXP_L02",
- "EDU_FIN_EXP_L1",
- "EDU_FIN_EXP_L2",
- "EDU_FIN_EXP_L3",
- "EDU_FIN_EXP_L4",
- "EDU_FIN_EXP_L5T8",
- "EDU_FIN_EXP_CONST_PPP",
- ],
- "default": "EDU_FIN_EXP_L2",
- },
- "AREA_4": {
- "name": "Administration of learning assessments",
- "graphs": {
- "bar": {
- "options": dict(
- x="Country_name",
- y="OBS_VALUE",
- barmode="group",
- # text="TIME_PERIOD",
- text="OBS_VALUE",
- hover_name="TIME_PERIOD",
- ),
- },
- "line": {
- "options": dict(
- x="TIME_PERIOD",
- y="OBS_VALUE",
- color="Country_name",
- hover_name="Country_name",
- line_shape="spline",
- render_mode="svg",
- ),
- "trace_options": dict(mode="lines+markers"),
- },
- },
- "default_graph": "bar",
- "indicators": {
- "EDUNF_ADMIN_L1_GLAST_REA": {},
- "EDUNF_ADMIN_L1_GLAST_MAT": {},
- "EDUNF_ADMIN_L2_REA": {},
- "EDUNF_ADMIN_L2_MAT": {"dtype": "str"},
- "EDUNF_ADMIN_L1_G2OR3_REA": {},
- "EDUNF_ADMIN_L1_G2OR3_MAT": {},
- },
- "default": "EDUNF_ADMIN_L2_MAT",
- },
},
}
-main_title = "Education, Leisure, and Culture"
+main_title = "Education"
def get_layout(**kwargs):
kwargs["indicators"] = indicators_dict
kwargs["main_title"] = main_title
- return get_base_layout(**kwargs)
+ return get_base_layout(**kwargs)
\ No newline at end of file
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/home.py b/transmonee_dashboard/src/transmonee_dashboard/pages/home.py
index f5aff8f1..3e1fd207 100644
--- a/transmonee_dashboard/src/transmonee_dashboard/pages/home.py
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages/home.py
@@ -39,15 +39,9 @@ def get_layout(**kwargs):
# dbc.CardHeader(html.H3("State of Children Rights")),
dbc.CardBody(
[
- html.Img(
- src="assets/home.png",
- className="rounded mx-auto d-block",
- ),
- html.Br(),
- # html.H4(
- # "What you can find here...",
- # className="card-title",
- # ),
+ html.Div([
+ html.P("Brazil info")
+ ])
]
),
]
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/__init__.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/__init__.py
new file mode 100644
index 00000000..46cd8726
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/__init__.py
@@ -0,0 +1,1074 @@
+import json
+import logging
+import pathlib
+import collections
+from io import BytesIO
+from re import L
+import urllib
+
+import dash_html_components as html
+import numpy as np
+import pandas as pd
+import requests
+from requests.exceptions import HTTPError
+
+import pandasdmx as sdmx
+
+
+# TODO: Move all of these to env/setting vars from production
+sdmx_url = "https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/data/ECARO,TRANSMONEE,1.0/.{}....?format=csv&startPeriod={}&endPeriod={}"
+
+geo_json_file = (
+ pathlib.Path(__file__).parent.parent.absolute() / "assets/countries.geo.json"
+)
+with open(geo_json_file) as shapes_file:
+ geo_json_countries = json.load(shapes_file)
+
+with open(
+ pathlib.Path(__file__).parent.parent.absolute() / "assets/indicator_config.json"
+) as config_file:
+ indicators_config = json.load(config_file)
+
+unicef = sdmx.Request("UNICEF")
+
+metadata = unicef.dataflow("TRANSMONEE", provider="ECARO", version="1.0")
+dsd = metadata.structure["DSD_ECARO_TRANSMONEE"]
+
+indicator_names = {
+ code.id: code.name.en
+ for code in dsd.dimensions.get("INDICATOR").local_representation.enumerated
+}
+# lbassil: get the age groups code list as it is not in the DSD
+cl_age = unicef.codelist("CL_AGE", version="1.0")
+age_groups = sdmx.to_pandas(cl_age)
+dict_age_groups = age_groups["codelist"]["CL_AGE"].reset_index()
+age_groups_names = {
+ age["CL_AGE"]: age["name"]
+ for index, age in dict_age_groups.iterrows()
+ if age["CL_AGE"] != "_T"
+}
+
+units_names = {
+ unit.id: str(unit.name)
+ for unit in dsd.attributes.get("UNIT_MEASURE").local_representation.enumerated
+}
+
+# lbassil: get the names of the residence dimensions
+residence_names = {
+ residence.id: str(residence.name)
+ for residence in dsd.dimensions.get("RESIDENCE").local_representation.enumerated
+}
+
+# lbassil: get the names of the wealth quintiles dimensions
+wealth_names = {
+ wealth.id: str(wealth.name)
+ for wealth in dsd.dimensions.get("WEALTH_QUINTILE").local_representation.enumerated
+}
+
+gender_names = {"F": "Female", "M": "Male", "_T": "Total"}
+
+dimension_names = {
+ "SEX": "Sex_name",
+ "AGE": "Age_name",
+ "RESIDENCE": "Residence_name",
+ "WEALTH_QUINTILE": "Wealth_name",
+}
+
+adolescent_codes = [
+ "DM_ASYL_FRST",
+ "DM_ASYL_UASC",
+ "HT_ADOL_UNMETMED_FEAR",
+ "HT_ADOL_UNMETMED_HOPING",
+ "HT_ADOL_UNMETMED_NOKNOW",
+ "HT_ADOL_UNMETMED_NOTIME",
+ "HT_ADOL_UNMETMED_NOUNMET",
+ "HT_ADOL_UNMETMED_TOOEFW",
+ "HT_ADOL_UNMETMED_TOOEXP",
+ "HT_ADOL_UNMETMED_TOOFAR",
+ "HT_ADOL_UNMETMED_WAITING",
+ "HT_ADOL_UNMETMED_OTH",
+ "HT_CDRT_SELF_HARM",
+ "HT_SH_HIV_INCD",
+ "HVA_EPI_LHIV_0-19",
+ "HVA_EPI_LHIV_15-24",
+ "JJ_PRISIONERS_RT",
+ "MNCH_CSEC",
+ "MNCH_PNCMOM",
+ "PT_ADLT_PS_NEC",
+ "PT_CHLD_1-14_PS-PSY-V_CGVR",
+ "PT_CHLD_5-17_LBR_ECON",
+ "PT_CHLD_5-17_LBR_ECON-HC",
+ "PT_CHLD_CARED_BY_FOSTER",
+ "PT_CHLD_ENTEREDFOSTER",
+ "PT_CHLD_INRESIDENTIAL",
+ "PT_F_15-49_W-BTNG",
+ "PT_F_GE15_PS-SX-EM_V_PTNR_12MNTH",
+ "PT_M_15-49_W-BTNG",
+ "PT_ST_13-15_BUL_30-DYS",
+ "PV_AROPE",
+ "PV_AROPRT",
+ "PV_SD_MDP_MUHC",
+ "PV_SEV_MAT_DPRT",
+ "PV_SI_POV_EMP1",
+]
+
+adolescent_age_groups = [
+ "_T",
+ "Y14T17",
+ "Y0T13",
+ "Y1T4",
+ "Y0",
+ "Y0T14",
+ "Y14T15",
+ "Y16T17",
+ "Y16T19",
+ "Y16T24",
+ "Y10T14",
+ "Y15T19",
+ "Y15T24",
+ "Y10T19",
+ "Y0T17",
+ "Y15T49",
+ "Y20T24",
+ "Y18T49",
+ "Y18T29",
+ "Y0T24",
+ "Y25T39",
+ "Y40T59",
+ "Y_GE50",
+ "Y_GE15",
+ "Y1T14",
+ "Y2T14",
+ "Y5T14",
+ "Y5T17",
+ "Y7T17",
+ "Y10T17",
+ "Y13T15",
+ "Y15",
+ "Y11T15",
+ "Y12T17",
+ "Y0T15",
+ "Y0T4",
+ "M0",
+]
+
+# Add all indicators found in the data dictionary to get their data to the query data page
+data_query_codes = [
+ "DM_BRTS",
+ # "DM_POP_TOT",
+ # "DM_AVG_POP_TOT",
+ # "DM_POP_PROP",
+ "DM_DPR_AGE",
+ "DM_DPR_CHD",
+ "DM_DPR_OLD",
+ # "DM_IMG",
+ # "DM_EMG",
+ # "DM_NEXTRT_MG",
+ "DM_MRG_AGE",
+ "DM_DIV",
+ "DM_CRDIVRT",
+ "DM_CHLD_DIV",
+ "DM_CHLDRT_DIV",
+ "DM_POP_TOT_AGE",
+ "FT_WHS_PBR",
+ "MT_SP_DYN_CDRT_IN",
+ "DM_LIFE_EXP",
+ "HT_SH_HAP_HBSAG",
+ "HT_SH_TBS_INCD",
+ "HT_SH_SUD_ALCOL",
+ "HT_DIST79DTP3_P",
+ "IM_MCV2",
+ "HT_DIST79MCV2_P",
+ "HT_SDG_PM25",
+ "ECD_CHLD_36-59M_ADLT_SRC",
+ "HT_SH_SUD_TREAT",
+ "EDUNF_STU_L01_TOT",
+ "EDU_SDG_GER_L01",
+ "EDUNF_STU_L02_TOT",
+ "EDUNF_FEP_L02",
+ "EDUNF_NARA_L1_UNDER1",
+ "EDUNF_FEP_L1",
+ "EDUNF_FEP_L2",
+ "EDUNF_FEP_L3",
+ "EDUNF_STU_L3_GEN",
+ "EDUNF_STU_L3_VOC",
+ "EDUNF_STU_L3_GEN_PUB",
+ "EDUNF_STU_L3_GEN_PRV",
+ "EDUNF_STU_L3_VOC_PUB",
+ "EDUNF_STU_L3_VOC_PRV",
+ "EDUNF_GER_L1AND2",
+ "EDU_TIMSS_MAT4",
+ "EDU_TIMSS_SCI4",
+ "EDU_TIMSS_MAT8",
+ "EDU_TIMSS_SCI8",
+ "EDU_PIRLS_REA",
+ "EDUNF_REPP_L1",
+ "EDUNF_REPP_L2",
+ "EDUNF_FRP_L1",
+ "EDUNF_SR_L1",
+ "EDUNF_SR_L2",
+ "EDUNF_STU_L4_TOT",
+ "EDUNF_STU_L4_PUB",
+ "EDUNF_STU_L4_PRV",
+ "EDUNF_PRP_L4",
+ "EDUNF_FEP_L4",
+ "EDUNF_STU_L5T8_TOT",
+ "EDUNF_STU_L5T8_PUB",
+ "EDUNF_STU_L5T8_PRV",
+ "EDUNF_PRP_L5T8",
+ "EDUNF_FEP_L5T8",
+ "EDUNF_GER_GPI_L02",
+ "EDUNF_GER_GPI_L1",
+ "EDUNF_GER_GPI_L2",
+ "EDUNF_GER_GPI_L3",
+ "EDUNF_GER_GPI_L2AND3",
+ "EDUNF_PTR_L1",
+ "EDUNF_PTR_L2",
+ "EDUNF_PTR_L2AND3",
+ "EDUNF_PTR_L3",
+ "EDU_SDG_PTTR_L02",
+ "EDU_SDG_PTTR_L1",
+ "EDU_SDG_PTTR_L2",
+ "EDU_SDG_PTTR_L3",
+ "EDU_SDG_PQTR_L02",
+ "EDU_SDG_PQTR_L1",
+ "EDU_SDG_PQTR_L2",
+ "EDU_SDG_PQTR_L3",
+ "ECD_CHLD_U5_BKS-HM",
+ "ECD_CHLD_U5_PLYTH-HM",
+ "EDUNF_EA_L2T8",
+ "EDU_PISA_MAT2",
+ "EDU_PISA_MAT3",
+ "EDU_PISA_MAT4",
+ "EDU_PISA_MAT5",
+ "EDU_PISA_MAT6",
+ "EDU_PISA_REA2",
+ "EDU_PISA_REA3",
+ "EDU_PISA_REA4",
+ "EDU_PISA_REA5",
+ "EDU_PISA_REA6",
+ "EDU_PISA_SCI2",
+ "EDU_PISA_SCI3",
+ "EDU_PISA_SCI4",
+ "EDU_PISA_SCI5",
+ "EDU_PISA_SCI6",
+ "EDU_CHLD_DISAB_L02",
+ "EDU_CHLD_DISAB_L1",
+ "EDU_CHLD_DISAB_L2",
+ "EDU_CHLD_DISAB_L3",
+ "EDUNF_SAP_L02",
+ "EDUNF_SAP_L1",
+ "EDUNF_SAP_L2",
+ "EDUNF_SAP_L3",
+ "EDUNF_SAP_L2_GLAST",
+ "EDUNF_FRP_L2AND3",
+ "EDUNF_GER_GPI_L01",
+ "EDUNF_OFST_L1T3",
+ "EDUNF_PRP_L2AND3",
+ "EDUNF_STU_L2AND3_PRV",
+ "EDUNF_COMP_YR",
+ "EDUNF_COMP_YR_L02T3",
+ "EDUNF_PTR_L02",
+ "EDUNF_GECER_L01",
+ "EDUNF_GECER_L02",
+ "ED_ANAR_L1",
+ "ED_ANAR_L2",
+ "ED_ANAR_L3",
+ "EDU_SE_ACS_INTNT_L1",
+ "EDU_SE_ACS_INTNT_L2",
+ "EDU_SE_ACS_INTNT_L3",
+ "EDU_SE_ACS_CMPTR_L1",
+ "EDU_SE_ACS_CMPTR_L2",
+ "EDU_SE_ACS_CMPTR_L3",
+ "EDU_SE_ACS_ELECT_L1",
+ "EDU_SE_ACS_ELECT_L2",
+ "EDU_SE_ACS_ELECT_L3",
+ "EDU_SE_TOT_GPI_L1_REA",
+ "EDU_SE_TOT_GPI_L2_REA",
+ "EDU_SE_TOT_GPI_L1_MAT",
+ "EDU_SE_TOT_GPI_L2_MAT",
+ "EDU_SE_TOT_GPI_FS_LIT",
+ "EDU_SE_TOT_GPI_FS_NUM",
+ "EDU_SE_AGP_CPRA_L1",
+ "EDU_SE_AGP_CPRA_L2",
+ "EDU_SE_AGP_CPRA_L3",
+ "EDU_SE_GPI_PART",
+ "EDU_SE_GPI_PTNPRE",
+ "EDU_SE_GPI_TCAQ_L02",
+ "EDU_SE_GPI_TCAQ_L1",
+ "EDU_SE_GPI_TCAQ_L2",
+ "EDU_SE_GPI_TCAQ_L3",
+ "EDU_SE_NAP_ACHI_L1_REA",
+ "EDU_SE_NAP_ACHI_L2_REA",
+ "EDU_SE_NAP_ACHI_L1_MAT",
+ "EDU_SE_NAP_ACHI_L2_MAT",
+ "EDU_SE_IMP_FPOF_LIT",
+ "EDU_SE_IMP_FPOF_NUM",
+ "EDU_SE_LGP_ACHI_L1_REA",
+ "EDU_SE_LGP_ACHI_L2_REA",
+ "EDU_SE_LGP_ACHI_L1_MAT",
+ "EDU_SE_LGP_ACHI_L2_MAT",
+ "EDU_SE_ALP_CPLR_L1",
+ "EDU_SE_ALP_CPLR_L2",
+ "EDU_SE_ALP_CPLR_L3",
+ "EDU_SE_TOT_SESPI_L1_REA",
+ "EDU_SE_TOT_SESPI_L2_REA",
+ "EDU_SE_TOT_SESPI_L1_MAT",
+ "EDU_SE_TOT_SESPI_L2_MAT",
+ "EDU_SE_TOT_SESPI_FS_LIT",
+ "EDU_SE_TOT_SESPI_FS_NUM",
+ "EDU_SE_TOT_RUPI_L1_REA",
+ "EDU_SE_TOT_RUPI_L2_REA",
+ "EDU_SE_TOT_RUPI_L1_MAT",
+ "EDU_SE_TOT_RUPI_L2_MAT",
+ "EDU_SE_AWP_CPRA_L1",
+ "EDU_SE_AWP_CPRA_L2",
+ "EDU_SE_AWP_CPRA_L3",
+ "EDU_SE_GPI_ICTS_ATCH",
+ "EDU_SE_GPI_ICTS_CPT",
+ "EDU_SE_GPI_ICTS_CDV",
+ "EDU_SE_GPI_ICTS_SSHT",
+ "EDU_SE_GPI_ICTS_PRGM",
+ "EDU_SE_GPI_ICTS_PST",
+ "EDU_SE_GPI_ICTS_SFWR",
+ "EDU_SE_GPI_ICTS_TRFF",
+ "EDU_SE_GPI_ICTS_CMFL",
+ "EDUNF_STEM_GRAD_RT",
+ "DM_TOT_POP_PROSP",
+ "DM_SP_POP_BRTH_MF",
+ "DM_ADOL_YOUTH_POP",
+ "DM_REPD_AGE_POP",
+ "GN_MTNTY_LV_BNFTS",
+ "GN_PTNTY_LV_BNFTS",
+ "EC_GDI",
+ "EC_HCI_OVRL",
+ "EC_MIN_WAGE",
+ "EC_IQ_CPA_GNDR_XQ",
+ "EC_SIGI",
+ "EC_YOUTH_UNE_RT",
+ "EC_EAP_RT",
+ "EC_GNI_PCAP_PPP",
+ "EC_FB_BNK_ACCSS",
+ "SL_DOM_TSPD",
+ "SG_GEN_PARL",
+ "CR_VC_VOV_GDSD",
+ "PT_ADLS_10-14_LBR_HC",
+ "ECD_CHLD_U5_LFT-ALN",
+ "MNCH_MATERNAL_DEATHS",
+ "MNCH_SH_MMR_RISK",
+ "MNCH_SH_MMR_RISK_ZS",
+ "MNCH_INSTDEL",
+ "MNCH_BIRTH18",
+ "HT_NCD_BMI_18A",
+ "HVA_EPI_INF_ANN_15-24",
+ "CR_CCRI_VUL_HT",
+ "CR_CCRI_VUL_EDU",
+ "CR_CCRI_VUL_WASH",
+ "CR_CCRI_VUL_SP",
+ "CR_CCRI_VUL_ES",
+ "CR_CCRI",
+ "CR_CCRI_EXP_WS",
+ "CR_CCRI_EXP_RF",
+ "CR_CCRI_EXP_CF",
+ "CR_CCRI_EXP_TC",
+ "CR_CCRI_EXP_VBD",
+ "CR_CCRI_EXP_HEAT",
+ "CR_CCRI_EXP_AP",
+ "CR_CCRI_EXP_SWP",
+ "CR_CCRI_EXP_CESS",
+ "CR_UN_CHLD_RIGHTS",
+ "CR_UN_CHLD_SALE",
+ "CR_UN_RIGHTS_DISAB",
+]
+
+years = list(range(2010, 2022))
+
+# a key:value dictionary of countries where the 'key' is the country name as displayed in the selection
+# tree whereas the 'value' is the country name as returned by the sdmx list: https://sdmx.data.unicef.org/ws/public/sdmxapi/rest/codelist/UNICEF/CL_COUNTRY/1.0
+countries_iso3_dict = {
+ "Albania": "ALB",
+ "Andorra": "AND",
+ "Armenia": "ARM",
+ "Austria": "AUT",
+ "Azerbaijan": "AZE",
+ "Belarus": "BLR",
+ "Belgium": "BEL",
+ "Bosnia and Herzegovina": "BIH",
+ "Bulgaria": "BGR",
+ "Croatia": "HRV",
+ "Cyprus": "CYP",
+ "Czech Republic": "CZE",
+ "Denmark": "DNK",
+ "Estonia": "EST",
+ "Finland": "FIN",
+ "France": "FRA",
+ "Georgia": "GEO",
+ "Germany": "DEU",
+ "Greece": "GRC",
+ "Holy See": "VAT",
+ "Hungary": "HUN",
+ "Iceland": "ISL",
+ "Ireland": "IRL",
+ "Italy": "ITA",
+ "Kazakhstan": "KAZ",
+ "Kosovo (UN SC resolution 1244)": "XKX", # UNDP defines it as KOS
+ "Kyrgyzstan": "KGZ",
+ "Latvia": "LVA",
+ "Liechtenstein": "LIE",
+ "Lithuania": "LTU",
+ "Luxembourg": "LUX",
+ "Malta": "MLT",
+ "Monaco": "MCO",
+ "Montenegro": "MNE",
+ "Netherlands": "NLD",
+ "North Macedonia": "MKD",
+ "Norway": "NOR",
+ "Poland": "POL",
+ "Portugal": "PRT",
+ "Republic of Moldova": "MDA",
+ "Romania": "ROU",
+ "Russian Federation": "RUS",
+ "San Marino": "SMR",
+ "Serbia": "SRB",
+ "Slovakia": "SVK",
+ "Slovenia": "SVN",
+ "Spain": "ESP",
+ "Sweden": "SWE",
+ "Switzerland": "CHE",
+ "Tajikistan": "TJK",
+ "Turkey": "TUR",
+ "Turkmenistan": "TKM",
+ "Ukraine": "UKR",
+ "United Kingdom": "GBR",
+ "Uzbekistan": "UZB",
+}
+
+# create a list of country names in the same order as the countries_iso3_dict
+countries = list(countries_iso3_dict.keys())
+
+unicef_country_prog = [
+ "Albania",
+ "Armenia",
+ "Azerbaijan",
+ "Belarus",
+ "Bosnia and Herzegovina",
+ "Bulgaria",
+ "Croatia",
+ "Georgia",
+ "Greece",
+ "Kazakhstan",
+ "Kosovo (UN SC resolution 1244)",
+ "Kyrgyzstan",
+ "Montenegro",
+ "North Macedonia",
+ "Republic of Moldova",
+ "Romania",
+ "Serbia",
+ "Tajikistan",
+ "Turkey",
+ "Turkmenistan",
+ "Ukraine",
+ "Uzbekistan",
+]
+
+country_selections = [
+ {
+ "label": "Eastern Europe and Central Asia",
+ "value": [
+ {"label": "Caucasus", "value": ["Armenia", "Azerbaijan", "Georgia"]},
+ {
+ "label": "Western Balkans",
+ "value": [
+ "Albania",
+ "Bosnia and Herzegovina",
+ "Croatia",
+ "Kosovo (UN SC resolution 1244)",
+ "North Macedonia",
+ "Montenegro",
+ "Serbia",
+ ],
+ },
+ {
+ "label": "Central Asia",
+ "value": [
+ "Kazakhstan",
+ "Kyrgyzstan",
+ "Tajikistan",
+ "Turkmenistan",
+ "Uzbekistan",
+ ],
+ },
+ {
+ "label": "Eastern Europe",
+ "value": [
+ "Bulgaria",
+ "Belarus",
+ "Republic of Moldova",
+ "Romania",
+ "Russian Federation",
+ "Turkey",
+ "Ukraine",
+ ],
+ },
+ ],
+ },
+ {
+ "label": "Western Europe",
+ "value": [
+ "Andorra",
+ "Austria",
+ "Belgium",
+ "Cyprus",
+ "Czech Republic",
+ "Denmark",
+ "Estonia",
+ "Finland",
+ "France",
+ "Germany",
+ "Greece",
+ "Holy See",
+ "Hungary",
+ "Iceland",
+ "Ireland",
+ "Italy",
+ "Latvia",
+ "Liechtenstein",
+ "Lithuania",
+ "Luxembourg",
+ "Malta",
+ "Monaco",
+ "Netherlands",
+ "Norway",
+ "Poland",
+ "Portugal",
+ "San Marino",
+ "Slovakia",
+ "Slovenia",
+ "Spain",
+ "Sweden",
+ "Switzerland",
+ "United Kingdom",
+ ],
+ },
+ {
+ "label": "By EU Engagement",
+ "value": [
+ {
+ "label": "Central Asia",
+ "value": [
+ "Kazakhstan",
+ "Kyrgyzstan",
+ "Tajikistan",
+ "Turkmenistan",
+ "Uzbekistan",
+ ],
+ },
+ {
+ "label": "Eastern Partnership",
+ "value": [
+ "Armenia",
+ "Azerbaijan",
+ "Belarus",
+ "Georgia",
+ "Republic of Moldova",
+ "Ukraine",
+ ],
+ },
+ {
+ "label": "EFTA",
+ "value": ["Iceland", "Liechtenstein", "Norway", "Switzerland"],
+ },
+ {
+ "label": "EU Member States",
+ "value": [
+ "Andorra",
+ "Austria",
+ "Belgium",
+ "Bulgaria",
+ "Croatia",
+ "Cyprus",
+ "Czech Republic",
+ "Denmark",
+ "Estonia",
+ "Finland",
+ "France",
+ "Germany",
+ "Greece",
+ "Hungary",
+ "Ireland",
+ "Italy",
+ "Latvia",
+ "Lithuania",
+ "Luxembourg",
+ "Malta",
+ "Netherlands",
+ "Poland",
+ "Portugal",
+ "Romania",
+ "Slovakia",
+ "Slovenia",
+ "Spain",
+ "Sweden",
+ ],
+ },
+ {
+ "label": "Other",
+ "value": [
+ "Andorra",
+ "Monaco",
+ "Holy See",
+ "San Marino",
+ ],
+ },
+ {
+ "label": "Pre-accession countries",
+ "value": [
+ "Albania",
+ "Bosnia and Herzegovina",
+ "Kosovo (UN SC resolution 1244)",
+ "North Macedonia",
+ "Montenegro",
+ "Serbia",
+ "Turkey",
+ ],
+ },
+ {
+ "label": "Russian Federation",
+ "value": ["Russian Federation"],
+ },
+ {
+ "label": "United Kingdom (left EU on January 31, 2020)",
+ "value": ["United Kingdom"],
+ },
+ ],
+ },
+]
+
+data_sources = {
+ "CDDEM": "CountDown 2030",
+ "CCRI": "Children's Climate Risk Index",
+ "UN Treaties": "UN Treaties",
+ "ESTAT": "Euro Stat",
+ "Helix": " Health Entrepreneurship and LIfestyle Xchange",
+ "ILO": "International Labour Organization",
+ "WHO": "World Health Organization",
+ "Immunization Monitoring (WHO)": "Immunization Monitoring (WHO)",
+ "WB": "World Bank",
+ "OECD": "Organisation for Economic Co-operation and Development",
+ "SDG": "Sustainable Development Goals",
+ "UIS": "UNESCO Institute for Statistics",
+ "UNDP": "United Nations Development Programme",
+ "TMEE": "Transformative Monitoring for Enhanced Equity",
+}
+
+topics_subtopics = {
+ "All": ["All"],
+ "Education, Leisure, and Culture": [
+ {"Participation": "Education access and participation"},
+ {"Quality": "Learning quality and skills"},
+ {"System": "Education system"},
+ ],
+ "Family Environment and Protection": [
+ {"Violence": "Violence against Children and Women"},
+ {"Care": "Children without parental care"},
+ {"Justice": "Justice for Children"},
+ {"Marriage": "Child marriage and other harmful practices"},
+ {"Labour": "Child labour and other forms of exploitation"},
+ ],
+ "Health and Nutrition": [
+ {"HS": "Health System"},
+ {"MNCH": "Maternal, newborn and child health"},
+ {"Immunization": "Immunization"},
+ {"Nutrition": "Nutrition"},
+ {"Adolescent": "Adolescent physical, mental, and reproductive health"},
+ {"HIVAIDS": "HIV/AIDS"},
+ {"Wash": "Water, sanitation and hygiene"},
+ ],
+ "Poverty and Social Protection": [
+ {"Poverty": "Child Poverty and Material Deprivation"},
+ {"Protection": "Social protection system"},
+ ],
+ "Child Rights Landscape and Governance": [
+ {"Demography": "Demographics"},
+ {"Economy": "Political Economy"},
+ {"Migration": "Migration and Displacement"},
+ {"Access": "Access to Justice"},
+ {"Data": "Data on Children"},
+ {"Spending": "Public spending on Children"},
+ ],
+ "Participation and Civil Rights": [
+ {"Registration": "Birth registration and identity"},
+ {"Information": "Information, Internet and Protection of privacy"},
+ {"Leisure": "Education, Leisure, and Culture"},
+ ],
+}
+
+dict_topics_subtopics = {
+ "Education, Leisure, and Culture": [
+ "Education access and participation",
+ "Learning quality and skills",
+ "Education System",
+ ],
+ "Family Environment and Protection": [
+ "Violence against Children and Women",
+ "Children without parental care",
+ "Justice for Children",
+ "Child marriage and other harmful practices",
+ "Child labour and other forms of exploitation",
+ ],
+ "Health and Nutrition": [
+ "Health System",
+ "Maternal, newborn and child health",
+ "Immunization",
+ "Nutrition",
+ "Adolescent physical, mental, and reproductive health",
+ "HIV/AIDS",
+ "Water, sanitation and hygiene",
+ ],
+ "Poverty and Social Protection": [
+ "Child Poverty and Material Deprivation",
+ "Social protection system",
+ ],
+ "Child Rights Landscape and Governance": [
+ "Demographics",
+ "Political Economy",
+ "Migration and Displacement",
+ "Access to Justice",
+ "Data on Children",
+ "Public spending on Children",
+ ],
+ "Participation and Civil Rights": [
+ "Birth registration and identity",
+ "Information, Internet and Protection of privacy",
+ "Education, Leisure, and Culture",
+ ],
+}
+
+
+def get_search_countries(add_all):
+ all_countries = {"label": "All", "value": "All"}
+ countries_list = [
+ {
+ "label": key,
+ "value": countries_iso3_dict[key],
+ }
+ for key in countries_iso3_dict.keys()
+ ]
+ if add_all:
+ countries_list.insert(0, all_countries)
+ return countries_list
+
+
+def get_sector(subtopic):
+ for key in dict_topics_subtopics.keys():
+ if subtopic.strip() in dict_topics_subtopics.get(key):
+ return key
+ return ""
+
+
+# function to check if the config of a certain indicator are only about its dtype
+def only_dtype(config):
+ return list(config.keys()) == ["DTYPE"]
+
+
+def get_filtered_dataset(
+ indicators: list,
+ years: list,
+ country_codes: list,
+ breakdown: str = "TOTAL", # send default breakdown as Total
+ dimensions: dict = {},
+ latest_data: bool = True,
+) -> pd.DataFrame:
+
+ # TODO: This is temporary, need to move to config
+ # Add all dimensions by default to the keys
+ keys = {
+ "REF_AREA": country_codes,
+ "INDICATOR": indicators,
+ "SEX": [],
+ "AGE": [],
+ "RESIDENCE": [],
+ "WEALTH_QUINTILE": [],
+ }
+
+ # get the first indicator of the list... we have more than one indicator in the cards
+ indicator_config = (
+ indicators_config[indicators[0]] if indicators[0] in indicators_config else {}
+ )
+ # check if the indicator has special config, update the keys from the config
+ if indicator_config and not only_dtype(indicator_config):
+ # TODO: need to confirm that a TOTAL is always available when a config is available for the indicator
+ card_keys = indicator_config[breakdown]
+ if (
+ dimensions
+ ): # if we are sending cards related filters, update the keys with the set values
+ card_keys.update(dimensions)
+ keys.update(card_keys) # update the keys with the sent values
+
+ try:
+ data = unicef.data(
+ "TRANSMONEE",
+ provider="ECARO",
+ key=keys,
+ params=dict(
+ startPeriod=years[0],
+ endPeriod=years[-1],
+ lastNObservations=1 if latest_data else 0,
+ ),
+ dsd=dsd,
+ )
+ logging.debug(f"URL: {data.response.url} CACHED: {data.response.from_cache}")
+ except HTTPError as e:
+ logging.exception(f"URL: {e.response}", e)
+ # TODO: Maybe do something better here
+ return pd.DataFrame()
+
+ # lbassil: add sorting by Year to display the years in proper order on the x-axis
+ dtype = (
+ eval(indicator_config["DTYPE"]) if "DTYPE" in indicator_config else np.float64
+ )
+ data = (
+ data.to_pandas(attributes="o", rtype="rows", dtype=dtype)
+ .sort_values(by=["TIME_PERIOD"])
+ .reset_index()
+ )
+ data.rename(columns={"value": "OBS_VALUE", "INDICATOR": "CODE"}, inplace=True)
+ # replace Yes by 1 and No by 0
+ data.OBS_VALUE.replace({"Yes": "1", "No": "0", "<": "", ">": ""}, inplace=True)
+
+ # convert to numeric and round
+ data["OBS_VALUE"] = pd.to_numeric(data.OBS_VALUE, errors="coerce")
+ data.dropna(subset=["OBS_VALUE"], inplace=True)
+ data = data.round({"OBS_VALUE": 2})
+ # converting TIME_PERIOD to numeric: we should get integers by default
+ data["TIME_PERIOD"] = pd.to_numeric(data.TIME_PERIOD)
+
+ # lbassil: add the code to fill the country names
+ countries_val_list = list(countries_iso3_dict.values())
+
+ def create_labels(row):
+ row["Country_name"] = countries[countries_val_list.index(row["REF_AREA"])]
+ row["Unit_name"] = str(units_names.get(str(row["UNIT_MEASURE"]), ""))
+ row["Sex_name"] = str(gender_names.get(str(row["SEX"]), ""))
+ row["Residence_name"] = str(residence_names.get(str(row["RESIDENCE"]), ""))
+ row["Wealth_name"] = str(wealth_names.get(str(row["WEALTH_QUINTILE"]), ""))
+ row["Age_name"] = str(age_groups_names.get(str(row["AGE"]), ""))
+ return row
+
+ data = data.apply(create_labels, axis="columns")
+
+ return data
+
+
+# create two dicts, one for display tree and one with the index of all possible selections
+selection_index = collections.OrderedDict({"0": countries})
+selection_tree = dict(title="Select All", key="0", children=[])
+for num1, group in enumerate(country_selections):
+ parent = dict(title=group["label"], key=f"0-{num1}", children=[])
+ group_countries = []
+
+ for num2, region in enumerate(group["value"]):
+ child_region = dict(
+ title=region["label"] if "label" in region else region,
+ key=f"0-{num1}-{num2}",
+ children=[],
+ )
+ parent.get("children").append(child_region)
+ if "value" in region:
+ selection_index[f"0-{num1}-{num2}"] = (
+ region["value"]
+ if isinstance(region["value"], list)
+ else [region["value"]]
+ )
+ for num3, country in enumerate(region["value"]):
+ child_country = dict(title=country, key=f"0-{num1}-{num2}-{num3}")
+ if len(region["value"]) > 1:
+ # only create child nodes for more then one child
+ child_region.get("children").append(child_country)
+ selection_index[f"0-{num1}-{num2}-{num3}"] = [country]
+ group_countries.append(country)
+ else:
+ selection_index[f"0-{num1}-{num2}"] = [region]
+ group_countries.append(region)
+
+ selection_index[f"0-{num1}"] = group_countries
+ selection_tree.get("children").append(parent)
+
+programme_country_indexes = [
+ next(
+ key
+ for key, value in selection_index.items()
+ if value[0] == item and len(value) == 1
+ )
+ for item in unicef_country_prog
+]
+
+data = pd.DataFrame()
+data_query_inds = set(data_query_codes)
+
+# column data types coerced
+col_types = {
+ "COVERAGE_TIME": str,
+ "OBS_FOOTNOTE": str,
+ "OBS_VALUE": str,
+ "Frequency": str,
+ "Unit multiplier": str,
+ "OBS_STATUS": str,
+ "Observation Status": str,
+ "TIME_PERIOD": int,
+}
+
+# avoid a loop to query SDMX
+try:
+ data_query_sdmx = pd.read_csv(
+ sdmx_url.format("+".join(data_query_inds), years[0], years[-1]),
+ dtype=col_types,
+ storage_options={"Accept-Encoding": "gzip"},
+ low_memory=False,
+ )
+except urllib.error.HTTPError as e:
+ raise e
+
+data = data.append(data_query_sdmx)
+# no need to create column CODE, just rename indicator
+data.rename(columns={"INDICATOR": "CODE"}, inplace=True)
+
+# replace Yes by 1 and No by 0
+data.OBS_VALUE.replace({"Yes": "1", "No": "0"}, inplace=True)
+
+
+# check and drop non-numeric observations, eg: SDMX accepts > 95 as an OBS_VALUE
+filter_non_num = pd.to_numeric(data.OBS_VALUE, errors="coerce").isnull()
+if filter_non_num.any():
+ not_num_code_val = data[["CODE", "OBS_VALUE"]][filter_non_num]
+ f"Non-numeric observations in {not_num_code_val.CODE.unique()}\ndiscarded: {not_num_code_val.OBS_VALUE.unique()}"
+ data.drop(data[filter_non_num].index, inplace=True)
+
+# convert to numeric
+data["OBS_VALUE"] = pd.to_numeric(data.OBS_VALUE)
+data = data.round({"OBS_VALUE": 2})
+# print(data.shape)
+
+# TODO: calculations for children age population
+indicators = data["Indicator"].unique()
+
+# extract the indicators that have gender/sex disaggregation
+gender_indicators = data.groupby("CODE").agg({"SEX": "nunique"}).reset_index()
+# Keep only indicators with gender/sex disaggregation
+gender_indicators = gender_indicators[gender_indicators["SEX"] > 1]
+
+# path to excel data dictionary in repo
+github_url = "https://github.com/UNICEFECAR/data-etl/raw/proto_API/tmee/data_in/data_dictionary/indicator_dictionary_TM_v8.xlsx"
+data_dict_content = requests.get(github_url).content
+# Reading the downloaded content and turning it into a pandas dataframe and read Snapshot sheet from excel data-dictionary
+snapshot_df = pd.read_excel(BytesIO(data_dict_content), sheet_name="Snapshot")
+snapshot_df.dropna(subset=["Source_name"], inplace=True)
+snapshot_df["Source"] = snapshot_df["Source_name"].apply(lambda x: x.split(":")[0])
+# read indicators table from excel data-dictionary
+df_topics_subtopics = pd.read_excel(BytesIO(data_dict_content), sheet_name="Indicator")
+df_topics_subtopics.dropna(subset=["Issue"], inplace=True)
+df_sources = pd.merge(df_topics_subtopics, snapshot_df, how="outer", on=["Code"])
+# assign source = TMEE to all indicators without a source since they all come from excel data collection files
+df_sources.fillna("TMEE", inplace=True)
+# Concatenate sectors/subtopics dictionary value lists (mapping str lower)
+sitan_subtopics = list(map(str.lower, sum(dict_topics_subtopics.values(), [])))
+
+df_sources.rename(
+ columns={
+ "Name_x": "Indicator",
+ "Issue": "Subdomain",
+ },
+ inplace=True,
+)
+# filter the sources to keep only sitan related sectors and sub-topics
+df_sources["Subdomain"] = df_sources["Subdomain"].str.strip()
+df_sources["Domain"] = df_sources["Subdomain"].apply(
+ lambda x: get_sector(x) if not pd.isna(x) else ""
+)
+df_sources["Source_Full"] = df_sources["Source"].apply(
+ lambda x: data_sources[x] if not pd.isna(x) else ""
+)
+
+df_sources = df_sources[df_sources["Subdomain"].str.lower().isin(sitan_subtopics)]
+# read source table from excel data-dictionary and merge
+source_table_df = pd.read_excel(BytesIO(data_dict_content), sheet_name="Source")
+df_sources = df_sources.merge(
+ source_table_df[["Source_Id", "Source_Link"]],
+ on="Source_Id",
+ how="left",
+ sort=False,
+)
+# assign source link for TMEE, url: UNICEF_RDM/indicator_code
+tmee_source_link = df_sources.Source_Link.isnull()
+unicef_rdm_url = "https://data.unicef.org/indicator-profile/{helix_code}/"
+df_sources.loc[tmee_source_link, "Source_Link"] = df_sources[
+ tmee_source_link
+].Code.apply(lambda x: unicef_rdm_url.format(helix_code=x))
+df_sources_groups = df_sources.groupby("Source")
+df_sources_summary_groups = df_sources.groupby("Source_Full")
+# Extract the indicators' potential unique disaggregations.
+# Group by indicator code and keep only unique aggregations for the 4 possible dimensions:
+# Sex, Age, Residence and Wealth.
+indicators_disagg = (
+ data.groupby("CODE")
+ .agg(
+ {
+ "AGE": "nunique",
+ "SEX": "nunique",
+ "RESIDENCE": "nunique",
+ "WEALTH_QUINTILE": "nunique",
+ }
+ )
+ .reset_index()
+)
+# Filter the dimensions with count greater than 1 which means Total is there (default) in addition to other possible values.
+indicators_disagg_no_total = indicators_disagg[
+ (indicators_disagg["AGE"] > 1)
+ | (indicators_disagg["SEX"] > 1)
+ | (indicators_disagg["RESIDENCE"] > 1)
+ | (indicators_disagg["WEALTH_QUINTILE"] > 1)
+]
+
+# include the indicators with Total only to show in the data query
+indicators_disagg_with_total = indicators_disagg[
+ (indicators_disagg["AGE"] >= 1)
+ | (indicators_disagg["SEX"] >= 1)
+ | (indicators_disagg["RESIDENCE"] >= 1)
+ | (indicators_disagg["WEALTH_QUINTILE"] >= 1)
+]
+# Get the data for all the indicators having disaggregated data by any of the 4 dimensions.
+indicators_disagg_details = data[
+ data["CODE"].isin(indicators_disagg_with_total["CODE"])
+]
+
+# Filter the dataframe to be used in the data query to keep indicators code and the possible disaggregations.
+indicators_disagg_details = indicators_disagg_details[
+ ["CODE", "Age", "Sex", "Residence", "Wealth Quintile"]
+]
+indicators_disagg_details = indicators_disagg_details.drop_duplicates()
+
+# extract the indicators that have gender/sex disaggregation
+age_indicators_counts = data.groupby("CODE").agg({"AGE": "nunique"}).reset_index()
+# Keep only indicators with gender/sex disaggregation
+age_indicators_counts = age_indicators_counts[age_indicators_counts["AGE"] > 1]
+# age_indicators_counts.to_csv("age_indicators_counts.csv", index=True)
+age_indicators = pd.merge(data, age_indicators_counts, on=["CODE"])
+age_indicators = age_indicators[["CODE", "Indicator", "Age"]]
+age_indicators = age_indicators.drop_duplicates()
+age_indicators = age_indicators.sort_values(by=["CODE", "Age"])
+# age_indicators.to_csv("age_indicators.csv", index=False)
+
+# extract the indicators that have gender/sex disaggregation
+age_indicators_counts = data.groupby("CODE").agg({"AGE": "nunique"}).reset_index()
+# Keep only indicators with gender/sex disaggregation
+age_indicators_counts = age_indicators_counts[age_indicators_counts["AGE"] > 1]
+# age_indicators_counts.to_csv("age_indicators_counts.csv", index=True)
+age_indicators = pd.merge(data, age_indicators_counts, on=["CODE"])
+age_indicators = age_indicators[["CODE", "Indicator", "Age"]]
+age_indicators = age_indicators.drop_duplicates()
+age_indicators = age_indicators.sort_values(by=["CODE", "Age"])
+# age_indicators.to_csv("age_indicators.csv", index=False)
+
+
+def page_not_found(pathname):
+ return html.P("No page '{}'".format(pathname))
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/adolescent.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/adolescent.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/adolescent.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/adolescent.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/base_page.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/base_page.py
new file mode 100644
index 00000000..4f7d9bfc
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/base_page.py
@@ -0,0 +1,1096 @@
+import textwrap
+
+import dash
+import dash_bootstrap_components as dbc
+import dash_core_components as dcc
+import dash_html_components as html
+import dash_treeview_antd
+import numpy as np
+import plotly.express as px
+import plotly.graph_objects as go
+import plotly.io as pio
+from dash.dependencies import MATCH, ClientsideFunction, Input, Output, State
+from scipy.stats import zscore
+
+from ..app import app
+from ..components import fa
+from . import (
+ countries,
+ countries_iso3_dict,
+ df_sources,
+ dimension_names,
+ geo_json_countries,
+ get_filtered_dataset,
+ indicator_names,
+ indicators_config,
+ programme_country_indexes,
+ selection_index,
+ selection_tree,
+ unicef_country_prog,
+ years,
+ get_search_countries,
+)
+
+# set defaults
+pio.templates.default = "plotly_white"
+px.defaults.color_continuous_scale = px.colors.sequential.BuGn
+px.defaults.color_discrete_sequence = px.colors.qualitative.Dark24
+
+colours = [
+ "primary",
+ "success",
+ "warning",
+ "danger",
+ "secondary",
+ "info",
+ "success",
+ "danger",
+]
+AREA_KEYS = ["MAIN", "AREA_1", "AREA_2", "AREA_3", "AREA_4", "AREA_5", "AREA_6"]
+DEFAULT_LABELS = {
+ "Country_name": "Country",
+ "TIME_PERIOD": "Year",
+ "Sex_name": "Sex",
+ "Residence_name": "Residence",
+ "Age_name": "Age",
+ "Wealth_name": "Wealth Quintile",
+}
+CARD_TEXT_STYLE = {"textAlign": "center", "color": "#0074D9"}
+
+EMPTY_CHART = {
+ "layout": {
+ "xaxis": {"visible": False},
+ "yaxis": {"visible": False},
+ "annotations": [
+ {
+ "text": "No data is available for the selected filters",
+ "xref": "paper",
+ "yref": "paper",
+ "showarrow": False,
+ "font": {"size": 28},
+ }
+ ],
+ }
+}
+
+
+def make_area(area_name):
+ area_id = {"type": "area", "index": area_name}
+ popover_id = {"type": "area_sources", "index": area_name}
+ historical_data_style = {"display": "none"}
+ exclude_outliers_style = {"paddingLeft": 20, "display": "block"}
+ breakdowns_style = {"display": "block"}
+
+ # lbassil: still differentiating main area id from other areas ids because the call backs are still not unified
+ if area_name == "MAIN":
+ area_id = f"{area_name.lower()}_area"
+ popover_id = f"{area_name.lower()}_area_sources"
+ historical_data_style = {"display": "block"}
+ exclude_outliers_style = {"display": "none"}
+ breakdowns_style = {"display": "none"}
+
+ # lbassil: unifying both main and figure area generations by tweaking the ids and styles
+ area = dbc.Card(
+ [
+ dbc.CardHeader(
+ id={"type": "area_title", "index": area_name},
+ style={"fontWeight": "bold"},
+ ),
+ dbc.CardBody(
+ [
+ dcc.Dropdown(
+ id={"type": "area_options", "index": area_name},
+ className="dcc_control",
+ ),
+ html.Br(),
+ dbc.Checklist(
+ options=[
+ {
+ "label": "Show historical data",
+ "value": 1,
+ }
+ ],
+ value=[],
+ id={
+ "type": "historical_data_toggle",
+ "index": area_name,
+ },
+ switch=True,
+ style=historical_data_style,
+ ),
+ html.Br(),
+ dbc.RadioItems(
+ id={"type": "area_types", "index": area_name},
+ inline=True,
+ ),
+ dcc.Loading([dcc.Graph(id=area_id)]),
+ dbc.Checklist(
+ options=[
+ {
+ "label": "Exclude outliers ",
+ "value": 1,
+ }
+ ],
+ value=[1],
+ id={
+ "type": "exclude_outliers_toggle",
+ "index": area_name,
+ },
+ switch=True,
+ style=exclude_outliers_style,
+ ),
+ html.Br(),
+ dbc.RadioItems(
+ id={"type": "area_breakdowns", "index": area_name},
+ inline=True,
+ style=breakdowns_style,
+ ),
+ html.Div(
+ fa("fas fa-info-circle"),
+ id=f"{area_name.lower()}_area_info",
+ className="float-right",
+ ),
+ dbc.Popover(
+ [
+ dbc.PopoverHeader("Sources"),
+ dbc.PopoverBody(id=popover_id),
+ ],
+ id="hover",
+ target=f"{area_name.lower()}_area_info",
+ trigger="hover",
+ ),
+ ]
+ ),
+ ],
+ id={"type": "area_parent", "index": area_name},
+ )
+ return area
+
+
+def get_base_layout(**kwargs):
+ indicators_dict = kwargs.get("indicators")
+ main_title = kwargs.get("main_title")
+ is_country_profile = kwargs.get("is_country_profile")
+ country_dropdown_style = {"display": "none"}
+ themes_row_style = {"verticalAlign": "center", "display": "flex"}
+ countries_filter_style = {"display": "block"}
+ programme_toggle_style = {"display": "block"}
+ main_area_style = {"display": "block"}
+
+ if is_country_profile:
+ country_dropdown_style = {
+ "minWidth": 400,
+ "maxWidth": 600,
+ "paddingRight": 4,
+ "verticalAlign": "center",
+ "display": "visible",
+ }
+ themes_row_style = {"display": "none"}
+ countries_filter_style = {"display": "none"}
+ programme_toggle_style = {"display": "none"}
+ main_area_style = {"display": "none"}
+
+ return html.Div(
+ [
+ dcc.Store(id="indicators", data=indicators_dict),
+ dcc.Location(id="theme"),
+ html.Div(
+ className="heading",
+ style={"padding": 36},
+ children=[
+ html.Div(
+ className="heading-content",
+ children=[
+ html.Div(
+ className="heading-panel",
+ style={"padding": 20},
+ children=[
+ html.H1(
+ main_title,
+ id="main_title",
+ className="heading-title",
+ ),
+ html.P(
+ id="subtitle",
+ className="heading-subtitle",
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ ),
+ dbc.Row(
+ children=[
+ dbc.Col(
+ [
+ dbc.Row(
+ [
+ dbc.ButtonGroup(
+ id="themes",
+ ),
+ ],
+ id="theme-row",
+ # width=4,
+ className="my-2",
+ # no_gutters=True,
+ justify="center",
+ style=themes_row_style,
+ ),
+ dbc.Row(
+ [
+ dbc.DropdownMenu(
+ label=f"Years: {years[0]} - {years[-1]}",
+ id="collapse-years-button",
+ className="m-2",
+ color="info",
+ # block=True,
+ children=[
+ dbc.Card(
+ dcc.RangeSlider(
+ id="year_slider",
+ min=0,
+ max=len(years) - 1,
+ step=None,
+ marks={
+ index: str(year)
+ for index, year in enumerate(
+ years
+ )
+ },
+ value=[0, len(years) - 1],
+ ),
+ style={
+ "maxHeight": "250px",
+ "minWidth": "500px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+ dcc.Dropdown(
+ id="country_profile_selector",
+ options=get_search_countries(False),
+ value="ALB",
+ multi=False,
+ placeholder="Select a country...",
+ className="m-2",
+ style=country_dropdown_style,
+ ),
+ dbc.DropdownMenu(
+ label=f"Countries: {len(countries)}",
+ id="collapse-countries-button",
+ className="m-2",
+ color="info",
+ style=countries_filter_style,
+ children=[
+ dbc.Card(
+ dash_treeview_antd.TreeView(
+ id="country_selector",
+ multiple=True,
+ checkable=True,
+ checked=["0"],
+ # selected=[],
+ expanded=["0"],
+ data=selection_tree,
+ ),
+ style={
+ "maxHeight": "250px",
+ # "maxWidth": "300px",
+ },
+ className="overflow-auto",
+ body=True,
+ ),
+ ],
+ ),
+ dbc.FormGroup(
+ [
+ dbc.Checkbox(
+ id="programme-toggle",
+ className="custom-control-input",
+ ),
+ dbc.Label(
+ "UNICEF Country Programmes",
+ html_for="programme-toggle",
+ className="custom-control-label",
+ color="primary",
+ ),
+ ],
+ className="custom-control custom-switch m-2",
+ check=True,
+ inline=True,
+ style=programme_toggle_style,
+ ),
+ ],
+ id="filter-row",
+ no_gutters=True,
+ justify="center",
+ ),
+ ]
+ ),
+ ],
+ # sticky="top",
+ className="sticky-top bg-light",
+ ),
+ dbc.Row(
+ [
+ dbc.CardDeck(
+ id="cards_row",
+ className="mt-3",
+ ),
+ ],
+ justify="center",
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["MAIN"]],
+ style=main_area_style,
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["AREA_1", "AREA_2"]],
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["AREA_3", "AREA_4"]],
+ ),
+ html.Br(),
+ dbc.CardDeck(
+ [make_area(area) for area in ["AREA_5", "AREA_6"]],
+ ),
+ html.Br(),
+ ],
+ )
+
+
+def make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+):
+ card = dbc.Card(
+ [
+ dbc.CardBody(
+ [
+ html.H1(
+ indicator_header,
+ className="display-4",
+ style={
+ # "fontSize": 50,
+ "textAlign": "center",
+ "color": "#1cabe2",
+ },
+ ),
+ html.H4(suffix, className="card-title"),
+ html.P(name, className="lead"),
+ html.Div(
+ fa("fas fa-info-circle"),
+ id=f"{card_id}_info",
+ # className="float-right",
+ style={
+ "position": "absolute",
+ "bottom": "10px",
+ "right": "10px",
+ },
+ ),
+ ],
+ style={
+ # "fontSize": 50,
+ "textAlign": "center",
+ },
+ ),
+ dbc.Popover(
+ [
+ dbc.PopoverHeader(
+ html.A(
+ html.P(f"Sources: {indicator_sources}"),
+ href=source_link,
+ target="_blank",
+ )
+ ),
+ dbc.PopoverBody(
+ dcc.Markdown(get_card_popover_body(numerator_pairs))
+ ),
+ ],
+ id="hover",
+ target=f"{card_id}_info",
+ trigger="hover",
+ ),
+ ],
+ color="primary",
+ outline=True,
+ id=card_id,
+ )
+ return card
+
+
+def get_card_popover_body(sources):
+ """This function is used to generate the list of countries that are part of the card's
+ displayed result; it displays the countries as a list, each on a separate line
+
+ Args:
+ sources (_type_): _description_
+
+ Returns:
+ _type_: _description_
+ """
+ countries = []
+ # lbassil: added this condition to stop the exception when sources is empty
+ if len(sources) > 0:
+ for index, source_info in sources.sort_values(by="OBS_VALUE").iterrows():
+ countries.append(f"- {index[0]}, {source_info[0]} ({index[1]})")
+ card_countries = "\n".join(countries)
+ return card_countries
+ else:
+ return "NA"
+
+
+# TODO: Move to client side call back
+@app.callback(
+ Output("collapse-years", "is_open"),
+ Output("collapse-countries", "is_open"),
+ Output("collapse-engagements", "is_open"),
+ [
+ Input("collapse-years-button", "n_clicks"),
+ Input("collapse-countries-button", "n_clicks"),
+ Input("collapse-engagements-button", "n_clicks"),
+ ],
+ [
+ State("collapse-years-button", "is_open"),
+ State("collapse-countries-button", "is_open"),
+ State("collapse-engagements-button", "is_open"),
+ ],
+)
+def toggle_collapse(n1, n2, n3, is_open1, is_open2, is_open3):
+ ctx = dash.callback_context
+
+ if not ctx.triggered:
+ return False, False, False
+ else:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+
+ if button_id == "collapse-years-button" and n1:
+ return not is_open1, False, False
+ elif button_id == "collapse-countries-button" and n2:
+ return False, not is_open2, False
+ elif button_id == "collapse-engagements-button" and n3:
+ return False, False, not is_open3
+ return False, False, False
+
+
+@app.callback(
+ Output({"type": "area_parent", "index": MATCH}, "hidden"),
+ Input("theme", "hash"),
+ [
+ State("indicators", "data"),
+ State({"type": "area_parent", "index": MATCH}, "id"),
+ ],
+)
+def display_areas(theme, indicators_dict, id):
+ area = id["index"]
+ theme = theme[1:].upper() if theme else next(iter(indicators_dict.keys()))
+ return area not in indicators_dict[theme]
+
+
+@app.callback(
+ Output("store", "data"),
+ Output("country_selector", "checked"),
+ Output("programme-toggle", "checked"),
+ Output("collapse-years-button", "label"),
+ Output("collapse-countries-button", "label"),
+ [
+ Input("theme", "hash"),
+ Input("year_slider", "value"),
+ Input("country_selector", "checked"),
+ Input("programme-toggle", "checked"),
+ Input("country_profile_selector", "value"),
+ ],
+ State("indicators", "data"),
+)
+def apply_filters(
+ theme,
+ years_slider,
+ country_selector,
+ programme_toggle,
+ selected_country,
+ indicators,
+):
+ ctx = dash.callback_context
+ selected = ctx.triggered[0]["prop_id"].split(".")[0]
+ countries_selected = set()
+ current_theme = theme[1:].upper() if theme else next(iter(indicators.keys()))
+ # check if it is the country profile page
+ is_country_profile = current_theme == "COUNTRYPROFILE"
+ # check if the user clicked on the generate button in the country profile page
+ if is_country_profile:
+ key_list = list(countries_iso3_dict.keys())
+ val_list = list(countries_iso3_dict.values())
+ # get the name of the selected country in the dropdown to filter the data accordingly
+ countries_selected = (
+ [key_list[val_list.index(selected_country)]] if selected_country else []
+ )
+ elif programme_toggle and selected == "programme-toggle":
+ countries_selected = unicef_country_prog
+ country_selector = programme_country_indexes
+ # Add the condition to know when the user unchecks the UNICEF country programs!
+ elif not country_selector or (
+ not programme_toggle and selected == "programme-toggle"
+ ):
+ countries_selected = countries
+ # Add this to check all the items in the selection tree
+ country_selector = ["0"]
+ else:
+ for index in country_selector:
+ countries_selected.update(selection_index[index])
+ if countries_selected == countries:
+ # if all countries are all selected then stop
+ break
+
+ countries_selected = list(countries_selected)
+ country_text = f"{len(countries_selected)} Selected"
+ # need to include the last selected year as it was exluded in the previous method
+ selected_years = years[years_slider[0] : years_slider[1] + 1]
+ # selected_years = years[slice(*years_slider)]
+
+ # Use the dictionary to return the values of the selected countries based on the SDMX ISO3 codes
+ countries_selected_codes = [
+ countries_iso3_dict[country] for country in countries_selected
+ ]
+ selections = dict(
+ theme=current_theme,
+ indicators_dict=indicators,
+ years=selected_years,
+ countries=countries_selected_codes,
+ is_adolescent=("ADOLESCENT" in indicators),
+ )
+
+ return (
+ selections,
+ country_selector,
+ countries_selected == unicef_country_prog,
+ f"Years: {selected_years[0]} - {selected_years[-1]}",
+ "Countries: {}".format(country_text),
+ )
+
+
+def indicator_card(
+ selections,
+ card_id,
+ name,
+ numerator,
+ suffix,
+ denominator=None,
+ absolute=False,
+ average=False,
+ min_max=False,
+ sex_code=None,
+ age_group=None,
+):
+ indicators = numerator.split(",")
+
+ # TODO: Change to use albertos config
+ # lbassil: had to change this to cater for 2 dimensions set to the indicator card like age and sex
+ breakdown = "TOTAL"
+ # define the empty dimensions dict to be filled based on the card data filters
+ dimensions = {}
+ if age_group is not None:
+ dimensions["AGE"] = [age_group]
+ if sex_code is not None:
+ dimensions["SEX"] = [sex_code]
+
+ filtered_data = get_filtered_dataset(
+ indicators,
+ selections["years"],
+ selections["countries"],
+ breakdown,
+ dimensions,
+ latest_data=True,
+ )
+
+ df_indicator_sources = df_sources[df_sources["Code"].isin(indicators)]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ indicator_sources = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ # lbassil: add this check because we are getting an exception where there is no data; i.e. no totals for all dimensions mostly age for the selected indicator
+ if filtered_data.empty:
+ indicator_header = "No data"
+ indicator_sources = "NA"
+ numerator_pairs = []
+ return make_card(
+ card_id,
+ name,
+ indicator_header,
+ indicator_sources,
+ source_link,
+ numerator_pairs,
+ )
+
+ # select last value for each country
+ indicator_values = (
+ filtered_data.groupby(
+ [
+ "Country_name",
+ "TIME_PERIOD",
+ ]
+ ).agg({"OBS_VALUE": "sum", "CODE": "count"})
+ ).reset_index()
+
+ numerator_pairs = (
+ indicator_values[indicator_values.CODE == len(indicators)]
+ .groupby("Country_name", as_index=False)
+ .last()
+ .set_index(["Country_name", "TIME_PERIOD"])
+ )
+
+ if suffix.lower() == "countries":
+ # this is a hack to accomodate small cases (to discuss with James)
+ if "FREE" in numerator:
+ # trick to filter number of years of free education
+ indicator_sum = (numerator_pairs.OBS_VALUE >= 1).to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ numerator_pairs = numerator_pairs[numerator_pairs.OBS_VALUE >= 1]
+ else:
+ # trick to accomodate cards for admin exams (AND for boolean indicators)
+ # filter exams according to number of indicators
+ indicator_sum = (
+ (numerator_pairs.OBS_VALUE == len(indicators)).to_numpy().sum()
+ )
+ sources = numerator_pairs.index.tolist()
+
+ else:
+ indicator_sum = numerator_pairs["OBS_VALUE"].to_numpy().sum()
+ sources = numerator_pairs.index.tolist()
+ if average and len(sources) > 1:
+ indicator_sum = indicator_sum / len(sources)
+
+ # define indicator header text: the resultant number except for the min-max range
+ if min_max and len(sources) > 1:
+ indicator_min = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].min())
+ indicator_max = "{:,.1f}".format(numerator_pairs["OBS_VALUE"].max())
+ indicator_header = f"[{indicator_min} - {indicator_max}]"
+ else:
+ indicator_header = "{:,.0f}".format(indicator_sum)
+
+ return make_card(
+ card_id,
+ name,
+ suffix,
+ indicator_sources,
+ source_link,
+ indicator_header,
+ numerator_pairs,
+ )
+
+
+@app.callback(
+ Output("cards_row", "children"),
+ [
+ Input("store", "data"),
+ ],
+ [State("cards_row", "children"), State("indicators", "data")],
+)
+def show_cards(selections, current_cards, indicators_dict):
+ cards = [
+ indicator_card(
+ selections,
+ f"card-{num}",
+ card["name"],
+ card["indicator"],
+ card["suffix"],
+ card.get("denominator"),
+ card.get("absolute"),
+ card.get("average"),
+ card.get("min_max"),
+ card.get("sex"),
+ card.get("age"),
+ )
+ for num, card in enumerate(indicators_dict[selections["theme"]]["CARDS"])
+ ]
+ return cards
+
+
+@app.callback(
+ Output("subtitle", "children"),
+ Output("themes", "children"),
+ [
+ Input("store", "data"),
+ Input("country_profile_selector", "value"),
+ ],
+ State("indicators", "data"),
+)
+def show_themes(selections, selected_country, indicators_dict):
+ # check if it is the country profile page
+ is_country_profile = selections["theme"] == "COUNTRYPROFILE"
+ ctx = dash.callback_context
+ ctrl_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ # check if the countries dropdown is causing this callback in order to set the sub-title to the country's name
+ if is_country_profile or ctrl_id == "countries":
+ key_list = list(countries_iso3_dict.keys())
+ val_list = list(countries_iso3_dict.values())
+ subtitle = (
+ key_list[val_list.index(selected_country)]
+ if selected_country
+ else "Country Name"
+ )
+ return subtitle, []
+
+ subtitle = indicators_dict[selections["theme"]].get("NAME")
+ url_hash = "#{}".format((next(iter(selections.items())))[1].lower())
+ # hide the buttons when only one option is available
+ if len(indicators_dict.items()) == 1:
+ return subtitle, []
+ buttons = [
+ dbc.Button(
+ value["NAME"],
+ id=key,
+ color=colours[num],
+ className="theme mx-1",
+ href=f"#{key.lower()}",
+ active=url_hash == f"#{key.lower()}",
+ )
+ for num, (key, value) in enumerate(indicators_dict.items())
+ ]
+ return subtitle, buttons
+
+
+@app.callback(
+ Output({"type": "area_title", "index": MATCH}, "children"),
+ Output({"type": "area_options", "index": MATCH}, "options"),
+ Output({"type": "area_types", "index": MATCH}, "options"),
+ Output({"type": "area_options", "index": MATCH}, "value"),
+ Output({"type": "area_types", "index": MATCH}, "value"),
+ Input("store", "data"),
+ [
+ State("indicators", "data"),
+ State({"type": "area_options", "index": MATCH}, "id"),
+ ],
+)
+def set_options(theme, indicators_dict, id):
+
+ area = id["index"]
+
+ area_options = area_types = []
+ if area in indicators_dict[theme["theme"]]:
+ indicators = indicators_dict[theme["theme"]][area].get("indicators")
+ area_indicators = indicators.keys() if indicators is dict else indicators
+ area_options = [
+ {
+ "label": indicator_names[code],
+ "value": code,
+ }
+ for code in area_indicators
+ ]
+
+ area_types = [
+ {
+ "label": name.capitalize(),
+ "value": name,
+ }
+ for name in indicators_dict[theme["theme"]][area].get("graphs", {}).keys()
+ ]
+
+ name = (
+ indicators_dict[theme["theme"]][area].get("name")
+ if area in indicators_dict[theme["theme"]]
+ else ""
+ )
+ default_option = (
+ indicators_dict[theme["theme"]][area].get("default")
+ if area in indicators_dict[theme["theme"]]
+ else ""
+ )
+ default_graph = (
+ indicators_dict[theme["theme"]][area].get("default_graph")
+ if area in indicators_dict[theme["theme"]]
+ else ""
+ )
+
+ return name, area_options, area_types, default_option, default_graph
+
+
+@app.callback(
+ Output({"type": "area_breakdowns", "index": MATCH}, "options"),
+ [
+ Input({"type": "area_options", "index": MATCH}, "value"),
+ Input({"type": "area_types", "index": MATCH}, "value"),
+ ],
+ [
+ State({"type": "area_breakdowns", "index": MATCH}, "id"),
+ ],
+)
+def breakdown_options(indicator, fig_type, id):
+
+ options = [{"label": "Total", "value": "TOTAL"}]
+ # lbassil: change the disaggregation to use the names of the dimensions instead of the codes
+ all_breakdowns = [
+ {"label": "Sex", "value": "SEX"},
+ {"label": "Age", "value": "AGE"},
+ {"label": "Residence", "value": "RESIDENCE"},
+ {"label": "Wealth Quintile", "value": "WEALTH_QUINTILE"},
+ ]
+ dimensions = indicators_config.get(indicator, {}).keys()
+ # keep only TOTAL for line charts
+ if dimensions and fig_type != "line":
+ for breakdown in all_breakdowns:
+ if breakdown["value"] in dimensions:
+ options.append(breakdown)
+ return options
+
+
+@app.callback(
+ Output({"type": "area_breakdowns", "index": MATCH}, "value"),
+ [
+ Input("store", "data"),
+ Input({"type": "area_breakdowns", "index": MATCH}, "options"),
+ Input({"type": "area_types", "index": MATCH}, "value"),
+ ],
+ [
+ State("indicators", "data"),
+ State({"type": "area_breakdowns", "index": MATCH}, "id"),
+ ],
+)
+def set_default_compare(
+ selections, compare_options, selected_type, indicators_dict, id
+):
+ area = id["index"]
+ # lbassil: add this condition to stop the exception for the main area
+ if area in indicators_dict[selections["theme"]] and area != "MAIN":
+ default = indicators_dict[selections["theme"]][area]["default_graph"]
+ fig_type = selected_type if selected_type else default
+ config = indicators_dict[selections["theme"]][area]["graphs"][fig_type]
+ default_compare = config.get("compare")
+
+ return (
+ "TOTAL"
+ if fig_type == "line" or default_compare is None
+ else default_compare
+ if default_compare in compare_options
+ else compare_options[1]["value"]
+ if len(compare_options) > 1
+ else compare_options[0]["value"]
+ )
+ return "TOTAL"
+
+
+@app.callback(
+ Output("main_area", "figure"),
+ Output("main_area_sources", "children"),
+ [
+ Input({"type": "area_options", "index": "MAIN"}, "value"),
+ Input({"type": "historical_data_toggle", "index": "MAIN"}, "value"),
+ Input("store", "data"),
+ ],
+ [
+ State("indicators", "data"),
+ ],
+)
+def main_figure(indicator, show_historical_data, selections, indicators_dict):
+ latest_data = not show_historical_data
+ options = indicators_dict[selections["theme"]]["MAIN"]["options"]
+
+ data = get_filtered_dataset(
+ [indicator],
+ selections["years"],
+ selections["countries"],
+ latest_data=latest_data,
+ )
+
+ # check if the dataframe is empty meaning no data to display as per the user's selection
+ if data.empty:
+ return EMPTY_CHART, ""
+
+ # lbassil: replace UNIT_MEASURE by Unit_name to use the name of the unit instead of the code
+ name = (
+ data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()[0]
+ if len(data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()) > 0
+ else ""
+ )
+ df_indicator_sources = df_sources[df_sources["Code"] == indicator]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ source = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+
+ options["labels"] = DEFAULT_LABELS.copy()
+ options["labels"]["OBS_VALUE"] = name
+ options["labels"]["text"] = "OBS_VALUE"
+ options["geojson"] = geo_json_countries
+ if latest_data:
+ # remove the animation frame and show all countries at once
+ options.pop("animation_frame")
+ # add the year to show on hover
+ options["hover_name"] = "TIME_PERIOD"
+
+ main_figure = px.choropleth_mapbox(data, **options)
+ main_figure.update_layout(margin={"r": 0, "t": 1, "l": 2, "b": 1})
+
+ # check if this area's config has an animation frame and hence a slider
+ if len(main_figure.layout["sliders"]) > 0:
+ # set last frame as the active one; i.e. select the max year as the default displayed year
+ main_figure.layout["sliders"][0]["active"] = len(main_figure.frames) - 1
+ # assign the data of the last year to the map; without this line the data will show the first year;
+ main_figure = go.Figure(
+ data=main_figure["frames"][-1]["data"],
+ frames=main_figure["frames"],
+ layout=main_figure.layout,
+ )
+ return main_figure, html.A(html.P(source), href=source_link, target="_blank")
+
+
+@app.callback(
+ Output({"type": "area", "index": MATCH}, "figure"),
+ Output({"type": "area_sources", "index": MATCH}, "children"),
+ [
+ Input("store", "data"),
+ Input({"type": "area_options", "index": MATCH}, "value"),
+ Input({"type": "area_breakdowns", "index": MATCH}, "value"),
+ Input({"type": "area_types", "index": MATCH}, "value"),
+ Input({"type": "exclude_outliers_toggle", "index": MATCH}, "value"),
+ ],
+ [
+ State("indicators", "data"),
+ State({"type": "area_options", "index": MATCH}, "id"),
+ ],
+)
+def area_figure(
+ selections,
+ indicator,
+ compare,
+ selected_type,
+ exclude_outliers,
+ indicators_dict,
+ id,
+):
+ # only run if indicator not empty
+ if not indicator:
+ return {}, {}
+ # check if it is the country profile page
+ is_country_profile = selections["theme"] == "COUNTRYPROFILE"
+
+ area = id["index"]
+ indicators = indicators_dict[selections["theme"]][area]["indicators"]
+ default_graph = indicators_dict[selections["theme"]][area].get(
+ "default_graph", "line"
+ )
+ fig_type = selected_type if selected_type else default_graph
+ fig_config = indicators_dict[selections["theme"]][area]["graphs"][fig_type]
+ options = fig_config.get("options")
+ traces = fig_config.get("trace_options")
+ dimension = False if fig_type == "line" or compare == "TOTAL" else compare
+
+ indicator_name = str(indicator_names.get(indicator, ""))
+ # do we need `indicator_settings` below?
+ indicator_settings = (
+ indicators.get(indicator, {}) if type(indicators) is dict else {}
+ )
+ data = get_filtered_dataset(
+ [indicator],
+ selections["years"],
+ selections["countries"],
+ compare,
+ latest_data=False if fig_type == "line" or is_country_profile else True,
+ ).sort_values("OBS_VALUE", ascending=False)
+ # check if the dataframe is empty meaning no data to display as per the user's selection
+ if data.empty:
+ return EMPTY_CHART, ""
+
+ # check if the exclude outliers checkbox is checked
+ if exclude_outliers:
+ # filter the data to the remove the outliers
+ # (df < df.quantile(0.1)).any() (df > df.quantile(0.9)).any()
+ data["z_scores"] = np.abs(zscore(data["OBS_VALUE"])) # calculate z-scores of df
+ # filter the data entries to remove the outliers
+ data = data[(data["z_scores"] < 3) | (data["z_scores"].isnull())]
+
+ # lbassil: was UNIT_MEASURE
+ name = (
+ data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()[0]
+ if len(data[data["CODE"] == indicator]["Unit_name"].astype(str).unique()) > 0
+ else ""
+ )
+ df_indicator_sources = df_sources[df_sources["Code"] == indicator]
+ unique_indicator_sources = df_indicator_sources["Source_Full"].unique()
+ source = (
+ "; ".join(list(unique_indicator_sources))
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+ source_link = (
+ df_indicator_sources["Source_Link"].unique()[0]
+ if len(unique_indicator_sources) > 0
+ else ""
+ )
+
+ options["labels"] = DEFAULT_LABELS.copy()
+ options["labels"]["OBS_VALUE"] = name
+
+ # set the chart title, wrap the text when the indicator name is too long
+ chart_title = textwrap.wrap(
+ indicator_name,
+ width=74,
+ )
+ chart_title = "
".join(chart_title)
+
+ # set the layout to center the chart title and change its font size and color
+ layout = go.Layout(
+ title=chart_title,
+ title_x=0.5,
+ font=dict(family="Arial", size=12),
+ legend=dict(x=0.9, y=0.5),
+ )
+
+ # Add this code to avoid having decimal year on the x-axis for time series charts
+ if fig_type == "line" or is_country_profile:
+ data.sort_values(by=["TIME_PERIOD"], inplace=True)
+ layout["xaxis"] = dict(
+ tickmode="linear",
+ tick0=selections["years"][0],
+ dtick=1,
+ categoryorder="total ascending",
+ )
+
+ if dimension:
+ # lbassil: use the dimension name instead of the code
+ dimension_name = str(dimension_names.get(dimension, ""))
+ options["color"] = dimension_name
+ if compare == "WEALTH_QUINTILE":
+ wealth_dict = {
+ "Lowest": 0,
+ "Second": 1,
+ "Middle": 2,
+ "Fourth": 3,
+ "Highest": 4,
+ }
+ data.sort_values(
+ by=[dimension], key=lambda x: x.map(wealth_dict), inplace=True
+ )
+ else:
+ # sort by the compare value to have the legend in the right ascending order
+ data.sort_values(by=[dimension], inplace=True)
+
+ fig = getattr(px, fig_type)(data, **options)
+ fig.update_layout(layout)
+ if traces:
+ fig.update_traces(**traces)
+
+ return fig, html.A(html.P(source), href=source_link, target="_blank")
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_education.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_education.py
new file mode 100644
index 00000000..0d6a627b
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_education.py
@@ -0,0 +1,672 @@
+from collections import defaultdict
+import plotly.express as px
+
+from . import data, years
+from .base_page import get_base_layout
+
+indicators_dict = {
+ "PARTICIPATION": {
+ "NAME": "Education access and participation",
+ "CARDS": [
+ {
+ "name": "Who are Out-of-School",
+ "indicator": "EDUNF_OFST_L1,EDUNF_OFST_L2,EDUNF_OFST_L3",
+ "suffix": "Primary to upper secondary aged Children and Adolescents",
+ "age": "SCHOOL_AGE",
+ },
+ {
+ "name": "Who are Out-of-School",
+ "indicator": "EDUNF_OFST_L1,EDUNF_OFST_L2,EDUNF_OFST_L3",
+ "suffix": "Primary to upper secondary aged Girls",
+ "sex": "F",
+ "age": "SCHOOL_AGE",
+ },
+ {
+ "name": "Who are Out-of-School",
+ "indicator": "EDUNF_OFST_L1_UNDER1",
+ "suffix": "Children one year younger than the official primary entry age",
+ "age": "UNDER1_SCHOOL_ENTRY",
+ },
+ ],
+ "MAIN": {
+ "name": "Out-of-School Children",
+ "geo": "Country_name", # REF_AREA
+ "options": dict(
+ locations="REF_AREA",
+ featureidkey="id",
+ color="OBS_VALUE",
+ color_continuous_scale=px.colors.sequential.GnBu,
+ mapbox_style="carto-positron",
+ zoom=2,
+ center={"lat": 62.995158, "lon": 88.048713},
+ opacity=0.5,
+ labels={
+ "OBS_VALUE": "Value",
+ "REF_AREA": "ISO3 Code",
+ "TIME_PERIOD": "Year",
+ "Country_name": "Country",
+ },
+ hover_data={
+ "OBS_VALUE": True,
+ "REF_AREA": False,
+ "Country_name": True,
+ "TIME_PERIOD": True,
+ },
+ animation_frame="TIME_PERIOD",
+ height=750,
+ ),
+ "indicators": [
+ "EDUNF_ROFST_L1",
+ "EDUNF_ROFST_L2",
+ "EDUNF_ROFST_L3",
+ "EDUNF_OFST_L1",
+ "EDUNF_OFST_L2",
+ "EDUNF_OFST_L3",
+ "EDU_SDG_PRYA",
+ "EDUNF_ROFST_L1_UNDER1",
+ "EDUNF_ROFST_L1T3",
+ ],
+ "default": "EDUNF_ROFST_L1",
+ },
+ "AREA_1": {
+ "name": "Education entry and transition",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDUNF_ROFST_L1",
+ "EDUNF_ROFST_L2",
+ "EDUNF_ROFST_L3",
+ "EDUNF_ROFST_L1_UNDER1",
+ "EDUNF_ROFST_L1T3",
+ "EDUNF_STU_L1_TOT",
+ "EDUNF_STU_L2_TOT",
+ "EDUNF_STU_L3_TOT",
+ "EDUNF_GER_L2_GEN",
+ "EDUNF_GER_L2_VOC",
+ "EDUNF_GER_L3",
+ "EDUNF_GER_L3_GEN",
+ "EDUNF_GER_L3_VOC",
+ ],
+ "default": "EDUNF_ROFST_L1",
+ },
+ "AREA_2": {
+ "name": "Education entry and transition",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "line",
+ "indicators": [
+ "EDUNF_NER_L02",
+ "EDUNF_NERA_L1_UNDER1",
+ "EDUNF_NERA_L1",
+ "EDUNF_NERA_L2",
+ "EDUNF_GER_L1",
+ "EDUNF_GER_L2",
+ "EDUNF_GER_L3",
+ "EDUNF_NIR_L1_ENTRYAGE",
+ "EDUNF_TRANRA_L2",
+ ],
+ "default": "EDUNF_TRANRA_L2",
+ },
+ "AREA_3": {
+ "name": "Safe and inclusive learning environments",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDU_SDG_SCH_L1",
+ "EDU_SDG_SCH_L2",
+ "EDU_SDG_SCH_L3",
+ "WS_SCH_H-B",
+ "WS_SCH_S-B",
+ "WS_SCH_W-B",
+ "EDU_CHLD_DISAB",
+ "EDU_CHLD_DISAB_GENERAL",
+ "EDU_CHLD_DISAB_SPECIAL",
+ "EDU_CHLD_DISAB_GENERAL_BOARDING",
+ "EDU_CHLD_DISAB_SPECIAL_BOARDING",
+ "EDU_CHLD_DISAB_HOME",
+ "EDU_SDG_PRYA",
+ ],
+ "default": "EDU_CHLD_DISAB",
+ },
+ "AREA_4": {
+ "name": "Education completion",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDUNF_CR_L1",
+ "EDUNF_CR_L2",
+ "EDUNF_CR_L3",
+ "EDUNF_DR_L1",
+ "EDUNF_DR_L2",
+ "EDUNF_GER_L2",
+ ],
+ "default": "EDUNF_CR_L1",
+ },
+ },
+ "QUALITY": {
+ "NAME": "Learning quality and skills",
+ "CARDS": [
+ {
+ "name": "(enrolled in the same grade for a second or further year) in primary and lower secondary education",
+ "indicator": "EDUNF_RPTR_L1,EDUNF_RPTR_L2",
+ "suffix": "Children and adolescent repeaters",
+ },
+ {
+ "name": "from primary education",
+ "indicator": "EDUNF_ESL_L1",
+ "suffix": "Early school leavers",
+ },
+ {
+ "name": "administering nationally representative learning assessment in both reading and math at the end of primary education",
+ "indicator": "EDUNF_ADMIN_L1_GLAST_REA,EDUNF_ADMIN_L1_GLAST_MAT",
+ "suffix": "Countries",
+ },
+ {
+ "name": "participating in the latest round of PISA",
+ "indicator": "EDU_PISA_MAT,EDU_PISA_REA,EDU_PISA_SCI",
+ "suffix": "Countries",
+ "absolute": True,
+ },
+ ],
+ "MAIN": {
+ "name": "What students know and can do",
+ "geo": "Country_name",
+ "options": dict(
+ locations="REF_AREA",
+ featureidkey="id",
+ color="OBS_VALUE",
+ color_continuous_scale=px.colors.sequential.GnBu,
+ mapbox_style="carto-positron",
+ zoom=2,
+ center={"lat": 62.995158, "lon": 88.048713},
+ opacity=0.5,
+ labels={
+ "OBS_VALUE": "Value",
+ "Country_name": "Country",
+ "TIME_PERIOD": "Year",
+ "REF_AREA": "ISO3 Code",
+ },
+ hover_data={
+ "OBS_VALUE": True,
+ "REF_AREA": False,
+ "Country_name": True,
+ "TIME_PERIOD": True,
+ },
+ animation_frame="TIME_PERIOD",
+ height=750,
+ ),
+ "indicators": ["EDU_PISA_MAT", "EDU_PISA_REA", "EDU_PISA_SCI"],
+ "default": "EDU_PISA_MAT",
+ },
+ "AREA_1": {
+ "name": "Foundational skills",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "ECD_CHLD_36-59M_LMPSL",
+ "EDU_SDG_STU_L2_GLAST_MAT",
+ "EDU_SDG_STU_L2_GLAST_REA",
+ "EDU_SDG_STU_L1_GLAST_MAT",
+ "EDU_SDG_STU_L1_G2OR3_MAT",
+ "EDU_SDG_STU_L1_GLAST_REA",
+ "EDU_SDG_STU_L1_G2OR3_REA",
+ "EDUNF_LR_YOUTH",
+ "EDUNF_LR_ADULT",
+ ],
+ "default": "EDU_SDG_STU_L2_GLAST_MAT",
+ },
+ "AREA_2": {
+ "name": "Foundational skills",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "line",
+ "indicators": [
+ "ECD_CHLD_36-59M_LMPSL",
+ "EDU_SDG_STU_L2_GLAST_MAT",
+ "EDU_SDG_STU_L2_GLAST_REA",
+ "EDU_SDG_STU_L1_GLAST_MAT",
+ "EDU_SDG_STU_L1_G2OR3_MAT",
+ "EDU_SDG_STU_L1_GLAST_REA",
+ "EDU_SDG_STU_L1_G2OR3_REA",
+ "EDUNF_LR_YOUTH",
+ "EDUNF_LR_ADULT",
+ ],
+ "default": "EDU_SDG_STU_L2_GLAST_MAT",
+ },
+ "AREA_3": {
+ "name": "Trained and qualified teachers",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDU_SDG_TRTP_L02",
+ "EDU_SDG_TRTP_L1",
+ "EDU_SDG_TRTP_L2",
+ "EDU_SDG_TRTP_L3",
+ ],
+ "default": "EDU_SDG_TRTP_L2",
+ },
+ "AREA_4": {
+ "name": "Trained and qualified teachers",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDU_SDG_QUTP_L02",
+ "EDU_SDG_QUTP_L1",
+ "EDU_SDG_QUTP_L2",
+ "EDU_SDG_QUTP_L3",
+ ],
+ "default": "EDU_SDG_QUTP_L2",
+ },
+ },
+ "SYSTEM": {
+ "NAME": "Education System",
+ "CARDS": [
+ {
+ "name": "Guaranteeing at least one year of free pre-primary education in their legal frameworks",
+ "indicator": "EDU_SDG_FREE_EDU_L02",
+ "suffix": "Countries",
+ },
+ {
+ "name": "Enrolled in private institutions (primary, lower secondary and upper secondary education)",
+ "indicator": "EDUNF_STU_L1_PRV,EDUNF_STU_L2_PRV,EDUNF_STU_L3_PRV",
+ "suffix": "Children and Adolescents",
+ },
+ {
+ "name": "Total in primary, lower secondary and upper secondary education",
+ "indicator": "EDUNF_TEACH_L1,EDUNF_TEACH_L2,EDUNF_TEACH_L3",
+ "suffix": "Classroom Teachers",
+ },
+ ],
+ "MAIN": {
+ "name": "Guaranteeing and paying for education", # "Education Expenditures and Legal Frameworks",
+ "geo": "Country_name",
+ "options": dict(
+ locations="REF_AREA",
+ featureidkey="id",
+ color="OBS_VALUE",
+ color_continuous_scale=px.colors.sequential.GnBu,
+ mapbox_style="carto-positron",
+ zoom=2,
+ center={"lat": 62.995158, "lon": 88.048713},
+ opacity=0.5,
+ labels={
+ "OBS_VALUE": "Value",
+ "Country_name": "Country",
+ "TIME_PERIOD": "Year",
+ "REF_AREA": "ISO3 Code",
+ },
+ hover_data={
+ "OBS_VALUE": True,
+ "REF_AREA": False,
+ "Country_name": True,
+ "TIME_PERIOD": True,
+ },
+ animation_frame="TIME_PERIOD",
+ height=750,
+ ),
+ "indicators": [
+ "EDU_FIN_EXP_PT_GDP",
+ "EDU_FIN_EXP_PT_TOT",
+ "EDU_SDG_FREE_EDU_L02",
+ "EDU_SDG_COMP_EDU_L02",
+ "EDU_FIN_EXP_CONST_PPP",
+ ],
+ "default": "EDU_FIN_EXP_PT_GDP",
+ },
+ "AREA_1": {
+ "name": "Public and private enrolments",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDUNF_PRP_L02",
+ "EDUNF_PRP_L1",
+ "EDUNF_PRP_L2",
+ "EDUNF_PRP_L3",
+ "EDUNF_STU_L01_PUB",
+ "EDUNF_STU_L02_PUB",
+ "EDUNF_STU_L1_PUB",
+ "EDUNF_STU_L2_PUB",
+ "EDUNF_STU_L3_PUB",
+ "EDUNF_STU_L01_PRV",
+ "EDUNF_STU_L02_PRV",
+ "EDUNF_STU_L1_PRV",
+ "EDUNF_STU_L2_PRV",
+ "EDUNF_STU_L3_PRV",
+ ],
+ "default": "EDUNF_PRP_L1",
+ },
+ "AREA_2": {
+ "name": "Public and private enrolments",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "line",
+ "indicators": [
+ "EDUNF_PRP_L02",
+ "EDUNF_PRP_L1",
+ "EDUNF_PRP_L2",
+ "EDUNF_PRP_L3",
+ "EDUNF_STU_L01_PUB",
+ "EDUNF_STU_L02_PUB",
+ "EDUNF_STU_L1_PUB",
+ "EDUNF_STU_L2_PUB",
+ "EDUNF_STU_L3_PUB",
+ "EDUNF_STU_L01_PRV",
+ "EDUNF_STU_L02_PRV",
+ "EDUNF_STU_L1_PRV",
+ "EDUNF_STU_L2_PRV",
+ "EDUNF_STU_L3_PRV",
+ ],
+ "default": "EDUNF_PRP_L2",
+ },
+ "AREA_3": {
+ "name": "Government education expenditure",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ # "compare": "Sex",
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": [
+ "EDU_FIN_EXP_L02",
+ "EDU_FIN_EXP_L1",
+ "EDU_FIN_EXP_L2",
+ "EDU_FIN_EXP_L3",
+ "EDU_FIN_EXP_L4",
+ "EDU_FIN_EXP_L5T8",
+ "EDU_FIN_EXP_CONST_PPP",
+ ],
+ "default": "EDU_FIN_EXP_L2",
+ },
+ "AREA_4": {
+ "name": "Administration of learning assessments",
+ "graphs": {
+ "bar": {
+ "options": dict(
+ x="Country_name",
+ y="OBS_VALUE",
+ barmode="group",
+ # text="TIME_PERIOD",
+ text="OBS_VALUE",
+ hover_name="TIME_PERIOD",
+ ),
+ },
+ "line": {
+ "options": dict(
+ x="TIME_PERIOD",
+ y="OBS_VALUE",
+ color="Country_name",
+ hover_name="Country_name",
+ line_shape="spline",
+ render_mode="svg",
+ ),
+ "trace_options": dict(mode="lines+markers"),
+ },
+ },
+ "default_graph": "bar",
+ "indicators": {
+ "EDUNF_ADMIN_L1_GLAST_REA": {},
+ "EDUNF_ADMIN_L1_GLAST_MAT": {},
+ "EDUNF_ADMIN_L2_REA": {},
+ "EDUNF_ADMIN_L2_MAT": {"dtype": "str"},
+ "EDUNF_ADMIN_L1_G2OR3_REA": {},
+ "EDUNF_ADMIN_L1_G2OR3_MAT": {},
+ },
+ "default": "EDUNF_ADMIN_L2_MAT",
+ },
+ },
+}
+
+
+main_title = "Education, Leisure, and Culture"
+
+
+def get_layout(**kwargs):
+ kwargs["indicators"] = indicators_dict
+ kwargs["main_title"] = main_title
+ return get_base_layout(**kwargs)
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/child_health.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_health.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/child_health.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_health.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/child_participation.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_participation.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/child_participation.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_participation.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/child_poverty.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_poverty.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/child_poverty.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_poverty.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/child_protection.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_protection.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/child_protection.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_protection.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/child_rights.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_rights.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/child_rights.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/child_rights.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/climate.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/climate.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/climate.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/climate.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/country_profiles.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/country_profiles.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/country_profiles.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/country_profiles.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/data_query.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/data_query.py
similarity index 99%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/data_query.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/data_query.py
index a81056a2..8f0b0ed8 100644
--- a/transmonee_dashboard/src/transmonee_dashboard/pages/data_query.py
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/data_query.py
@@ -13,7 +13,7 @@
from pandas.io.formats import style
import re
-from transmonee_dashboard.pages.base_page import indicator_card
+from transmonee_dashboard.pages__TMONEE.base_page import indicator_card
from ..app import app
from . import (
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/disability.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/disability.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/disability.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/disability.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/ecd.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/ecd.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/ecd.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/ecd.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/gender.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/gender.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/gender.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/gender.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/home.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/home.py
new file mode 100644
index 00000000..f5aff8f1
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/home.py
@@ -0,0 +1,60 @@
+import dash_core_components as dcc
+import dash_html_components as html
+import dash_bootstrap_components as dbc
+from dash.dependencies import Input, State, Output
+
+from ..app import app
+
+
+def get_layout(**kwargs):
+ return html.Div(
+ children=[
+ html.Div(
+ className="heading",
+ style={"padding": 36},
+ children=[
+ html.Div(
+ className="heading-content",
+ children=[
+ html.Div(
+ className="heading-panel",
+ style={"padding": 20},
+ children=[
+ html.H1(
+ "Home",
+ id="main_title",
+ className="heading-title",
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ html.Br(),
+ html.Div(
+ children=[
+ dbc.Card(
+ [
+ # dbc.CardHeader(html.H3("State of Children Rights")),
+ dbc.CardBody(
+ [
+ html.Img(
+ src="assets/home.png",
+ className="rounded mx-auto d-block",
+ ),
+ html.Br(),
+ # html.H4(
+ # "What you can find here...",
+ # className="card-title",
+ # ),
+ ]
+ ),
+ ]
+ ),
+ ],
+ style={"textAlign": "center"},
+ ),
+ html.Br(),
+ ],
+ )
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/overview.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/overview.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/overview.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/overview.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/resources.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/resources.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/resources.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/resources.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/pages/risks.py b/transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/risks.py
similarity index 100%
rename from transmonee_dashboard/src/transmonee_dashboard/pages/risks.py
rename to transmonee_dashboard/src/transmonee_dashboard/pages__TMONEE/risks.py
diff --git a/transmonee_dashboard/src/transmonee_dashboard/sdmx_utils/__init__.py b/transmonee_dashboard/src/transmonee_dashboard/sdmx_utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/transmonee_dashboard/src/transmonee_dashboard/sdmx_utils/utils.py b/transmonee_dashboard/src/transmonee_dashboard/sdmx_utils/utils.py
new file mode 100644
index 00000000..1eec73dd
--- /dev/null
+++ b/transmonee_dashboard/src/transmonee_dashboard/sdmx_utils/utils.py
@@ -0,0 +1,49 @@
+import requests
+import json
+
+
+def get_codelist(url, lang=None):
+ codelist_get_params = {"format": "sdmx-json", "detail": "full", "references": "none"}
+ if lang is None:
+ r = requests.get(url, params=codelist_get_params)
+ else:
+ r = requests.get(url, params=codelist_get_params, headers={"Accept-Language": lang})
+
+ if r.ok:
+ ret = []
+ # jsondata = r.content.decode("utf-8")
+ # jsondata = json.loads(jsondata)
+ jsondata = r.json()
+ for c in jsondata['data']['codelists'][0]['codes']:
+ ret.append({"id": c["id"], "name": c["name"]})
+ if "description" in c:
+ ret[-1]["description"] = c["description"]
+ if "parent" in c:
+ ret[-1]["parent"] = c["parent"]
+ return ret
+ else:
+ raise (ConnectionError(
+ "Error downloading " + url + " status code: " + str(r.status_code) + r.raise_for_status()))
+
+
+'''
+def _json_codelist_to_dataframe(codelist_sdmx_json, lang = None, force_any_language_when_no_lang = True):
+ ret = pd.DataFrame(columns=["id", "name", "desc", "parent"], dtype=str)
+
+ for i in codelist_sdmx_json['data']['codelists'][0]['codes']:
+ #toAdd = {"id": i["id"], "name": i["names"].items()[0]}
+ toAdd = {"id": i["id"]}
+ if lang is not None and lang in i["names"]:
+ toAdd["name"] = i["names"][lang]
+ elif force_any_language_when_no_lang: #fallback
+ toAdd["name"] = list(i["names"].values())[0]
+ else: toAdd["name"] = None
+
+ if "description" in i:
+ toAdd["desc"] = i["description"]
+ if "parent" in i:
+ toAdd["parent"] = i["parent"]
+ ret = ret.append(toAdd, ignore_index=True)
+ return ret
+
+ '''