diff --git a/places_insights/notebooks/custom_location_scores/README.md b/places_insights/notebooks/custom_location_scores/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/places_insights/notebooks/custom_location_scores/places_insights_custom_location_scores.ipynb b/places_insights/notebooks/custom_location_scores/places_insights_custom_location_scores.ipynb
new file mode 100644
index 0000000..0b203cd
--- /dev/null
+++ b/places_insights/notebooks/custom_location_scores/places_insights_custom_location_scores.ipynb
@@ -0,0 +1,571 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": [],
+ "private_outputs": true
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "code",
+ "source": [
+ "# Copyright 2026 Google LLC\n",
+ "#\n",
+ "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ],
+ "metadata": {
+ "id": "hWTdLZpCewIw"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# AI-Powered Custom Location Scoring with Places Insights & Gemini\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ "  Open in Colab\n",
+ " \n",
+ " | \n",
+ " \n",
+ " \n",
+ "  Open in Colab Enterprise\n",
+ " \n",
+ " | \n",
+ " \n",
+ " \n",
+ "  Open in BigQuery Studio\n",
+ " \n",
+ " | \n",
+ " \n",
+ " \n",
+ "  View on GitHub\n",
+ " \n",
+ " | \n",
+ "
"
+ ],
+ "metadata": {
+ "id": "uQAT7_P4fqT3"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Overall Goal\n",
+ "\n",
+ "This notebook demonstrates a powerful workflow for generating **Custom Location Scores** (\"suitability scores\") for real estate or travel. Instead of relying solely on raw counts of nearby amenities, we combine **quantitative data** (Google Maps Platform Public Datasets) with **qualitative data** (listing descriptions) and use Generative AI to calculate a nuanced score for a specific user persona (e.g., a \"Young Family\").\n",
+ "\n",
+ "The result is an **interactive map with color-coded markers**, where the AI not only rates a location (0-10) but provides the **reasoning** behind the score directly in the map popup.\n",
+ "\n",
+ "### Key Technologies Used\n",
+ "\n",
+ "* **[Places Insights](https://developers.google.com/maps/documentation/placesinsights):** A Google Maps Platform dataset available in BigQuery that allows for advanced statistical analysis. It provides aggregated counts and density information for Places-of-Interest (POI).\n",
+ "* **[BigQuery](https://cloud.google.com/bigquery):** The serverless data warehouse used to store the listings and execute the analysis.\n",
+ "* **[BigQuery AI.GENERATE](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-generate):** A SQL function that allows us to call Generative AI models (like Gemini) directly within a query to generate scores and text reasoning.\n",
+ "* **[Vertex AI (Gemini)](https://cloud.google.com/vertex-ai/):** We use the `gemini-flash-latest` model to analyze the combined data.\n",
+ "* **[Google Maps 2D Tiles](https://developers.google.com/maps/documentation/tile/2d-tiles-overview):** To use Google Maps as the high-resolution basemap for visualization.\n",
+ "* **Python Libraries:**\n",
+ " * **[Folium](https://python-visualization.github.io/folium/latest/)** for creating the final interactive, layered map.\n",
+ " * **[Pandas](https://pandas.pydata.org/)** for handling the synthetic listing data.\n",
+ "\n",
+ "See [Google Maps Platform Pricing](https://mapsplatform.google.com/intl/en_uk/pricing/) and [BigQuery Pricing](https://cloud.google.com/bigquery/pricing) for costs associated with running this notebook.\n",
+ "\n",
+ "**Note:** The `AI.GENERATE` function passes input to a Gemini model and incurs charges in Vertex AI each time it is called. For details on managing these costs, see the **[Best Practices](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-generate#best_practices)** section of the documentation.\n",
+ "\n",
+ "### The Step-by-Step Workflow\n",
+ "\n",
+ "1. **Generate Target Candidates:** We begin by creating a dataset of hypothetical apartment listings (including descriptions like \"Quiet street\" vs. \"Busy intersection\"). **To store this data, the notebook automatically creates a dedicated BigQuery dataset in your project.**\n",
+ "\n",
+ "2. **The AI-SQL Scoring Engine:** We execute a single, powerful SQL query in BigQuery that:\n",
+ " * Performs a spatial join to count specific amenities (Parks, Schools, Museums) within 800m of each listing.\n",
+ " * Constructs a natural language prompt combining these counts with the listing description.\n",
+ " * Calls **Gemini** via `AI.GENERATE` to calculate a \"Family-Friendliness Score\" and explain *why* that score was given.\n",
+ "\n",
+ "3. **Visual Analysis:** In the final step, we render the scored locations on a Google Map. We use **color-coded pins** (Green=High Score, Red=Low Score) to instantly identify the best candidates. Clicking a marker reveals a popup with the AI's detailed reasoning.\n",
+ "\n",
+ "4. **Resource Cleanup:** A final, interactive cell allows you to delete the temporary BigQuery dataset and all its contents, ensuring you leave your project clean.\n",
+ "\n",
+ "### **How to Use This Notebook**\n",
+ "\n",
+ "1. **Set Up Secrets:** Before you begin, you must configure two secrets in the Colab \"Secrets\" tab (the **key icon** on the left menu):\n",
+ " * `GCP_PROJECT_ID`: Your Google Cloud Project ID.\n",
+ " * `GMP_API_KEY`: Your Google Maps Platform API key.\n",
+ "\n",
+ "2. **Enable APIs:** Ensure the following APIs are enabled in your Google Cloud Console for the project:\n",
+ " * **Maps Tiles API** (for the visualization).\n",
+ " * **Vertex AI API** (for the AI scoring).\n",
+ "\n",
+ "3. **Subscribe to Places Insights (US Data):**\n",
+ " This notebook analyzes locations in New York, so you must have access to the United States dataset in BigQuery.\n",
+ " * **Sign Up:** If you haven't already, [sign up for access](https://developers.google.com/maps/documentation/placesinsights/cloud-setup) to Places Insights.\n",
+ " * **Choose Your Dataset:**\n",
+ " * **Full Data Option:** Subscribe to the full **\"Places Insights - US\"** listing. Ensure your linked dataset is named `places_insights___us`.\n",
+ " * **Sample Data Option:** You may also use the **[US Sample Data](https://developers.google.com/maps/documentation/placesinsights/cloud-setup#sample_data)** (\"Places Insights - US - sample\"), as it covers New York City.\n",
+ " * **Important:** If you use the **Sample Data** (or name your dataset differently), you **must update** the `PLACES_TABLE` variable in **Phase 3** of this notebook to match your linked dataset name (e.g., `places_insights___us___sample`).\n",
+ "\n",
+ "4. **Run the Cells:** Once the secrets and data subscriptions are ready, simply run the cells in order."
+ ],
+ "metadata": {
+ "id": "8jjHCex58gYW"
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "J7ij3r2truTB"
+ },
+ "outputs": [],
+ "source": [
+ "# @title Phase 1: Authentication & Setup\n",
+ "# @markdown This cell authenticates you to Google Cloud, retrieves API keys, and **creates a new BigQuery dataset** (`places_insights_custom_score_demo`) in your project to store the demo data.\n",
+ "\n",
+ "import sys\n",
+ "from google.colab import userdata, auth\n",
+ "from google.cloud import bigquery\n",
+ "import google.auth\n",
+ "import pandas as pd\n",
+ "\n",
+ "# 1. Retrieve Secrets\n",
+ "try:\n",
+ " GCP_PROJECT_ID = userdata.get('GCP_PROJECT_ID')\n",
+ " GMP_API_KEY = userdata.get('GMP_API_KEY')\n",
+ " print(f\"β
Secrets retrieved for project: {GCP_PROJECT_ID}\")\n",
+ "except userdata.SecretNotFoundError as e:\n",
+ " raise ValueError(\"Missing Secrets! Please add 'GCP_PROJECT' and 'GMP_API_KEY' to Colab Secrets.\") from e\n",
+ "\n",
+ "# 2. Authenticate User\n",
+ "auth.authenticate_user(project_id=GCP_PROJECT_ID)\n",
+ "print(\"β
User Authenticated.\")\n",
+ "\n",
+ "# 3. Initialize BigQuery Client\n",
+ "client = bigquery.Client(project=GCP_PROJECT_ID)\n",
+ "REGION = \"US\" # Places Insights is in the US multi-region\n",
+ "DATASET_ID = f\"{GCP_PROJECT_ID}.places_insights_custom_score_demo\"\n",
+ "\n",
+ "# Create a working dataset to store our staging table\n",
+ "ds = bigquery.Dataset(DATASET_ID)\n",
+ "ds.location = REGION\n",
+ "client.create_dataset(ds, exists_ok=True)\n",
+ "print(f\"β
Working dataset ready: {DATASET_ID}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# @title Phase 2: Create Target Listings Data\n",
+ "# @markdown Generates a hypothetical portfolio of **candidate properties** to simulate a real-world scenario.\n",
+ "# @markdown\n",
+ "# @markdown Each listing combines **geospatial coordinates** (for spatial analysis) with **text descriptions** (for AI sentiment analysis), staging the data in **BigQuery** for the scoring engine.\n",
+ "\n",
+ "# 1. Define Apartment Listings (Quantitative + Qualitative)\n",
+ "data = [\n",
+ " {\n",
+ " \"id\": 1,\n",
+ " \"name\": \"The Midtown Lofts\",\n",
+ " \"lat\": 40.7511, \"lng\": -73.9846, # Times Square area\n",
+ " \"description\": \"Modern loft in the heart of the city. High energy, constant noise, and nightlife right downstairs. Great for singles, difficult for quiet sleepers.\"\n",
+ " },\n",
+ " {\n",
+ " \"id\": 2,\n",
+ " \"name\": \"Suburban Garden Suites\",\n",
+ " \"lat\": 40.6586, \"lng\": -73.9764, # Near Prospect Park\n",
+ " \"description\": \"Quiet, tree-lined street near a major park. Large backyard, very low traffic. Perfect for pets and kids.\"\n",
+ " },\n",
+ " {\n",
+ " \"id\": 3,\n",
+ " \"name\": \"The Transit Hub\",\n",
+ " \"lat\": 40.6892, \"lng\": -73.9772, # Near Atlantic Terminal\n",
+ " \"description\": \"Unbeatable commute. Located directly above a major subway hub. Extremely busy intersection, no green space immediately adjacent.\"\n",
+ " },\n",
+ " {\n",
+ " \"id\": 4,\n",
+ " \"name\": \"Riverside Retreat\",\n",
+ " \"lat\": 40.8000, \"lng\": -73.9700, # Upper West Side / Riverside\n",
+ " \"description\": \"Scenic views of the river. historic building, slower pace of life but plenty of cultural amenities and museums nearby.\"\n",
+ " },\n",
+ " {\n",
+ " \"id\": 5,\n",
+ " \"name\": \"Industrial Fixer-Upper\",\n",
+ " \"lat\": 40.7050, \"lng\": -73.9200, # Bushwick area\n",
+ " \"description\": \"Converted warehouse space. Very gritty, few amenities nearby, but huge open floor plan. Developing neighborhood.\"\n",
+ " }\n",
+ "]\n",
+ "\n",
+ "df_listings = pd.DataFrame(data)\n",
+ "\n",
+ "# 2. Upload to BigQuery\n",
+ "table_id = f\"{DATASET_ID}.apartment_listings\"\n",
+ "\n",
+ "job_config = bigquery.LoadJobConfig(\n",
+ " # specific schema to ensure Lat/Lng are floats\n",
+ " schema=[\n",
+ " bigquery.SchemaField(\"id\", \"INTEGER\"),\n",
+ " bigquery.SchemaField(\"name\", \"STRING\"),\n",
+ " bigquery.SchemaField(\"lat\", \"FLOAT\"),\n",
+ " bigquery.SchemaField(\"lng\", \"FLOAT\"),\n",
+ " bigquery.SchemaField(\"description\", \"STRING\"),\n",
+ " ],\n",
+ " write_disposition=\"WRITE_TRUNCATE\", # Overwrite if exists\n",
+ ")\n",
+ "\n",
+ "job = client.load_table_from_dataframe(df_listings, table_id, job_config=job_config)\n",
+ "job.result() # Wait for job to complete\n",
+ "\n",
+ "print(f\"β
Data uploaded to `{table_id}`\")\n",
+ "print(f\" Rows inserted: {job.output_rows}\")\n",
+ "df_listings.head()"
+ ],
+ "metadata": {
+ "id": "jBg8EY7dsEUi"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# @title Phase 3: Custom Location Scoring Engine\n",
+ "# @markdown Executes the core **AI-SQL Scoring Engine** by fusing **Places Insights** data with **BigQuery `AI.GENERATE`**.\n",
+ "# @markdown\n",
+ "# @markdown We first perform a spatial join to quantify nearby amenities (parks, schools, etc.), then pass these metrics, along with listing descriptions, directly into **Gemini** to calculate a nuanced Family-Friendliness Score and explanation.\n",
+ "\n",
+ "from google.cloud import bigquery\n",
+ "import pandas as pd\n",
+ "\n",
+ "# 1. Define Resources\n",
+ "PLACES_TABLE = f\"{GCP_PROJECT_ID}.places_insights___us.places\"\n",
+ "LISTINGS_TABLE = f\"{DATASET_ID}.apartment_listings\"\n",
+ "\n",
+ "# 2. Construct the Global Endpoint URL\n",
+ "# This explicit URL ensures we target the global publisher model directly\n",
+ "MODEL_NAME = \"gemini-flash-latest\"\n",
+ "ENDPOINT_URL = f\"https://aiplatform.googleapis.com/v1/projects/{GCP_PROJECT_ID}/locations/global/publishers/google/models/{MODEL_NAME}\"\n",
+ "\n",
+ "sql_query = f\"\"\"\n",
+ "WITH insight_counts AS (\n",
+ " -- Mandatory Privacy Clause\n",
+ " SELECT WITH AGGREGATION_THRESHOLD\n",
+ " listings.id,\n",
+ " listings.name,\n",
+ " listings.lat,\n",
+ " listings.lng,\n",
+ " listings.description,\n",
+ " -- Quantitative Metrics (COALESCE handles privacy NULLs later)\n",
+ " COUNTIF(places.primary_type = 'park') AS park_count,\n",
+ " COUNTIF(places.primary_type = 'museum') AS museum_count,\n",
+ " COUNTIF(places.primary_type = 'restaurant' AND places.good_for_children = TRUE) AS family_restaurant_count,\n",
+ " COUNTIF(places.primary_type IN ('primary_school', 'secondary_school')) AS school_count\n",
+ " FROM\n",
+ " `{LISTINGS_TABLE}` AS listings\n",
+ " LEFT JOIN\n",
+ " `{PLACES_TABLE}` AS places\n",
+ " ON ST_DWITHIN(ST_GEOGPOINT(listings.lng, listings.lat), places.point, 800)\n",
+ " GROUP BY\n",
+ " listings.id, listings.name, listings.lat, listings.lng, listings.description\n",
+ "),\n",
+ "\n",
+ "prepared_prompts AS (\n",
+ " SELECT\n",
+ " *,\n",
+ " -- Prompt Engineering with Null Handling\n",
+ " FORMAT('''\n",
+ " You are an expert real estate analyst. Generate a 'Family-Friendliness Score' (0-10) for this location.\n",
+ "\n",
+ " Target User: Young family with a toddler, looking for safety, green space, and convenience.\n",
+ "\n",
+ " Location Data:\n",
+ " - Description: %s\n",
+ " - Parks nearby (800m): %d\n",
+ " - Schools (Primary/Secondary) nearby: %d\n",
+ " - Museums nearby: %d\n",
+ " - Family-friendly restaurants: %d\n",
+ "\n",
+ " Scoring Rules:\n",
+ " - High importance: High park count and presence of schools.\n",
+ " - Negative modifiers: Descriptions mentioning \"noise\", \"nightlife\", or \"industrial\".\n",
+ " - Positive modifiers: Descriptions mentioning \"quiet\", \"backyard\", or \"community\".\n",
+ "\n",
+ " Output only the score and a 1-sentence reasoning.\n",
+ " ''',\n",
+ " description,\n",
+ " COALESCE(park_count, 0),\n",
+ " COALESCE(school_count, 0),\n",
+ " COALESCE(museum_count, 0),\n",
+ " COALESCE(family_restaurant_count, 0)\n",
+ " ) AS prompt_text\n",
+ " FROM insight_counts\n",
+ ")\n",
+ "\n",
+ "-- Final Step: Call AI.GENERATE with Explicit Endpoint\n",
+ "SELECT\n",
+ " id,\n",
+ " name,\n",
+ " lat,\n",
+ " lng,\n",
+ " generated_output.family_friendliness_score AS score,\n",
+ " generated_output.reasoning\n",
+ "FROM (\n",
+ " SELECT\n",
+ " *,\n",
+ " AI.GENERATE(\n",
+ " prompt_text,\n",
+ " endpoint => '{ENDPOINT_URL}',\n",
+ " output_schema => 'family_friendliness_score FLOAT64, reasoning STRING'\n",
+ " ) AS generated_output\n",
+ " FROM prepared_prompts\n",
+ ")\n",
+ "ORDER BY score DESC;\n",
+ "\"\"\"\n",
+ "\n",
+ "print(f\"π§ Running AI Scoring Query...\")\n",
+ "print(f\" Target Model: {MODEL_NAME}\")\n",
+ "\n",
+ "try:\n",
+ " query_job = client.query(sql_query)\n",
+ " df_scored = query_job.to_dataframe()\n",
+ " print(\"β
Scoring Complete!\")\n",
+ "\n",
+ " # Display results\n",
+ " pd.set_option('display.max_colwidth', None)\n",
+ " display(df_scored[['name', 'score', 'reasoning']])\n",
+ "\n",
+ "except Exception as e:\n",
+ " print(f\"β Query Failed. Error: {e}\")"
+ ],
+ "metadata": {
+ "id": "Wirb8GlpuXMX"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# @title Phase 4a: Maps Backend Initialization (Session, Copyright & Assets)\n",
+ "# @markdown This cell manages the Google Maps 2D Tiles API handshake. It performs the following steps:\n",
+ "# @markdown 1. **Session Creation:** Authenticates and requests a \"Roadmap\" session for the target region.\n",
+ "# @markdown 2. **Attribution Fetching:** Queries the API for the specific copyright text required for the configured viewport.\n",
+ "# @markdown 3. **Asset Preparation:** Generates the compliant HTML for the Google Maps logo overlay.\n",
+ "\n",
+ "import requests\n",
+ "\n",
+ "# 1. Validation & Data Bounds\n",
+ "if 'df_scored' not in locals() or df_scored.empty:\n",
+ " raise ValueError(\"No data found! Please run Phase 3 successfully first.\")\n",
+ "if not GMP_API_KEY:\n",
+ " raise ValueError(\"GMP_API_KEY is missing. Please set it in Phase 1.\")\n",
+ "\n",
+ "# Calculate center and bounds dynamically from the dataframe\n",
+ "center_lat = df_scored['lat'].mean()\n",
+ "center_lng = df_scored['lng'].mean()\n",
+ "min_lat, max_lat = df_scored['lat'].min(), df_scored['lat'].max()\n",
+ "min_lng, max_lng = df_scored['lng'].min(), df_scored['lng'].max()\n",
+ "ZOOM_LEVEL = 12\n",
+ "\n",
+ "# 2. Create Google Maps Session\n",
+ "print(\"πΊοΈ Initializing Google Maps Session...\")\n",
+ "session_url = f\"https://tile.googleapis.com/v1/createSession?key={GMP_API_KEY}\"\n",
+ "headers = {\"Content-Type\": \"application/json\"}\n",
+ "payload = {\n",
+ " \"mapType\": \"roadmap\",\n",
+ " \"language\": \"en-US\",\n",
+ " \"region\": \"US\"\n",
+ "}\n",
+ "\n",
+ "try:\n",
+ " response = requests.post(session_url, json=payload, headers=headers)\n",
+ " response.raise_for_status()\n",
+ " session_token = response.json().get(\"session\")\n",
+ " print(f\"β
Session Token acquired: {session_token[:10]}...\")\n",
+ "except Exception as e:\n",
+ " raise RuntimeError(f\"Failed to initialize Google Maps session: {e}\")\n",
+ "\n",
+ "# 3. Fetch Dynamic Attribution\n",
+ "# We use the bounds of our data to request the specific copyright for this area\n",
+ "print(\"Fetching dynamic attribution...\")\n",
+ "viewport_url = (\n",
+ " f\"https://tile.googleapis.com/tile/v1/viewport?key={GMP_API_KEY}\"\n",
+ " f\"&session={session_token}\"\n",
+ " f\"&zoom={ZOOM_LEVEL}\"\n",
+ " f\"&north={max_lat}&south={min_lat}\"\n",
+ " f\"&west={min_lng}&east={max_lng}\"\n",
+ ")\n",
+ "\n",
+ "try:\n",
+ " vp_response = requests.get(viewport_url)\n",
+ " vp_response.raise_for_status()\n",
+ " google_attribution = vp_response.json().get('copyright', 'Map data Β© Google')\n",
+ " print(f\"β
Attribution received: {google_attribution}\")\n",
+ "except Exception as e:\n",
+ " print(f\"β οΈ Warning: Could not fetch attribution ({e}). Defaulting.\")\n",
+ " google_attribution = \"Map data Β© Google\"\n",
+ "\n",
+ "# 4. Construct Logo HTML\n",
+ "# Standard Google on-map logo asset\n",
+ "logo_url = \"https://maps.gstatic.com/mapfiles/api-3/images/google_white3.png\"\n",
+ "\n",
+ "logo_html = f\"\"\"\n",
+ " \n",
+ "

\n",
+ "
\n",
+ "\"\"\"\n",
+ "print(\"β
Logo HTML prepared.\")"
+ ],
+ "metadata": {
+ "cellView": "form",
+ "id": "EWAWR0K6SJRR"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# @title Phase 4b: Map Visualization\n",
+ "# @markdown Visualizes the **custom location scores** generated by combining **Places Insights** data with **BigQuery `AI.GENERATE`**.\n",
+ "# @markdown\n",
+ "# @markdown This interactive map plots the apartment listings, allowing you to instantly see which locations best fit the target persona and explore the reasoning.\n",
+ "\n",
+ "import folium\n",
+ "\n",
+ "# 1. Construct Tile URL\n",
+ "# Note: {{z}}, {{x}}, {{y}} are kept as literals for Folium/Leaflet to substitute.\n",
+ "tiles_url = f\"https://tile.googleapis.com/v1/2dtiles/{{z}}/{{x}}/{{y}}?session={session_token}&key={GMP_API_KEY}\"\n",
+ "\n",
+ "# 2. Initialize Map\n",
+ "m = folium.Map(\n",
+ " location=[center_lat, center_lng],\n",
+ " zoom_start=ZOOM_LEVEL,\n",
+ " tiles=tiles_url,\n",
+ " attr=google_attribution, # Uses the dynamic copyright fetched in Phase 4a\n",
+ " name=\"Google Maps\",\n",
+ " control_scale=True,\n",
+ " prefer_canvas=True\n",
+ ")\n",
+ "\n",
+ "# 3. Create Custom Legend HTML\n",
+ "# Positioned at Bottom-Right to avoid the Google Logo at Bottom-Left.\n",
+ "legend_html = '''\n",
+ " \n",
+ " AI Score Legend
\n",
+ " High (8-10)
\n",
+ " Medium (5-7)
\n",
+ " Low (0-4)
\n",
+ "
\n",
+ " '''\n",
+ "\n",
+ "# 4. Inject Overlays (Logo + Legend)\n",
+ "# We add the logo to the left, and the legend to the right.\n",
+ "m.get_root().html.add_child(folium.Element(logo_html)) # Logo (from Phase 4a)\n",
+ "m.get_root().html.add_child(folium.Element(legend_html)) # Legend\n",
+ "\n",
+ "# 5. Add Scored Markers\n",
+ "print(\"π Plotting AI-scored locations...\")\n",
+ "\n",
+ "for _, row in df_scored.iterrows():\n",
+ " # Dynamic Color Logic based on AI Score\n",
+ " score = row['score']\n",
+ " if score >= 8.0:\n",
+ " icon_color = 'green'\n",
+ " icon_type = 'star'\n",
+ " elif score >= 5.0:\n",
+ " icon_color = 'orange'\n",
+ " icon_type = 'info-sign'\n",
+ " else:\n",
+ " icon_color = 'red'\n",
+ " icon_type = 'exclamation-sign'\n",
+ "\n",
+ " # Popup Content (HTML)\n",
+ " popup_html = f\"\"\"\n",
+ " \n",
+ "
{row['name']}
\n",
+ "
Score: {score}/10
\n",
+ "
\"{row['reasoning']}\"
\n",
+ "
\n",
+ " \"\"\"\n",
+ "\n",
+ " folium.Marker(\n",
+ " location=[row['lat'], row['lng']],\n",
+ " popup=folium.Popup(popup_html, max_width=300),\n",
+ " tooltip=f\"{row['name']} ({score})\",\n",
+ " icon=folium.Icon(color=icon_color, icon=icon_type)\n",
+ " ).add_to(m)\n",
+ "\n",
+ "# 6. Display\n",
+ "display(m)"
+ ],
+ "metadata": {
+ "cellView": "form",
+ "id": "HeS4nK6WTmLf"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# @title Phase 5: Clean Up Resources\n",
+ "# @markdown This cell deletes the demo dataset and all tables within it.\n",
+ "# @markdown\n",
+ "# @markdown **You will be prompted to confirm before deletion.**\n",
+ "\n",
+ "from google.cloud.exceptions import NotFound\n",
+ "\n",
+ "# Validation\n",
+ "print(f\"β οΈ WARNING: You are about to DELETE the dataset: `{DATASET_ID}`\")\n",
+ "print(f\" Project: `{GCP_PROJECT_ID}`\")\n",
+ "print(\" This action cannot be undone.\")\n",
+ "\n",
+ "# Interactive Input\n",
+ "confirmation = input(\"Type 'yes' to proceed with deletion: \").strip().lower()\n",
+ "\n",
+ "if confirmation == 'yes':\n",
+ " print(f\"\\nποΈ Deleting dataset: {DATASET_ID}...\")\n",
+ " try:\n",
+ " # delete_contents=True removes tables inside the dataset\n",
+ " client.delete_dataset(DATASET_ID, delete_contents=True, not_found_ok=True)\n",
+ " print(f\"β
Successfully deleted dataset '{DATASET_ID}' and all contents.\")\n",
+ " except Exception as e:\n",
+ " print(f\"β Error deleting dataset: {e}\")\n",
+ "else:\n",
+ " print(f\"\\nπ Operation cancelled. Dataset `{DATASET_ID}` was NOT deleted.\")"
+ ],
+ "metadata": {
+ "id": "4MBgXd_p0aR1"
+ },
+ "execution_count": null,
+ "outputs": []
+ }
+ ]
+}
\ No newline at end of file