From 77de720087f90771f32893cbaa083bac31de4ba7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 06:29:01 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Cache=20FAQs=20YAML=20load?= =?UTF-8?q?=20to=20improve=20UI=20rendering=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: alinelena <3306823+alinelena@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ ml_peg/app/utils/build_components.py | 33 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index b140c4a7c..90abeffba 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,7 @@ ## 2024-05-19 - Caching YAML Load for Framework Registry **Learning:** `yaml.safe_load` on `frameworks.yml` within `load_framework_registry()` was taking ~2-3 ms per call and it was repeatedly called for every framework entry via `get_framework_config()`. This was a micro-bottleneck, especially when dealing with lists or multiple frameworks. **Action:** Applied the `@lru_cache` and `deepcopy` pattern successfully again to `load_framework_registry()` and `get_framework_config()` to avoid caching a mutable dictionary directly and avoid repeated YAML I/O parsing. + +## 2024-05-19 - Caching YAML Load for FAQs +**Learning:** `yaml.safe_load` on `faqs.yml` within `build_faqs()` takes ~5 ms per call. This causes unnecessary overhead each time the UI components are built. This pattern confirms that any static `.yml` reading throughout the app during request cycles is a micro-bottleneck that should be avoided. +**Action:** Applied the same `@cache` and `copy.deepcopy` pattern used for `models.yml` and `frameworks.yml` to `load_faq_data()` to eliminate repetitive I/O and parsing time during UI rendering. diff --git a/ml_peg/app/utils/build_components.py b/ml_peg/app/utils/build_components.py index 66de62710..2c60f492e 100644 --- a/ml_peg/app/utils/build_components.py +++ b/ml_peg/app/utils/build_components.py @@ -2,6 +2,8 @@ from __future__ import annotations +import copy +from functools import cache from importlib import metadata from pathlib import Path import time @@ -468,6 +470,24 @@ def build_plot_download_controls(graph_id: str) -> Div: ) +@cache +def load_faq_data() -> list[dict[str, str]]: + """ + Load FAQ configuration from ``faqs.yml``. + + Returns + ------- + list[dict[str, str]] + List of FAQ entries from the YAML configuration. + """ + faqs_path = Path(__file__).parent / "faqs.yml" + try: + with open(faqs_path, encoding="utf8") as f: + return yaml.safe_load(f) or [] + except FileNotFoundError: + return [] + + def build_faqs() -> Div: """ Build FAQ section with collapsible dropdowns from YAML file. @@ -477,15 +497,12 @@ def build_faqs() -> Div: Div Styled FAQ section with questions as dropdown titles and answers inside. """ - # Load FAQs from YAML file - faqs_path = Path(__file__).parent / "faqs.yml" + # Load FAQs from cached YAML data + faqs_data = copy.deepcopy(load_faq_data()) - try: - with open(faqs_path, encoding="utf8") as f: - faqs_data = yaml.safe_load(f) - except FileNotFoundError: + if not faqs_data: return Div( - "FAQs file not found", + "FAQs file not found or empty", style={ "color": "#dc3545", "padding": "10px", @@ -493,7 +510,7 @@ def build_faqs() -> Div: }, ) - if not faqs_data or not isinstance(faqs_data, list): + if not isinstance(faqs_data, list): return Div("No FAQs available") # Build FAQ dropdowns