TrungtamTaichinhBenvung / sustainable_finance_hub.py
gaialive's picture
Upload 881 files
17c0198 verified
raw
history blame
8.19 kB
"""Core logic for the AI‑driven Sustainable Finance Hub.
This module defines data structures and helper functions to manage
financial providers and project proposals for green and sustainable
finance in Southeast Asia and Vietnam. It also includes simple
matching logic to recommend providers based on project needs. Data
are persisted to JSON files in the local filesystem to keep the
application state across sessions.
The design emphasises simplicity and readability: it avoids external
dependencies beyond Python’s standard library, enabling easy
modification and debugging. Translation of interface strings and
content is handled by the GUI module via Groq’s API (see
``sustainable_finance_hub_gui.py``).
"""
from __future__ import annotations
import json
import os
from dataclasses import dataclass, asdict, field
from typing import List, Dict, Optional
DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
def ensure_data_dir() -> None:
"""Ensure the data directory exists."""
if not os.path.exists(DATA_DIR):
os.makedirs(DATA_DIR, exist_ok=True)
@dataclass
class FinancialProvider:
"""Represent a financial provider entity (e.g., bank, fund, VC).
Attributes
----------
name : str
The provider's name.
provider_type : str
Type of provider (bank, fund, VC, etc.).
description : str
A brief description of the provider and its focus areas.
available_funds : float
The amount of funds available for investment in USD (or local currency).
sectors : List[str]
List of sectors the provider is interested in (e.g., renewable energy, climate tech).
location : str
Primary location or region served (e.g., Vietnam, Southeast Asia).
languages : List[str]
Languages the provider can operate in ("en", "vi", etc.).
"""
name: str
provider_type: str
description: str
available_funds: float
sectors: List[str]
location: str
languages: List[str] = field(default_factory=lambda: ["en"])
@dataclass
class ProjectProposal:
"""Represent a sustainable project proposal or SME seeking funding.
Attributes
----------
name : str
Name of the project or business.
description : str
Summary of the project and its goals.
sector : str
Primary sector of the project (e.g., renewable energy).
location : str
Project location (e.g., Vietnam).
funding_needed : float
Amount of funding required in USD (or local currency).
languages : List[str]
Languages the project can operate in ("en", "vi", etc.).
"""
name: str
description: str
sector: str
location: str
funding_needed: float
languages: List[str] = field(default_factory=lambda: ["en"])
class SustainableFinanceHub:
"""Core class managing providers and project proposals.
This class handles reading and writing provider and project data
from JSON files. It also implements a simple recommendation
algorithm that matches project proposals to providers based on
shared sectors and geographic alignment. The matching is naive
but sufficient for prototype purposes; more sophisticated logic
could be integrated later (e.g., weighting by fund size or using
semantic similarity).
"""
def __init__(self) -> None:
ensure_data_dir()
self.providers_file = os.path.join(DATA_DIR, "providers.json")
self.projects_file = os.path.join(DATA_DIR, "projects.json")
self.providers: List[FinancialProvider] = []
self.projects: List[ProjectProposal] = []
self.load_providers()
self.load_projects()
# -- Data persistence -----------------------------------------------------
def load_providers(self) -> None:
"""Load providers from the JSON file into memory."""
if os.path.exists(self.providers_file):
try:
with open(self.providers_file, "r", encoding="utf-8") as f:
data = json.load(f)
self.providers = [FinancialProvider(**p) for p in data]
except Exception:
self.providers = []
else:
self.providers = []
def save_providers(self) -> None:
"""Write provider list to the JSON file."""
with open(self.providers_file, "w", encoding="utf-8") as f:
json.dump([asdict(p) for p in self.providers], f, ensure_ascii=False, indent=2)
def load_projects(self) -> None:
"""Load project proposals from the JSON file into memory."""
if os.path.exists(self.projects_file):
try:
with open(self.projects_file, "r", encoding="utf-8") as f:
data = json.load(f)
self.projects = [ProjectProposal(**p) for p in data]
except Exception:
self.projects = []
else:
self.projects = []
def save_projects(self) -> None:
"""Write project list to the JSON file."""
with open(self.projects_file, "w", encoding="utf-8") as f:
json.dump([asdict(p) for p in self.projects], f, ensure_ascii=False, indent=2)
# -- CRUD operations ------------------------------------------------------
def add_provider(self, provider: FinancialProvider) -> None:
"""Add a financial provider to the database and save."""
self.providers.append(provider)
self.save_providers()
def add_project(self, project: ProjectProposal) -> None:
"""Add a project proposal to the database and save."""
self.projects.append(project)
self.save_projects()
# -- Search and recommendation -------------------------------------------
def search_providers(self, sector: Optional[str] = None, location: Optional[str] = None) -> List[FinancialProvider]:
"""Return providers matching the given sector and location.
Parameters
----------
sector : Optional[str]
Sector filter; if None, all sectors are matched.
location : Optional[str]
Location filter; if None, all locations are matched.
Returns
-------
List[FinancialProvider]
Providers that match the given criteria.
"""
results = []
for p in self.providers:
sector_match = True if (sector is None or sector.strip() == "") else (sector.lower() in [s.lower() for s in p.sectors])
location_match = True if (location is None or location.strip() == "") else (location.lower() in p.location.lower())
if sector_match and location_match:
results.append(p)
return results
def recommend_providers(self, project: ProjectProposal, top_n: int = 5) -> List[FinancialProvider]:
"""Recommend providers for a given project based on sector and location.
The recommendation score is computed as the number of matching
sectors plus 1 if locations overlap (case-insensitive substring).
Providers are sorted by score (descending) and by available funds
(descending) to prioritise larger funders. Only the top `top_n`
providers are returned.
Parameters
----------
project : ProjectProposal
The project for which to find funding matches.
top_n : int
Number of top providers to return.
Returns
-------
List[FinancialProvider]
The recommended providers.
"""
scored: List[tuple[FinancialProvider, int]] = []
for p in self.providers:
# Count sector overlap
sector_overlap = sum(1 for s in p.sectors if s.lower() == project.sector.lower())
if sector_overlap == 0:
continue
location_score = 1 if project.location.lower() in p.location.lower() else 0
score = sector_overlap + location_score
scored.append((p, score))
# Sort by score and available funds
scored.sort(key=lambda x: (x[1], x[0].available_funds), reverse=True)
return [p for p, _ in scored[:top_n]]