File size: 14,470 Bytes
d551fc8
 
 
 
 
 
 
 
 
 
 
 
c8e0175
 
d551fc8
e22c7b1
f123b98
6a85a81
c8e0175
d551fc8
 
55a6bd8
d551fc8
 
 
 
 
 
 
 
 
4135c81
d551fc8
 
7b52ef0
 
 
 
 
 
 
 
 
2788caf
d551fc8
 
 
 
 
 
 
 
 
 
 
 
 
 
21b6daa
 
 
 
d551fc8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3302f1
 
 
 
 
 
 
 
 
 
 
 
d551fc8
 
 
 
 
 
 
 
 
 
 
 
 
 
7b52ef0
cceb9c7
d551fc8
 
7b52ef0
 
21b6daa
d551fc8
 
 
 
 
e3302f1
 
71de22d
d551fc8
fd577be
d551fc8
21b6daa
94abe53
8118f8d
94fd083
8118f8d
 
 
 
 
 
 
94fd083
b88f1e8
9e1ae23
d551fc8
5a0ec90
52b7f30
9c2fca9
 
 
52b7f30
9e1ae23
 
 
 
 
 
 
 
 
d551fc8
 
 
 
9c2fca9
d551fc8
55a6bd8
 
 
 
 
 
 
 
 
 
 
 
 
9c2fca9
55a6bd8
 
 
 
 
9c2fca9
55a6bd8
9c2fca9
55a6bd8
 
 
52b7f30
9c2fca9
d551fc8
52b7f30
e3302f1
 
 
 
aa3820f
e3302f1
 
 
 
 
 
 
 
 
 
aa3820f
e3302f1
71de22d
4fa711d
 
d551fc8
 
 
 
55a6bd8
 
 
 
30488e6
 
55a6bd8
 
d551fc8
e3302f1
 
 
 
 
 
f123b98
71de22d
 
343d08b
a272945
6a85a81
 
3089c42
 
6a85a81
 
 
 
a272945
3089c42
 
a272945
3089c42
 
2788caf
a272945
c8e0175
a272945
2788caf
343d08b
b88f1e8
343d08b
 
fd82343
a272945
 
21b6daa
 
 
 
 
 
94fd083
8118f8d
 
 
 
 
 
94fd083
c8bd695
f73005b
5a0ec90
 
 
c8bd695
5a0ec90
 
 
 
 
 
 
e7fe5d8
5a0ec90
 
05fcf5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21b6daa
e7fe5d8
5a0ec90
 
 
 
21b6daa
806b350
 
5a0ec90
806b350
21b6daa
5a0ec90
806b350
5a0ec90
867f2d7
 
 
 
 
 
 
 
5dbafb0
867f2d7
 
784ae4f
c8e0175
6f41b63
e22c7b1
 
 
 
 
 
 
e6dc9fc
 
867f2d7
 
 
e6dc9fc
 
867f2d7
e6dc9fc
 
3089c42
5d7d815
21b6daa
4bcd90c
 
 
 
 
920d9b8
4bcd90c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
"""
Page for similarities
"""

################
# DEPENDENCIES #
################
import streamlit as st
import pandas as pd
from scipy.sparse import load_npz
import pickle
from sentence_transformers import SentenceTransformer
from modules.multimatch_result_table import show_multi_table
from modules.singlematch_result_table import show_single_table
from functions.filter_projects import filter_projects
from functions.filter_single import filter_single
from functions.calc_matches import calc_matches
from functions.same_country_filter import same_country_filter
from functions.single_similar import find_similar
import psutil
import os
import gc

def get_process_memory():
    process = psutil.Process(os.getpid())
    return process.memory_info().rss / (1024 * 1024) 

# Catch DATA
# Load Similarity matrix
@st.cache_data
def load_sim_matrix():
    loaded_matrix = load_npz("src/extended_similarities.npz")
    dense_matrix = loaded_matrix.toarray()

    return dense_matrix

# Load Non Similar Orga Matrix
@st.cache_data
def load_nonsameorga_sim_matrix():
    loaded_matrix = load_npz("src/extended_similarities_nonsimorga.npz")
    dense_matrix = loaded_matrix.toarray()

    return dense_matrix

