Upload 42 files
Browse files- .gitignore +2 -0
- README.md +80 -14
- Synergy_requirements.txt +126 -0
- app.py +27 -0
- app_matching_page.py +888 -0
- functions/__pycache__/calc_matches.cpython-310.pyc +0 -0
- functions/__pycache__/filter_all_project_matching.cpython-311.pyc +0 -0
- functions/__pycache__/filter_multi_project_matching.cpython-311.pyc +0 -0
- functions/__pycache__/filter_projects.cpython-310.pyc +0 -0
- functions/__pycache__/filter_single_project_matching.cpython-311.pyc +0 -0
- functions/__pycache__/multi_project_matching.cpython-311.pyc +0 -0
- functions/__pycache__/same_country_filter.cpython-311.pyc +0 -0
- functions/__pycache__/semantic_search.cpython-310.pyc +0 -0
- functions/__pycache__/semantic_search.cpython-311.pyc +0 -0
- functions/__pycache__/single_project_matching.cpython-311.pyc +0 -0
- functions/__pycache__/single_similar.cpython-310.pyc +0 -0
- functions/filter_all_project_matching.py +38 -0
- functions/filter_multi_project_matching.py +57 -0
- functions/filter_single_project_matching.py +28 -0
- functions/multi_project_matching.py +81 -0
- functions/same_country_filter.py +19 -0
- functions/semantic_search.py +25 -0
- functions/single_project_matching.py +46 -0
- modules/__init__.py +1 -0
- modules/__pycache__/__init__.cpython-311.pyc +0 -0
- modules/__pycache__/allprojects_result_table.cpython-311.pyc +0 -0
- modules/__pycache__/crs_table.cpython-310.pyc +0 -0
- modules/__pycache__/filter_modules.cpython-310.pyc +0 -0
- modules/__pycache__/filter_projects.cpython-310.pyc +0 -0
- modules/__pycache__/multimatch_result_table.cpython-311.pyc +0 -0
- modules/__pycache__/navbar.cpython-310.pyc +0 -0
- modules/__pycache__/navbar.cpython-311.pyc +0 -0
- modules/__pycache__/result_table.cpython-310.pyc +0 -0
- modules/__pycache__/sdg_table.cpython-310.pyc +0 -0
- modules/__pycache__/semantic_search.cpython-310.pyc +0 -0
- modules/__pycache__/similarity_table.cpython-310.pyc +0 -0
- modules/__pycache__/singlematch_result_table.cpython-311.pyc +0 -0
- modules/allprojects_result_table.py +99 -0
- modules/multimatch_result_table.py +155 -0
- modules/navbar.py +46 -0
- modules/singlematch_result_table.py +190 -0
- requirements.txt +10 -3
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
env/
|
| 2 |
+
src/
|
README.md
CHANGED
|
@@ -1,19 +1,85 @@
|
|
| 1 |
---
|
| 2 |
-
title: Synergy
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Development Project Synergy Finder
|
| 3 |
+
emoji: 🌍
|
| 4 |
+
colorFrom: pink
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
sdk_version: 1.32.2
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: true
|
| 10 |
+
short_description: Discover Development Organization's Projects
|
| 11 |
+
license: mit
|
| 12 |
---
|
| 13 |
|
| 14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 15 |
|
| 16 |
+
# Technical Documentation of the system in accordance with EU AI Act.
|
| 17 |
|
| 18 |
+
|
| 19 |
+
## System Name: Development Project Synergy Finder
|
| 20 |
+
|
| 21 |
+
Provider / Supplier: GIZ Data Service Center
|
| 22 |
+
|
| 23 |
+
As of: July 2025
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
### General Description of the System
|
| 27 |
+
|
| 28 |
+
Multiple international organizations have projects in the same field and region. These projects could collaborate or learn from
|
| 29 |
+
each other to increase their impact if they were aware of one another. The Project Synergy Finder facilitates the search for
|
| 30 |
+
similar projects across different development organizations and banks. Note that this app is a prototype, results may be
|
| 31 |
+
incomplete or inaccurate.
|
| 32 |
+
|
| 33 |
+
### Models
|
| 34 |
+
|
| 35 |
+
**SDG Text Classifier**
|
| 36 |
+
|
| 37 |
+
Model Name: bert-base-uncased-finetuned-sdg (link will follow soon, if you have questions please contact us at dataservicecenter@giz.de)
|
| 38 |
+
Base Model: [google-bert/bert-base-uncased](https://huggingface.co/google-bert/bert-base-uncased)
|
| 39 |
+
Usage: The SDG categorization in this tool is AI-predicted based on project descriptions and titles using a SDG Classifier trainded on the OSDG dataset.
|
| 40 |
+
License: apache-2.0
|
| 41 |
+
Model Training Data: The training data has been collected by human annotators that are expert in their fields. If you have questions regarding the dataset, please
|
| 42 |
+
reach out to dataservicecenter@giz.de.
|
| 43 |
+
Bias: The data does not contain any known bias, however risk of potential bias can never be fully excluded.
|
| 44 |
+
|
| 45 |
+
### Data
|
| 46 |
+
|
| 47 |
+
**IATI Data**: The data is sourced from the IATI d-portal, providing project-level information. The International Aid Transparency Initiative (IATI) aims
|
| 48 |
+
to enhance transparency and effectiveness in development cooperation by making data publicly accessible.
|
| 49 |
+
Data Update: The data is updated irregularly, with the last retrieval on 10th May 2024.
|
| 50 |
+
Project Data: Data from projects labeled as active during the last data retrieval are included. The data includes Project Title, Description, URL, Country, and Sector classification (CRS). The CRS5 and CRS3 classifications organize development cooperation into categories, with the 5-digit level providing more specific details within the broader 3-digit categories.
|
| 51 |
+
|
| 52 |
+
**Organizations**: The tool currently includes projects from the following organizations:
|
| 53 |
+
|
| 54 |
+
- IAD: Inter-American Development Bank
|
| 55 |
+
- ADB: Asian Development Bank
|
| 56 |
+
- AfDB: African Development Bank
|
| 57 |
+
- EIB: European Investment Bank
|
| 58 |
+
- WB: World Bank
|
| 59 |
+
- WBTF: World Bank Trust Fund
|
| 60 |
+
- BMZ: Federal Ministry for Economic Cooperation and Development (Germany)
|
| 61 |
+
- KfW: KfW Development Bank (Germany)
|
| 62 |
+
- GIZ: Deutsche Gesellschaft für Internationale Zusammenarbeit (Germany)
|
| 63 |
+
- AA: German Federal Foreign Office (Germany)
|
| 64 |
+
|
| 65 |
+
**Additional Data:** The Sustainable Development Goals (SDGs) are 17 UN goals aimed at achieving global sustainability, peace, and prosperity by 2030.
|
| 66 |
+
The SDG categorization in this tool is AI-predicted based on project descriptions and titles using a SDG Classifier trainded on the OSDG dataset.
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
### System Usage
|
| 70 |
+
|
| 71 |
+
The system is a prototype designed to facilitate the quick search of similar projects across different development organizations.
|
| 72 |
+
The system is NOT designed to give a complete overview of all ongoing projects and their matching counterparts. Output may always be incomplete or falsly
|
| 73 |
+
classified and should ALWAYS be reviewed by a human.
|
| 74 |
+
The system does not make autonomous decisions but just provides information.
|
| 75 |
+
No personal data of users is being processed.
|
| 76 |
+
Results are intended for orientation only – not for legal or political advice.
|
| 77 |
+
|
| 78 |
+
### Transparency Towards Users
|
| 79 |
+
The user interface clearly indicates the use of a fine-tuned text classification transformer models and outlines that the system is a prototype.
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
### Contact & Feedback
|
| 83 |
+
Technical development is carried out by the GIZ Data Service Center.
|
| 84 |
+
Please reach out through the contact details provided below, if there are any issues or feedback.
|
| 85 |
+
Contact: For any questions, please contact us via dataservicecenter@giz.de
|
Synergy_requirements.txt
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
certifi==2024.8.30
|
| 2 |
+
regex==2024.7.24
|
| 3 |
+
fsspec==2024.6.1
|
| 4 |
+
pytz==2024.1
|
| 5 |
+
tzdata==2024.1
|
| 6 |
+
jsonschema-specifications==2023.12.1
|
| 7 |
+
setuptools==65.5.1
|
| 8 |
+
attrs==24.2.0
|
| 9 |
+
aiofiles==23.2.1
|
| 10 |
+
packaging==23.2
|
| 11 |
+
pip==22.3.1
|
| 12 |
+
pyarrow==17.0.0
|
| 13 |
+
rich==13.8.0
|
| 14 |
+
nvidia-nvjitlink-cu12==12.6.68
|
| 15 |
+
nvidia-cuda-cupti-cu12==12.1.105
|
| 16 |
+
nvidia-cuda-nvrtc-cu12==12.1.105
|
| 17 |
+
nvidia-cuda-runtime-cu12==12.1.105
|
| 18 |
+
nvidia-nvtx-cu12==12.1.105
|
| 19 |
+
nvidia-cublas-cu12==12.1.3.1
|
| 20 |
+
nvidia-cusparse-cu12==12.1.0.106
|
| 21 |
+
websockets==12.0
|
| 22 |
+
nvidia-cusolver-cu12==11.4.5.107
|
| 23 |
+
nvidia-cufft-cu12==11.0.2.54
|
| 24 |
+
pillow==10.4.0
|
| 25 |
+
nvidia-curand-cu12==10.3.2.106
|
| 26 |
+
nvidia-cudnn-cu12==9.1.0.70
|
| 27 |
+
tenacity==8.5.0
|
| 28 |
+
click==8.0.4
|
| 29 |
+
importlib-resources==6.4.5
|
| 30 |
+
tornado==6.4.1
|
| 31 |
+
multidict==6.1.0
|
| 32 |
+
pyyaml==6.0.2
|
| 33 |
+
psutil==5.9.8
|
| 34 |
+
cachetools==5.5.0
|
| 35 |
+
altair==5.4.1
|
| 36 |
+
watchdog==5.0.2
|
| 37 |
+
smmap==5.0.1
|
| 38 |
+
tqdm==4.66.5
|
| 39 |
+
fonttools==4.53.1
|
| 40 |
+
transformers==4.44.2
|
| 41 |
+
gradio==4.43.0
|
| 42 |
+
jsonschema==4.23.0
|
| 43 |
+
typing-extensions==4.12.2
|
| 44 |
+
anyio==4.4.0
|
| 45 |
+
gitdb==4.0.11
|
| 46 |
+
async-timeout==4.0.3
|
| 47 |
+
protobuf==3.20.3
|
| 48 |
+
filelock==3.16.0
|
| 49 |
+
orjson==3.10.7
|
| 50 |
+
aiohttp==3.10.5
|
| 51 |
+
matplotlib==3.9.2
|
| 52 |
+
python-decouple==3.8
|
| 53 |
+
idna==3.8
|
| 54 |
+
threadpoolctl==3.5.0
|
| 55 |
+
xxhash==3.5.0
|
| 56 |
+
charset-normalizer==3.3.2
|
| 57 |
+
networkx==3.3
|
| 58 |
+
xlsxwriter==3.2.0
|
| 59 |
+
gitpython==3.1.43
|
| 60 |
+
pyparsing==3.1.4
|
| 61 |
+
jinja2==3.1.4
|
| 62 |
+
markdown-it-py==3.0.0
|
| 63 |
+
triton==3.0.0
|
| 64 |
+
requests==2.32.3
|
| 65 |
+
pydantic-core==2.23.3
|
| 66 |
+
datasets==2.21.0
|
| 67 |
+
nvidia-nccl-cu12==2.20.5
|
| 68 |
+
pygments==2.18.0
|
| 69 |
+
semantic-version==2.10.0
|
| 70 |
+
pydantic==2.9.1
|
| 71 |
+
python-dateutil==2.9.0.post0
|
| 72 |
+
sentence-transformers==2.5.1
|
| 73 |
+
torch==2.4.1
|
| 74 |
+
aiohappyeyeballs==2.4.0
|
| 75 |
+
urllib3==2.2.2
|
| 76 |
+
markupsafe==2.1.5
|
| 77 |
+
pandas==2.1.4
|
| 78 |
+
streamlit==1.32.2
|
| 79 |
+
numpy==1.26.4
|
| 80 |
+
six==1.16.0
|
| 81 |
+
sympy==1.13.2
|
| 82 |
+
scipy==1.12.0
|
| 83 |
+
yarl==1.11.1
|
| 84 |
+
blinker==1.8.2
|
| 85 |
+
faiss-cpu==1.8.0
|
| 86 |
+
narwhals==1.6.4
|
| 87 |
+
shellingham==1.5.4
|
| 88 |
+
scikit-learn==1.5.1
|
| 89 |
+
kiwisolver==1.4.7
|
| 90 |
+
joblib==1.4.2
|
| 91 |
+
frozenlist==1.4.1
|
| 92 |
+
sniffio==1.3.1
|
| 93 |
+
aiosignal==1.3.1
|
| 94 |
+
contourpy==1.3.0
|
| 95 |
+
gradio-client==1.3.0
|
| 96 |
+
mpmath==1.3.0
|
| 97 |
+
exceptiongroup==1.2.2
|
| 98 |
+
httpcore==1.0.5
|
| 99 |
+
fastapi==0.112.4
|
| 100 |
+
multiprocess==0.70.16
|
| 101 |
+
wheel==0.44.0
|
| 102 |
+
starlette==0.38.5
|
| 103 |
+
referencing==0.35.1
|
| 104 |
+
uvicorn==0.30.6
|
| 105 |
+
spaces==0.30.2
|
| 106 |
+
httpx==0.27.2
|
| 107 |
+
pydub==0.25.1
|
| 108 |
+
huggingface-hub==0.24.6
|
| 109 |
+
rpds-py==0.20.0
|
| 110 |
+
tokenizers==0.19.1
|
| 111 |
+
h11==0.14.0
|
| 112 |
+
typer==0.12.5
|
| 113 |
+
cycler==0.12.1
|
| 114 |
+
tomlkit==0.12.0
|
| 115 |
+
toml==0.10.2
|
| 116 |
+
pydeck==0.9.1
|
| 117 |
+
annotated-types==0.7.0
|
| 118 |
+
ruff==0.6.4
|
| 119 |
+
safetensors==0.4.5
|
| 120 |
+
ffmpy==0.4.0
|
| 121 |
+
streamlit-option-menu==0.3.12
|
| 122 |
+
dill==0.3.8
|
| 123 |
+
streamlit-aggrid==0.3.4
|
| 124 |
+
hf-transfer==0.1.8
|
| 125 |
+
mdurl==0.1.2
|
| 126 |
+
python-multipart==0.0.9
|
app.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
# PAGE CONFIG
|
| 5 |
+
st.set_page_config(
|
| 6 |
+
page_title='Development Banks Collaboration Analyzer',
|
| 7 |
+
page_icon='📋',
|
| 8 |
+
layout='wide',
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
from modules.navbar import show_navbar
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# reduce space to the top
|
| 15 |
+
st.markdown("""
|
| 16 |
+
<style>
|
| 17 |
+
.block-container {
|
| 18 |
+
padding-top: 1rem;
|
| 19 |
+
padding-bottom: 4rem;
|
| 20 |
+
padding-left: 3rem;
|
| 21 |
+
padding-right: 3rem;
|
| 22 |
+
}
|
| 23 |
+
</style>
|
| 24 |
+
""", unsafe_allow_html=True)
|
| 25 |
+
|
| 26 |
+
# NAVBAR
|
| 27 |
+
navbar = show_navbar()
|
app_matching_page.py
ADDED
|
@@ -0,0 +1,888 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import io
|
| 4 |
+
import xlsxwriter
|
| 5 |
+
from scipy.sparse import load_npz
|
| 6 |
+
import pickle
|
| 7 |
+
from sentence_transformers import SentenceTransformer
|
| 8 |
+
from modules.multimatch_result_table import show_multi_table
|
| 9 |
+
from modules.singlematch_result_table import show_single_table
|
| 10 |
+
from modules.allprojects_result_table import show_all_projects_table
|
| 11 |
+
from functions.filter_multi_project_matching import filter_multi
|
| 12 |
+
from functions.filter_single_project_matching import filter_single
|
| 13 |
+
from functions.filter_all_project_matching import filter_all_projects
|
| 14 |
+
from functions.multi_project_matching import calc_multi_matches
|
| 15 |
+
from functions.same_country_filter import same_country_filter
|
| 16 |
+
from functions.single_project_matching import find_similar
|
| 17 |
+
import gc
|
| 18 |
+
|
| 19 |
+
# Catch DATA
|
| 20 |
+
# Load Similarity matrix
|
| 21 |
+
@st.cache_data
|
| 22 |
+
def load_sim_matrix():
|
| 23 |
+
"""
|
| 24 |
+
!!! Similarities when matches between same orgas are allowed
|
| 25 |
+
"""
|
| 26 |
+
loaded_matrix = load_npz("src/extended_similarities.npz")
|
| 27 |
+
return loaded_matrix
|
| 28 |
+
|
| 29 |
+
# Load Non Similar Orga Matrix
|
| 30 |
+
def load_nonsameorga_sim_matrix():
|
| 31 |
+
"""
|
| 32 |
+
!!! Similarities when matches between same orgas are NOT allowed
|
| 33 |
+
"""
|
| 34 |
+
loaded_matrix = load_npz("src/extended_similarities_nonsimorga.npz")
|
| 35 |
+
return loaded_matrix
|
| 36 |
+
|
| 37 |
+
# Load Projects DFs
|
| 38 |
+
@st.cache_data
|
| 39 |
+
def load_projects():
|
| 40 |
+
def fix_faulty_descriptions(description): # In some BMZ projects there are duplicate descriptions
|
| 41 |
+
if description and ';' in description:
|
| 42 |
+
parts = description.split(';')
|
| 43 |
+
if len(parts) == 2 and parts[0].strip() == parts[1].strip():
|
| 44 |
+
return parts[0].strip()
|
| 45 |
+
return description
|
| 46 |
+
|
| 47 |
+
orgas_df = pd.read_csv("src/projects/project_orgas.csv")
|
| 48 |
+
region_df = pd.read_csv("src/projects/project_region.csv")
|
| 49 |
+
sector_df = pd.read_csv("src/projects/project_sector.csv")
|
| 50 |
+
status_df = pd.read_csv("src/projects/project_status.csv")
|
| 51 |
+
texts_df = pd.read_csv("src/projects/project_texts.csv")
|
| 52 |
+
|
| 53 |
+
projects_df = pd.merge(orgas_df, region_df, on='iati_id', how='inner')
|
| 54 |
+
projects_df = pd.merge(projects_df, sector_df, on='iati_id', how='inner')
|
| 55 |
+
projects_df = pd.merge(projects_df, status_df, on='iati_id', how='inner')
|
| 56 |
+
projects_df = pd.merge(projects_df, texts_df, on='iati_id', how='inner')
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# Add regions (should have been done in the preprocessing instead of here, so is just a quick fix to be able to add the region filter)
|
| 61 |
+
region_lookup_df = pd.read_csv('src/codelists/regions.csv', usecols=['alpha-2', 'region', 'sub-region'])
|
| 62 |
+
|
| 63 |
+
projects_df['country_code'] = projects_df['country'].str.replace(';', '').str.strip()
|
| 64 |
+
# Replace empty values in the 'country_code' column with 'Unknown'
|
| 65 |
+
projects_df['country_code'] = projects_df['country_code'].fillna('Unknown')
|
| 66 |
+
|
| 67 |
+
region_lookup_df['alpha-2'] = region_lookup_df['alpha-2'].str.strip()
|
| 68 |
+
projects_df = pd.merge(projects_df, region_lookup_df[['alpha-2', 'region', 'sub-region']], left_on='country_code', right_on='alpha-2', how='left')
|
| 69 |
+
|
| 70 |
+
projects_df.rename(columns={'region': 'continent', 'sub-region': 'region'}, inplace=True)
|
| 71 |
+
projects_df['continent'] = projects_df['continent'].fillna('Unknown')
|
| 72 |
+
projects_df['region'] = projects_df['region'].fillna('Unknown')
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# Fix faulty descriptions for BMZ projects
|
| 76 |
+
bmz_mask = projects_df['orga_abbreviation'].str.lower() == 'bmz'
|
| 77 |
+
projects_df.loc[bmz_mask, 'description_main'] = projects_df.loc[bmz_mask, 'description_main'].apply(fix_faulty_descriptions)
|
| 78 |
+
|
| 79 |
+
# Add Project Link column
|
| 80 |
+
projects_df['Project Link'] = projects_df['iati_id'].apply(
|
| 81 |
+
lambda x: f'https://d-portal.org/ctrack.html#view=act&aid={x}'
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# Create necessary columns for consistency
|
| 85 |
+
projects_df['crs_3_code_list'] = projects_df['crs_3_name'].apply(
|
| 86 |
+
lambda x: [""] if pd.isna(x) else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 87 |
+
)
|
| 88 |
+
projects_df['crs_5_code_list'] = projects_df['crs_5_name'].apply(
|
| 89 |
+
lambda x: [""] if pd.isna(x) else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 90 |
+
)
|
| 91 |
+
projects_df['sdg_list'] = projects_df['sgd_pred_code'].apply(
|
| 92 |
+
lambda x: [""] if pd.isna(x) else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
# Ensure country_flag is set to None if country_name is missing or "NA"
|
| 96 |
+
projects_df['country_flag'] = projects_df.apply(
|
| 97 |
+
lambda row: None if pd.isna(row['country_name']) or row['country_name'] == "NA" else row['country_flag'],
|
| 98 |
+
axis=1
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
iati_search_list = [f'{row.iati_id}' for row in projects_df.itertuples()]
|
| 102 |
+
title_search_list = [f'{row.title_main} ({row.orga_abbreviation.upper()})' for row in projects_df.itertuples()]
|
| 103 |
+
|
| 104 |
+
return projects_df, iati_search_list, title_search_list
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# Load CRS 3 data
|
| 108 |
+
@st.cache_data
|
| 109 |
+
def getCRS3():
|
| 110 |
+
# Read in CRS3 CODELISTS
|
| 111 |
+
crs3_df = pd.read_csv('src/codelists/crs3_codes.csv')
|
| 112 |
+
CRS3_CODES = crs3_df['code'].tolist()
|
| 113 |
+
CRS3_NAME = crs3_df['name'].tolist()
|
| 114 |
+
CRS3_MERGED = {f"{name} - {code}": code for name, code in zip(CRS3_NAME, CRS3_CODES)}
|
| 115 |
+
return CRS3_MERGED
|
| 116 |
+
|
| 117 |
+
# Load CRS 5 data
|
| 118 |
+
@st.cache_data
|
| 119 |
+
def getCRS5():
|
| 120 |
+
# Read in CRS3 CODELISTS
|
| 121 |
+
crs5_df = pd.read_csv('src/codelists/crs5_codes.csv')
|
| 122 |
+
CRS5_CODES = crs5_df['code'].tolist()
|
| 123 |
+
CRS5_NAME = crs5_df['name'].tolist()
|
| 124 |
+
CRS5_MERGED = {code: [f"{name} - {code}"] for name, code in zip(CRS5_NAME, CRS5_CODES)}
|
| 125 |
+
return CRS5_MERGED
|
| 126 |
+
|
| 127 |
+
# Load SDG data
|
| 128 |
+
@st.cache_data
|
| 129 |
+
def getSDG():
|
| 130 |
+
# Read in SDG CODELISTS
|
| 131 |
+
sdg_df = pd.read_csv('src/codelists/sdg_goals.csv')
|
| 132 |
+
SDG_NAMES = sdg_df['name'].tolist()
|
| 133 |
+
return SDG_NAMES
|
| 134 |
+
|
| 135 |
+
@st.cache_data
|
| 136 |
+
def getCountry():
|
| 137 |
+
# Read in countries from codelist
|
| 138 |
+
country_df = pd.read_csv('src/codelists/country_codes_ISO3166-1alpha-2.csv')
|
| 139 |
+
|
| 140 |
+
# Read in regions from codelist, keeping only the relevant columns
|
| 141 |
+
region_lookup_df = pd.read_csv('src/codelists/regions.csv', usecols=['alpha-2', 'region', 'sub-region'])
|
| 142 |
+
|
| 143 |
+
# Strip quotes from the 'Alpha-2 code' column in country_df
|
| 144 |
+
country_df['Alpha-2 code'] = country_df['Alpha-2 code'].str.replace('"', '').str.strip()
|
| 145 |
+
|
| 146 |
+
# Ensure no leading/trailing spaces in the 'alpha-2' column in region_lookup_df
|
| 147 |
+
region_lookup_df['alpha-2'] = region_lookup_df['alpha-2'].str.strip()
|
| 148 |
+
|
| 149 |
+
# Merge country and region dataframes on 'Alpha-2 code' from country_df and 'alpha-2' from region_lookup_df
|
| 150 |
+
merged_df = pd.merge(country_df, region_lookup_df, how='left', left_on='Alpha-2 code', right_on='alpha-2')
|
| 151 |
+
|
| 152 |
+
# Handle any missing regions or sub-regions
|
| 153 |
+
merged_df['region'] = merged_df['region'].fillna('Unknown')
|
| 154 |
+
merged_df['sub-region'] = merged_df['sub-region'].fillna('Unknown')
|
| 155 |
+
|
| 156 |
+
# Extract necessary columns as lists
|
| 157 |
+
COUNTRY_CODES = merged_df['Alpha-2 code'].tolist()
|
| 158 |
+
COUNTRY_NAMES = merged_df['Country'].tolist()
|
| 159 |
+
REGIONS = merged_df['region'].tolist()
|
| 160 |
+
SUB_REGIONS = merged_df['sub-region'].tolist()
|
| 161 |
+
|
| 162 |
+
# Create the original COUNTRY_OPTION_LIST without regions
|
| 163 |
+
COUNTRY_OPTION_LIST = [f"{COUNTRY_NAMES[i]} ({COUNTRY_CODES[i]})" for i in range(len(COUNTRY_NAMES))]
|
| 164 |
+
|
| 165 |
+
# Create a hierarchical filter structure for sub-regions
|
| 166 |
+
sub_region_hierarchy = {}
|
| 167 |
+
sub_region_to_region = {}
|
| 168 |
+
for i in range(len(SUB_REGIONS)):
|
| 169 |
+
sub_region = SUB_REGIONS[i]
|
| 170 |
+
country = COUNTRY_CODES[i]
|
| 171 |
+
region = REGIONS[i]
|
| 172 |
+
if sub_region not in sub_region_hierarchy:
|
| 173 |
+
sub_region_hierarchy[sub_region] = []
|
| 174 |
+
sub_region_hierarchy[sub_region].append(country)
|
| 175 |
+
|
| 176 |
+
# Map sub-regions to regions
|
| 177 |
+
sub_region_to_region[sub_region] = region
|
| 178 |
+
|
| 179 |
+
# Sort the subregions by regions
|
| 180 |
+
sorted_sub_regions = sorted(sub_region_hierarchy.keys(), key=lambda x: sub_region_to_region[x])
|
| 181 |
+
|
| 182 |
+
return COUNTRY_OPTION_LIST, sorted_sub_regions
|
| 183 |
+
|
| 184 |
+
# Call the function to load and display the country data
|
| 185 |
+
COUNTRY_OPTION_LIST, REGION_OPTION_LIST = getCountry()
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# Load Sentence Transformer Model
|
| 189 |
+
@st.cache_resource
|
| 190 |
+
def load_model():
|
| 191 |
+
model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 192 |
+
return model
|
| 193 |
+
|
| 194 |
+
# Load Embeddings
|
| 195 |
+
@st.cache_data
|
| 196 |
+
def load_embeddings_and_index():
|
| 197 |
+
# Load embeddings
|
| 198 |
+
with open("src/embeddings.pkl", "rb") as fIn:
|
| 199 |
+
stored_data = pickle.load(fIn)
|
| 200 |
+
embeddings = stored_data["embeddings"]
|
| 201 |
+
return embeddings
|
| 202 |
+
|
| 203 |
+
# USE CACHE FUNCTIONS
|
| 204 |
+
sim_matrix = load_sim_matrix() # For similarities when matches between same orgas are allowed
|
| 205 |
+
nonsameorgas_sim_matrix = load_nonsameorga_sim_matrix() #For similarities when matches between same orgas are NOT allowed
|
| 206 |
+
projects_df, iati_search_list, title_search_list = load_projects()
|
| 207 |
+
|
| 208 |
+
CRS3_MERGED = getCRS3()
|
| 209 |
+
CRS5_MERGED = getCRS5()
|
| 210 |
+
SDG_NAMES = getSDG()
|
| 211 |
+
|
| 212 |
+
# LOAD MODEL FROM CACHE FOR SEMANTIC SEARCH
|
| 213 |
+
model = load_model()
|
| 214 |
+
embeddings = load_embeddings_and_index()
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
##################################
|
| 219 |
+
|
| 220 |
+
def show_landing_page():
|
| 221 |
+
st.title("Development Project Synergy Finder")
|
| 222 |
+
|
| 223 |
+
st.subheader("About")
|
| 224 |
+
st.markdown("""
|
| 225 |
+
Multiple international organizations have projects in the same field and region. These projects could collaborate or learn from each other to increase their impact if they were aware of one another. The Project Synergy Finder facilitates the search for similar projects across different development organizations and banks in three distinct ways. Note that this app is a prototype, results may be incomplete or inaccurate. """)
|
| 226 |
+
st.markdown("<br><br>", unsafe_allow_html=True) # Add two line breaks
|
| 227 |
+
|
| 228 |
+
st.subheader("Pages")
|
| 229 |
+
st.markdown("""
|
| 230 |
+
1. **📊 All Projects**: Displays all projects included in the analysis.
|
| 231 |
+
*Example Use Case*: Show all World Bank and African Development Bank projects in East Africa working towards the Sustainable Development Goal of achieving gender equality.
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
2. **🎯 Single-Project Matching**: Finds the top similar projects to a selected one.
|
| 235 |
+
*Example Use Case*: Show projects in Eastern Europe that are similar to the "Second Irrigation and Drainage Improvement Project" by the World Bank.
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
3. **🔍 Multi-Project Matching**: Searches for matching pairs of projects.
|
| 239 |
+
*Example Use Case*: Show pairs of similar projects in the "Energy Policy" sector from different organizations within the same country.
|
| 240 |
+
""")
|
| 241 |
+
st.markdown("<br><br>", unsafe_allow_html=True) # Add two line breaks
|
| 242 |
+
|
| 243 |
+
st.subheader("Data")
|
| 244 |
+
st.markdown("""
|
| 245 |
+
**IATI Data**: The data is sourced from the [IATI d-portal](https://d-portal.org/), providing project-level information. The International Aid Transparency Initiative (IATI) aims to enhance transparency and effectiveness in development cooperation by making data publicly accessible.
|
| 246 |
+
|
| 247 |
+
**Data Update**: The data is updated irregularly, with the last retrieval on 10th May 2024.
|
| 248 |
+
|
| 249 |
+
**Project Data**: Data from projects labeled as active during the last data retrieval are included. The data includes Project Title, Description, URL, Country, and Sector classification (CRS). The CRS5 and CRS3 classifications organize development cooperation into categories, with the 5-digit level providing more specific details within the broader 3-digit categories.
|
| 250 |
+
|
| 251 |
+
**Organizations**: The tool currently includes projects from the following organizations:
|
| 252 |
+
- **IAD**: Inter-American Development Bank
|
| 253 |
+
- **ADB**: Asian Development Bank
|
| 254 |
+
- **AfDB**: African Development Bank
|
| 255 |
+
- **EIB**: European Investment Bank
|
| 256 |
+
- **WB**: World Bank
|
| 257 |
+
- **WBTF**: World Bank Trust Fund
|
| 258 |
+
- **BMZ**: Federal Ministry for Economic Cooperation and Development (Germany)
|
| 259 |
+
- **KfW**: KfW Development Bank (Germany)
|
| 260 |
+
- **GIZ**: Deutsche Gesellschaft für Internationale Zusammenarbeit (Germany)
|
| 261 |
+
- **AA**: German Federal Foreign Office (Germany)
|
| 262 |
+
|
| 263 |
+
**Additional Data**: The Sustainable Development Goals (SDGs) are 17 UN goals aimed at achieving global sustainability, peace, and prosperity by 2030. The SDG categorization in this tool is AI-predicted based on project descriptions and titles using a [SDG Classifier](https://huggingface.co/jonas/bert-base-uncased-finetuned-sdg) trainded on the OSDG dataset.
|
| 264 |
+
""")
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
##################################
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def show_all_projects_page():
|
| 271 |
+
# Define the page size at the beginning
|
| 272 |
+
page_size = 30
|
| 273 |
+
|
| 274 |
+
def reset_pagination():
|
| 275 |
+
st.session_state.current_end_idx_all = page_size
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
col1, col2, col3 = st.columns([10, 1, 10])
|
| 279 |
+
with col1:
|
| 280 |
+
st.subheader("Project Filter")
|
| 281 |
+
|
| 282 |
+
st.session_state.crs5_option_disabled = True
|
| 283 |
+
col1, col2, col3 = st.columns([10, 1, 10])
|
| 284 |
+
with col1:
|
| 285 |
+
# CRS 3 SELECTION
|
| 286 |
+
crs3_option = st.multiselect(
|
| 287 |
+
'CRS 3',
|
| 288 |
+
CRS3_MERGED,
|
| 289 |
+
placeholder="Select a CRS 3 code",
|
| 290 |
+
on_change=reset_pagination,
|
| 291 |
+
key='crs3_all_projects_page'
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
# CRS 5 SELECTION
|
| 295 |
+
# Only enable crs5 select field when crs3 code is selected
|
| 296 |
+
if crs3_option:
|
| 297 |
+
st.session_state.crs5_option_disabled = False
|
| 298 |
+
|
| 299 |
+
# Define list of crs5 codes depending on crs3 codes
|
| 300 |
+
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:]]
|
| 301 |
+
|
| 302 |
+
# crs5 select field
|
| 303 |
+
crs5_option = st.multiselect(
|
| 304 |
+
'CRS 5',
|
| 305 |
+
crs5_list,
|
| 306 |
+
placeholder="Select a CRS 5 code",
|
| 307 |
+
disabled=st.session_state.crs5_option_disabled,
|
| 308 |
+
on_change=reset_pagination,
|
| 309 |
+
key='crs5_all_projects_page'
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
# SDG SELECTION
|
| 313 |
+
sdg_option = st.selectbox(
|
| 314 |
+
label='Sustainable Development Goal (AI-predicted)',
|
| 315 |
+
index=None,
|
| 316 |
+
placeholder="Select a SDG",
|
| 317 |
+
options=SDG_NAMES[:-1],
|
| 318 |
+
on_change=reset_pagination,
|
| 319 |
+
key='sdg_all_projects_page'
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
with col3:
|
| 323 |
+
# REGION SELECTION
|
| 324 |
+
region_option = st.multiselect(
|
| 325 |
+
'Regions',
|
| 326 |
+
REGION_OPTION_LIST,
|
| 327 |
+
placeholder="All regions selected",
|
| 328 |
+
on_change=reset_pagination,
|
| 329 |
+
key='regions_all_projects_page'
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
# COUNTRY SELECTION
|
| 333 |
+
country_option = st.multiselect(
|
| 334 |
+
'Countries',
|
| 335 |
+
COUNTRY_OPTION_LIST,
|
| 336 |
+
placeholder="All countries selected",
|
| 337 |
+
on_change=reset_pagination,
|
| 338 |
+
key='country_all_projects_page'
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
# ORGA SELECTION
|
| 342 |
+
orga_abbreviation = projects_df["orga_abbreviation"].unique()
|
| 343 |
+
orga_full_names = projects_df["orga_full_name"].unique()
|
| 344 |
+
orga_list = [f"{orga_full_names[i]} ({orga_abbreviation[i].upper()})" for i in range(len(orga_abbreviation))]
|
| 345 |
+
|
| 346 |
+
orga_option = st.multiselect(
|
| 347 |
+
'Organizations',
|
| 348 |
+
orga_list,
|
| 349 |
+
placeholder="All organizations selected",
|
| 350 |
+
on_change=reset_pagination,
|
| 351 |
+
key='orga_all_projects_page'
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
# CRS CODE LIST
|
| 355 |
+
crs3_list = [i[-3:] for i in crs3_option]
|
| 356 |
+
crs5_list = [i[-5:] for i in crs5_option]
|
| 357 |
+
|
| 358 |
+
# SDG CODE LIST
|
| 359 |
+
if sdg_option is not None:
|
| 360 |
+
sdg_str = sdg_option.split(".")[0]
|
| 361 |
+
else:
|
| 362 |
+
sdg_str = ""
|
| 363 |
+
|
| 364 |
+
# COUNTRY CODES LIST
|
| 365 |
+
country_code_list = [option[-3:-1] for option in country_option]
|
| 366 |
+
|
| 367 |
+
# ORGANIZATION CODES LIST
|
| 368 |
+
orga_code_list = [option.split("(")[1][:-1].lower() for option in orga_option]
|
| 369 |
+
|
| 370 |
+
st.write("-----")
|
| 371 |
+
|
| 372 |
+
# FILTER DF WITH SELECTED FILTER OPTIONS
|
| 373 |
+
filtered_df = filter_all_projects(projects_df, country_code_list, orga_code_list, crs3_list, crs5_list, sdg_str, region_option)
|
| 374 |
+
if isinstance(filtered_df, pd.DataFrame) and len(filtered_df) != 0:
|
| 375 |
+
# Implement pagination
|
| 376 |
+
if 'current_end_idx_all' not in st.session_state:
|
| 377 |
+
st.session_state.current_end_idx_all = page_size
|
| 378 |
+
|
| 379 |
+
end_idx = st.session_state.current_end_idx_all
|
| 380 |
+
|
| 381 |
+
paginated_df = filtered_df.iloc[:end_idx]
|
| 382 |
+
|
| 383 |
+
col1, col2 = st.columns([7, 3])
|
| 384 |
+
with col1:
|
| 385 |
+
st.subheader("Filtered Projects")
|
| 386 |
+
with col2:
|
| 387 |
+
# Add a download button for the paginated results
|
| 388 |
+
def to_excel(df, sheet_name):
|
| 389 |
+
# Rename columns
|
| 390 |
+
df = df.rename(columns={
|
| 391 |
+
"iati_id": "IATI Identifier",
|
| 392 |
+
"title_main": "Title",
|
| 393 |
+
"orga_abbreviation": "Organization",
|
| 394 |
+
"description_main": "Description",
|
| 395 |
+
"country_name": "Country",
|
| 396 |
+
"sdg_list": "SDG List",
|
| 397 |
+
"crs_3_code_list": "CRS 3 Codes",
|
| 398 |
+
"crs_5_code_list": "CRS 5 Codes",
|
| 399 |
+
"Project Link": "Project Link"
|
| 400 |
+
})
|
| 401 |
+
output = io.BytesIO()
|
| 402 |
+
writer = pd.ExcelWriter(output, engine='xlsxwriter')
|
| 403 |
+
df.to_excel(writer, index=False, sheet_name=sheet_name)
|
| 404 |
+
writer.close()
|
| 405 |
+
processed_data = output.getvalue()
|
| 406 |
+
return processed_data
|
| 407 |
+
|
| 408 |
+
# Direct download buttons
|
| 409 |
+
columns_to_include = ["iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]
|
| 410 |
+
|
| 411 |
+
with st.expander("Excel Download"):
|
| 412 |
+
# First 15 Results Button
|
| 413 |
+
df_to_download_15 = filtered_df[columns_to_include].head(15)
|
| 414 |
+
excel_data_15 = to_excel(df_to_download_15, "Sheet1")
|
| 415 |
+
st.download_button(label="First 30 Projects", data=excel_data_15, file_name="First_15_All_Projects_Filtered.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
| 416 |
+
|
| 417 |
+
# All Results Button
|
| 418 |
+
df_to_download_all = filtered_df[columns_to_include]
|
| 419 |
+
excel_data_all = to_excel(df_to_download_all, "Sheet1")
|
| 420 |
+
st.download_button(label="All", data=excel_data_all, file_name="All_All_Projects_Filtered.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
| 421 |
+
|
| 422 |
+
show_all_projects_table(projects_df, paginated_df)
|
| 423 |
+
|
| 424 |
+
st.write(f"Showing 1 to {min(end_idx, len(filtered_df))} of {len(filtered_df)} projects")
|
| 425 |
+
|
| 426 |
+
# Center the buttons and place them close together
|
| 427 |
+
col1, col2, col3, col4, col5 = st.columns([2, 1, 1, 1, 2])
|
| 428 |
+
with col2:
|
| 429 |
+
if st.button('Show More', key='show_more'):
|
| 430 |
+
st.session_state.current_end_idx_all = min(end_idx + page_size, len(filtered_df))
|
| 431 |
+
st.experimental_rerun()
|
| 432 |
+
with col4:
|
| 433 |
+
if st.button('Show Less', key='show_less') and end_idx > page_size:
|
| 434 |
+
st.session_state.current_end_idx_all = max(end_idx - page_size, page_size)
|
| 435 |
+
st.experimental_rerun()
|
| 436 |
+
|
| 437 |
+
else:
|
| 438 |
+
st.write("-----")
|
| 439 |
+
col1, col2, col3 = st.columns([1, 1, 1])
|
| 440 |
+
with col2:
|
| 441 |
+
st.write(" ")
|
| 442 |
+
st.markdown("<span style='color: red'>There are no results for the applied filter. Try another filter!</span>", unsafe_allow_html=True)
|
| 443 |
+
|
| 444 |
+
del crs3_list, crs5_list, sdg_str, filtered_df
|
| 445 |
+
gc.collect()
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
##################################
|
| 450 |
+
|
| 451 |
+
def show_single_matching_page():
|
| 452 |
+
# Define the page size at the beginning
|
| 453 |
+
page_size = 15
|
| 454 |
+
|
| 455 |
+
def reset_pagination():
|
| 456 |
+
st.session_state.current_end_idx_single = page_size
|
| 457 |
+
|
| 458 |
+
with st.expander("Explanation"):
|
| 459 |
+
st.caption("""
|
| 460 |
+
Single Project Matching enables you to choose an individual project using either the project IATI ID or title, to display projects most similar to it.
|
| 461 |
+
|
| 462 |
+
**Similarity Score**:
|
| 463 |
+
- Similarity ranges from 0 to 100 (identical projects score 100%), and is calculated based on
|
| 464 |
+
- Text similarity of project description and title (MiniLMM & Cosine Similiarity).
|
| 465 |
+
- Matching of SDGs (AI-predicted).
|
| 466 |
+
- Matching of CRS-3 & CRS-5 sector codes.
|
| 467 |
+
- Components are weighted to give a normalized score.
|
| 468 |
+
|
| 469 |
+
Note that this app is a prototype, results may be incomplete or inaccurate.
|
| 470 |
+
""")
|
| 471 |
+
|
| 472 |
+
col1, col2, col3 = st.columns([10, 1, 10])
|
| 473 |
+
with col1:
|
| 474 |
+
st.subheader("Reference Project")
|
| 475 |
+
st.caption("""
|
| 476 |
+
Select a reference project either by its title or IATI ID.
|
| 477 |
+
""")
|
| 478 |
+
with col3:
|
| 479 |
+
st.subheader("Filters for Similar Projects")
|
| 480 |
+
st.caption("""
|
| 481 |
+
The filters are applied to find the similar projects and are independend of the selected reference project.
|
| 482 |
+
""")
|
| 483 |
+
|
| 484 |
+
col1, col2, col3 = st.columns([10, 1, 10])
|
| 485 |
+
with col1:
|
| 486 |
+
search_option = st.selectbox(
|
| 487 |
+
label='Search with project title or IATI ID',
|
| 488 |
+
index=0,
|
| 489 |
+
placeholder=" ",
|
| 490 |
+
options=["Search with IATI ID", "Search with project title"],
|
| 491 |
+
on_change=reset_pagination,
|
| 492 |
+
key='search_option_single'
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
if search_option == "Search with IATI ID":
|
| 496 |
+
search_list = iati_search_list
|
| 497 |
+
else:
|
| 498 |
+
search_list = title_search_list
|
| 499 |
+
|
| 500 |
+
project_option = st.selectbox(
|
| 501 |
+
label='Search for a project',
|
| 502 |
+
index=None,
|
| 503 |
+
placeholder=" ",
|
| 504 |
+
options=search_list,
|
| 505 |
+
on_change=reset_pagination,
|
| 506 |
+
key='project_option_single'
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
with col3:
|
| 510 |
+
orga_abbreviation = projects_df["orga_abbreviation"].unique()
|
| 511 |
+
orga_full_names = projects_df["orga_full_name"].unique()
|
| 512 |
+
orga_list = [f"{orga_full_names[i]} ({orga_abbreviation[i].upper()})" for i in range(len(orga_abbreviation))]
|
| 513 |
+
|
| 514 |
+
# REGION SELECTION
|
| 515 |
+
region_option_s = st.multiselect(
|
| 516 |
+
'Regions',
|
| 517 |
+
REGION_OPTION_LIST,
|
| 518 |
+
placeholder="All regions selected",
|
| 519 |
+
on_change=reset_pagination,
|
| 520 |
+
key='regions_single_projects_page'
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
country_option_s = st.multiselect(
|
| 524 |
+
'Countries ',
|
| 525 |
+
COUNTRY_OPTION_LIST,
|
| 526 |
+
placeholder="All countries selected ",
|
| 527 |
+
on_change=reset_pagination,
|
| 528 |
+
key='country_option_single'
|
| 529 |
+
)
|
| 530 |
+
orga_option_s = st.multiselect(
|
| 531 |
+
'Organizations',
|
| 532 |
+
orga_list,
|
| 533 |
+
placeholder="All organizations selected ",
|
| 534 |
+
on_change=reset_pagination,
|
| 535 |
+
key='orga_option_single'
|
| 536 |
+
)
|
| 537 |
+
|
| 538 |
+
different_orga_checkbox_s = st.checkbox("Only matches between different organizations ", value=True, on_change=reset_pagination, key='different_orga_checkbox_single')
|
| 539 |
+
|
| 540 |
+
st.write("-----")
|
| 541 |
+
|
| 542 |
+
if project_option:
|
| 543 |
+
selected_project_index = search_list.index(project_option)
|
| 544 |
+
country_code_list = [option[-3:-1] for option in country_option_s]
|
| 545 |
+
orga_code_list = [option.split("(")[1][:-1].lower() for option in orga_option_s]
|
| 546 |
+
|
| 547 |
+
TOP_X_PROJECTS = 1000
|
| 548 |
+
with st.spinner('Please wait...'):
|
| 549 |
+
filtered_df_s = filter_single(projects_df, country_code_list, orga_code_list, region_option_s)
|
| 550 |
+
|
| 551 |
+
if isinstance(filtered_df_s, pd.DataFrame) and len(filtered_df_s) != 0:
|
| 552 |
+
if different_orga_checkbox_s:
|
| 553 |
+
with st.spinner('Please wait...'):
|
| 554 |
+
top_projects_df = find_similar(selected_project_index, nonsameorgas_sim_matrix, filtered_df_s, TOP_X_PROJECTS)
|
| 555 |
+
else:
|
| 556 |
+
with st.spinner('Please wait...'):
|
| 557 |
+
top_projects_df = find_similar(selected_project_index, sim_matrix, filtered_df_s, TOP_X_PROJECTS)
|
| 558 |
+
|
| 559 |
+
# Implement show more, show less, and show all functionality
|
| 560 |
+
if 'current_end_idx_single' not in st.session_state:
|
| 561 |
+
st.session_state.current_end_idx_single = page_size
|
| 562 |
+
|
| 563 |
+
end_idx = st.session_state.current_end_idx_single
|
| 564 |
+
|
| 565 |
+
paginated_df = top_projects_df.iloc[:end_idx]
|
| 566 |
+
|
| 567 |
+
# Add a download button for the paginated results
|
| 568 |
+
def to_excel(df, sheet_name):
|
| 569 |
+
# Rename columns
|
| 570 |
+
df = df.rename(columns={
|
| 571 |
+
"similarity": "Similarity Score",
|
| 572 |
+
"iati_id": "IATI Identifier",
|
| 573 |
+
"title_main": "Title",
|
| 574 |
+
"orga_abbreviation": "Organization",
|
| 575 |
+
"description_main": "Description",
|
| 576 |
+
"country_name": "Country",
|
| 577 |
+
"sdg_list": "SDG List",
|
| 578 |
+
"crs_3_code_list": "CRS 3 Codes",
|
| 579 |
+
"crs_5_code_list": "CRS 5 Codes",
|
| 580 |
+
"Project Link": "Project Link"
|
| 581 |
+
})
|
| 582 |
+
output = io.BytesIO()
|
| 583 |
+
writer = pd.ExcelWriter(output, engine='xlsxwriter')
|
| 584 |
+
df.to_excel(writer, index=False, sheet_name=sheet_name)
|
| 585 |
+
writer.close()
|
| 586 |
+
processed_data = output.getvalue()
|
| 587 |
+
return processed_data
|
| 588 |
+
|
| 589 |
+
# Direct download buttons
|
| 590 |
+
columns_to_include = ["similarity", "iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]
|
| 591 |
+
|
| 592 |
+
col1, col2 = st.columns([15, 5])
|
| 593 |
+
with col2:
|
| 594 |
+
with st.expander("Excel Download"):
|
| 595 |
+
# First 15 Results Button
|
| 596 |
+
df_to_download_15 = top_projects_df[columns_to_include].head(15)
|
| 597 |
+
excel_data_15 = to_excel(df_to_download_15, "Sheet1")
|
| 598 |
+
st.download_button(label="Download first 15 projects", data=excel_data_15, file_name="First_15_Single_Project_Matching_Results.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
| 599 |
+
df_to_download_all = top_projects_df[columns_to_include]
|
| 600 |
+
excel_data_all = to_excel(df_to_download_all, "Sheet1")
|
| 601 |
+
st.download_button(label="Download All", data=excel_data_all, file_name="All_Single_Project_Matching_Results.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
| 602 |
+
|
| 603 |
+
show_single_table(selected_project_index, projects_df, paginated_df)
|
| 604 |
+
|
| 605 |
+
st.write(f"Showing 1 to {min(end_idx, len(top_projects_df))} of {len(top_projects_df)} projects")
|
| 606 |
+
|
| 607 |
+
# Center the buttons and place them close together
|
| 608 |
+
col1, col2, col3, col4, col5 = st.columns([2, 1, 1, 1, 2])
|
| 609 |
+
with col2:
|
| 610 |
+
if st.button('Show More'):
|
| 611 |
+
st.session_state.current_end_idx_single = min(end_idx + page_size, len(top_projects_df))
|
| 612 |
+
st.experimental_rerun()
|
| 613 |
+
with col3:
|
| 614 |
+
if st.button('Show Less') and end_idx > page_size:
|
| 615 |
+
st.session_state.current_end_idx_single = max(end_idx - page_size, page_size)
|
| 616 |
+
st.experimental_rerun()
|
| 617 |
+
with col4:
|
| 618 |
+
if st.button('Show All'):
|
| 619 |
+
st.session_state.current_end_idx_single = len(top_projects_df)
|
| 620 |
+
st.experimental_rerun()
|
| 621 |
+
|
| 622 |
+
else:
|
| 623 |
+
st.write("-----")
|
| 624 |
+
col1, col2, col3 = st.columns([1, 1, 1])
|
| 625 |
+
with col2:
|
| 626 |
+
st.write(" ")
|
| 627 |
+
st.markdown("<span style='color: red'>There are no results for this filter!</span>", unsafe_allow_html=True)
|
| 628 |
+
gc.collect()
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
##################################
|
| 632 |
+
def show_multi_matching_page():
|
| 633 |
+
# Define the page size at the beginning
|
| 634 |
+
page_size = 30
|
| 635 |
+
|
| 636 |
+
def reset_pagination():
|
| 637 |
+
st.session_state.current_end_idx_multi = page_size
|
| 638 |
+
|
| 639 |
+
with st.expander("Explanation"):
|
| 640 |
+
st.caption("""
|
| 641 |
+
Multi-Project Matching enables to find collaboration opportunities by identifying matching (=similar) projects.
|
| 642 |
+
|
| 643 |
+
**How It Works**:
|
| 644 |
+
- Filter projects by CRS sector, SDG, country, and organization.
|
| 645 |
+
- Each match displays two similar projects side-by-side.
|
| 646 |
+
|
| 647 |
+
**Similarity Score**:
|
| 648 |
+
- Similarity ranges from 0 to 100 (Identical projects score 100%), and is calculated based on
|
| 649 |
+
- Text similarity of project description and title (MiniLMM & Cosine Similiarity).
|
| 650 |
+
- Matching of SDGs (AI-predicted).
|
| 651 |
+
- Matching of CRS-3 & CRS-5 sector codes.
|
| 652 |
+
- Components are weighted to give a normalized score.
|
| 653 |
+
|
| 654 |
+
Note that this app is a prototype, results may be incomplete or inaccurate.
|
| 655 |
+
""")
|
| 656 |
+
col1, col2, col3 = st.columns([10, 1, 10])
|
| 657 |
+
with col1:
|
| 658 |
+
st.subheader("Sector Filters")
|
| 659 |
+
st.caption("""
|
| 660 |
+
At least one sector filter must be applied to see results.
|
| 661 |
+
""")
|
| 662 |
+
with col3:
|
| 663 |
+
st.subheader("Additional Filters")
|
| 664 |
+
|
| 665 |
+
st.session_state.crs5_option_disabled = True
|
| 666 |
+
col1, col2, col3 = st.columns([10, 1, 10])
|
| 667 |
+
with col1:
|
| 668 |
+
crs3_option = st.multiselect(
|
| 669 |
+
'CRS 3',
|
| 670 |
+
CRS3_MERGED,
|
| 671 |
+
placeholder="Select a CRS 3 code",
|
| 672 |
+
on_change=reset_pagination,
|
| 673 |
+
key='crs3_multi_projects_page'
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
if crs3_option:
|
| 677 |
+
st.session_state.crs5_option_disabled = False
|
| 678 |
+
|
| 679 |
+
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:]]
|
| 680 |
+
|
| 681 |
+
crs5_option = st.multiselect(
|
| 682 |
+
'CRS 5',
|
| 683 |
+
crs5_list,
|
| 684 |
+
placeholder="Select a CRS 5 code",
|
| 685 |
+
disabled=st.session_state.crs5_option_disabled,
|
| 686 |
+
on_change=reset_pagination,
|
| 687 |
+
key='crs5_multi_projects_page'
|
| 688 |
+
)
|
| 689 |
+
|
| 690 |
+
sdg_option = st.selectbox(
|
| 691 |
+
label='Sustainable Development Goal (AI-predicted)',
|
| 692 |
+
index=None,
|
| 693 |
+
placeholder="Select a SDG",
|
| 694 |
+
options=SDG_NAMES[:-1],
|
| 695 |
+
on_change=reset_pagination,
|
| 696 |
+
key='sdg_multi_projects_page'
|
| 697 |
+
)
|
| 698 |
+
|
| 699 |
+
query = ""
|
| 700 |
+
|
| 701 |
+
with col3:
|
| 702 |
+
region_option = st.multiselect(
|
| 703 |
+
'Regions',
|
| 704 |
+
REGION_OPTION_LIST,
|
| 705 |
+
placeholder="All regions selected",
|
| 706 |
+
on_change=reset_pagination,
|
| 707 |
+
key='regions_multi_projects_page'
|
| 708 |
+
)
|
| 709 |
+
country_option = st.multiselect(
|
| 710 |
+
'Countries',
|
| 711 |
+
COUNTRY_OPTION_LIST,
|
| 712 |
+
placeholder="All countries selected",
|
| 713 |
+
on_change=reset_pagination,
|
| 714 |
+
key='country_multi_projects_page'
|
| 715 |
+
)
|
| 716 |
+
|
| 717 |
+
orga_abbreviation = projects_df["orga_abbreviation"].unique()
|
| 718 |
+
orga_full_names = projects_df["orga_full_name"].unique()
|
| 719 |
+
orga_list = [f"{orga_full_names[i]} ({orga_abbreviation[i].upper()})" for i in range(len(orga_abbreviation))]
|
| 720 |
+
|
| 721 |
+
orga_option = st.multiselect(
|
| 722 |
+
'Organizations',
|
| 723 |
+
orga_list,
|
| 724 |
+
placeholder="All organizations selected",
|
| 725 |
+
on_change=reset_pagination,
|
| 726 |
+
key='orga_multi_projects_page'
|
| 727 |
+
)
|
| 728 |
+
|
| 729 |
+
identical_country_checkbox = st.checkbox("Only matches where country is identical", value=True, on_change=reset_pagination, key='identical_country_checkbox_multi')
|
| 730 |
+
different_orga_checkbox = st.checkbox("Only matches between different organizations", value=True, on_change=reset_pagination, key='different_orga_checkbox_multi')
|
| 731 |
+
filtered_country_only_checkbox = st.checkbox("Only matches between filtered countries", value=True, on_change=reset_pagination, key='filtered_country_only_checkbox_multi')
|
| 732 |
+
filtered_orga_only_checkbox = st.checkbox("Only matches between filtered organisations", value=True, on_change=reset_pagination, key='filtered_orga_only_checkbox_multi')
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
# CRS CODE LIST
|
| 736 |
+
crs3_list = [i[-3:] for i in crs3_option]
|
| 737 |
+
crs5_list = [i[-5:] for i in crs5_option]
|
| 738 |
+
|
| 739 |
+
# SDG CODE LIST
|
| 740 |
+
sdg_str = sdg_option.split(".")[0] if sdg_option else ""
|
| 741 |
+
|
| 742 |
+
# COUNTRY CODES LIST
|
| 743 |
+
country_code_list = [option[-3:-1] for option in country_option]
|
| 744 |
+
|
| 745 |
+
# ORGANIZATION CODES LIST
|
| 746 |
+
orga_code_list = [option.split("(")[1][:-1].lower() for option in orga_option]
|
| 747 |
+
|
| 748 |
+
# Handle case where no organizations are selected but the checkbox is checked
|
| 749 |
+
if filtered_orga_only_checkbox and not orga_code_list:
|
| 750 |
+
orga_code_list = projects_df["orga_abbreviation"].unique().tolist()
|
| 751 |
+
|
| 752 |
+
# FILTER DF WITH SELECTED FILTER OPTIONS
|
| 753 |
+
TOP_X_PROJECTS = 2000
|
| 754 |
+
filtered_df = filter_multi(projects_df, crs3_list, crs5_list, sdg_str, country_code_list, orga_code_list, region_option, query, model, embeddings, TOP_X_PROJECTS)
|
| 755 |
+
if isinstance(filtered_df, pd.DataFrame) and len(filtered_df) != 0:
|
| 756 |
+
# FIND MATCHES
|
| 757 |
+
# If only same country checkbox is activated
|
| 758 |
+
if filtered_country_only_checkbox:
|
| 759 |
+
with st.spinner('Please wait...'):
|
| 760 |
+
compare_df = same_country_filter(projects_df, country_code_list)
|
| 761 |
+
else:
|
| 762 |
+
compare_df = projects_df
|
| 763 |
+
|
| 764 |
+
if filtered_orga_only_checkbox:
|
| 765 |
+
compare_df = compare_df[compare_df['orga_abbreviation'].isin(orga_code_list)]
|
| 766 |
+
|
| 767 |
+
# if show only different orgas checkbox is activated
|
| 768 |
+
with st.spinner('Please wait...'):
|
| 769 |
+
p1_df, p2_df = calc_multi_matches(filtered_df, compare_df, nonsameorgas_sim_matrix if different_orga_checkbox else sim_matrix, TOP_X_PROJECTS, identical_country=identical_country_checkbox)
|
| 770 |
+
|
| 771 |
+
# Sort by similarity before pagination
|
| 772 |
+
p1_df = p1_df.sort_values(by='similarity', ascending=False)
|
| 773 |
+
p2_df = p2_df.sort_values(by='similarity', ascending=False)
|
| 774 |
+
|
| 775 |
+
# Implement pagination
|
| 776 |
+
if 'current_end_idx_multi' not in st.session_state:
|
| 777 |
+
st.session_state.current_end_idx_multi = page_size
|
| 778 |
+
|
| 779 |
+
end_idx = st.session_state.current_end_idx_multi
|
| 780 |
+
|
| 781 |
+
paginated_p1_df = p1_df.iloc[:end_idx]
|
| 782 |
+
paginated_p2_df = p2_df.iloc[:end_idx]
|
| 783 |
+
|
| 784 |
+
if not paginated_p1_df.empty and not paginated_p2_df.empty:
|
| 785 |
+
col1, col2 = st.columns([10, 2])
|
| 786 |
+
with col1:
|
| 787 |
+
st.subheader("Matched Projects")
|
| 788 |
+
with col2:
|
| 789 |
+
# Add a download button for the paginated results
|
| 790 |
+
def to_excel(p1_df, p2_df, sheet_name):
|
| 791 |
+
# Rename columns
|
| 792 |
+
p1_df = p1_df.rename(columns={
|
| 793 |
+
"similarity": "Similarity Score",
|
| 794 |
+
"iati_id": "IATI Identifier",
|
| 795 |
+
"title_main": "Title",
|
| 796 |
+
"orga_abbreviation": "Organization",
|
| 797 |
+
"description_main": "Description",
|
| 798 |
+
"country_name": "Country",
|
| 799 |
+
"sdg_list": "SDG List",
|
| 800 |
+
"crs_3_code_list": "CRS 3 Codes",
|
| 801 |
+
"crs_5_code_list": "CRS 5 Codes",
|
| 802 |
+
"Project Link": "Project Link"
|
| 803 |
+
})
|
| 804 |
+
p2_df = p2_df.rename(columns={
|
| 805 |
+
"similarity": "Similarity Score",
|
| 806 |
+
"iati_id": "IATI Identifier",
|
| 807 |
+
"title_main": "Title",
|
| 808 |
+
"orga_abbreviation": "Organization",
|
| 809 |
+
"description_main": "Description",
|
| 810 |
+
"country_name": "Country",
|
| 811 |
+
"sdg_list": "SDG List",
|
| 812 |
+
"crs_3_code_list": "CRS 3 Codes",
|
| 813 |
+
"crs_5_code_list": "CRS 5 Codes",
|
| 814 |
+
"Project Link": "Project Link"
|
| 815 |
+
})
|
| 816 |
+
|
| 817 |
+
combined_df = pd.concat([p1_df, pd.DataFrame([{}]), p2_df], ignore_index=True)
|
| 818 |
+
combined_df.fillna('', inplace=True)
|
| 819 |
+
|
| 820 |
+
empty_row = pd.DataFrame([{}])
|
| 821 |
+
combined_df_list = []
|
| 822 |
+
|
| 823 |
+
for idx in range(0, len(p1_df), 2):
|
| 824 |
+
combined_df_list.append(p1_df.iloc[[idx]])
|
| 825 |
+
combined_df_list.append(p2_df.iloc[[idx]])
|
| 826 |
+
combined_df_list.append(empty_row)
|
| 827 |
+
|
| 828 |
+
combined_df = pd.concat(combined_df_list, ignore_index=True)
|
| 829 |
+
|
| 830 |
+
output = io.BytesIO()
|
| 831 |
+
writer = pd.ExcelWriter(output, engine='xlsxwriter')
|
| 832 |
+
combined_df.to_excel(writer, index=False, sheet_name=sheet_name)
|
| 833 |
+
writer.close()
|
| 834 |
+
processed_data = output.getvalue()
|
| 835 |
+
return processed_data
|
| 836 |
+
|
| 837 |
+
# Direct download buttons
|
| 838 |
+
columns_to_include = ["similarity", "iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]
|
| 839 |
+
|
| 840 |
+
with st.expander("Excel Download"):
|
| 841 |
+
# First 15 Results Button
|
| 842 |
+
p1_df_to_download_15 = p1_df[columns_to_include].head(30)
|
| 843 |
+
p2_df_to_download_15 = p2_df[columns_to_include].head(30)
|
| 844 |
+
excel_data_15 = to_excel(p1_df_to_download_15, p2_df_to_download_15, "Sheet1")
|
| 845 |
+
st.download_button(label="First 15 Matches", data=excel_data_15, file_name="First_15_Multi_Projects_Matching_Results.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
| 846 |
+
|
| 847 |
+
# All Results Button
|
| 848 |
+
p1_df_to_download_all = p1_df[columns_to_include]
|
| 849 |
+
p2_df_to_download_all = p2_df[columns_to_include]
|
| 850 |
+
excel_data_all = to_excel(p1_df_to_download_all, p2_df_to_download_all, "Sheet1")
|
| 851 |
+
st.download_button(label="All", data=excel_data_all, file_name="All_Multi_Projects_Matching_Results.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
| 852 |
+
|
| 853 |
+
show_multi_table(paginated_p1_df, paginated_p2_df)
|
| 854 |
+
|
| 855 |
+
st.write(f"Showing 1 to {min(end_idx // 2, len(p1_df) // 2)} of {len(p1_df) // 2} matches")
|
| 856 |
+
|
| 857 |
+
# Center the buttons and place them close together
|
| 858 |
+
col1, col2, col3, col4, col5 = st.columns([2, 1, 1, 1, 2])
|
| 859 |
+
with col2:
|
| 860 |
+
if st.button('Show More', key='show_more_button'):
|
| 861 |
+
st.session_state.current_end_idx_multi = min(end_idx + page_size, len(p1_df))
|
| 862 |
+
st.experimental_rerun()
|
| 863 |
+
with col3:
|
| 864 |
+
if st.button('Show Less', key='show_less_button') and end_idx > page_size:
|
| 865 |
+
st.session_state.current_end_idx_multi = max(end_idx - page_size, page_size)
|
| 866 |
+
st.experimental_rerun()
|
| 867 |
+
with col4:
|
| 868 |
+
if st.button('Show All', key='show_all_button'):
|
| 869 |
+
st.session_state.current_end_idx_multi = len(p1_df)
|
| 870 |
+
st.experimental_rerun()
|
| 871 |
+
|
| 872 |
+
del p1_df, p2_df
|
| 873 |
+
else:
|
| 874 |
+
st.write("-----")
|
| 875 |
+
col1, col2, col3 = st.columns([1, 1, 1])
|
| 876 |
+
with col2:
|
| 877 |
+
st.write(" ")
|
| 878 |
+
st.markdown("<span style='color: red'>There are no results for the applied filter. Try another filter!</span>", unsafe_allow_html=True)
|
| 879 |
+
|
| 880 |
+
else:
|
| 881 |
+
st.write("-----")
|
| 882 |
+
col1, col2, col3 = st.columns([1, 1, 1])
|
| 883 |
+
with col2:
|
| 884 |
+
st.write(" ")
|
| 885 |
+
st.markdown("<span style='color: red'>There are no results for the applied filter. Try another filter!</span>", unsafe_allow_html=True)
|
| 886 |
+
|
| 887 |
+
del crs3_list, crs5_list, sdg_str, filtered_df
|
| 888 |
+
gc.collect()
|
functions/__pycache__/calc_matches.cpython-310.pyc
ADDED
|
Binary file (922 Bytes). View file
|
|
|
functions/__pycache__/filter_all_project_matching.cpython-311.pyc
ADDED
|
Binary file (2.81 kB). View file
|
|
|
functions/__pycache__/filter_multi_project_matching.cpython-311.pyc
ADDED
|
Binary file (3.27 kB). View file
|
|
|
functions/__pycache__/filter_projects.cpython-310.pyc
ADDED
|
Binary file (1.81 kB). View file
|
|
|
functions/__pycache__/filter_single_project_matching.cpython-311.pyc
ADDED
|
Binary file (1.69 kB). View file
|
|
|
functions/__pycache__/multi_project_matching.cpython-311.pyc
ADDED
|
Binary file (2.87 kB). View file
|
|
|
functions/__pycache__/same_country_filter.cpython-311.pyc
ADDED
|
Binary file (882 Bytes). View file
|
|
|
functions/__pycache__/semantic_search.cpython-310.pyc
ADDED
|
Binary file (1.07 kB). View file
|
|
|
functions/__pycache__/semantic_search.cpython-311.pyc
ADDED
|
Binary file (1.36 kB). View file
|
|
|
functions/__pycache__/single_project_matching.cpython-311.pyc
ADDED
|
Binary file (2.16 kB). View file
|
|
|
functions/__pycache__/single_similar.cpython-310.pyc
ADDED
|
Binary file (672 Bytes). View file
|
|
|
functions/filter_all_project_matching.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
|
| 3 |
+
def contains_code(crs_codes, code_list):
|
| 4 |
+
codes = str(crs_codes).split(';')
|
| 5 |
+
return any(code in code_list for code in codes)
|
| 6 |
+
|
| 7 |
+
def filter_all_projects(df, country_code_list, orga_code_list, crs3_list, crs5_list, sdg_str, region_list):
|
| 8 |
+
# Check if filters where not all can be selected are empty
|
| 9 |
+
if crs3_list or crs5_list or sdg_str:
|
| 10 |
+
# FILTER CRS
|
| 11 |
+
if crs3_list and not crs5_list:
|
| 12 |
+
df = df[df['crs_3_code'].apply(lambda x: contains_code(x, crs3_list))]
|
| 13 |
+
elif crs3_list and crs5_list:
|
| 14 |
+
df = df[df['crs_5_code'].apply(lambda x: contains_code(x, crs5_list))]
|
| 15 |
+
elif not crs3_list and crs5_list:
|
| 16 |
+
df = df[df['crs_5_code'].apply(lambda x: contains_code(x, crs5_list))]
|
| 17 |
+
|
| 18 |
+
# FILTER SDG
|
| 19 |
+
if sdg_str:
|
| 20 |
+
df = df[df["sgd_pred_code"] == int(sdg_str)]
|
| 21 |
+
|
| 22 |
+
# FILTER COUNTRY
|
| 23 |
+
if country_code_list:
|
| 24 |
+
country_filtered_df = pd.DataFrame()
|
| 25 |
+
for c in country_code_list:
|
| 26 |
+
c_df = df[df["country"].str.contains(c, na=False)]
|
| 27 |
+
country_filtered_df = pd.concat([country_filtered_df, c_df], ignore_index=False)
|
| 28 |
+
df = country_filtered_df
|
| 29 |
+
|
| 30 |
+
# FILTER REGION
|
| 31 |
+
if region_list:
|
| 32 |
+
df = df[df["region"].isin(region_list)]
|
| 33 |
+
|
| 34 |
+
# FILTER ORGANIZATION
|
| 35 |
+
if orga_code_list:
|
| 36 |
+
df = df[df['orga_abbreviation'].isin(orga_code_list)]
|
| 37 |
+
|
| 38 |
+
return df
|
functions/filter_multi_project_matching.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from functions.semantic_search import search
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Filter for the multi project matching
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
def contains_code(crs_codes, code_list):
|
| 9 |
+
codes = str(crs_codes).split(';')
|
| 10 |
+
return any(code in code_list for code in codes)
|
| 11 |
+
|
| 12 |
+
def filter_multi(df, crs3_list, crs5_list, sdg_str, country_code_list, orga_code_list, region_list, query, model, embeddings, TOP_X_PROJECTS=30):
|
| 13 |
+
# Check if filters where not all can be selected are empty
|
| 14 |
+
if crs3_list != [] or crs5_list != [] or sdg_str != "" or query != "":
|
| 15 |
+
|
| 16 |
+
# FILTER CRS
|
| 17 |
+
if crs3_list and not crs5_list:
|
| 18 |
+
df = df[df['crs_3_code'].apply(lambda x: contains_code(x, crs3_list))]
|
| 19 |
+
elif crs3_list and crs5_list:
|
| 20 |
+
df = df[df['crs_5_code'].apply(lambda x: contains_code(x, crs5_list))]
|
| 21 |
+
elif not crs3_list and crs5_list:
|
| 22 |
+
df = df[df['crs_5_code'].apply(lambda x: contains_code(x, crs5_list))]
|
| 23 |
+
|
| 24 |
+
# FILTER SDG
|
| 25 |
+
if sdg_str != "":
|
| 26 |
+
df = df[df["sgd_pred_code"] == int(sdg_str)]
|
| 27 |
+
|
| 28 |
+
# FILTER COUNTRY
|
| 29 |
+
if country_code_list != []:
|
| 30 |
+
country_filtered_df = pd.DataFrame()
|
| 31 |
+
for c in country_code_list:
|
| 32 |
+
c_df = df[df["country"].str.contains(c, na=False)]
|
| 33 |
+
country_filtered_df = pd.concat([country_filtered_df, c_df], ignore_index=False)
|
| 34 |
+
|
| 35 |
+
df = country_filtered_df
|
| 36 |
+
|
| 37 |
+
# FILTER REGION
|
| 38 |
+
if region_list:
|
| 39 |
+
df = df[df["region"].isin(region_list)]
|
| 40 |
+
|
| 41 |
+
# FILTER ORGANIZATION
|
| 42 |
+
if orga_code_list != []:
|
| 43 |
+
df = df[df['orga_abbreviation'].isin(orga_code_list)]
|
| 44 |
+
|
| 45 |
+
# FILTER QUERY
|
| 46 |
+
if query != "" and len(df) > 0:
|
| 47 |
+
if len(df) < TOP_X_PROJECTS:
|
| 48 |
+
TOP_X_PROJECTS = len(df)
|
| 49 |
+
df = search(query, model, embeddings, df, TOP_X_PROJECTS)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
return df
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
|
functions/filter_single_project_matching.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Filter for the single project matching
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
def contains_code(crs_codes, code_list):
|
| 8 |
+
codes = str(crs_codes).split(';')
|
| 9 |
+
return any(code in code_list for code in codes)
|
| 10 |
+
|
| 11 |
+
def filter_single(df, country_code_list, orga_code_list, region_list):
|
| 12 |
+
# FILTER COUNTRY
|
| 13 |
+
if country_code_list:
|
| 14 |
+
country_filtered_df = pd.DataFrame()
|
| 15 |
+
for c in country_code_list:
|
| 16 |
+
c_df = df[df["country"].str.contains(c, na=False)]
|
| 17 |
+
country_filtered_df = pd.concat([country_filtered_df, c_df], ignore_index=False)
|
| 18 |
+
df = country_filtered_df
|
| 19 |
+
|
| 20 |
+
# FILTER REGION
|
| 21 |
+
if region_list:
|
| 22 |
+
df = df[df["region"].isin(region_list)]
|
| 23 |
+
|
| 24 |
+
# FILTER ORGANIZATION
|
| 25 |
+
if orga_code_list:
|
| 26 |
+
df = df[df['orga_abbreviation'].isin(orga_code_list)]
|
| 27 |
+
|
| 28 |
+
return df
|
functions/multi_project_matching.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from scipy.sparse import csr_matrix
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Function to calculate the multi project matching results
|
| 6 |
+
|
| 7 |
+
The Multi-Project Matching Feature uncovers synergy opportunities among various development banks and organizations by facilitating the search for similar projects
|
| 8 |
+
within a selected filter setting (filtered_df) and all projects (project_df).
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
def calc_multi_matches(filtered_df, project_df, similarity_matrix, top_x, identical_country=False):
|
| 12 |
+
"""
|
| 13 |
+
filtered_df: df with applied filters
|
| 14 |
+
project_df: df with all projects
|
| 15 |
+
similarity_matrix: np sparse matrix with all similarities between projects
|
| 16 |
+
top_x: top x project which should be displayed
|
| 17 |
+
identical_country: boolean flag to filter matches where country is identical
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
# convert npz sparse matrix into csr matrix
|
| 21 |
+
if not isinstance(similarity_matrix, csr_matrix):
|
| 22 |
+
similarity_matrix = csr_matrix(similarity_matrix)
|
| 23 |
+
|
| 24 |
+
# extract indices of the projects
|
| 25 |
+
filtered_indices = filtered_df.index.to_list()
|
| 26 |
+
project_indices = project_df.index.to_list()
|
| 27 |
+
|
| 28 |
+
# size down the matrix to only projects within the filter and convert to dense matrix and flatten it
|
| 29 |
+
match_matrix = similarity_matrix[project_indices, :][:, filtered_indices] # row / column
|
| 30 |
+
dense_match_matrix = match_matrix.toarray()
|
| 31 |
+
flat_matrix = dense_match_matrix.flatten()
|
| 32 |
+
|
| 33 |
+
# get the indices of the top X values in the flattened matrix
|
| 34 |
+
top_indices = np.argsort(flat_matrix)[-top_x:]
|
| 35 |
+
|
| 36 |
+
# Convert flat indices back to 2D indices
|
| 37 |
+
top_2d_indices = np.unravel_index(top_indices, dense_match_matrix.shape)
|
| 38 |
+
|
| 39 |
+
# Extract the corresponding values
|
| 40 |
+
top_values = flat_matrix[top_indices]
|
| 41 |
+
|
| 42 |
+
# Prepare the result with row and column indices from original dataframes
|
| 43 |
+
org_rows = []
|
| 44 |
+
org_cols = []
|
| 45 |
+
for value, row, col in zip(top_values, top_2d_indices[0], top_2d_indices[1]):
|
| 46 |
+
original_row_index = project_indices[row]
|
| 47 |
+
original_col_index = filtered_indices[col]
|
| 48 |
+
org_rows.append(original_row_index)
|
| 49 |
+
org_cols.append(original_col_index)
|
| 50 |
+
|
| 51 |
+
# create two result dataframes
|
| 52 |
+
|
| 53 |
+
"""
|
| 54 |
+
p1_df: first results of match
|
| 55 |
+
p2_df: matching result
|
| 56 |
+
|
| 57 |
+
matches are displayed through the indices of p1 and p2 dfs
|
| 58 |
+
|
| 59 |
+
match1 p1_df.iloc[0] & p2_df.iloc[0]
|
| 60 |
+
match2 p1_df.iloc[1] & p2_df.iloc[1]
|
| 61 |
+
"""
|
| 62 |
+
p1_df = filtered_df.loc[org_cols].copy()
|
| 63 |
+
p1_df['similarity'] = top_values
|
| 64 |
+
# filter out rows with similarity score less than 50
|
| 65 |
+
p1_df = p1_df[p1_df['similarity'] > 0.50]
|
| 66 |
+
|
| 67 |
+
p2_df = project_df.loc[org_rows].copy()
|
| 68 |
+
p2_df['similarity'] = top_values
|
| 69 |
+
p2_df = p2_df[p2_df['similarity'] > 0.50]
|
| 70 |
+
|
| 71 |
+
if identical_country:
|
| 72 |
+
# Reset indices before comparison
|
| 73 |
+
p1_df = p1_df.reset_index(drop=True)
|
| 74 |
+
p2_df = p2_df.reset_index(drop=True)
|
| 75 |
+
# Filter to only include matches with identical countries
|
| 76 |
+
identical_country_mask = p1_df['country'] == p2_df['country']
|
| 77 |
+
p1_df = p1_df[identical_country_mask]
|
| 78 |
+
p2_df = p2_df[identical_country_mask]
|
| 79 |
+
|
| 80 |
+
# return both results df with matching projects
|
| 81 |
+
return p1_df, p2_df
|
functions/same_country_filter.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Filter for the show matching between different countries option
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
def same_country_filter(df, country_code_list):
|
| 8 |
+
# FILTER COUNTRY
|
| 9 |
+
if country_code_list != []:
|
| 10 |
+
country_filtered_df = pd.DataFrame()
|
| 11 |
+
for c in country_code_list:
|
| 12 |
+
c_df = df[df["country"].str.contains(c, na=False)]
|
| 13 |
+
country_filtered_df = pd.concat([country_filtered_df, c_df], ignore_index=False)
|
| 14 |
+
|
| 15 |
+
df = country_filtered_df
|
| 16 |
+
|
| 17 |
+
return country_filtered_df
|
| 18 |
+
else:
|
| 19 |
+
return df
|
functions/semantic_search.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import faiss
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Semantic Search Function
|
| 5 |
+
"""
|
| 6 |
+
def search(query, model, embeddings, filtered_df, top_x=20):
|
| 7 |
+
|
| 8 |
+
filtered_df_indecies_list = filtered_df.index
|
| 9 |
+
filtered_embeddings = embeddings[filtered_df_indecies_list]
|
| 10 |
+
|
| 11 |
+
# Load or create FAISS index
|
| 12 |
+
dimension = filtered_embeddings.shape[1]
|
| 13 |
+
faiss_index = faiss.IndexFlatL2(dimension)
|
| 14 |
+
faiss_index.add(filtered_embeddings)
|
| 15 |
+
|
| 16 |
+
# Convert query to embedding
|
| 17 |
+
query_embedding = model.encode([query])[0].reshape(1, -1)
|
| 18 |
+
|
| 19 |
+
# Perform search
|
| 20 |
+
D, I = faiss_index.search(query_embedding, k=top_x) # Search for top x similar items
|
| 21 |
+
|
| 22 |
+
# Extract the sentences corresponding to the top indices
|
| 23 |
+
top_indecies = [i for i in I[0]]
|
| 24 |
+
|
| 25 |
+
return filtered_df.iloc[top_indecies]
|
functions/single_project_matching.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from scipy.sparse import csr_matrix
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Function to find similar project for the single project matching
|
| 6 |
+
|
| 7 |
+
Single Project Matching empowers you to choose an individual project using
|
| 8 |
+
either the project IATI ID or title, and then unveils the top x projects within a filter (filtered_df) that
|
| 9 |
+
bear the closest resemblance to your selected one (p_index).
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
def find_similar(p_index, similarity_matrix, filtered_df, top_x):
|
| 13 |
+
"""
|
| 14 |
+
p_index: index of selected project
|
| 15 |
+
similarity_matrix: matrix with similarities of all projects
|
| 16 |
+
filtered_df: df with filter applied
|
| 17 |
+
top_x: top x project which should be displayed
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
# convert npz sparse matrix into csr matrix
|
| 21 |
+
if not isinstance(similarity_matrix, csr_matrix):
|
| 22 |
+
similarity_matrix = csr_matrix(similarity_matrix)
|
| 23 |
+
|
| 24 |
+
# filter out just projects from filtered_df
|
| 25 |
+
filtered_indices = filtered_df.index.tolist()
|
| 26 |
+
filtered_column_sim_matrix = similarity_matrix[:, filtered_indices]
|
| 27 |
+
|
| 28 |
+
# create a mapping from new position to original indices
|
| 29 |
+
index_position_mapping = {position: index for position, index in enumerate(filtered_indices)}
|
| 30 |
+
|
| 31 |
+
# select just the row of th similarity matrix of the selected project index
|
| 32 |
+
project_row = filtered_column_sim_matrix.getrow(p_index).toarray().ravel()
|
| 33 |
+
|
| 34 |
+
# find top_x indices with the highest similarity scores in the row
|
| 35 |
+
sorted_indices = np.argsort(project_row)[-top_x:][::-1]
|
| 36 |
+
top_indices = [index_position_mapping[i] for i in sorted_indices]
|
| 37 |
+
top_values = project_row[sorted_indices]
|
| 38 |
+
|
| 39 |
+
# create result df with all top_x similar projects
|
| 40 |
+
result_df = filtered_df.loc[top_indices]
|
| 41 |
+
result_df['similarity'] = top_values
|
| 42 |
+
|
| 43 |
+
# filter out rows with similarity score less than 30
|
| 44 |
+
result_df = result_df[result_df['similarity'] > 0]
|
| 45 |
+
|
| 46 |
+
return result_df
|
modules/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# __init__.py (empty)
|
modules/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (263 Bytes). View file
|
|
|
modules/__pycache__/allprojects_result_table.cpython-311.pyc
ADDED
|
Binary file (4.93 kB). View file
|
|
|
modules/__pycache__/crs_table.cpython-310.pyc
ADDED
|
Binary file (1.21 kB). View file
|
|
|
modules/__pycache__/filter_modules.cpython-310.pyc
ADDED
|
Binary file (997 Bytes). View file
|
|
|
modules/__pycache__/filter_projects.cpython-310.pyc
ADDED
|
Binary file (979 Bytes). View file
|
|
|
modules/__pycache__/multimatch_result_table.cpython-311.pyc
ADDED
|
Binary file (6.64 kB). View file
|
|
|
modules/__pycache__/navbar.cpython-310.pyc
ADDED
|
Binary file (784 Bytes). View file
|
|
|
modules/__pycache__/navbar.cpython-311.pyc
ADDED
|
Binary file (1.94 kB). View file
|
|
|
modules/__pycache__/result_table.cpython-310.pyc
ADDED
|
Binary file (2.65 kB). View file
|
|
|
modules/__pycache__/sdg_table.cpython-310.pyc
ADDED
|
Binary file (1.19 kB). View file
|
|
|
modules/__pycache__/semantic_search.cpython-310.pyc
ADDED
|
Binary file (1.17 kB). View file
|
|
|
modules/__pycache__/similarity_table.cpython-310.pyc
ADDED
|
Binary file (1.41 kB). View file
|
|
|
modules/__pycache__/singlematch_result_table.cpython-311.pyc
ADDED
|
Binary file (8.35 kB). View file
|
|
|
modules/allprojects_result_table.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Result table for the All Projects Page
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
def show_all_projects_table(projects_df, result_df):
|
| 9 |
+
result_df['crs_3_code_list'] = result_df['crs_3_name'].apply(
|
| 10 |
+
lambda x: [""] if pd.isna(x) else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 11 |
+
)
|
| 12 |
+
result_df['crs_5_code_list'] = result_df['crs_5_name'].apply(
|
| 13 |
+
lambda x: [""] if pd.isna(x) else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 14 |
+
)
|
| 15 |
+
result_df['sdg_list'] = result_df['sgd_pred_code'].apply(
|
| 16 |
+
lambda x: [""] if pd.isna(x) else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
# Convert orga_abbreviation to uppercase for the selected project
|
| 20 |
+
result_df['orga_abbreviation'] = result_df['orga_abbreviation'].str.upper()
|
| 21 |
+
|
| 22 |
+
# Set country_flag to None if country_name is missing
|
| 23 |
+
result_df['country_flag'] = result_df.apply(
|
| 24 |
+
lambda row: None if pd.isna(row['country_name']) else row['country_flag'],
|
| 25 |
+
axis=1
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# Convert the Project Link column to clickable Markdown links
|
| 29 |
+
#result_df['Project Link'] = result_df['Project Link'].apply(lambda x: f"[Link]({x})")
|
| 30 |
+
|
| 31 |
+
if len(result_df) == 0:
|
| 32 |
+
st.write("No results found!")
|
| 33 |
+
else:
|
| 34 |
+
result_df = result_df.reset_index(drop=True)
|
| 35 |
+
|
| 36 |
+
st.dataframe(
|
| 37 |
+
result_df[["iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "country_flag", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]],
|
| 38 |
+
use_container_width=True,
|
| 39 |
+
height=35 + 35 * len(result_df),
|
| 40 |
+
column_config={
|
| 41 |
+
"iati_id": st.column_config.TextColumn(
|
| 42 |
+
"IATI ID",
|
| 43 |
+
help="IATI Project ID",
|
| 44 |
+
disabled=True,
|
| 45 |
+
width="small"
|
| 46 |
+
),
|
| 47 |
+
"orga_abbreviation": st.column_config.TextColumn(
|
| 48 |
+
"Organization",
|
| 49 |
+
help="If description not in English, description in other language provided",
|
| 50 |
+
disabled=True,
|
| 51 |
+
width="small"
|
| 52 |
+
),
|
| 53 |
+
"title_main": st.column_config.TextColumn(
|
| 54 |
+
"Title",
|
| 55 |
+
help="If title not in English, title in other language provided",
|
| 56 |
+
disabled=True,
|
| 57 |
+
width="large"
|
| 58 |
+
),
|
| 59 |
+
"description_main": st.column_config.TextColumn(
|
| 60 |
+
"Description",
|
| 61 |
+
help="If description not in English, description in other language provided",
|
| 62 |
+
disabled=True,
|
| 63 |
+
width="large"
|
| 64 |
+
),
|
| 65 |
+
"country_name": st.column_config.TextColumn(
|
| 66 |
+
"Country",
|
| 67 |
+
help="Country of project",
|
| 68 |
+
disabled=True,
|
| 69 |
+
width="small"
|
| 70 |
+
),
|
| 71 |
+
"country_flag": st.column_config.ImageColumn(
|
| 72 |
+
"Flag",
|
| 73 |
+
help="country flag",
|
| 74 |
+
width="small"
|
| 75 |
+
),
|
| 76 |
+
"sdg_list": st.column_config.ListColumn(
|
| 77 |
+
"SDG Prediction",
|
| 78 |
+
help="Prediction of SDG's",
|
| 79 |
+
width="small"
|
| 80 |
+
),
|
| 81 |
+
"crs_3_code_list": st.column_config.ListColumn(
|
| 82 |
+
"CRS 3",
|
| 83 |
+
help="CRS 3 code given by organization",
|
| 84 |
+
width="medium"
|
| 85 |
+
),
|
| 86 |
+
"crs_5_code_list": st.column_config.ListColumn(
|
| 87 |
+
"CRS 5",
|
| 88 |
+
help="CRS 5 code given by organization",
|
| 89 |
+
width="medium"
|
| 90 |
+
),
|
| 91 |
+
"Project Link": st.column_config.TextColumn(
|
| 92 |
+
"Project Link",
|
| 93 |
+
help="Link to the project",
|
| 94 |
+
disabled=True,
|
| 95 |
+
width="small"
|
| 96 |
+
),
|
| 97 |
+
},
|
| 98 |
+
hide_index=True,
|
| 99 |
+
)
|
modules/multimatch_result_table.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Result table of the Multi Project Matching
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
def show_multi_table(p1_df, p2_df):
|
| 9 |
+
"""
|
| 10 |
+
p1_df & p2_df from functions/multi_project_matching
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
st.write("------------------")
|
| 14 |
+
|
| 15 |
+
p1_df = p1_df.reset_index(drop=True)
|
| 16 |
+
p2_df = p2_df.reset_index(drop=True)
|
| 17 |
+
# Convert orga_abbreviation to uppercase for the selected project
|
| 18 |
+
p2_df['orga_abbreviation'] = p2_df['orga_abbreviation'].str.upper()
|
| 19 |
+
p1_df['orga_abbreviation'] = p1_df['orga_abbreviation'].str.upper()
|
| 20 |
+
|
| 21 |
+
actual_ind = 0
|
| 22 |
+
|
| 23 |
+
# Loop to display every matching pair from p1 and p2 dfs
|
| 24 |
+
for i in range(0, len(p1_df), 2): # stepsize 2 to not display duplicates
|
| 25 |
+
actual_ind += 1
|
| 26 |
+
match_df = pd.DataFrame()
|
| 27 |
+
row_from_p1 = p1_df.iloc[[i]]
|
| 28 |
+
row_from_p2 = p2_df.iloc[[i]]
|
| 29 |
+
|
| 30 |
+
# INTEGRATE IN PREPROCESSING !!!
|
| 31 |
+
# transform strings to list
|
| 32 |
+
"""
|
| 33 |
+
Add this to preprocessing
|
| 34 |
+
- flag url
|
| 35 |
+
- crs code lists
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
row_from_p1["crs_3_code_list"] = [row_from_p1['crs_3_name'].item().split(";")[:-1]]
|
| 40 |
+
row_from_p2["crs_3_code_list"] = [row_from_p2['crs_3_name'].item().split(";")[:-1]]
|
| 41 |
+
except:
|
| 42 |
+
row_from_p1["crs_3_code_list"] = [""]
|
| 43 |
+
row_from_p2["crs_3_code_list"] = [""]
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
row_from_p1["crs_5_code_list"] = [row_from_p1['crs_5_name'].item().split(";")[:-1]]
|
| 47 |
+
row_from_p2["crs_5_code_list"] = [row_from_p2['crs_5_name'].item().split(";")[:-1]]
|
| 48 |
+
except:
|
| 49 |
+
row_from_p1["crs_5_code_list"] = [""]
|
| 50 |
+
row_from_p2["crs_5_code_list"] = [""]
|
| 51 |
+
|
| 52 |
+
row_from_p1["sdg_list"] = [row_from_p1['sgd_pred_code'].item()]
|
| 53 |
+
row_from_p2["sdg_list"] = [row_from_p2['sgd_pred_code'].item()]
|
| 54 |
+
|
| 55 |
+
# Check for missing country and set flag URL accordingly
|
| 56 |
+
def get_flag_url(country):
|
| 57 |
+
if pd.isna(country) or country.strip() == "":
|
| 58 |
+
return ""
|
| 59 |
+
return f"https://flagicons.lipis.dev/flags/4x3/{country[:2].lower()}.svg"
|
| 60 |
+
|
| 61 |
+
row_from_p1["flag"] = get_flag_url(row_from_p1['country'].item())
|
| 62 |
+
row_from_p2["flag"] = get_flag_url(row_from_p2['country'].item())
|
| 63 |
+
|
| 64 |
+
# concat p1_df and p2_df rows
|
| 65 |
+
match_df = pd.concat([row_from_p1, row_from_p2], ignore_index=True)
|
| 66 |
+
|
| 67 |
+
col1, col2 = st.columns([1, 12])
|
| 68 |
+
|
| 69 |
+
# MATCHING INFOS
|
| 70 |
+
with col1:
|
| 71 |
+
|
| 72 |
+
# remove arrow from standard st.metric()
|
| 73 |
+
st.write(
|
| 74 |
+
"""
|
| 75 |
+
<style>
|
| 76 |
+
[data-testid="stMetricDelta"] svg {
|
| 77 |
+
display: none;
|
| 78 |
+
}
|
| 79 |
+
</style>
|
| 80 |
+
""",
|
| 81 |
+
unsafe_allow_html=True,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
st.metric(label="Match", value=f"{actual_ind}", delta=f"~ {str(round(row_from_p1['similarity'].item(), 5) * 100)[:4]} %")
|
| 85 |
+
|
| 86 |
+
# MATCHING Project Informations as table
|
| 87 |
+
with col2:
|
| 88 |
+
st.write(" ")
|
| 89 |
+
st.dataframe(
|
| 90 |
+
match_df[["iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "flag", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]],
|
| 91 |
+
use_container_width=True,
|
| 92 |
+
height=35 + 35 * len(match_df),
|
| 93 |
+
column_config={
|
| 94 |
+
"iati_id": st.column_config.TextColumn(
|
| 95 |
+
"IATI ID",
|
| 96 |
+
help="IATI Project ID",
|
| 97 |
+
disabled=True,
|
| 98 |
+
width="small"
|
| 99 |
+
),
|
| 100 |
+
"orga_abbreviation": st.column_config.TextColumn(
|
| 101 |
+
"Organization",
|
| 102 |
+
help="If description not in English, description in other language provided",
|
| 103 |
+
disabled=True,
|
| 104 |
+
width="small"
|
| 105 |
+
),
|
| 106 |
+
"title_main": st.column_config.TextColumn(
|
| 107 |
+
"Title",
|
| 108 |
+
help="If title not in English, title in other language provided",
|
| 109 |
+
disabled=True,
|
| 110 |
+
width="large"
|
| 111 |
+
),
|
| 112 |
+
"description_main": st.column_config.TextColumn(
|
| 113 |
+
"Description",
|
| 114 |
+
help="If description not in English, description in other language provided",
|
| 115 |
+
disabled=True,
|
| 116 |
+
width="large"
|
| 117 |
+
),
|
| 118 |
+
"country_name": st.column_config.TextColumn(
|
| 119 |
+
"Country",
|
| 120 |
+
help="Country of project",
|
| 121 |
+
disabled=True,
|
| 122 |
+
width="small"
|
| 123 |
+
),
|
| 124 |
+
"flag": st.column_config.ImageColumn(
|
| 125 |
+
"Flag",
|
| 126 |
+
help="country flag",
|
| 127 |
+
width="small"
|
| 128 |
+
),
|
| 129 |
+
"sdg_list": st.column_config.ListColumn(
|
| 130 |
+
"SDG Prediction",
|
| 131 |
+
help="Prediction of SDG's",
|
| 132 |
+
width="small"
|
| 133 |
+
),
|
| 134 |
+
"crs_3_code_list": st.column_config.ListColumn(
|
| 135 |
+
"CRS 3",
|
| 136 |
+
help="CRS 3 code given by organization",
|
| 137 |
+
width="medium"
|
| 138 |
+
),
|
| 139 |
+
"crs_5_code_list": st.column_config.ListColumn(
|
| 140 |
+
"CRS 5",
|
| 141 |
+
help="CRS 5 code given by organization",
|
| 142 |
+
width="medium"
|
| 143 |
+
),
|
| 144 |
+
"Project Link": st.column_config.TextColumn(
|
| 145 |
+
"Project Link",
|
| 146 |
+
help="Link to the project",
|
| 147 |
+
disabled=True,
|
| 148 |
+
width="small"
|
| 149 |
+
),
|
| 150 |
+
},
|
| 151 |
+
hide_index=True,
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
st.write("------------------")
|
| 155 |
+
|
modules/navbar.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import app_matching_page
|
| 3 |
+
|
| 4 |
+
# giz-dsc colors
|
| 5 |
+
# orange: #e5b50d
|
| 6 |
+
# green: #48d47b
|
| 7 |
+
# blue: #0da2dc
|
| 8 |
+
# grey: #dadada
|
| 9 |
+
|
| 10 |
+
# giz colors https://www.giz.de/cdc/en/html/59638.html
|
| 11 |
+
# red: #c80f0f
|
| 12 |
+
# grey: #6f6f6f
|
| 13 |
+
# light_grey: #b2b2b2
|
| 14 |
+
# light_red: #eba1a3
|
| 15 |
+
|
| 16 |
+
def show_navbar():
|
| 17 |
+
#st.markdown("<h1 style='color: red;'>THIS APP IS WORK IN PROGRESS ...</h1>", unsafe_allow_html=True)
|
| 18 |
+
|
| 19 |
+
# enlarge tab fontsizes
|
| 20 |
+
css = '''
|
| 21 |
+
<style>
|
| 22 |
+
.stTabs [data-baseweb="tab-list"] button [data-testid="stMarkdownContainer"] p {
|
| 23 |
+
font-size:1rem;
|
| 24 |
+
}
|
| 25 |
+
</style>
|
| 26 |
+
'''
|
| 27 |
+
st.markdown(css, unsafe_allow_html=True)
|
| 28 |
+
tab1, tab2, tab3, tab4 = st.tabs([
|
| 29 |
+
"📘 Landing Page",
|
| 30 |
+
"📊 All Projects",
|
| 31 |
+
"🎯 Single-Project Matching",
|
| 32 |
+
"🔍 Multi-Project Matching"
|
| 33 |
+
])
|
| 34 |
+
|
| 35 |
+
with tab1:
|
| 36 |
+
app_matching_page.show_landing_page()
|
| 37 |
+
|
| 38 |
+
with tab2:
|
| 39 |
+
app_matching_page.show_all_projects_page()
|
| 40 |
+
|
| 41 |
+
with tab3:
|
| 42 |
+
app_matching_page.show_single_matching_page()
|
| 43 |
+
|
| 44 |
+
with tab4:
|
| 45 |
+
app_matching_page.show_multi_matching_page()
|
| 46 |
+
|
modules/singlematch_result_table.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
"""
|
| 6 |
+
Result table of the Single Project Matching
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def show_single_table(selected_project_index, projects_df, result_df):
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
TODO: Add this to preprocessing
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
result_df['crs_3_code_list'] = result_df['crs_3_name'].apply(
|
| 17 |
+
lambda x: [""] if x is None else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";")[:-1])
|
| 18 |
+
)
|
| 19 |
+
result_df['crs_5_code_list'] = result_df['crs_5_name'].apply(
|
| 20 |
+
lambda x: [""] if x is None else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";")[:-1])
|
| 21 |
+
)
|
| 22 |
+
result_df['sdg_list'] = result_df['sgd_pred_code'].apply(
|
| 23 |
+
lambda x: [""] if x is None else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# Convert orga_abbreviation to uppercase for the selected project
|
| 27 |
+
result_df['orga_abbreviation'] = result_df['orga_abbreviation'].str.upper()
|
| 28 |
+
|
| 29 |
+
# Set country_flag to None if country_name is missing
|
| 30 |
+
result_df['country_flag'] = result_df.apply(
|
| 31 |
+
lambda row: None if pd.isna(row['country_name']) else row['country_flag'],
|
| 32 |
+
axis=1
|
| 33 |
+
)
|
| 34 |
+
sel_p_row = projects_df.iloc[[selected_project_index]]
|
| 35 |
+
|
| 36 |
+
sel_p_row['crs_3_code_list'] = sel_p_row['crs_3_name'].apply(
|
| 37 |
+
lambda x: [""] if x is None else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";")[:-1])
|
| 38 |
+
)
|
| 39 |
+
sel_p_row['crs_5_code_list'] = sel_p_row['crs_5_name'].apply(
|
| 40 |
+
lambda x: [""] if x is None else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";")[:-1])
|
| 41 |
+
)
|
| 42 |
+
sel_p_row['sdg_list'] = sel_p_row['sgd_pred_code'].apply(
|
| 43 |
+
lambda x: [""] if x is None else (str(x).split(";")[:-1] if str(x).endswith(";") else str(x).split(";"))
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# Convert orga_abbreviation to uppercase for the selected project
|
| 47 |
+
sel_p_row['orga_abbreviation'] = sel_p_row['orga_abbreviation'].str.upper()
|
| 48 |
+
|
| 49 |
+
# Displaye selected project and infos
|
| 50 |
+
st.subheader("Reference Project")
|
| 51 |
+
st.dataframe(
|
| 52 |
+
sel_p_row[["iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "country_flag", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]],
|
| 53 |
+
use_container_width = True,
|
| 54 |
+
height = 35 + 35 * len(sel_p_row),
|
| 55 |
+
column_config={
|
| 56 |
+
"iati_id": st.column_config.TextColumn(
|
| 57 |
+
"IATI ID",
|
| 58 |
+
help="IATI Project ID",
|
| 59 |
+
disabled=True,
|
| 60 |
+
width="small"
|
| 61 |
+
),
|
| 62 |
+
"orga_abbreviation": st.column_config.TextColumn(
|
| 63 |
+
"Organization",
|
| 64 |
+
help="If description not in English, description in other language provided",
|
| 65 |
+
disabled=True,
|
| 66 |
+
width="small"
|
| 67 |
+
),
|
| 68 |
+
"title_main": st.column_config.TextColumn(
|
| 69 |
+
"Title",
|
| 70 |
+
help="If title not in English, title in other language provided",
|
| 71 |
+
disabled=True,
|
| 72 |
+
width="large"
|
| 73 |
+
),
|
| 74 |
+
"description_main": st.column_config.TextColumn(
|
| 75 |
+
"Description",
|
| 76 |
+
help="If description not in English, description in other language provided",
|
| 77 |
+
disabled=True,
|
| 78 |
+
width="large"
|
| 79 |
+
),
|
| 80 |
+
"country_name": st.column_config.TextColumn(
|
| 81 |
+
"Country",
|
| 82 |
+
help="Country of project",
|
| 83 |
+
disabled=True,
|
| 84 |
+
width="small"
|
| 85 |
+
),
|
| 86 |
+
"country_flag": st.column_config.ImageColumn(
|
| 87 |
+
"Flag",
|
| 88 |
+
help="country flag",
|
| 89 |
+
width="small"
|
| 90 |
+
),
|
| 91 |
+
"sdg_list": st.column_config.ListColumn(
|
| 92 |
+
"SDG Prediction",
|
| 93 |
+
help="Prediction of SDG's",
|
| 94 |
+
width="small"
|
| 95 |
+
),
|
| 96 |
+
"crs_3_code_list": st.column_config.ListColumn(
|
| 97 |
+
"CRS 3",
|
| 98 |
+
help="CRS 3 code given by organization",
|
| 99 |
+
width="medium"
|
| 100 |
+
),
|
| 101 |
+
"crs_5_code_list": st.column_config.ListColumn(
|
| 102 |
+
"CRS 5",
|
| 103 |
+
help="CRS 5 code given by organization",
|
| 104 |
+
width="medium"
|
| 105 |
+
),
|
| 106 |
+
"Project Link": st.column_config.TextColumn(
|
| 107 |
+
"Project Link",
|
| 108 |
+
help="Link to the project",
|
| 109 |
+
disabled=True,
|
| 110 |
+
width="small"
|
| 111 |
+
),
|
| 112 |
+
},
|
| 113 |
+
hide_index=True,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# Display the similar projects of the selected project
|
| 118 |
+
if len(result_df) == 0:
|
| 119 |
+
st.write("No results found!")
|
| 120 |
+
else:
|
| 121 |
+
result_df = result_df.reset_index(drop=True)
|
| 122 |
+
result_df['similarity'] = (result_df['similarity'] * 100).round(4)
|
| 123 |
+
|
| 124 |
+
st.write("----------------------")
|
| 125 |
+
st.subheader("Similar Projects")
|
| 126 |
+
st.dataframe(
|
| 127 |
+
result_df[["similarity", "iati_id", "title_main", "orga_abbreviation", "description_main", "country_name", "country_flag", "sdg_list", "crs_3_code_list", "crs_5_code_list", "Project Link"]],
|
| 128 |
+
use_container_width = True,
|
| 129 |
+
height = 35 + 35 * len(result_df),
|
| 130 |
+
column_config={
|
| 131 |
+
"similarity": st.column_config.ProgressColumn(
|
| 132 |
+
"Similarity",
|
| 133 |
+
help="Similarity",
|
| 134 |
+
format=" %f %%",
|
| 135 |
+
min_value=0,
|
| 136 |
+
max_value=100,
|
| 137 |
+
),
|
| 138 |
+
"iati_id": st.column_config.TextColumn(
|
| 139 |
+
"IATI ID",
|
| 140 |
+
help="IATI Project ID",
|
| 141 |
+
disabled=True,
|
| 142 |
+
width="small"
|
| 143 |
+
),
|
| 144 |
+
"orga_abbreviation": st.column_config.TextColumn(
|
| 145 |
+
"Organization",
|
| 146 |
+
help="If description not in English, description in other language provided",
|
| 147 |
+
disabled=True,
|
| 148 |
+
width="small"
|
| 149 |
+
),
|
| 150 |
+
"title_main": st.column_config.TextColumn(
|
| 151 |
+
"Title",
|
| 152 |
+
help="If title not in English, title in other language provided",
|
| 153 |
+
disabled=True,
|
| 154 |
+
width="large"
|
| 155 |
+
),
|
| 156 |
+
"description_main": st.column_config.TextColumn(
|
| 157 |
+
"Description",
|
| 158 |
+
help="If description not in English, description in other language provided",
|
| 159 |
+
disabled=True,
|
| 160 |
+
width="large"
|
| 161 |
+
),
|
| 162 |
+
"country_name": st.column_config.TextColumn(
|
| 163 |
+
"Country",
|
| 164 |
+
help="Country of project",
|
| 165 |
+
disabled=True,
|
| 166 |
+
width="small"
|
| 167 |
+
),
|
| 168 |
+
"country_flag": st.column_config.ImageColumn(
|
| 169 |
+
"Flag",
|
| 170 |
+
help="country flag",
|
| 171 |
+
width="small"
|
| 172 |
+
),
|
| 173 |
+
"sdg_list": st.column_config.ListColumn(
|
| 174 |
+
"SDG Prediction",
|
| 175 |
+
help="Prediction of SDG's",
|
| 176 |
+
width="small"
|
| 177 |
+
),
|
| 178 |
+
"crs_3_code_list": st.column_config.ListColumn(
|
| 179 |
+
"CRS 3",
|
| 180 |
+
help="CRS 3 code given by organization",
|
| 181 |
+
width="medium"
|
| 182 |
+
),
|
| 183 |
+
"crs_5_code_list": st.column_config.ListColumn(
|
| 184 |
+
"CRS 5",
|
| 185 |
+
help="CRS 5 code given by organization",
|
| 186 |
+
width="medium"
|
| 187 |
+
),
|
| 188 |
+
},
|
| 189 |
+
hide_index=True,
|
| 190 |
+
)
|
requirements.txt
CHANGED
|
@@ -1,3 +1,10 @@
|
|
| 1 |
-
|
| 2 |
-
pandas
|
| 3 |
-
streamlit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy==1.26.4
|
| 2 |
+
pandas==2.1.4
|
| 3 |
+
streamlit==1.32.2
|
| 4 |
+
streamlit-option-menu==0.3.12
|
| 5 |
+
scipy==1.12.0
|
| 6 |
+
faiss-cpu==1.8.0
|
| 7 |
+
#faiss-gpu==1.7.2
|
| 8 |
+
sentence-transformers==2.5.1
|
| 9 |
+
streamlit-aggrid==0.3.4
|
| 10 |
+
xlsxwriter==3.2.0
|