diff --git a/analytics/analytics_package/analytics/_report_utils.py b/analytics/analytics_package/analytics/_report_utils.py index 3b49feff4..d4768ad1c 100644 --- a/analytics/analytics_package/analytics/_report_utils.py +++ b/analytics/analytics_package/analytics/_report_utils.py @@ -137,10 +137,9 @@ def get_one_period_change_series( series_previous_reindexed = ( series_previous.reindex(combined_index) * current_length / previous_length ) - change = ((series_current_reindexed / series_previous_reindexed) - 1).replace( + return ((series_current_reindexed / series_previous_reindexed) - 1).replace( {np.inf: np.nan} ) - return change def get_change_over_time_df( diff --git a/analytics/analytics_package/analytics/api.py b/analytics/analytics_package/analytics/api.py index 5637bf712..2625e6e35 100644 --- a/analytics/analytics_package/analytics/api.py +++ b/analytics/analytics_package/analytics/api.py @@ -53,7 +53,7 @@ def authenticate( *other_service_params, port=None, ): - service_param_sets = (first_service_params,) + other_service_params + service_param_sets = (first_service_params, *other_service_params) all_scopes = { scope for service_params in service_param_sets for scope in service_params[0] @@ -68,10 +68,7 @@ def authenticate( global next_port if port is None: - if next_port is None: - port = 8082 - else: - port = next_port + port = 8082 if next_port is None else next_port next_port = port + 1 elif next_port is None: next_port = port + 1 @@ -192,9 +189,7 @@ def get_metrics_by_dimensions_v3_style( results.append(result) params[start_index_key] += params[max_results_key] - df = results_to_df(results) - - return df + return results_to_df(results) def get_metrics_by_dimensions_v4_style( @@ -258,9 +253,7 @@ def get_metrics_by_dimensions_v4_style( offset += max_results params["offset"] = offset - df = v4_results_to_df(results, dimensions, metrics) - - return df + return v4_results_to_df(results, dimensions, metrics) def v4_results_to_df(results, dimensions, metrics): diff --git a/analytics/analytics_package/analytics/report_elements.py b/analytics/analytics_package/analytics/report_elements.py index 83e716f08..5f9bd3024 100644 --- a/analytics/analytics_package/analytics/report_elements.py +++ b/analytics/analytics_package/analytics/report_elements.py @@ -129,8 +129,7 @@ def get_outbound_links_df(analytics_params, ignore_index=True): if not ignore_index: return df_all_links.set_index(dimension_aliases_to_keep) - else: - return df_all_links.reset_index(drop=True) + return df_all_links.reset_index(drop=True) def get_outbound_links_change( @@ -288,8 +287,7 @@ def get_one_period_change_df( ) if ignore_index: return df_current_with_changes - else: - return df_current_with_changes.reset_index() + return df_current_with_changes.reset_index() def get_page_views_over_time_df( diff --git a/analytics/analytics_package/analytics/static_site/__init__.py b/analytics/analytics_package/analytics/static_site/__init__.py index 34efb2690..068bf1505 100644 --- a/analytics/analytics_package/analytics/static_site/__init__.py +++ b/analytics/analytics_package/analytics/static_site/__init__.py @@ -3,8 +3,8 @@ from .resolve import enrich_detail_records, fetch_entity_title_map __all__ = [ - "generate_site", - "fetch_entity_title_map", "enrich_detail_records", + "fetch_entity_title_map", + "generate_site", "make_event_charts", ] diff --git a/analytics/analytics_package/analytics/static_site/export.py b/analytics/analytics_package/analytics/static_site/export.py index 1edb37e52..66edb123f 100644 --- a/analytics/analytics_package/analytics/static_site/export.py +++ b/analytics/analytics_package/analytics/static_site/export.py @@ -1,9 +1,8 @@ """Export analytics DataFrames to JSON files for the static site.""" -import glob import json -import os from datetime import datetime +from pathlib import Path import pandas as pd from pandas.api.types import is_object_dtype, is_string_dtype @@ -28,7 +27,7 @@ def export_df_as_json(df, col_map, change_col, filename, output_dir): col_map: Dict mapping source column names to output names. change_col: Source column name for the change metric (may be absent). filename: Output JSON filename. - output_dir: Output directory. + output_dir: Output directory, as a Path. """ if df is None or len(df) == 0: records = [] @@ -56,7 +55,7 @@ def export_df_as_json(df, col_map, change_col, filename, output_dir): if pd.isna(record.get("change")): record["change"] = None - with open(os.path.join(output_dir, filename), "w") as f: + with (output_dir / filename).open("w") as f: json.dump(records, f, indent=2) print(f" Wrote {filename} ({len(records)} records)") @@ -78,12 +77,13 @@ def export_data( current_month: Current month string (YYYY-MM). analytics_start: Analytics start date string. custom_events: List of custom event dicts with results. - output_dir: Output directory for JSON files. + output_dir: Output directory for JSON files, as a str or Path. """ - os.makedirs(output_dir, exist_ok=True) + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) - for old_file in glob.glob(os.path.join(output_dir, "event_*_detail.json")): - os.remove(old_file) + for old_file in output_dir.glob("event_*_detail.json"): + old_file.unlink() df_monthly_traffic = data["monthly_traffic"] dates = data.get("dates", {}) @@ -96,7 +96,7 @@ def export_data( traffic_data["users"] = traffic_data["users"].fillna(0).astype(int) traffic_data["pageviews"] = traffic_data["pageviews"].fillna(0).astype(int) - with open(os.path.join(output_dir, "monthly_traffic.json"), "w") as f: + with (output_dir / "monthly_traffic.json").open("w") as f: json.dump(traffic_data.to_dict(orient="records"), f, indent=2) print(f" Wrote monthly_traffic.json ({len(traffic_data)} records)") @@ -138,28 +138,28 @@ def export_data( # File downloads print("Exporting file downloads data...") file_downloads = data.get("file_downloads", 0) - with open(os.path.join(output_dir, "file_downloads.json"), "w") as f: + with (output_dir / "file_downloads.json").open("w") as f: json.dump({"total": file_downloads}, f, indent=2) print(f" Wrote file_downloads.json (total: {file_downloads})") # Access requests print("Exporting access requests data...") access_requests = data.get("access_requests", []) - with open(os.path.join(output_dir, "access_requests.json"), "w") as f: + with (output_dir / "access_requests.json").open("w") as f: json.dump(access_requests, f, indent=2) print(f" Wrote access_requests.json ({len(access_requests)} records)") # File download events (GA4 enhanced measurement) print("Exporting file download events data...") file_download_events = data.get("file_download_events", 0) - with open(os.path.join(output_dir, "file_download_events.json"), "w") as f: + with (output_dir / "file_download_events.json").open("w") as f: json.dump({"total": file_download_events}, f, indent=2) print(f" Wrote file_download_events.json (total: {file_download_events})") # Search queries print("Exporting search queries data...") search_queries = data.get("search_queries", {"total": 0, "queries": []}) - with open(os.path.join(output_dir, "search_queries.json"), "w") as f: + with (output_dir / "search_queries.json").open("w") as f: json.dump(search_queries, f, indent=2) print( f" Wrote search_queries.json ({len(search_queries.get('queries', []))} queries)" @@ -180,7 +180,7 @@ def export_data( event_entry["detail_file_column"] = event["detail_file_column"] events_output.append(event_entry) - with open(os.path.join(output_dir, "custom_events.json"), "w") as f: + with (output_dir / "custom_events.json").open("w") as f: json.dump(events_output, f, indent=2) print(f" Wrote custom_events.json ({len(events_output)} events)") @@ -190,7 +190,7 @@ def export_data( detail = data.get(f"event_{key}_detail") if detail is not None: filename = f"event_{key}_detail.json" - with open(os.path.join(output_dir, filename), "w") as f: + with (output_dir / filename).open("w") as f: json.dump(detail, f, indent=2) print(f" Wrote {filename} ({len(detail)} records)") @@ -218,13 +218,13 @@ def export_data( ) chart_output["charts"].append(chart_data) - with open(os.path.join(output_dir, "event_charts.json"), "w") as f: + with (output_dir / "event_charts.json").open("w") as f: json.dump(chart_output, f, indent=2) print(f" Wrote event_charts.json ({len(chart_output['charts'])} charts)") # Config (for the HTML template) print("Exporting site config...") - with open(os.path.join(output_dir, "config.json"), "w") as f: + with (output_dir / "config.json").open("w") as f: json.dump(config, f, indent=2) print(" Wrote config.json") @@ -242,6 +242,6 @@ def export_data( "engagement_rate": data.get("engagement_rate", {}), } - with open(os.path.join(output_dir, "meta.json"), "w") as f: + with (output_dir / "meta.json").open("w") as f: json.dump(meta, f, indent=2) print(" Wrote meta.json") diff --git a/analytics/analytics_package/analytics/static_site/generator.py b/analytics/analytics_package/analytics/static_site/generator.py index e8e9e7fee..abd2f42b9 100644 --- a/analytics/analytics_package/analytics/static_site/generator.py +++ b/analytics/analytics_package/analytics/static_site/generator.py @@ -1,12 +1,12 @@ """Generate a static analytics site from GA4 data.""" -import os import shutil +from pathlib import Path from .export import export_data from .fetch import fetch_data -TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "template") +TEMPLATE_DIR = Path(__file__).parent / "template" def generate_site( @@ -39,7 +39,7 @@ def generate_site( property_id: GA4 property ID. current_month: Current month string (YYYY-MM). analytics_start: Start date for all-time data (YYYY-MM-DD). - output_dir: Output directory for the generated site. + output_dir: Output directory for the generated site, as a str or Path. custom_events: List of dicts with "event_name" and "label" keys. Example: [{"event_name": "chat_submitted", "label": "Chat Submissions"}] historic_data_path: Path to historic UA data JSON file (optional). @@ -55,6 +55,7 @@ def generate_site( """ if custom_events is None: custom_events = [] + output_dir = Path(output_dir) print("=" * 50) print(f"Generating analytics site: {config['site_title']}") @@ -80,13 +81,13 @@ def generate_site( print("Resolving entity titles...") title_resolver(data) - os.makedirs(output_dir, exist_ok=True) - template_html = os.path.join(TEMPLATE_DIR, "index.html") - output_html = os.path.join(output_dir, "index.html") + output_dir.mkdir(parents=True, exist_ok=True) + template_html = TEMPLATE_DIR / "index.html" + output_html = output_dir / "index.html" shutil.copy2(template_html, output_html) print(f"Copied template to {output_html}") - data_dir = os.path.join(output_dir, "data") + data_dir = output_dir / "data" export_data( data=data, config=config, @@ -99,7 +100,7 @@ def generate_site( print("\n" + "=" * 50) print("Static site generation complete!") - print(f"Files written to: {os.path.abspath(output_dir)}") + print(f"Files written to: {output_dir.resolve()}") print("\nTo view the site locally, run:") print(f" cd {output_dir} && python -m http.server 8080") print("Then open http://localhost:8080 in your browser.") diff --git a/analytics/pyproject.toml b/analytics/pyproject.toml index 8b1ee0ccf..89f6b3c86 100644 --- a/analytics/pyproject.toml +++ b/analytics/pyproject.toml @@ -18,4 +18,4 @@ analytics = { workspace = true } dev = ["ruff==0.16.3"] [tool.ruff.lint] -select = ["E4", "E7", "E9", "F", "I", "W", "B"] +select = ["E4", "E7", "E9", "F", "I", "W", "B", "C4", "PIE", "PTH", "RET", "RUF", "SIM", "UP"]