# Load Projects DFs
@st.cache_data
def load_projects():
    orgas_df = pd.read_csv("src/projects/project_orgas.csv")
    region_df = pd.read_csv("src/projects/project_region.csv")
    sector_df = pd.read_csv("src/projects/project_sector.csv")
    status_df = pd.read_csv("src/projects/project_status.csv")
    texts_df = pd.read_csv("src/projects/project_texts.csv")

    projects_df = pd.merge(orgas_df, region_df, on='iati_id', how='inner')
    projects_df = pd.merge(projects_df, sector_df, on='iati_id', how='inner')
    projects_df = pd.merge(projects_df, status_df, on='iati_id', how='inner')
    projects_df = pd.merge(projects_df, texts_df, on='iati_id', how='inner')

    iati_search_list = [f'{row.iati_id}' for row in projects_df.itertuples()]
    title_search_list = [f'{row.title_main} ({row.orga_abbreviation.upper()})' for row in projects_df.itertuples()]

    return projects_df, iati_search_list, title_search_list

# Load CRS 3 data
@st.cache_data
def getCRS3():
    # Read in CRS3 CODELISTS
    crs3_df = pd.read_csv('src/codelists/crs3_codes.csv')
    CRS3_CODES = crs3_df['code'].tolist()
    CRS3_NAME = crs3_df['name'].tolist()
    CRS3_MERGED = {f"{name} - {code}": code for name, code in zip(CRS3_NAME, CRS3_CODES)}

    return CRS3_MERGED

# Load CRS 5 data
@st.cache_data
def getCRS5():
    # Read in CRS3 CODELISTS
    crs5_df = pd.read_csv('src/codelists/crs5_codes.csv')
    CRS5_CODES = crs5_df['code'].tolist()
    CRS5_NAME = crs5_df['name'].tolist()
    CRS5_MERGED = {code: [f"{name} - {code}"] for name, code in zip(CRS5_NAME, CRS5_CODES)}

    return CRS5_MERGED

# Load SDG data
@st.cache_data
def getSDG():
    # Read in SDG CODELISTS
    sdg_df = pd.read_csv('src/codelists/sdg_goals.csv')
    SDG_NAMES = sdg_df['name'].tolist()

    return SDG_NAMES

# Load Country Data
@st.cache_data
def getCountry():
    # Read in countries from codelist
    country_df = pd.read_csv('src/codelists/country_codes_ISO3166-1alpha-2.csv')
    COUNTRY_CODES = country_df['Alpha-2 code'].tolist()
    COUNTRY_NAMES = country_df['Country'].tolist()

    COUNTRY_OPTION_LIST = [f"{COUNTRY_NAMES[i]} ({COUNTRY_CODES[i][-3:-1].upper()})"for i in range(len(COUNTRY_NAMES))]

    return COUNTRY_OPTION_LIST

# Load Sentence Transformer Model
@st.cache_resource
def load_model():
    model = SentenceTransformer('all-MiniLM-L6-v2')
    return model

# Load Embeddings
@st.cache_data 
def load_embeddings_and_index():
    # Load embeddings
    with open("src/embeddings.pkl", "rb") as fIn:
        stored_data = pickle.load(fIn)
    embeddings = stored_data["embeddings"]

    return embeddings
    

# USE CACHE FUNCTIONS 
sim_matrix = load_sim_matrix()
nonsameorgas_sim_matrix = load_nonsameorga_sim_matrix()
projects_df, iati_search_list, title_search_list = load_projects()

CRS3_MERGED = getCRS3()
CRS5_MERGED = getCRS5()
SDG_NAMES = getSDG()

COUNTRY_OPTION_LIST = getCountry()

# LOAD MODEL FROM CACHE FO SEMANTIC SEARCH
model = load_model()
embeddings = load_embeddings_and_index()

