focus-mcp 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
focus_mcp/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """FOCUS MCP server: query FOCUS billing data over the Model Context Protocol."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("focus-mcp")
7
+ except PackageNotFoundError: # running from a source tree with no install
8
+ __version__ = "0.0.0.dev0"
9
+
10
+ __all__ = ["__version__"]
focus_mcp/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m focus_mcp`."""
2
+
3
+ from focus_mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
focus_mcp/config.py ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ FOCUS MCP Server Configuration - Environment-based settings.
4
+
5
+ This module centralizes all configuration for the FOCUS MCP server,
6
+ providing a single point of control for data paths, FOCUS version,
7
+ and other runtime settings.
8
+
9
+ Configuration is environment-driven to support different deployment
10
+ scenarios (development, staging, production) without code changes.
11
+ All settings have sensible defaults for quick local development.
12
+
13
+ Environment Variables:
14
+ FOCUS_DATA_LOCATION: Path to FOCUS data (local path, s3:// or gs:// URI)
15
+ Default: "data/focus-export"
16
+ Relative local paths are resolved against the working
17
+ directory at startup and reported as absolute.
18
+
19
+ AWS_REGION: AWS region for S3 access
20
+ Default: "us-east-1"
21
+
22
+ AWS_PROFILE: Optional AWS profile for authentication
23
+ Uses credential chain if not specified
24
+
25
+ GCS_HMAC_KEY_ID: GCS HMAC access key for gs:// locations
26
+ (create with: gcloud storage hmac create <service-account>)
27
+
28
+ GCS_HMAC_SECRET: GCS HMAC secret paired with GCS_HMAC_KEY_ID.
29
+ If unset, Application Default Credentials are used
30
+ when gcsfs is installed (gcs extra).
31
+
32
+ FOCUS_VERSION: FOCUS specification version
33
+ Default: "1.0"
34
+
35
+ The configuration supports both absolute and relative paths for data,
36
+ with relative paths resolved from the server's working directory.
37
+ """
38
+
39
+ import os
40
+ from pathlib import Path
41
+
42
+ # Data source configuration
43
+ # Supports local paths, S3 locations, and GCS locations
44
+ # Examples:
45
+ # Local: "/path/to/focus/data" or "data/focus-export"
46
+ # S3: "s3://bucket-name/path/to/focus/data"
47
+ # GCS: "gs://bucket-name/path/to/focus/data"
48
+ #
49
+ # For S3, authentication is handled automatically via AWS credential chain:
50
+ # - IAM roles (EC2, ECS, Lambda)
51
+ # - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
52
+ # - AWS CLI profiles (AWS_PROFILE)
53
+ # - Instance metadata service
54
+ #
55
+ # For GCS, authentication is tiered (see storage_backends.py):
56
+ # - HMAC env vars (GCS_HMAC_KEY_ID, GCS_HMAC_SECRET) if set
57
+ # - Application Default Credentials via gcsfs (gcs extra) otherwise
58
+ # - Keyless access as last resort (public buckets only)
59
+ def _resolve_location(location: str) -> str:
60
+ """Make local paths absolute; leave remote URIs exactly as given.
61
+
62
+ A relative path resolved against an MCP client's working directory is not
63
+ something a user can debug from an error message. Remote URIs must survive
64
+ verbatim: Path() would collapse the double slash in "s3://bucket".
65
+ """
66
+ if "://" in location:
67
+ return location
68
+ return str(Path(location).expanduser().resolve())
69
+
70
+
71
+ DATA_LOCATION = _resolve_location(os.getenv("FOCUS_DATA_LOCATION", "data/focus-export"))
72
+
73
+ # AWS Configuration (optional)
74
+ # Region for S3 access - defaults to us-east-1 if not specified
75
+ AWS_REGION = os.getenv("AWS_REGION", os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
76
+
77
+ # FOCUS specification version
78
+ # Determines which queries are available based on their focus_versions field
79
+ # Queries are filtered from the unified YAML file at runtime
80
+ # This allows supporting multiple FOCUS versions with version-specific query sets
81
+ FOCUS_VERSION = os.getenv("FOCUS_VERSION", "1.0")
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Data Loading Module - How focus_data_table sources its Parquet files.
4
+
5
+ Two strategies, picked at view-creation time:
6
+
7
+ 1. Manifest-driven, for AWS BCM Data Exports. AWS writes
8
+ {location}/metadata/<PARTITION>/<export>-Manifest.json once per
9
+ billing period and rewrites it on every delivery; its dataFiles array
10
+ lists the full URIs of exactly the current delivery. Globbing such an
11
+ export is wrong: the prefix also holds blanked OVERWRITE_REPORT
12
+ chunks (zero-byte Parquet that crashes read_parquet), superseded
13
+ CREATE_NEW_REPORT executions, and partition folders orphaned when AWS
14
+ switches the casing of billing_period= (which also makes DuckDB's
15
+ Hive inference throw). None of those appear in a manifest, so reading
16
+ the manifests needs no special-casing of any of them.
17
+ 2. Glob fallback, for everything else - bundled sample data, BigQuery
18
+ FOCUS exports written to GCS, arbitrary directories - which has no
19
+ metadata/ prefix. An export whose manifests cannot be read is an
20
+ error rather than a reason to glob it: the manifests are proof that
21
+ globbing would double-count.
22
+
23
+ Both strategies run over the connection the storage backend prepared, so
24
+ they behave the same on local paths, s3:// and gs://.
25
+ """
26
+
27
+ import logging
28
+ import re
29
+
30
+ import duckdb
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ # Exactly one directory level: partition-level manifests only. Manifests
35
+ # of individual CREATE_NEW_REPORT executions live a level deeper, and the
36
+ # sibling <export>-Manifest-FOCUS.json does not end in "Manifest.json".
37
+ MANIFEST_GLOB = "{location}/metadata/*/*Manifest.json"
38
+
39
+ # AWS documents BILLING_PERIOD= but has delivered billing_period= too,
40
+ # so both the discovery filter and the derived column ignore casing.
41
+ PARTITION_DIR_PATTERN = re.compile(r"billing_period=\d{4}-\d{2}", re.IGNORECASE)
42
+ BILLING_PERIOD_EXPR = (
43
+ r"regexp_extract(filename, '(?i)billing_period=(\d{4}-\d{2})', 1)"
44
+ )
45
+
46
+ # dataFiles holds the URIs of the bucket the export was delivered to. A
47
+ # copy of that export (aws s3 sync into a directory, a mirror in another
48
+ # bucket) keeps AWS's data/<PARTITION>/[<execution>/]<file> layout but not
49
+ # those URIs, so only the layout tail is trusted.
50
+ DATA_FILE_TAIL_PATTERN = re.compile(
51
+ r"(?:^|/)(data/billing_period=\d{4}-\d{2}/.+)$", re.IGNORECASE
52
+ )
53
+
54
+
55
+ def _sql_literal(value: str) -> str:
56
+ """Quote a string for inlining: views cannot carry bound parameters."""
57
+ escaped = value.replace("'", "''")
58
+ return f"'{escaped}'"
59
+
60
+
61
+ def _is_partition_manifest(path: str) -> bool:
62
+ """Whether the manifest sits directly in a billing-period directory."""
63
+ segments = path.split("/")
64
+ return len(segments) >= 2 and bool(
65
+ PARTITION_DIR_PATTERN.fullmatch(segments[-2])
66
+ )
67
+
68
+
69
+ def discover_manifests(
70
+ conn: duckdb.DuckDBPyConnection, location: str
71
+ ) -> list[str]:
72
+ """Return the partition-level export manifests under the location.
73
+
74
+ A location that is not an export simply globs to nothing. An error
75
+ means the metadata/ prefix could not be listed, which must not be
76
+ read as "no manifests here": that silently downgrades an export to
77
+ the glob, so it propagates instead.
78
+ """
79
+ pattern = MANIFEST_GLOB.format(location=location)
80
+ rows = conn.execute("SELECT file FROM glob(?)", [pattern]).fetchall()
81
+ return sorted(p for (p,) in rows if _is_partition_manifest(p))
82
+
83
+
84
+ def read_manifest_data_files(
85
+ conn: duckdb.DuckDBPyConnection, manifest: str
86
+ ) -> list[str]:
87
+ """Return the dataFiles URIs of one manifest, [] if it can't be read."""
88
+ try:
89
+ rows = conn.execute(
90
+ "SELECT dataFiles FROM read_json_auto(?)", [manifest]
91
+ ).fetchall()
92
+ except duckdb.Error as e:
93
+ logger.warning("Skipping unreadable manifest %s: %s", manifest, e)
94
+ return []
95
+ # dataFiles is a flat array of URIs; anything else isn't a file path
96
+ return [
97
+ entry
98
+ for (data_files,) in rows
99
+ for entry in (data_files or [])
100
+ if isinstance(entry, str)
101
+ ]
102
+
103
+
104
+ def rebase_data_file(location: str, uri: str) -> str:
105
+ """Point a dataFiles URI at the location the export is read from.
106
+
107
+ Reading the export where it was delivered rebases it onto itself; a
108
+ copy of it is read from the copy instead of from the original bucket.
109
+ """
110
+ match = DATA_FILE_TAIL_PATTERN.search(uri)
111
+ if not match:
112
+ # Layout AWS does not write: the manifest's own URI is all we have
113
+ logger.warning("Unexpected data file path in manifest: %s", uri)
114
+ return uri
115
+ return f"{location}/{match.group(1)}"
116
+
117
+
118
+ def collect_data_files(
119
+ conn: duckdb.DuckDBPyConnection, manifests: list[str], location: str
120
+ ) -> list[str]:
121
+ """Union the manifests' dataFiles, deduped, in discovery order."""
122
+ files: list[str] = []
123
+ for manifest in manifests:
124
+ files.extend(
125
+ rebase_data_file(location, uri)
126
+ for uri in read_manifest_data_files(conn, manifest)
127
+ )
128
+ return list(dict.fromkeys(files))
129
+
130
+
131
+ def manifest_view_sql(data_files: list[str]) -> str:
132
+ """View over an explicit file list, with billing_period derived.
133
+
134
+ union_by_name absorbs schema drift between billing periods (FOCUS 1.0
135
+ months next to 1.2 months); Hive inference stays off because the file
136
+ list already excludes the orphaned partitions it would trip over.
137
+ """
138
+ file_list = ", ".join(_sql_literal(f) for f in data_files)
139
+ return f"""
140
+ CREATE OR REPLACE VIEW focus_data_table AS
141
+ SELECT * EXCLUDE (filename), {BILLING_PERIOD_EXPR} AS billing_period
142
+ FROM read_parquet(
143
+ [{file_list}],
144
+ union_by_name=true,
145
+ hive_partitioning=false,
146
+ filename=true
147
+ )
148
+ """
149
+
150
+
151
+ def glob_view_sql(location: str, hive_partitioning: bool) -> str:
152
+ """View over every Parquet file under the location.
153
+
154
+ union_by_name absorbs schema drift between billing periods here for
155
+ the same reason it does in manifest_view_sql.
156
+ """
157
+ pattern = _sql_literal(f"{location}/**/*.parquet")
158
+ if hive_partitioning:
159
+ return f"""
160
+ CREATE OR REPLACE VIEW focus_data_table AS
161
+ SELECT * FROM read_parquet(
162
+ {pattern}, union_by_name=true, hive_partitioning=true
163
+ )
164
+ """
165
+ # Retry path: inference is off, so billing_period is derived from the
166
+ # path instead of being dropped from the view.
167
+ return f"""
168
+ CREATE OR REPLACE VIEW focus_data_table AS
169
+ SELECT * EXCLUDE (filename), {BILLING_PERIOD_EXPR} AS billing_period
170
+ FROM read_parquet(
171
+ {pattern},
172
+ union_by_name=true,
173
+ hive_partitioning=false,
174
+ filename=true
175
+ )
176
+ """
177
+
178
+
179
+ def _is_readable(conn: duckdb.DuckDBPyConnection, path: str) -> bool:
180
+ """Whether DuckDB can still see a file a manifest points at."""
181
+ try:
182
+ return bool(
183
+ conn.execute("SELECT count(*) FROM glob(?)", [path]).fetchone()[0]
184
+ )
185
+ except duckdb.Error:
186
+ return False
187
+
188
+
189
+ def _create_manifest_view(
190
+ conn: duckdb.DuckDBPyConnection, location: str, manifests: list[str]
191
+ ) -> str:
192
+ """Create the view from the export's own manifests."""
193
+ data_files = collect_data_files(conn, manifests, location)
194
+ if not data_files:
195
+ # The manifests are proof this is an export, and globbing an
196
+ # export double-counts it, so there is nothing to fall back to.
197
+ raise RuntimeError(
198
+ f"Found {len(manifests)} export manifest(s) under {location} but "
199
+ "no data file in any of them; the export cannot be loaded "
200
+ "without readable manifests"
201
+ )
202
+ logger.info(
203
+ "Loading FOCUS data from %d export manifest(s): %d file(s)",
204
+ len(manifests),
205
+ len(data_files),
206
+ )
207
+ try:
208
+ conn.execute(manifest_view_sql(data_files))
209
+ except duckdb.IOException as e:
210
+ # A manifest outlives its data when a period is expired or cleaned
211
+ # up by hand. Serve the periods that are left rather than nothing.
212
+ present = [f for f in data_files if _is_readable(conn, f)]
213
+ if not present or len(present) == len(data_files):
214
+ raise
215
+ logger.warning(
216
+ "%d of %d manifested file(s) under %s cannot be read (%s); "
217
+ "loading the other %d",
218
+ len(data_files) - len(present),
219
+ len(data_files),
220
+ location,
221
+ e,
222
+ len(present),
223
+ )
224
+ conn.execute(manifest_view_sql(present))
225
+ return "manifest-partial"
226
+ return "manifest"
227
+
228
+
229
+ def _create_glob_view(conn: duckdb.DuckDBPyConnection, location: str) -> str:
230
+ """Create the view from every Parquet file under the location."""
231
+ logger.info(
232
+ "No export manifests under %s; loading every Parquet file "
233
+ "under the location",
234
+ location,
235
+ )
236
+ try:
237
+ conn.execute(glob_view_sql(location, hive_partitioning=True))
238
+ except duckdb.BinderException as e:
239
+ if "hive partition" not in str(e).lower():
240
+ raise
241
+ # Mixed partition-key casings, no manifests to disambiguate them
242
+ logger.warning(
243
+ "Hive partition mismatch under %s (%s); loading without "
244
+ "partition inference",
245
+ location,
246
+ e,
247
+ )
248
+ conn.execute(glob_view_sql(location, hive_partitioning=False))
249
+ return "glob-no-hive"
250
+ return "glob"
251
+
252
+
253
+ def create_focus_view(conn: duckdb.DuckDBPyConnection, location: str) -> str:
254
+ """Create the focus_data_table view over the data at the location.
255
+
256
+ Returns the name of the strategy used, which callers surface: a glob
257
+ over what turns out to be an export is the failure mode this module
258
+ exists to avoid, and it is invisible otherwise.
259
+
260
+ Errors propagate: the caller decides whether to attach a backend hint
261
+ to them.
262
+ """
263
+ manifests = discover_manifests(conn, location)
264
+ if manifests:
265
+ return _create_manifest_view(conn, location, manifests)
266
+ return _create_glob_view(conn, location)
focus_mcp/paths.py ADDED
@@ -0,0 +1,22 @@
1
+ """Locate data files that ship inside the focus_mcp package.
2
+
3
+ Resources are addressed through importlib.resources rather than __file__ or a
4
+ relative path, so they resolve identically whether the server was started from
5
+ a checkout, a wheel installed by uvx, or the Docker image.
6
+ """
7
+
8
+ from importlib.resources import files
9
+ from pathlib import Path
10
+
11
+
12
+ def resource_path(*parts: str) -> Path:
13
+ """Return the absolute path to a file under focus_mcp/resources/.
14
+
15
+ Args:
16
+ parts: Path components below the resources directory, for example
17
+ ("specifications", "columns.yaml").
18
+
19
+ The package is always installed unzipped, so this yields a real filesystem
20
+ path that DuckDB and PyYAML can open directly.
21
+ """
22
+ return Path(str(files("focus_mcp").joinpath("resources", *parts)))
focus_mcp/queries.py ADDED
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ FOCUS Query Library - Dynamic query loader for FOCUS billing analytics.
4
+
5
+ This module provides a structured way to load and manage predefined analytical
6
+ queries for FOCUS (FinOps Open Cost and Usage Specification) billing data.
7
+ Queries are loaded from a comprehensive JSON file containing all use cases
8
+ from focus.finops.org, with automatic filtering based on the configured
9
+ FOCUS version.
10
+
11
+ The query library provides:
12
+ - Version-specific query filtering (v1.0, v1.1, v1.2)
13
+ - Comprehensive metadata including columns and parameters
14
+ - Parameter descriptions with types and examples
15
+ - Column identification for query validation
16
+ - Source attribution for all queries
17
+ """
18
+
19
+ import sys
20
+ import yaml
21
+ from dataclasses import dataclass, field
22
+ from typing import List, Dict, Optional
23
+ from . import config
24
+ from .paths import resource_path
25
+
26
+
27
+ @dataclass
28
+ class Query:
29
+ """
30
+ Represents a single FOCUS analytical query with comprehensive metadata.
31
+
32
+ This class encapsulates all information needed to execute, understand,
33
+ and validate a FOCUS billing query, including the SQL text and metadata.
34
+
35
+ Attributes:
36
+ name: Human-readable name of the query
37
+ description: Description of what the query analyzes
38
+ query: The SQL text with ? placeholders for parameters
39
+ focus_versions: FOCUS specification versions this query supports
40
+ citation: Source URL from focus.finops.org
41
+ slug: URL-friendly identifier
42
+ """
43
+ name: str
44
+ description: str
45
+ query: str
46
+ focus_versions: List[str] = field(default_factory=list)
47
+ citation: str = ""
48
+ slug: str = ""
49
+
50
+
51
+ class QueryLoader:
52
+ """
53
+ Manages loading and accessing FOCUS analytical queries from JSON.
54
+
55
+ This class loads all FOCUS use cases from a comprehensive JSON file
56
+ and filters them based on the configured FOCUS version. It provides
57
+ rich metadata for each query including columns, parameters, and descriptions.
58
+
59
+ The loader automatically filters queries at initialization based on
60
+ the FOCUS_VERSION environment variable and provides methods to access
61
+ queries by ID, slug, or iterate through all available queries.
62
+ """
63
+
64
+ def __init__(self):
65
+ """Initialize the query loader and load version-specific queries."""
66
+ self.queries: Dict[str, Query] = {}
67
+ self.adjustments: Dict[str, dict] = {} # Store raw adjustments with comments
68
+ self._load_queries()
69
+
70
+ def _load_queries(self):
71
+ """
72
+ Load all queries from the YAML file and filter by FOCUS version.
73
+
74
+ The loading process:
75
+ 1. Loads the comprehensive YAML file with all use cases
76
+ 2. Loads adjustments from focus_query_adjustments.yaml
77
+ 3. Applies adjustments by overriding fields
78
+ 4. Filters queries based on configured FOCUS_VERSION
79
+ 5. Converts YAML data to Query objects with full metadata
80
+ 6. Indexes queries by slug for flexible access
81
+
82
+ This approach provides version-specific query sets while maintaining
83
+ all metadata from the focus.finops.org website.
84
+ """
85
+ # Find the YAML files that ship inside the package
86
+ yaml_file = resource_path("queries", "focus_use_cases.yaml")
87
+ adjustments_file = resource_path("queries", "focus_use_cases_adjustments.yaml")
88
+
89
+ if not yaml_file.exists():
90
+ print(f"Warning: Query file {yaml_file} does not exist", file=sys.stderr)
91
+ print("Run 'python scrape_to_yaml.py' to generate it", file=sys.stderr)
92
+ return
93
+
94
+ try:
95
+ with open(yaml_file, 'r', encoding='utf-8') as f:
96
+ all_queries = yaml.safe_load(f)
97
+ except Exception as e:
98
+ print(f"Error loading queries from {yaml_file}: {e}", file=sys.stderr)
99
+ return
100
+
101
+ # Load adjustments if file exists
102
+ if adjustments_file.exists():
103
+ try:
104
+ with open(adjustments_file, 'r', encoding='utf-8') as f:
105
+ self.adjustments = yaml.safe_load(f) or {}
106
+ print(f"Loaded {len(self.adjustments)} query adjustments", file=sys.stderr)
107
+ except Exception as e:
108
+ print(f"Error loading adjustments from {adjustments_file}: {e}", file=sys.stderr)
109
+ self.adjustments = {}
110
+
111
+ # Normalize the configured version (e.g., "1.0" -> "v1.0")
112
+ configured_version = f"v{config.FOCUS_VERSION}"
113
+
114
+ # Process each query
115
+ for key, query_data in all_queries.items():
116
+ # Apply adjustments if they exist for this query
117
+ if key in self.adjustments:
118
+ adjustment = self.adjustments[key]
119
+ # Override fields from adjustment (except fix_comment)
120
+ for field, value in adjustment.items():
121
+ if field != 'fix_comment':
122
+ query_data[field] = value
123
+
124
+ # Filter by FOCUS version
125
+ focus_versions = query_data.get('focus_versions', [])
126
+ if configured_version not in focus_versions:
127
+ continue # Skip queries not compatible with configured version
128
+
129
+ # Create Query object with all metadata
130
+ query = Query(
131
+ name=query_data.get('title', ''),
132
+ description=query_data.get('description', ''),
133
+ query=query_data.get('sql', ''),
134
+ focus_versions=focus_versions,
135
+ citation=query_data.get('source_url', ''),
136
+ slug=query_data.get('slug', key)
137
+ )
138
+
139
+ # Index by slug (key)
140
+ self.queries[key] = query
141
+
142
+ print(f"Loaded {len(self.queries)} queries for FOCUS {config.FOCUS_VERSION}", file=sys.stderr)
143
+
144
+ def get_query(self, query_identifier: str) -> Optional[Query]:
145
+ """
146
+ Retrieve a specific query by its slug identifier.
147
+
148
+ Args:
149
+ query_identifier: The query slug (underscore-separated key)
150
+
151
+ Returns:
152
+ Query object if found, None otherwise
153
+ """
154
+ return self.queries.get(query_identifier)
155
+
156
+ def list_queries(self) -> List[Dict[str, str]]:
157
+ """
158
+ List all available queries with basic metadata.
159
+
160
+ Returns:
161
+ List of dictionaries with query metadata for display
162
+ """
163
+ return [
164
+ {
165
+ 'slug': query.slug,
166
+ 'name': query.name,
167
+ 'description': query.description or 'No description available',
168
+ 'parameter_count': query.query.count('?'),
169
+ 'versions': ', '.join(query.focus_versions)
170
+ }
171
+ for query in self.queries.values()
172
+ ]
173
+
174
+ def get_query_info(self, query: Query) -> str:
175
+ """
176
+ Generate comprehensive information about a query for the LLM.
177
+
178
+ This method creates a detailed description of the query that helps
179
+ the LLM understand what parameters are needed and how to use the query.
180
+
181
+ Args:
182
+ query: The Query object to describe
183
+
184
+ Returns:
185
+ Formatted string with complete query information
186
+ """
187
+ info = []
188
+ info.append(f"Query: {query.name}")
189
+
190
+ if query.description:
191
+ info.append(f"Description: {query.description}")
192
+
193
+ info.append(f"\nFOCUS Versions: {', '.join(query.focus_versions)}")
194
+
195
+ # Just show parameter count from SQL
196
+ param_count = query.query.count('?')
197
+ if param_count > 0:
198
+ info.append(f"\nParameters Required: {param_count}")
199
+
200
+ # Add SQL preview
201
+ sql_preview = query.query[:200] + "..." if len(query.query) > 200 else query.query
202
+ info.append("\nSQL Preview:")
203
+ info.append(sql_preview)
204
+
205
+ # Add source
206
+ if query.citation:
207
+ info.append(f"\nSource: {query.citation}")
208
+
209
+ return "\n".join(info)
210
+
211
+
212
+ # Global query loader instance
213
+ # Initialized at module import to pre-load all available queries
214
+ # This singleton pattern ensures queries are loaded once and cached
215
+ focus_queries = QueryLoader()