kalim / src /data /loader.py
YasserHaidar
Kalim Space deploy (e803a10)
bae15d1
Raw History Blame Contribute Delete
4.38 kB
"""
Data Loader for Kalim Chatbot
Utilities for loading and accessing the processed knowledge base
"""
import json
from typing import Dict, List, Optional, Any
from functools import lru_cache
from config.settings import (
MAJORS_JSON_FILE,
LOCATIONS_JSON_FILE,
HOLLAND_MAPPING_FILE,
HOLLAND_CODES,
SECONDARY_TRACKS,
GOVERNORATES
)
@lru_cache(maxsize=1)
def load_majors() -> List[Dict[str, Any]]:
"""Load majors data from JSON file."""
if not MAJORS_JSON_FILE.exists():
raise FileNotFoundError(
f"Majors file not found: {MAJORS_JSON_FILE}\n"
"Run 'python -m src.data.processor' first to generate the data."
)
with open(MAJORS_JSON_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('majors', [])
@lru_cache(maxsize=1)
def load_holland_mapping() -> Dict[str, Any]:
"""Load Holland RIASEC mapping from JSON file."""
if not HOLLAND_MAPPING_FILE.exists():
# Return default if file doesn't exist
return {
"codes": HOLLAND_CODES,
"code_list": ["R", "I", "A", "S", "E", "C"],
"dropdown_options": [
{"code": "R", "label": "R - الواقعي (Realistic)"},
{"code": "I", "label": "I - الباحث (Investigative)"},
{"code": "A", "label": "A - الفني (Artistic)"},
{"code": "S", "label": "S - الاجتماعي (Social)"},
{"code": "E", "label": "E - المغامر (Enterprising)"},
{"code": "C", "label": "C - التقليدي (Conventional)"},
]
}
with open(HOLLAND_MAPPING_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def get_major_by_id(major_id: int) -> Optional[Dict[str, Any]]:
"""Get a specific major by ID."""
majors = load_majors()
for major in majors:
if major['id'] == major_id:
return major
return None
def get_governorates() -> List[str]:
"""Get list of all governorates."""
return list(GOVERNORATES.keys())
def get_districts(governorate: str) -> List[str]:
"""Get districts for a specific governorate."""
return GOVERNORATES.get(governorate, [])
@lru_cache(maxsize=1)
def get_dataset_governorates() -> List[str]:
"""Governorates that actually occur in majors.json, in GOVERNORATES order.
The UI must offer only these. `GOVERNORATES` follows Lebanon's newer
eight-governorate division, in which عكار and بعلبك-الهرمل are governorates
of their own; the dataset follows the older six, filing حلبا under
الشمال/عكار and دورس under البقاع/بعلبك. Offering the config's list let a
student from Akkar finish the whole wizard and be told no major matched —
a dead end that reads as "the university has nothing for you" when the
branch exists one level down.
"""
present = {
loc.get('governorate')
for major in load_majors()
for loc in major.get('locations', [])
if loc.get('governorate')
}
return [g for g in GOVERNORATES if g != "الكل" and g in present]
@lru_cache(maxsize=1)
def _dataset_districts_by_gov() -> Dict[str, List[str]]:
by_gov: Dict[str, set] = {}
for major in load_majors():
for loc in major.get('locations', []):
gov, district = loc.get('governorate'), loc.get('district')
if gov and district:
by_gov.setdefault(gov, set()).add(district)
return {gov: sorted(names) for gov, names in by_gov.items()}
def get_dataset_districts(governorate: str) -> List[str]:
"""Districts of this governorate present in majors.json.
Kept in the GOVERNORATES reference order where possible, with anything the
config map does not list appended — that is how عكار (a district of الشمال
in the data, a governorate in the config) still reaches the dropdown.
"""
present = set(_dataset_districts_by_gov().get(governorate, []))
ordered = [d for d in GOVERNORATES.get(governorate, []) if d in present]
return ordered + sorted(present - set(ordered))
# Export commonly used functions
__all__ = [
'load_majors',
'load_holland_mapping',
'get_major_by_id',
'get_governorates',
'get_districts',
'get_dataset_governorates',
'get_dataset_districts',
]