def show_multi_matching_page():
    #st.write(f"Current RAM usage of this app: {get_process_memory():.2f} MB")
    #st.write(" ")
    with st.expander("Explanation"):
        st.caption("""
                    The Multi-Project Matching Feature uncovers synergy opportunities among various development banks and organizations by facilitating the search for 
                    similar projects within a selected filter setting. This innovative tool leverages an AI-driven similarity score to compare all available projects 
                    against those meeting the filter criteria, identifying potential matches that may not directly qualify under the selected filter settings. 
                    It integrates projects listed in the IATI database from leading organizations, including BMZ, KfW, GIZ, IAD, ADB, AfDB, EIB, WB, WBTF, and the German 
                    Federal Foreign Office (AA), offering a comprehensive platform for enhancing collaboration and maximizing the impact of development efforts.
                """)
        #st.write("----------------------")

    col1, col2, col3 = st.columns([10, 1, 10])
    with col1:
        st.subheader("Sector Filters (required)")
        st.caption("""
                    Sector filters must be applied to see results. The CRS5 and CRS3 classifications organise development aid into categories, 
                    with the 5-digit level providing more specific detail within the broader 3-digit categories. 
                    The SDGs are 17 UN goals that aim to achieve global sustainability, peace and prosperity by 2030. Futhermore you can Search for projects with the query field.
                    """)
    with col3:
        st.subheader("Additional Filters")
        st.caption("""
                    The additional filters allow for a more detailed search for the Multi-Project Matching.
                """)
    
    st.session_state.crs5_option_disabled = True
    col1, col2, col3 = st.columns([10, 1, 10])
    with col1:
        # CRS 3 SELECTION
        crs3_option = st.multiselect(
                        'CRS 3',
                        CRS3_MERGED,
                        placeholder="Select a CRS 3 code"
                        )

        # CRS 5 SELECTION
        ## Only enable crs5 select field when crs3 code is selected
        if crs3_option != []:
            st.session_state.crs5_option_disabled = False

        ## define list of crs5 codes dependend on crs3 codes
        crs5_list = [txt[0].replace('"', "") for crs3_item in crs3_option for code, txt in CRS5_MERGED.items() if str(code)[:3] == str(crs3_item)[-3:]]

        ## crs5 select field
        crs5_option = st.multiselect(
            'CRS 5',
            crs5_list,
            placeholder="Select a CRS 5 code",
            disabled=st.session_state.crs5_option_disabled
            )
        
        # SDG SELECTION
        sdg_option = st.selectbox(
                label = 'Sustainable Development Goal (SDG)',
                index = None,
                placeholder = "Select a SDG",
                options = SDG_NAMES[:-1],
                )

        # SEARCH BOX
        query = st.text_input("Search Query")
    
    with col3:
        # COUNTRY SELECTION
        country_option = st.multiselect(
                'Country / Countries',
                COUNTRY_OPTION_LIST,
                placeholder="All countries selected"
                )
            
        # ORGA SELECTION
        orga_abbreviation = projects_df["orga_abbreviation"].unique()
        orga_full_names = projects_df["orga_full_name"].unique()
        orga_list = [f"{orga_full_names[i]} ({orga_abbreviation[i].upper()})"for i in range(len(orga_abbreviation))]

        orga_option = st.multiselect(
                'Development Bank / Organization',
                orga_list,
                placeholder="All organizations selected"
                )
        
        different_orga_checkbox = st.checkbox("Only matches between different organizations", value=True)
        filterd_country_only_checkbox = st.checkbox("Only matches between filtered countries", value=True)


    # CRS CODE LIST
    crs3_list = [i[-3:] for i in crs3_option]
    crs5_list = [i[-5:] for i in crs5_option]

    # SDG CODE LIST
    if sdg_option != None:
        sdg_str = sdg_option.split(".")[0]
        print(sdg_str)
    else:
        sdg_str = ""

    # COUNTRY CODES LIST
    country_code_list = [option[-3:-1] for option in country_option]

    # ORGANIZATION CODES LIST
    orga_code_list = [option.split("(")[1][:-1].lower() for option in orga_option]

    # FILTER DF WITH SELECTED FILTER OPTIONS
    TOP_X_PROJECTS = 30
    filtered_df = filter_projects(projects_df, crs3_list, crs5_list, sdg_str, country_code_list, orga_code_list, query, model, embeddings, TOP_X_PROJECTS)
    if isinstance(filtered_df, pd.DataFrame) and len(filtered_df) != 0:
        # FIND MATCHES
        ## If only same country checkbox i sactivated
        if filterd_country_only_checkbox:
            with st.spinner('Please wait...'):
                compare_df = same_country_filter(projects_df, country_code_list)
        else:
            compare_df = projects_df
        
        ## if show only different orgas checkbox is activated
        if different_orga_checkbox:
            with st.spinner('Please wait...'):
                p1_df, p2_df = calc_matches(filtered_df, compare_df, nonsameorgas_sim_matrix, TOP_X_PROJECTS)
        else:
            with st.spinner('Please wait...'):
                p1_df, p2_df = calc_matches(filtered_df, compare_df, sim_matrix, TOP_X_PROJECTS)

        # SHOW THE RESULT
        show_multi_table(p1_df, p2_df)
        del p1_df, p2_df
    else:
        st.write("-----")
        col1, col2, col3 = st.columns([1, 1, 1])
        with col2:
            st.write("  ")
            st.markdown("<span style='color: red'>There are no results for the applied filter. Try another filter!</span>", unsafe_allow_html=True)
        
    del crs3_list, crs5_list, sdg_str, filtered_df
    gc.collect()



