From c1ebe32cdc911e86133134472909a93e4856eedf Mon Sep 17 00:00:00 2001 From: DESIREDDY MOHITH REDDY Date: Fri, 26 Jun 2026 16:32:54 +0530 Subject: [PATCH] Perf: Implement in-memory caching for Graph and Cluster JSON files 1174 --- src/utils/recommender.py | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/utils/recommender.py b/src/utils/recommender.py index 45d4fa85..8e644298 100644 --- a/src/utils/recommender.py +++ b/src/utils/recommender.py @@ -18,6 +18,19 @@ "clusters.json", ) +_cached_clusters = None +_clusters_loaded = False +_cached_skill_graph = None +_skill_graph_loaded = False + +def clear_caches(): + """Clear all in-memory JSON caches.""" + global _cached_clusters, _clusters_loaded, _cached_skill_graph, _skill_graph_loaded + _cached_clusters = None + _clusters_loaded = False + _cached_skill_graph = None + _skill_graph_loaded = False + VALID_LEVELS = {"beginner", "intermediate", "advanced"} VALID_INTERESTS = {"web", "data", "education", "automation", "games", "cybersecurity", "devops", "backend", "tools", "productivity", "business logic", "mobile", "machine learning/ai"} VALID_TIME_AVAILABILITY = {"low", "medium", "high"} @@ -190,17 +203,24 @@ def score_single_project(project, user_skills, level, interest, time_availabilit def _load_skill_graph(): """Load skill_graph.json from data/. Returns empty dict on failure.""" + global _cached_skill_graph, _skill_graph_loaded + if _skill_graph_loaded: + return _cached_skill_graph + + _skill_graph_loaded = True path = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "data", "skill_graph.json" ) if not os.path.exists(path): - return {} + _cached_skill_graph = {} + return _cached_skill_graph try: with open(path, "r", encoding="utf-8") as f: - return json.load(f) + _cached_skill_graph = json.load(f) except (json.JSONDecodeError, OSError): - return {} + _cached_skill_graph = {} + return _cached_skill_graph def _hops_to_skill(target, user_skills, graph, max_hops=3): @@ -288,13 +308,20 @@ def _load_clusters(): A missing file is a soft failure — the recommender still works, it just won't return related projects. """ + global _cached_clusters, _clusters_loaded + if _clusters_loaded: + return _cached_clusters + + _clusters_loaded = True if not os.path.exists(_CLUSTERS_PATH): - return None + _cached_clusters = None + return _cached_clusters try: with open(_CLUSTERS_PATH, "r", encoding="utf-8") as f: - return json.load(f) + _cached_clusters = json.load(f) except (json.JSONDecodeError, OSError): - return None + _cached_clusters = None + return _cached_clusters def _get_related(recommended_ids, all_projects, cluster_data):