def show_single_matching_page():

    with st.expander("Explanation"):
        st.caption("""
                    Single Project Matching empowers you to choose an individual project using either the project IATI ID or title, and then unveils the top 10 projects 
                    that bear the closest resemblance to your selected one. This selection is refined using a sophisticated AI algorithm that evaluates similarity based 
                    on several key dimensions: Sustainable Development Goals (SDG), Creditor Reporting System (CRS) codes, and textual analysis of project titles and 
                    descriptions. 
                    """)
        #st.write("---------")

    col1, col2 = st.columns([11, 20])
    with col1:
        st.subheader("Select a reference project")
        st.caption("""
                    Select a reference project either by its title or IATI ID to find the 10 projects most similar to it. 
                    """)
    with col2:
        st.subheader("Filters for similar projects")
        st.caption("""
                    The filters are applied to find the similar projects and are independend of the selected reference project.
                """)

    col1, col2, col3, col4 = st.columns([10, 1, 10, 10])
    with col1:
        search_option = st.selectbox(
                    label = 'Search with project title or IATI ID',
                    index = 0,
                    placeholder = " ",
                    options = ["Search with IATI ID", "Search with project title"],
                    )
        
        if search_option == "Search with IATI ID":
            search_list = iati_search_list
        else:
            search_list = title_search_list

        project_option = st.selectbox(
                    label = 'Search for a project',
                    index = None,
                    placeholder = " ",
                    options = search_list,
                    )
    
    with col3:
        # ORGA SELECTION
        orga_abbreviation = projects_df["orga_abbreviation"].unique()
        orga_full_names = projects_df["orga_full_name"].unique()
        orga_list = [f"{orga_full_names[i]} ({orga_abbreviation[i].upper()})"for i in range(len(orga_abbreviation))]

        orga_option_s = st.multiselect(
                'Development Bank / Organization ',
                orga_list,
                placeholder="All organizations selected "
                )
        
        different_orga_checkbox_s = st.checkbox("Only matches between different organizations ", value=True)

        
    with col4:
        # COUNTRY SELECTION
        country_option_s = st.multiselect(
                'Country / Countries ',
                COUNTRY_OPTION_LIST,
                placeholder="All countries selected "
                )

    st.write("--------------")

    #selected_index = None
    if project_option:
        selected_project_index = search_list.index(project_option)
        # COUNTRY CODES LIST
        country_code_list = [option[-3:-1] for option in country_option_s]

        # ORGANIZATION CODES LIST
        orga_code_list = [option.split("(")[1][:-1].lower() for option in orga_option_s]

        TOP_X_PROJECTS = 10
        with st.spinner('Please wait...'):
            filtered_df_s = filter_single(projects_df, country_code_list, orga_code_list)

        if isinstance(filtered_df_s, pd.DataFrame) and len(filtered_df_s) != 0:      
            if different_orga_checkbox_s:
                with st.spinner('Please wait...'):
                    top_projects_df = find_similar(selected_project_index, nonsameorgas_sim_matrix, filtered_df_s, 10)
            else:
                with st.spinner('Please wait...'):
                    top_projects_df = find_similar(selected_project_index, sim_matrix, filtered_df_s, 10)

            show_single_table(selected_project_index, projects_df, top_projects_df)

        else:
            st.write("-----")
            col1, col2, col3 = st.columns([1, 1, 1])
            with col2:
                st.write("  ")
                st.markdown("<span style='color: red'>Ther are no results for this filter!</span>", unsafe_allow_html=True)