{"partnerBranding":null,"ghostPresences":[],"layerProcessingEmailSubscriptions":[],"pipeline":{"syncUploadMaxBytes":15360},"sharing":{"publicAccess":"view_only"},"maxImageFileSizeBytes":20000000,"validGeoDataExtensions":[".json",".geojson"],"layerGroups":[{"id":"b0fe67f8-b8f5-4a79-8c83-56f29d136da1","name":"Untitled group (copy) (copy)","visible":false,"description":"","created_at":"2025-10-09T21:34:57","layers":[{"legend_items":[],"geocoder_metadata":null,"stats_url":"https://us1.data-pipeline.felt.com/5746e0ee-6748-46fe-b1e7-40e600004453/stats/stats.json","geomatch_metadata":null,"bounding_box":{"coordinates":[[[-124.733174,24.514962],[-66.949895,24.514962],[-66.949895,49.384358],[-124.733174,49.384358],[-124.733174,24.514962]]],"crs":{"properties":{"name":"EPSG:4326"},"type":"name"},"type":"Polygon"},"edit_version":null,"processing_time_seconds":12,"runCause":"reprocess","pipeline_version":"0.35.11063","visible":false,"source_id":null,"is_spreadsheet":false,"parsed_size_bytes":634880,"raster_preview_url":null,"h3_levels":null,"stac_url":null,"created_at":"2025-10-09T21:34:58","index_json_url":"https://us1.data-pipeline.felt.com/upload/996ef342-9010-5326-a3a5-26190000554c.json","column_selections":[],"max_zoom":18,"status":"completed","legendDisplay":"default","errorMessage":null,"html_popup_source":null,"errorType":null,"stats":[],"feature_url":"https://us1.data-pipeline.felt.com/onefeature/5746e0ee-6748-46fe-b1e7-40e600004453/{feature}.geojson{?skip_geometry,zoom_level}","min_zoom":0,"html_popup_threads":[],"remote_data_url":null,"progress":100,"centroids_layer_name":"parsed-anchors","subtitle":"","pipeline_dataset_id":"5746e0ee-6748-46fe-b1e7-40e600004453","hash_url":"https://us1.data-pipeline.felt.com/hash/{hash}","raster_colors":null,"external_refresh_frequency_ms":null,"is_convertible_to_elements":true,"excerpt_url":"https://us1.data-pipeline.felt.com/5746e0ee-6748-46fe-b1e7-40e600004453/excerpt/excerpt.json","has_download_url":true,"raster_details":null,"tile_url":"https://us1.data-pipeline.felt.com/vectortile/5746e0ee-6748-46fe-b1e7-40e600004453/{z}/{x}/{y}.pbf{?attributes,layer,query}","tile_max_zoom":null,"hideFromLegend":true,"source_has_custom_query":false,"last_processed_at":"2024-12-14T00:57:16","sql_query_threads":[],"style":{"config":{"labelAttribute":["territory"]},"label":{"color":"rgb(51, 51, 51)","fontSize":13,"fontStyle":"normal","fontWeight":700,"haloColor":"auto","haloWidth":1,"isClickable":false,"isHoverable":false,"maxZoom":24,"minZoom":1,"textTransform":"uppercase"},"legend":{},"paint":[{"color":"rgba(0, 0, 0, 0)","isClickable":false,"isHoverable":false,"lineJoin":"bevel","opacity":0.55,"strokeColor":"rgb(78, 139, 212)","strokeWidth":1},{"color":"transparent","opacity":0.55,"strokeColor":"rgba(0, 0, 0, 0)","strokeWidth":2}],"type":"simple","version":"2.3.1"},"layer_name":"parsed","next_processing_state":null,"id":"b0d6bd23-8d7f-4891-946c-023c76dce456","initial_fill_color":null,"name":"Sales Territories (copy) (copy)","scheduled_refresh_status":null,"created_by":"Mamata Akella","semantic_columns":[],"data_last_updated_by_user_at":null,"source_dataset_id":null,"geometry_type":"Polygon","z_order":1,"legendVisibility":"hide","h3_geomatched_level":null,"initial_stroke_color":null,"scheduled_refresh_frequency":null,"table":{"name":"parsed","columns":[{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/0/1.json{?query}","count":6,"count_distinct":6,"max":9,"median":3,"min":1,"name":"gpkg_fid_1","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/0.json{?query}","type":"INTEGER","values":{"1":1,"2":1,"3":1,"6":1,"7":1,"9":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/1/1.json{?query}","count":6,"count_distinct":6,"name":"STATEFP","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/1.json{?query}","type":"TEXT","values":{"01":1,"06":1,"21":1,"31":1,"35":1,"53":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/2/1.json{?query}","count":6,"count_distinct":6,"name":"STATENS","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/2.json{?query}","type":"TEXT","values":{"00897535":1,"01779775":1,"01779778":1,"01779786":1,"01779792":1,"01779804":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/3/1.json{?query}","count":6,"count_distinct":6,"name":"AFFGEOID","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/3.json{?query}","type":"TEXT","values":{"0400000US01":1,"0400000US06":1,"0400000US21":1,"0400000US31":1,"0400000US35":1,"0400000US53":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/4/1.json{?query}","count":6,"count_distinct":6,"name":"GEOID","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/4.json{?query}","type":"TEXT","values":{"01":1,"06":1,"21":1,"31":1,"35":1,"53":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/5/1.json{?query}","count":6,"count_distinct":6,"name":"STUSPS","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/5.json{?query}","type":"TEXT","values":{"AL":1,"CA":1,"KY":1,"NE":1,"NM":1,"WA":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/6/1.json{?query}","count":6,"count_distinct":6,"name":"NAME","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/6.json{?query}","type":"TEXT","values":{"Alabama":1,"California":1,"Kentucky":1,"Nebraska":1,"New Mexico":1,"Washington":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/7/1.json{?query}","count":6,"count_distinct":1,"name":"LSAD","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/7.json{?query}","type":"TEXT","values":{"00":6}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/8/1.json{?query}","count":6,"count_distinct":6,"max":403503931312,"median":172112588220,"min":102279490672,"name":"ALAND","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/8.json{?query}","type":"INTEGER","values":{"102279490672":1,"131174048583":1,"172112588220":1,"198956658395":1,"314196306401":1,"403503931312":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/9/1.json{?query}","count":6,"count_distinct":6,"max":20463871877,"median":2375337755,"min":728776523,"name":"AWATER","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/9.json{?query}","type":"INTEGER","values":{"12559278850":1,"1371829134":1,"20463871877":1,"2375337755":1,"4593327154":1,"728776523":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/5746e0ee-6748-46fe-b1e7-40e600004453/10/1.json{?query}","count":6,"count_distinct":6,"name":"territory","stats_url":"https://us1.data-pipeline.felt.com/stats/5746e0ee-6748-46fe-b1e7-40e600004453/10.json{?query}","type":"TEXT","values":{"Central":1,"Midwest":1,"Northeast":1,"Northwest":1,"Southeast":1,"Southwest":1}}],"content_url":"https://us1.data-pipeline.felt.com/table/5746e0ee-6748-46fe-b1e7-40e600004453/{page}.json{?query}","row_count":6},"normalized":{"filename":"qgis_export_a1486a9527e94b26a41a17a02fb267b2.zip/qgis_export_a1486a9527e94b26a41a17a02fb267b2.gpkg","feature_id_field":null,"filetype":"GeoPackage","layername":"parsed"},"ready_for_immediate_export":true,"modified_at":"2025-10-09T21:34:58"}],"created_by":"Mamata Akella","modified_at":"2025-10-09T21:34:57","user_id":"2d8f0d35-09e0-4bed-9323-bc2695c2272e","index_json_url":"https://us1.data-pipeline.felt.com/upload/996ef342-9010-5326-a3a5-26190000554c.json","max_zoom":18,"subtitle":null,"z_order":7,"hideFromLegend":false,"isCollapsed":true,"errorMessage":null,"thumbnailUrl":null,"progress_percent":100,"visibilityInteraction":"checkbox","created_at_unix_time_ms":1760045697000,"duplicatedFromId":null,"errorType":null,"legendVisibility":"show","published_to_project_ids":null,"renderAsLayer":true},{"id":"2308e77d-6ffc-4e8e-bc4c-aa636162b73e","name":"Stores with demographics","visible":true,"description":"","created_at":"2025-10-09T21:34:57","layers":[{"legend_items":[{"id":"7f20d57a-ec2a-4d34-9e4f-00fdd17d4c29-0","visible":true}],"geocoder_metadata":null,"stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453.json","geomatch_metadata":null,"bounding_box":{"coordinates":[[[-123.13976,25.623256],[-70.259086,25.623256],[-70.259086,48.786082],[-123.13976,48.786082],[-123.13976,25.623256]]],"crs":{"properties":{"name":"EPSG:4326"},"type":"name"},"type":"Polygon"},"edit_version":null,"processing_time_seconds":34,"runCause":"rollup","pipeline_version":"0.47.13510","visible":true,"source_id":"2880b9f4-7705-46d9-b259-aa94197cb44e","is_spreadsheet":false,"parsed_size_bytes":3862528,"raster_preview_url":null,"h3_levels":[5,20,67,137,233,489,1056,1724,2075,2361,2503,2560,2568,2570,2570,2570],"stac_url":null,"created_at":"2025-10-09T21:34:58","index_json_url":"https://us1.data-pipeline.felt.com/upload/cca93ac4-9c6c-4e59-8af8-ce6b0000554c.json","column_selections":[],"max_zoom":18,"status":"completed","legendDisplay":"default","errorMessage":null,"html_popup_source":"<html class=\"{{_theme}}\">\n <head>\n <link rel=\"preload\" href=\"https://app-assets.felt.com/fonts/AtlasGrotesk-Regular-Cy-Web.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://app-assets.felt.com/fonts/fonts.css\">\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://app-assets.felt.com/css/root.css\">\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://app-assets.felt.com/css/html-popup.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n <style>\n .market-analysis {\n padding: var(--space-md);\n }\n \n .analysis-section {\n margin-bottom: var(--space-md);\n }\n \n .analysis-section:first-of-type {\n margin-top: var(--space-md);\n }\n \n .section-title {\n font-weight: 600;\n margin-bottom: var(--space-xs);\n color: var(--colors-textBody);\n border-bottom: 1px solid var(--colors-dividerSubtle);\n padding-bottom: 4px;\n font-size: 14px;\n }\n \n .chart-container {\n height: 240px;\n margin: var(--space-xs) 0;\n }\n \n .chart-container-radar {\n height: 200px;\n margin: var(--space-xs) 0;\n }\n \n .chart-description {\n font-size: 12px;\n color: var(--colors-textBodySubtle);\n margin-bottom: var(--space-xs);\n line-height: 1.4;\n }\n \n .top-insight {\n padding: var(--space-xs);\n margin-bottom: var(--space-md);\n border-radius: 2px;\n transition: all 0.2s ease;\n }\n \n .top-insight-text {\n font-size: 12px;\n color: var(--colors-textBody);\n line-height: 1.4;\n font-weight: 500;\n }\n </style>\n </head>\n <body>\n <div class=\"market-analysis\">\n \n <div class=\"top-insight\" id=\"topInsightCard\">\n <div class=\"top-insight-text\" id=\"topInsight\">Analyzing location performance...</div>\n </div>\n\n <div class=\"analysis-section\">\n <div class=\"section-title\">Performance vs Market Potential</div>\n <div class=\"chart-description\">\n Identifies strengths and gaps across key business drivers to highlight opportunities.\n </div>\n <div class=\"chart-container-radar\">\n <canvas id=\"performanceChart\"></canvas>\n </div>\n </div>\n\n <div class=\"analysis-section\">\n <div class=\"section-title\">Customer Segments</div>\n <div class=\"chart-description\">\n Economic and lifestyle segments that drive purchasing decisions and brand preferences.\n </div>\n <div class=\"chart-container\">\n <canvas id=\"segmentChart\"></canvas>\n </div>\n </div>\n\n <div class=\"analysis-section\">\n <div class=\"section-title\">Risk Assessment</div>\n <div class=\"chart-description\">\n Key economic and social challenges that could impact customer spending and market access.\n </div>\n <div class=\"chart-container\">\n <canvas id=\"riskChart\"></canvas>\n </div>\n </div>\n\n </div>\n\n <script>\n // Calculate market metrics with safe defaults\n const revenue = parseFloat({{ total_revenue | default: 0 }}) || 0;\n const orders = parseFloat({{ orders | default: 0 }}) || 0;\n const households = parseFloat({{ total_households | default: 1 }}) || 1;\n const population = parseFloat({{ total_population | default: 1 }}) || 1;\n const competitorDistance = parseFloat({{ distance_to_nearest_competitor_miles | default: 1 }}) || 1;\n const vulnerabilityScore = parseFloat({{ overall_social_vulnerability_ranking | default: 0.5 }}) || 0.5;\n \n // Demographic variables\n const ageUnder17 = parseFloat({{ percent_age_17_under | default: 0 }}) || 0;\n const ageOver65 = parseFloat({{ percent_age_65_plus | default: 0 }}) || 0;\n const ageWorking = 100 - ageUnder17 - ageOver65;\n const percentPoverty = parseFloat({{ percent_below_150_poverty | default: 0 }}) || 0;\n const percentUnemployed = parseFloat({{ percent_unemployed | default: 0 }}) || 0;\n const percentNoHighSchool = parseFloat({{ percent_no_high_school | default: 0 }}) || 0;\n const percentDisabled = parseFloat({{ percent_disabled | default: 0 }}) || 0;\n \n // Additional risk variables\n const percentUninsured = parseFloat({{ percent_uninsured | default: 0 }}) || 0;\n const percentSingleParent = parseFloat({{ percent_single_parent_households | default: 0 }}) || 0;\n const percentNoVehicle = parseFloat({{ percent_no_vehicle | default: 0 }}) || 0;\n const percentNoInternet = parseFloat({{ percent_no_internet | default: 0 }}) || 0;\n\n // Calculate performance metrics for insight generation\n const avgOrderValue = revenue / Math.max(orders, 1);\n const marketRisk = (percentPoverty + percentUnemployed) / 2;\n const demographicStrength = Math.max(0, 100 - marketRisk);\n \n // Detect dark mode\n const isDarkMode = document.documentElement.classList.contains('dark') || \n document.querySelector('html').className.includes('dark') ||\n getComputedStyle(document.documentElement).getPropertyValue('--colors-textBody').includes('255');\n \n // Theme-aware colors\n const textColor = isDarkMode ? '#ffffff' : '#000000';\n const gridColor = isDarkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(128, 128, 128, 0.3)';\n const borderColor = isDarkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(255, 255, 255, 0.8)';\n \n // Generate location-specific performance insight and determine status\n let locationInsight = '';\n let performanceStatus = 'moderate'; // default\n \n if (revenue > 15000 && competitorDistance > 1.5) {\n locationInsight = `Strong performer: $${revenue.toLocaleString()} revenue with ${competitorDistance.toFixed(1)}mi competitive advantage.`;\n performanceStatus = 'strong';\n } else if (revenue > 12000 && competitorDistance < 0.5) {\n locationInsight = `High revenue despite tight competition - excellent location.`;\n performanceStatus = 'strong';\n } else if (revenue < 8000 && marketRisk > 15) {\n locationInsight = `Underperforming location: $${revenue.toLocaleString()} revenue limited by ${marketRisk.toFixed(1)}% economic headwinds.`;\n performanceStatus = 'weak';\n } else if (revenue < 8000 && competitorDistance < 0.3) {\n locationInsight = `Revenue challenged by intense competition - competitor only ${(competitorDistance * 5280).toFixed(0)}ft away.`;\n performanceStatus = 'weak';\n } else if (orders > 200 && avgOrderValue < 60) {\n locationInsight = `High-traffic location with low spend per visit - opportunity to increase basket size.`;\n performanceStatus = 'moderate';\n } else if (revenue < 10000 && demographicStrength > 70) {\n locationInsight = `Underperforming vs demographics - strong market conditions suggest operational opportunity.`;\n performanceStatus = 'weak';\n } else if (revenue > 12000 && marketRisk < 10) {\n locationInsight = `Revenue performance aligns with favorable demographics - market expectations being met.`;\n performanceStatus = 'strong';\n } else {\n locationInsight = `Moderate performer: $${revenue.toLocaleString()} revenue in ${competitorDistance.toFixed(1)}mi competitive environment.`;\n performanceStatus = 'moderate';\n }\n \n // Set card styling based on performance with dark mode support\n const insightCard = document.getElementById('topInsightCard');\n if (performanceStatus === 'strong') {\n insightCard.style.background = isDarkMode ? 'rgba(46, 204, 113, 0.2)' : 'rgba(46, 204, 113, 0.1)';\n insightCard.style.borderLeft = '3px solid rgba(46, 204, 113, 0.8)';\n } else if (performanceStatus === 'weak') {\n insightCard.style.background = isDarkMode ? 'rgba(231, 76, 60, 0.2)' : 'rgba(231, 76, 60, 0.1)';\n insightCard.style.borderLeft = '3px solid rgba(231, 76, 60, 0.8)';\n } else {\n insightCard.style.background = isDarkMode ? 'rgba(54, 162, 235, 0.2)' : 'rgba(54, 162, 235, 0.1)';\n insightCard.style.borderLeft = '3px solid rgba(54, 162, 235, 0.8)';\n }\n \n document.getElementById('topInsight').textContent = locationInsight;\n\n // Performance vs Potential Chart - Enhanced grid contrast\n const ctx1 = document.getElementById('performanceChart').getContext('2d');\n new Chart(ctx1, {\n type: 'radar',\n data: {\n labels: ['Revenue', 'Orders', 'Market', 'Competition', 'Demographics'],\n datasets: [{\n label: 'Performance',\n data: [\n Math.min(revenue / 500, 100),\n Math.min(orders / 300, 100),\n Math.min(households / 20, 100),\n Math.min(competitorDistance * 25, 100),\n 100 - vulnerabilityScore * 100\n ],\n backgroundColor: 'rgba(54, 162, 235, 0.4)',\n borderColor: 'rgba(54, 162, 235, 1)',\n borderWidth: 3,\n pointBackgroundColor: 'rgba(54, 162, 235, 1)',\n pointBorderColor: isDarkMode ? '#111111' : '#fff',\n pointBorderWidth: 1,\n pointRadius: 4,\n pointHoverRadius: 6\n }]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: {\n duration: 1200,\n easing: 'easeInOutQuart'\n },\n plugins: {\n legend: { display: false }\n },\n scales: {\n r: {\n beginAtZero: true,\n max: 100,\n min: 0,\n ticks: { \n display: false,\n stepSize: 25\n },\n grid: {\n color: gridColor,\n lineWidth: 1\n },\n angleLines: {\n color: gridColor,\n lineWidth: 1\n },\n pointLabels: {\n font: {\n size: 10,\n weight: '500'\n },\n color: textColor,\n padding: 2,\n centerPointLabels: true\n }\n }\n },\n elements: {\n point: {\n radius: 4\n },\n line: {\n borderWidth: 2\n }\n }\n }\n });\n\n // Customer Segments Chart - Gentle in-place animation\n const segmentCtx = document.getElementById('segmentChart').getContext('2d');\n \n // Calculate segments based on economic indicators\n const affluent = Math.max(0, ageWorking * (1 - percentPoverty/100) * (1 - percentUnemployed/100) * 0.7);\n const workingClass = Math.max(0, ageWorking * 0.6 - affluent * 0.3);\n const budgetConscious = Math.max(0, percentPoverty + percentUnemployed + workingClass * 0.2);\n const seniors = ageOver65;\n const families = ageUnder17 * 0.8; // Approximate families with children\n \n new Chart(segmentCtx, {\n type: 'doughnut',\n data: {\n labels: ['Affluent Professionals', 'Working Class', 'Budget-Conscious', 'Senior Citizens', 'Young Families'],\n datasets: [{\n data: [affluent, workingClass, budgetConscious, seniors, families],\n backgroundColor: [\n 'rgba(54, 162, 235, 0.8)', // Blue (matches radar chart)\n 'rgba(155, 89, 182, 0.8)', // Purple \n 'rgba(255, 99, 132, 0.8)', // Red (matches risk chart)\n 'rgba(241, 196, 15, 0.8)', // Yellow \n 'rgba(46, 204, 113, 0.8)' // Green \n ],\n borderWidth: 1,\n borderColor: isDarkMode ? '#111111' : '#fff'\n }]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: {\n duration: 1000,\n easing: 'easeInOutQuart'\n },\n plugins: {\n legend: { \n position: 'bottom',\n labels: {\n boxWidth: 12,\n font: { \n size: 10,\n weight: '500'\n },\n color: textColor,\n filter: function(legendItem, chartData) {\n return chartData.datasets[0].data[legendItem.index] > 2;\n }\n }\n }\n }\n }\n });\n\n // Risk Assessment Chart - Gentle in-place animation\n const ctx3 = document.getElementById('riskChart').getContext('2d');\n new Chart(ctx3, {\n type: 'bar',\n data: {\n labels: ['Poverty', 'Unemployment', 'Education', 'Housing', 'Uninsured'],\n datasets: [{\n data: [\n percentPoverty,\n percentUnemployed,\n percentNoHighSchool, \n parseFloat({{ percent_housing_cost_burdened | default: 0 }}) || 0,\n percentUninsured\n ],\n backgroundColor: [\n 'rgba(255, 99, 132, 0.8)', // Red - Poverty\n 'rgba(255, 159, 64, 0.8)', // Orange - Unemployment\n 'rgba(255, 206, 86, 0.8)', // Yellow - Education\n 'rgba(75, 192, 192, 0.8)', // Teal - Housing\n 'rgba(153, 102, 255, 0.8)' // Purple - Uninsured\n ],\n borderWidth: 1,\n borderColor: borderColor,\n borderRadius: 2,\n borderSkipped: false\n }]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: {\n duration: 1000,\n easing: 'easeInOutQuart'\n },\n plugins: {\n legend: { display: false }\n },\n scales: {\n y: {\n beginAtZero: true,\n grid: {\n color: isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(128, 128, 128, 0.1)',\n drawBorder: false\n },\n ticks: {\n callback: function(value) { return value + '%'; },\n color: textColor,\n font: { \n size: 10,\n weight: '500'\n }\n }\n },\n x: {\n grid: {\n display: false\n },\n ticks: { \n color: textColor,\n font: { \n size: 10,\n weight: '500'\n }\n }\n }\n }\n }\n });\n </script>\n </body>\n</html>","errorType":null,"stats":[],"feature_url":"https://us1.data-pipeline.felt.com/onefeature/379fc603-fd1a-4188-a48f-89b400004453/{feature}.geojson{?skip_geometry,zoom_level}","min_zoom":0,"html_popup_threads":[{"id":"d610820e-f24e-43d1-a92c-c71af4ddcc26","insertedAt":"2025-10-09T21:35:24.604445","layerId":"45c64f76-b624-4ff3-9d30-e0d0d2467044"}],"remote_data_url":null,"progress":100,"centroids_layer_name":null,"subtitle":"","pipeline_dataset_id":"379fc603-fd1a-4188-a48f-89b400004453","hash_url":"https://us1.data-pipeline.felt.com/hash/{hash}","raster_colors":null,"external_refresh_frequency_ms":null,"is_convertible_to_elements":false,"excerpt_url":"https://us1.data-pipeline.felt.com/excerpt/379fc603-fd1a-4188-a48f-89b400004453.json","has_download_url":true,"raster_details":null,"tile_url":"https://us1.data-pipeline.felt.com/vectortile/379fc603-fd1a-4188-a48f-89b400004453/{z}/{x}/{y}.pbf{?attributes,layer,query}","tile_max_zoom":7,"hideFromLegend":false,"source_has_custom_query":true,"last_processed_at":"2025-09-21T08:00:43","sql_query_threads":[],"style":{"config":{"labelAttribute":["store_city"]},"label":{"color":"auto","fontSize":13,"fontStyle":"Normal","fontWeight":500,"haloColor":"auto","haloWidth":1,"justify":"auto","letterSpacing":0,"lineHeight":1.2,"maxLineChars":10,"maxZoom":23,"minZoom":23,"offset":[8,8],"padding":2,"placement":"auto","textTransform":"none"},"legend":{},"paint":{"color":"rgb(213, 96, 255)","opacity":0.9,"size":{"linear":[[13,5],[18,8]]},"strokeColor":"auto","strokeWidth":1},"popup":{"popupLocation":"leftSidebar","type":"html"},"type":"simple","version":"2.3.1"},"layer_name":"parsed","next_processing_state":null,"id":"45c64f76-b624-4ff3-9d30-e0d0d2467044","initial_fill_color":null,"name":"Stores with demographics","scheduled_refresh_status":"active","created_by":"Mamata Akella","semantic_columns":[],"data_last_updated_by_user_at":"2025-09-21T08:00:43","source_dataset_id":"dc6ce5f8-9de0-49cf-990e-880e25883be2","geometry_type":"Point","z_order":1,"legendVisibility":"show","h3_geomatched_level":null,"initial_stroke_color":null,"scheduled_refresh_frequency":0,"table":{"name":"parsed","columns":[{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/0/1.json{?query}","count":2573,"count_distinct":2573,"max":11434,"median":5198,"min":37,"name":"sale_id","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/0.json{?query}","type":"INTEGER","values":{"37":1,"38":1,"41":1,"44":1,"45":1,"46":1,"48":1,"49":1,"50":1,"51":1,"93":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/1/1.json{?query}","count":2573,"count_distinct":2563,"max":239999.0,"median":135637.0,"min":30035.0,"name":"orders","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/1.json{?query}","type":"REAL","values":{"125340.0":2,"134784.0":2,"147142.0":2,"158577.0":2,"160985.0":2,"167143.0":2,"179685.0":2,"195775.0":2,"226819.0":2,"236916.0":2,"30035.0":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/2/1.json{?query}","count":2573,"count_distinct":2432,"max":29998.0,"median":16244.0,"min":5010.0,"name":"total_revenue","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/2.json{?query}","type":"REAL","values":{"11238.0":3,"12319.0":3,"15714.0":3,"23218.0":3,"5124.0":2,"5254.0":2,"5440.0":2,"5455.0":2,"5558.0":2,"6072.0":2,"6177.0":2}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/3/1.json{?query}","count":2573,"count_distinct":10,"name":"sales_rep","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/3.json{?query}","type":"TEXT","values":{"Emma Thomas":257,"Ethan Smith":239,"Isabella Johnson":245,"Liam Brown":241,"Lucas Anderson":278,"Mia Taylor":284,"Michael Carter":243,"Noah Wilson":232,"Olivia Martinez":276,"Sophia Davis":278}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/4/1.json{?query}","count":2573,"count_distinct":6,"name":"territory","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/4.json{?query}","type":"TEXT","values":{"Central":380,"Midwest":249,"Northeast":841,"Northwest":112,"Southeast":439,"Southwest":552}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/5/1.json{?query}","count":2573,"count_distinct":4,"name":"product_category","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/5.json{?query}","type":"TEXT","values":{"Electronics":752,"Fashion and Apparel":797,"Health and Wellness":509,"Home Essentials":515}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/6/1.json{?query}","count":2573,"count_distinct":873,"name":"store_city","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/6.json{?query}","type":"TEXT","values":{"Atlanta":28,"Austin":25,"Chicago":54,"Dallas":21,"Houston":48,"Las Vegas":29,"Los Angeles":31,"Miami":20,"New York":41,"San Diego":21,"San Jose":19}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/7/1.json{?query}","count":2573,"count_distinct":45,"name":"store_state","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/7.json{?query}","type":"TEXT","values":{"California":424,"Colorado":87,"Florida":226,"Illinois":137,"Massachusetts":85,"New Jersey":137,"New York":158,"North Carolina":67,"Pennsylvania":81,"Texas":191,"Virginia":82}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/8/1.json{?query}","count":2573,"count_distinct":1391,"max":98466.0,"median":50325.0,"min":1035.0,"name":"store_zip","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/8.json{?query}","type":"REAL","values":{"27103.0":6,"28403.0":6,"29607.0":8,"40503.0":6,"44122.0":6,"46545.0":6,"7470.0":6,"7652.0":9,"80501.0":7,"80525.0":7,"95678.0":7}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/9/1.json{?query}","count":2573,"count_distinct":2571,"max":4.9999096376698855,"median":2.449057271466376,"min":0.022259892618839372,"name":"distance_to_nearest_competitor_miles","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/9.json{?query}","type":"REAL","values":{"0.022259892618839372":1,"0.02329991717750445":1,"0.03049710227494381":1,"0.03757152446787179":1,"0.038138926644849136":1,"0.04009082267797513":1,"0.04138636092094638":1,"0.04594655920884808":1,"0.04752135480977493":1,"0.5646820754535924":2,"4.435164279562624":2}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/10/1.json{?query}","count":2573,"count_distinct":1941,"name":"census_tract","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/10.json{?query}","type":"TEXT","values":{"06013327001":4,"06037570800":4,"06061021003":5,"06067008507":4,"06073009307":4,"06081601604":4,"08041003905":4,"09170150800":5,"27053026724":5,"34003042500":7,"47187050307":5}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/11/1.json{?query}","count":2573,"count_distinct":1941,"name":"tract_location","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/11.json{?query}","type":"TEXT","values":{"Census Tract 101; Franklin County; Ohio":4,"Census Tract 1035.05; Arlington County; Virginia":4,"Census Tract 116.46; Fulton County; Georgia":4,"Census Tract 118.05; Lancaster County; Pennsylvania":4,"Census Tract 146.08; Albany County; New York":4,"Census Tract 1508; South Central Connecticut Planning Region; Connecticut":5,"Census Tract 1581.07; Suffolk County; New York":4,"Census Tract 210.03; Placer County; California":5,"Census Tract 267.24; Hennepin County; Minnesota":5,"Census Tract 425; Bergen County; New Jersey":7,"Census Tract 503.07; Williamson County; Tennessee":5}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/12/1.json{?query}","count":2573,"count_distinct":1652,"max":15339,"median":4206,"min":0,"name":"total_population","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/12.json{?query}","type":"INTEGER","values":{"0":14,"1618":6,"3392":7,"3478":6,"3589":6,"3837":7,"4032":6,"4157":6,"4215":7,"4284":9,"6996":7}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/13/1.json{?query}","count":2573,"count_distinct":1796,"max":336494,"median":6582,"min":593,"name":"daytime_population","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/13.json{?query}","type":"INTEGER","values":{"11770":5,"18891":5,"25826":5,"26049":7,"2688":5,"3094":4,"3136":4,"4825":5,"7354":5,"8930":5,"9512":5}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/14/1.json{?query}","count":2573,"count_distinct":1337,"max":6780,"median":1773,"min":0,"name":"total_households","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/14.json{?query}","type":"INTEGER","values":{"0":15,"1220":8,"1225":7,"1381":7,"1496":7,"1654":8,"1672":7,"1689":9,"1707":8,"1720":9,"1979":9}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/15/1.json{?query}","count":2573,"count_distinct":1348,"max":8790,"median":1927,"min":0,"name":"total_housing_units","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/15.json{?query}","type":"INTEGER","values":{"0":15,"1277":7,"1348":7,"1604":8,"1655":7,"1664":7,"1667":7,"1731":8,"2051":10,"2107":8,"2123":9}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/16/1.json{?query}","count":2573,"count_distinct":470,"max":90.7,"median":13.7,"min":-999.0,"name":"percent_below_150_poverty","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/16.json{?query}","type":"REAL","values":{"6.1":19,"6.8":18,"7.2":17,"7.6":17,"8.1":26,"8.6":19,"8.7":18,"9.3":19,"9.4":19,"9.5":18,"9.6":18}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/17/1.json{?query}","count":2573,"count_distinct":173,"max":50.0,"median":4.1,"min":-999.0,"name":"percent_unemployed","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/17.json{?query}","type":"REAL","values":{"0.0":53,"2.4":44,"2.6":50,"2.9":42,"3.0":43,"3.1":44,"3.4":45,"3.5":43,"3.7":43,"3.9":44,"4.5":49}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/18/1.json{?query}","count":2573,"count_distinct":495,"max":78.7,"median":27.1,"min":-999.0,"name":"percent_housing_cost_burdened","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/18.json{?query}","type":"REAL","values":{"-999.0":15,"16.1":15,"17.8":15,"20.9":16,"21.5":18,"21.7":15,"23.5":15,"25.5":15,"25.7":16,"26.4":21,"30.6":16}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/19/1.json{?query}","count":2573,"count_distinct":316,"max":69.2,"median":5.3,"min":-999.0,"name":"percent_no_high_school","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/19.json{?query}","type":"REAL","values":{"0.0":59,"0.8":33,"1.0":30,"1.8":39,"1.9":34,"2.3":31,"2.8":39,"2.9":42,"3.3":30,"4.5":36,"5.6":36}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/20/1.json{?query}","count":2573,"count_distinct":363,"max":65.1,"median":15.4,"min":-999.0,"name":"percent_age_65_plus","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/20.json{?query}","type":"REAL","values":{"10.2":20,"11.2":20,"12.3":20,"12.4":23,"13.0":21,"13.1":21,"14.8":20,"15.3":22,"17.3":21,"17.4":27,"18.4":21}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/21/1.json{?query}","count":2573,"count_distinct":326,"max":51.0,"median":18.5,"min":-999.0,"name":"percent_age_17_under","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/21.json{?query}","type":"REAL","values":{"14.8":22,"15.0":28,"16.9":22,"19.5":25,"19.6":24,"19.9":25,"20.1":31,"20.7":28,"21.7":28,"21.8":22,"23.0":23}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/22/1.json{?query}","count":2573,"count_distinct":256,"max":53.0,"median":9.9,"min":-999.0,"name":"percent_disabled","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/22.json{?query}","type":"REAL","values":{"11.4":31,"6.6":28,"6.9":28,"7.4":35,"7.5":28,"8.1":36,"8.4":39,"8.6":32,"8.9":28,"9.1":31,"9.5":29}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/23/1.json{?query}","count":2573,"count_distinct":280,"max":53.5,"median":5.5,"min":-999.0,"name":"percent_uninsured","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/23.json{?query}","type":"REAL","values":{"0.0":39,"1.0":32,"1.7":30,"1.9":30,"2.1":39,"2.7":31,"2.9":30,"3.1":31,"3.2":39,"3.4":35,"3.9":34}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/24/1.json{?query}","count":2573,"count_distinct":196,"max":35.0,"median":3.9,"min":-999.0,"name":"percent_single_parent_households","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/24.json{?query}","type":"REAL","values":{"0.0":157,"0.5":36,"1.2":38,"1.8":45,"2.0":38,"2.1":36,"2.8":39,"3.1":36,"3.6":36,"4.5":38,"4.6":38}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/25/1.json{?query}","count":2573,"count_distinct":245,"max":53.9,"median":2.4,"min":-999.0,"name":"percent_limited_english","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/25.json{?query}","type":"REAL","values":{"0.0":228,"0.2":50,"0.3":57,"0.4":45,"0.5":58,"0.6":76,"0.7":54,"0.8":61,"0.9":57,"1.0":60,"1.1":46}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/26/1.json{?query}","count":2573,"count_distinct":781,"max":100.0,"median":41.2,"min":-999.0,"name":"percent_minority","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/26.json{?query}","type":"REAL","values":{"-999.0":14,"15.0":10,"22.9":10,"26.0":12,"27.5":12,"28.8":16,"31.2":11,"31.7":11,"51.1":11,"65.7":11,"70.3":11}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/27/1.json{?query}","count":2573,"count_distinct":410,"max":100.0,"median":4.9,"min":-999.0,"name":"percent_african_american","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/27.json{?query}","type":"REAL","values":{"0.0":124,"0.2":32,"0.4":34,"0.5":40,"0.8":43,"0.9":40,"1.2":39,"1.6":32,"1.8":37,"2.0":33,"2.5":30}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/28/1.json{?query}","count":2573,"count_distinct":553,"max":96.4,"median":12.0,"min":-999.0,"name":"percent_hispanic","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/28.json{?query}","type":"REAL","values":{"10.2":21,"2.2":18,"2.5":25,"3.3":18,"3.7":18,"4.3":23,"4.8":22,"5.5":22,"6.9":27,"9.3":19,"9.8":22}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/29/1.json{?query}","count":2573,"count_distinct":410,"max":85.1,"median":6.9,"min":-999.0,"name":"percent_asian","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/29.json{?query}","type":"REAL","values":{"0.0":93,"0.4":25,"1.3":25,"1.5":30,"1.6":31,"1.8":26,"2.4":29,"2.6":26,"4.2":28,"4.4":25,"4.5":26}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/30/1.json{?query}","count":2573,"count_distinct":56,"max":17.4,"median":0.2,"min":-999.0,"name":"percent_american_indian_alaska_native","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/30.json{?query}","type":"REAL","values":{"0.0":1784,"0.1":92,"0.2":127,"0.3":86,"0.4":77,"0.5":57,"0.6":49,"0.7":27,"0.8":30,"0.9":27,"1.0":24}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/31/1.json{?query}","count":2573,"count_distinct":37,"max":44.9,"median":0.2,"min":-999.0,"name":"percent_native_hawaiian_pacific_islander","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/31.json{?query}","type":"REAL","values":{"-999.0":14,"0.0":2241,"0.1":32,"0.2":43,"0.3":45,"0.4":23,"0.5":25,"0.6":14,"0.7":10,"0.8":16,"1.0":15}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/32/1.json{?query}","count":2573,"count_distinct":142,"max":20.4,"median":3.3,"min":-999.0,"name":"percent_two_or_more_races","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/32.json{?query}","type":"REAL","values":{"0.0":88,"1.3":51,"1.4":57,"2.0":44,"2.2":49,"2.5":50,"2.6":56,"2.8":47,"3.2":55,"3.5":67,"3.6":58}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/33/1.json{?query}","count":2573,"count_distinct":75,"max":16.7,"median":0.4,"min":-999.0,"name":"percent_other_race","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/33.json{?query}","type":"REAL","values":{"0.0":1401,"0.1":64,"0.2":121,"0.3":107,"0.4":95,"0.5":68,"0.6":70,"0.7":65,"0.8":82,"0.9":46,"1.0":38}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/34/1.json{?query}","count":2573,"count_distinct":753,"max":100.0,"median":27.4,"min":-999.0,"name":"percent_multi_unit_housing","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/34.json{?query}","type":"REAL","values":{"-999.0":15,"0.0":128,"0.5":11,"10.2":14,"11.1":13,"11.8":12,"2.9":10,"32.3":12,"36.2":11,"37.8":11,"5.1":12}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/35/1.json{?query}","count":2573,"count_distinct":156,"max":74.4,"median":0.7,"min":-999.0,"name":"percent_mobile_homes","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/35.json{?query}","type":"REAL","values":{"0.0":1848,"0.3":28,"0.4":43,"0.5":47,"0.6":52,"0.7":23,"0.8":24,"0.9":24,"1.1":19,"1.3":19,"1.4":18}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/36/1.json{?query}","count":2573,"count_distinct":211,"max":47.7,"median":2.1,"min":-999.0,"name":"percent_crowded_housing","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/36.json{?query}","type":"REAL","values":{"0.0":497,"0.5":55,"0.6":44,"0.7":50,"0.8":48,"0.9":50,"1.1":43,"1.6":47,"1.7":52,"2.1":42,"2.2":43}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/37/1.json{?query}","count":2573,"count_distinct":178,"max":100.0,"median":0.3,"min":-999.0,"name":"percent_group_quarters","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/37.json{?query}","type":"REAL","values":{"0.0":825,"0.1":171,"0.2":156,"0.3":127,"0.4":113,"0.5":85,"0.6":78,"0.7":52,"0.8":45,"1.4":39,"1.8":34}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/38/1.json{?query}","count":2573,"count_distinct":404,"max":96.0,"median":6.1,"min":-999.0,"name":"percent_no_vehicle","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/38.json{?query}","type":"REAL","values":{"0.0":122,"0.8":28,"0.9":26,"1.5":27,"1.7":38,"2.0":34,"2.3":33,"2.5":29,"2.7":30,"3.3":31,"4.8":32}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/39/1.json{?query}","count":2573,"count_distinct":279,"max":72.6,"median":6.7,"min":-999.0,"name":"percent_no_internet","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/39.json{?query}","type":"REAL","values":{"0.0":59,"2.7":27,"2.8":29,"3.3":32,"3.5":26,"4.1":34,"4.4":28,"4.9":37,"5.7":30,"6.0":34,"6.1":29}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/40/1.json{?query}","count":2573,"count_distinct":1797,"max":0.9999,"median":0.4467,"min":-999.0,"name":"overall_social_vulnerability_ranking","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/40.json{?query}","type":"REAL","values":{"-999.0":15,"0.0129":6,"0.0211":5,"0.0777":5,"0.1517":5,"0.2005":6,"0.3504":6,"0.4447":6,"0.4593":6,"0.5246":7,"0.6899":6}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/41/1.json{?query}","count":2573,"count_distinct":1746,"max":0.9991,"median":0.3986,"min":-999.0,"name":"socioeconomic_vulnerability_ranking","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/41.json{?query}","type":"REAL","values":{"-999.0":15,"0.0371":5,"0.0859":5,"0.1266":6,"0.1437":5,"0.2033":6,"0.2039":5,"0.2267":5,"0.2874":7,"0.4247":7,"0.4699":7}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/42/1.json{?query}","count":2573,"count_distinct":1748,"max":0.9973,"median":0.347,"min":-999.0,"name":"household_composition_vulnerability_ranking","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/42.json{?query}","type":"REAL","values":{"-999.0":15,"0.0148":5,"0.042":6,"0.0429":6,"0.0507":6,"0.0593":5,"0.06":6,"0.1117":6,"0.2919":6,"0.7587":7,"0.8645":7}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/43/1.json{?query}","count":2573,"count_distinct":781,"max":0.9954,"median":0.5844,"min":-999.0,"name":"minority_status_language_vulnerability_ranking","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/43.json{?query}","type":"REAL","values":{"-999.0":14,"0.251":10,"0.3737":10,"0.417":12,"0.4362":12,"0.4525":16,"0.4809":11,"0.4869":11,"0.6674":11,"0.7658":11,"0.7926":11}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/44/1.json{?query}","count":2573,"count_distinct":1741,"max":0.9999,"median":0.589,"min":-999.0,"name":"housing_transportation_vulnerability_ranking","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/44.json{?query}","type":"REAL","values":{"-999.0":15,"0.0":6,"0.2406":5,"0.2544":5,"0.3329":5,"0.3737":5,"0.4366":7,"0.4569":5,"0.5527":7,"0.6504":6,"0.7845":6}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/379fc603-fd1a-4188-a48f-89b400004453/45/1.json{?query}","count":2573,"count_distinct":1941,"max":119.64194218617,"median":1.008113094204,"min":0.026997410146,"name":"area_square_miles","stats_url":"https://us1.data-pipeline.felt.com/stats/379fc603-fd1a-4188-a48f-89b400004453/45.json{?query}","type":"REAL","values":{"0.208065734576":4,"0.425395986642":4,"0.570473813142":4,"0.636382582848":4,"0.680525238406":4,"0.895045440116":4,"2.133795405714":5,"2.504593866842":7,"2.702946819506":5,"2.941569438566":5,"3.13480653973":5}}],"content_url":"https://us1.data-pipeline.felt.com/table/379fc603-fd1a-4188-a48f-89b400004453/{page}.json{?query}","row_count":2573},"normalized":{"filename":"rolled-up.gpkg","feature_id_field":null,"filetype":"GeoPackage","layername":"parsed"},"ready_for_immediate_export":true,"modified_at":"2025-10-09T21:34:58"}],"created_by":"Mamata Akella","modified_at":"2025-10-09T21:34:57","user_id":"2d8f0d35-09e0-4bed-9323-bc2695c2272e","index_json_url":"https://us1.data-pipeline.felt.com/upload/cca93ac4-9c6c-4e59-8af8-ce6b0000554c.json","max_zoom":18,"subtitle":null,"z_order":1,"hideFromLegend":false,"isCollapsed":false,"errorMessage":null,"thumbnailUrl":null,"progress_percent":100,"visibilityInteraction":"checkbox","created_at_unix_time_ms":1760045697000,"duplicatedFromId":null,"errorType":null,"legendVisibility":"show","published_to_project_ids":null,"renderAsLayer":true},{"id":"eaaa4ae8-c641-4afa-9838-6515d3e85b53","name":"Competitors","visible":false,"description":"","created_at":"2025-10-09T21:34:57","layers":[{"legend_items":[{"id":"d0a92ed0-3900-4032-8d7a-af053982098e-0","visible":false}],"geocoder_metadata":null,"stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453.json","geomatch_metadata":null,"bounding_box":{"coordinates":[[[-157.852284,20.891489],[-70.258754,20.891489],[-70.258754,49.325758],[-157.852284,49.325758],[-157.852284,20.891489]]],"crs":{"properties":{"name":"EPSG:4326"},"type":"name"},"type":"Polygon"},"edit_version":null,"processing_time_seconds":22,"runCause":"upload","pipeline_version":"0.47.13507","visible":false,"source_id":"2880b9f4-7705-46d9-b259-aa94197cb44e","is_spreadsheet":false,"parsed_size_bytes":753664,"raster_preview_url":null,"h3_levels":[7,22,68,130,209,349,457,512,527,527,527,527,527,527,527,527],"stac_url":null,"created_at":"2025-10-09T21:34:58","index_json_url":"https://us1.data-pipeline.felt.com/upload/5c3bf294-be6a-4dea-a032-88df0000554c.json","column_selections":[{"type":"wkt_wkb_literal","column":"geom"}],"max_zoom":18,"status":"completed","legendDisplay":"default","errorMessage":null,"html_popup_source":null,"errorType":null,"stats":[{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/0/1.json{?query}","count":527,"count_distinct":527,"name":"id","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/0.json{?query}","type":"TEXT","values":{"--M3WkJ2TW0-62AtHF7cu3MCt84=":1,"-Bsjh3e8ufW3VP4oxAmoBPf4uqo=":1,"-Zmy0FKYHChdiB0E2LXTRLp3QZU=":1,"-a0aL305PDlCCNhGmTneKBT8sfw=":1,"0-fc0ABwVgRcYKmFseLEavbh33k=":1,"05ah4IPaOzUXI0hLaZelBeusdyo=":1,"0Gl6dEFqdPSfayet5CYgbN0dbXo=":1,"0OnaqpxcO6B7ZCv3Af7BTThOd2I=":1,"0e5BwAVZgHqBZBnalk1_UNiHYcQ=":1,"0fuCc2kSEVJsWIzTl5DjWbFSPzM=":1,"0iJyiu-yxhAFXTSLNL4eD4dNJiE=":1}},{"count":527,"count_distinct":527,"name":"geom","type":"GEOMETRY","values":{"[MULTIPOINT]":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/2/1.json{?query}","count":527,"count_distinct":527,"name":"ref","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/2.json{?query}","type":"TEXT","values":{"17thstreet":1,"2001marketstreet":1,"academy":1,"addison":1,"akron":1,"albany":1,"albuquerque":1,"alexandria":1,"allentown":1,"altamontesprings":1,"ambassadorcaffery":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/3/1.json{?query}","count":527,"count_distinct":1,"name":"@spider","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/3.json{?query}","type":"TEXT","values":{"whole_foods":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/4/1.json{?query}","count":527,"count_distinct":1,"name":"shop","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/4.json{?query}","type":"TEXT","values":{"supermarket":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/5/1.json{?query}","count":527,"count_distinct":527,"name":"addr:full","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/5.json{?query}","type":"TEXT","values":{"1 N Halsted St":1,"1 Ridge Hill Blvd":1,"10 Columbus Cir":1,"100 Pitt St":1,"100 S Green Valley Pkwy":1,"100 Sunset Dr":1,"100 W 125th St":1,"1001 Broadway":1,"1001 Galleria Blvd":1,"1001 Plymouth Rd":1,"10020 Regency Cir":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/6/1.json{?query}","count":527,"count_distinct":381,"name":"addr:city","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/6.json{?query}","type":"TEXT","values":{"Atlanta":6,"Austin":5,"Chicago":10,"Denver":5,"Houston":8,"Los Angeles":7,"New York":13,"Portland":5,"San Francisco":8,"Seattle":5,"Washington":8}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/7/1.json{?query}","count":527,"count_distinct":47,"name":"addr:state","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/7.json{?query}","type":"TEXT","values":{"CA":95,"CO":22,"FL":33,"IL":26,"MA":32,"NC":16,"NJ":21,"NY":30,"PA":15,"TX":35,"VA":15}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/8/1.json{?query}","count":527,"count_distinct":519,"name":"addr:postcode","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/8.json{?query}","type":"TEXT","values":{"01035":1,"01545":1,"01701":1,"02139":2,"10001":2,"60201":2,"90403":2,"91423":2,"94107":2,"94114":2,"94941":2}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/9/1.json{?query}","count":527,"count_distinct":2,"name":"addr:country","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/9.json{?query}","type":"TEXT","values":{"CA":14,"US":513}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/10/1.json{?query}","count":527,"count_distinct":527,"name":"name","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/10.json{?query}","type":"TEXT","values":{"17th Street":1,"2001 Market Street":1,"23rd & Wilshire Blvd":1,"3rd & Fairfax":1,"Academy":1,"Addison":1,"Akron":1,"Alamo Quarry":1,"Albany":1,"Albuquerque":1,"Allentown":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/11/1.json{?query}","count":526,"count_distinct":526,"name":"phone","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/11.json{?query}","type":"TEXT","values":{"+1 201-226-1244":1,"+1 201-367-9099":1,"+1 201-670-0383":1,"+1 202-237-5800":1,"+1 202-296-1660":1,"+1 202-332-4300":1,"+1 202-469-7280":1,"+1 202-469-7410":1,"+1 202-519-3400":1,"+1 202-747-5570":1,"null":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/12/1.json{?query}","count":527,"count_distinct":527,"name":"website","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/12.json{?query}","type":"TEXT","values":{"https://www.wholefoodsmarket.com/stores/17thstreet":1,"https://www.wholefoodsmarket.com/stores/2001marketstreet":1,"https://www.wholefoodsmarket.com/stores/academy":1,"https://www.wholefoodsmarket.com/stores/addison":1,"https://www.wholefoodsmarket.com/stores/akron":1,"https://www.wholefoodsmarket.com/stores/albany":1,"https://www.wholefoodsmarket.com/stores/albuquerque":1,"https://www.wholefoodsmarket.com/stores/alexandria":1,"https://www.wholefoodsmarket.com/stores/allentown":1,"https://www.wholefoodsmarket.com/stores/altamontesprings":1,"https://www.wholefoodsmarket.com/stores/ambassadorcaffery":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/13/1.json{?query}","count":527,"count_distinct":8,"name":"opening_hours","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/13.json{?query}","type":"TEXT","values":{"24/7":1,"Mo-Su 07:00-21:00":32,"Mo-Su 07:00-22:00":104,"Mo-Su 07:30-22:00":1,"Mo-Su 07:30-23:00":1,"Mo-Su 08:00-21:00":289,"Mo-Su 08:00-22:00":98,"Mo-Su 08:00-23:00":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/14/1.json{?query}","count":527,"count_distinct":1,"name":"brand","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/14.json{?query}","type":"TEXT","values":{"Whole Foods":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/15/1.json{?query}","count":527,"count_distinct":1,"name":"brand:wikidata","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/15.json{?query}","type":"TEXT","values":{"Q1809448":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/16/1.json{?query}","count":527,"count_distinct":1,"name":"nsi_id","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/16.json{?query}","type":"TEXT","values":{"wholefoodsmarket-90050a":527}}],"feature_url":"https://us1.data-pipeline.felt.com/onefeature/fd2af8f4-725f-580a-9cab-253600004453/{feature}.geojson{?skip_geometry,zoom_level}","min_zoom":0,"html_popup_threads":[],"remote_data_url":null,"progress":100,"centroids_layer_name":null,"subtitle":"","pipeline_dataset_id":"fd2af8f4-725f-580a-9cab-253600004453","hash_url":"https://us1.data-pipeline.felt.com/hash/{hash}","raster_colors":null,"external_refresh_frequency_ms":null,"is_convertible_to_elements":true,"excerpt_url":"https://us1.data-pipeline.felt.com/excerpt/fd2af8f4-725f-580a-9cab-253600004453.json","has_download_url":true,"raster_details":null,"tile_url":"https://us1.data-pipeline.felt.com/vectortile/fd2af8f4-725f-580a-9cab-253600004453/{z}/{x}/{y}.pbf{?attributes,layer,query}","tile_max_zoom":3,"hideFromLegend":false,"source_has_custom_query":false,"last_processed_at":"2025-09-19T19:31:47","sql_query_threads":[],"style":{"config":{"labelAttribute":["name"]},"label":{"color":"auto","fontSize":13,"fontStyle":"Normal","fontWeight":500,"haloColor":"auto","haloWidth":1,"isClickable":false,"isHoverable":false,"justify":"auto","letterSpacing":0,"lineHeight":1.2,"maxLineChars":10,"maxZoom":23,"minZoom":23,"offset":[8,8],"padding":2,"placement":"auto","textTransform":"none"},"legend":{},"paint":{"color":"hsl(213, 61%, 57%)","isClickable":false,"isHoverable":false,"opacity":0.9,"size":4,"strokeColor":"auto","strokeWidth":1},"type":"simple","version":"2.3.1"},"layer_name":"parsed","next_processing_state":null,"id":"61329ba5-837f-4d69-9d14-f454de8d050b","initial_fill_color":null,"name":"Competitors","scheduled_refresh_status":"active","created_by":"Mamata Akella","semantic_columns":[],"data_last_updated_by_user_at":"2025-09-19T19:31:47","source_dataset_id":"4d3cda7b-1ef0-48d6-bf29-417415113489","geometry_type":"Point","z_order":1,"legendVisibility":"show","h3_geomatched_level":null,"initial_stroke_color":null,"scheduled_refresh_frequency":0,"table":{"name":"parsed","columns":[{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/0/1.json{?query}","count":527,"count_distinct":527,"name":"id","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/0.json{?query}","type":"TEXT","values":{"--M3WkJ2TW0-62AtHF7cu3MCt84=":1,"-Bsjh3e8ufW3VP4oxAmoBPf4uqo=":1,"-Zmy0FKYHChdiB0E2LXTRLp3QZU=":1,"-a0aL305PDlCCNhGmTneKBT8sfw=":1,"0-fc0ABwVgRcYKmFseLEavbh33k=":1,"05ah4IPaOzUXI0hLaZelBeusdyo=":1,"0Gl6dEFqdPSfayet5CYgbN0dbXo=":1,"0OnaqpxcO6B7ZCv3Af7BTThOd2I=":1,"0e5BwAVZgHqBZBnalk1_UNiHYcQ=":1,"0fuCc2kSEVJsWIzTl5DjWbFSPzM=":1,"0iJyiu-yxhAFXTSLNL4eD4dNJiE=":1}},{"count":527,"count_distinct":527,"name":"geom","type":"GEOMETRY","values":{"[MULTIPOINT]":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/2/1.json{?query}","count":527,"count_distinct":527,"name":"ref","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/2.json{?query}","type":"TEXT","values":{"17thstreet":1,"2001marketstreet":1,"academy":1,"addison":1,"akron":1,"albany":1,"albuquerque":1,"alexandria":1,"allentown":1,"altamontesprings":1,"ambassadorcaffery":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/3/1.json{?query}","count":527,"count_distinct":1,"name":"@spider","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/3.json{?query}","type":"TEXT","values":{"whole_foods":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/4/1.json{?query}","count":527,"count_distinct":1,"name":"shop","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/4.json{?query}","type":"TEXT","values":{"supermarket":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/5/1.json{?query}","count":527,"count_distinct":527,"name":"addr:full","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/5.json{?query}","type":"TEXT","values":{"1 N Halsted St":1,"1 Ridge Hill Blvd":1,"10 Columbus Cir":1,"100 Pitt St":1,"100 S Green Valley Pkwy":1,"100 Sunset Dr":1,"100 W 125th St":1,"1001 Broadway":1,"1001 Galleria Blvd":1,"1001 Plymouth Rd":1,"10020 Regency Cir":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/6/1.json{?query}","count":527,"count_distinct":381,"name":"addr:city","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/6.json{?query}","type":"TEXT","values":{"Atlanta":6,"Austin":5,"Chicago":10,"Denver":5,"Houston":8,"Los Angeles":7,"New York":13,"Portland":5,"San Francisco":8,"Seattle":5,"Washington":8}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/7/1.json{?query}","count":527,"count_distinct":47,"name":"addr:state","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/7.json{?query}","type":"TEXT","values":{"CA":95,"CO":22,"FL":33,"IL":26,"MA":32,"NC":16,"NJ":21,"NY":30,"PA":15,"TX":35,"VA":15}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/8/1.json{?query}","count":527,"count_distinct":519,"name":"addr:postcode","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/8.json{?query}","type":"TEXT","values":{"01035":1,"01545":1,"01701":1,"02139":2,"10001":2,"60201":2,"90403":2,"91423":2,"94107":2,"94114":2,"94941":2}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/9/1.json{?query}","count":527,"count_distinct":2,"name":"addr:country","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/9.json{?query}","type":"TEXT","values":{"CA":14,"US":513}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/10/1.json{?query}","count":527,"count_distinct":527,"name":"name","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/10.json{?query}","type":"TEXT","values":{"17th Street":1,"2001 Market Street":1,"23rd & Wilshire Blvd":1,"3rd & Fairfax":1,"Academy":1,"Addison":1,"Akron":1,"Alamo Quarry":1,"Albany":1,"Albuquerque":1,"Allentown":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/11/1.json{?query}","count":526,"count_distinct":526,"name":"phone","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/11.json{?query}","type":"TEXT","values":{"+1 201-226-1244":1,"+1 201-367-9099":1,"+1 201-670-0383":1,"+1 202-237-5800":1,"+1 202-296-1660":1,"+1 202-332-4300":1,"+1 202-469-7280":1,"+1 202-469-7410":1,"+1 202-519-3400":1,"+1 202-747-5570":1,"null":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/12/1.json{?query}","count":527,"count_distinct":527,"name":"website","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/12.json{?query}","type":"TEXT","values":{"https://www.wholefoodsmarket.com/stores/17thstreet":1,"https://www.wholefoodsmarket.com/stores/2001marketstreet":1,"https://www.wholefoodsmarket.com/stores/academy":1,"https://www.wholefoodsmarket.com/stores/addison":1,"https://www.wholefoodsmarket.com/stores/akron":1,"https://www.wholefoodsmarket.com/stores/albany":1,"https://www.wholefoodsmarket.com/stores/albuquerque":1,"https://www.wholefoodsmarket.com/stores/alexandria":1,"https://www.wholefoodsmarket.com/stores/allentown":1,"https://www.wholefoodsmarket.com/stores/altamontesprings":1,"https://www.wholefoodsmarket.com/stores/ambassadorcaffery":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/13/1.json{?query}","count":527,"count_distinct":8,"name":"opening_hours","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/13.json{?query}","type":"TEXT","values":{"24/7":1,"Mo-Su 07:00-21:00":32,"Mo-Su 07:00-22:00":104,"Mo-Su 07:30-22:00":1,"Mo-Su 07:30-23:00":1,"Mo-Su 08:00-21:00":289,"Mo-Su 08:00-22:00":98,"Mo-Su 08:00-23:00":1}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/14/1.json{?query}","count":527,"count_distinct":1,"name":"brand","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/14.json{?query}","type":"TEXT","values":{"Whole Foods":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/15/1.json{?query}","count":527,"count_distinct":1,"name":"brand:wikidata","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/15.json{?query}","type":"TEXT","values":{"Q1809448":527}},{"autocomplete_url":"https://us1.data-pipeline.felt.com/autocomplete/fd2af8f4-725f-580a-9cab-253600004453/16/1.json{?query}","count":527,"count_distinct":1,"name":"nsi_id","stats_url":"https://us1.data-pipeline.felt.com/stats/fd2af8f4-725f-580a-9cab-253600004453/16.json{?query}","type":"TEXT","values":{"wholefoodsmarket-90050a":527}}],"content_url":"https://us1.data-pipeline.felt.com/table/fd2af8f4-725f-580a-9cab-253600004453/{page}.json{?query}","row_count":527},"normalized":{"filename":"https://felt.com/internal/source/2880b9f4-7705-46d9-b259-aa94197cb44e/upload/e9cce481-553d-4e99-9522-2b16a482590a","feature_id_field":null,"filetype":"SQLAlchemy DB","layername":"competitors"},"ready_for_immediate_export":true,"modified_at":"2025-10-09T21:34:58"}],"created_by":"Mamata Akella","modified_at":"2025-10-09T21:34:57","user_id":"2d8f0d35-09e0-4bed-9323-bc2695c2272e","index_json_url":"https://us1.data-pipeline.felt.com/upload/5c3bf294-be6a-4dea-a032-88df0000554c.json","max_zoom":18,"subtitle":null,"z_order":0,"hideFromLegend":false,"isCollapsed":false,"errorMessage":null,"thumbnailUrl":null,"progress_percent":100,"visibilityInteraction":"checkbox","created_at_unix_time_ms":1760045697000,"duplicatedFromId":null,"errorType":null,"legendVisibility":"show","published_to_project_ids":null,"renderAsLayer":true}],"kartaTeamId":"ccb99e8e-6539-4b08-bc41-758726f777fa","mapTeamId":null,"mapLinks":[],"canPurchasePlan":true,"folderId":null,"curatedLayerGroupIds":["03fd52c6-4fd0-4b94-94e6-fffb1632cc25","a2a1b588-0033-43d3-9904-1ffb6fc9a527","bb2287e9-0263-4086-a7f2-f4a47fca98c2","b14b3425-b2f9-4696-9903-4194f85a526d","c617d15e-35fa-4b13-ace4-6ac93553b955","a20b9bdb-dbf8-44f8-bdef-d50835a77419","3b3e9068-28b2-48ac-bd55-b571abdd8684","66d56eca-8248-423d-b2ba-fd62d6f94705","62f686e4-264f-44ec-a949-540642536966","b27bceff-7211-4b8d-bfd4-6df69055bda7","66b829b3-3b1f-4624-b85f-a7fbdc13e848","38f09207-4c6d-47f1-87c9-672beaa4fdd6","fc2c6ad1-3469-4c94-8c6c-c2bca4d3097e","84cbf52b-c9b1-4a6b-8a1e-25a03f2fc98d","5a11bd4c-e520-4623-9028-54e2806ba99e","1bd02aff-885c-4629-bb29-1dc74699caec","64f524c5-599f-400c-9ece-fa04cfde052a","c551a1e5-11aa-4766-98c1-700460d058da","5d06d982-7012-4788-ba2c-19d113cbedcf","6985fa13-ebc6-4805-80eb-98c48d04e9e4","9fb9eae9-daad-4fc2-9132-b0a4a66fd4f2","d8610b64-2331-4599-bf99-449176a02097","e7b36a2b-d93b-48bb-be74-7d021603c23e","f21a8ae4-452b-4872-966d-b3911af83704","6b65719f-827e-48dc-a750-28ad07a84bee","99d984de-e087-49a2-817e-842b70ac837c","02dff653-636f-4daf-8b4c-bc5b4f1cbbd5","b99dcc06-ed29-42dc-8cbe-cbef423bcac8","837a4162-186b-4b26-9879-cb67a4175248","7773c37b-bf89-4794-b5e6-7510af36c70b","709c4997-25b6-43a2-a9e0-695a5b92ecc1","6b9679fc-3f7d-49da-ba8d-28e25e96bdc2","a3b54a4d-e69e-4f7f-bb34-b481f788b4da","ed86369f-f4dd-4d8d-b146-b428e9dd36e2","b28d872b-8e20-4874-a6ee-eceda4459fd2","118e7b5c-a796-4ca3-ba85-62ad5844a7d3","6fe7b28c-d5e1-4a96-9d23-4fab58850fd2","a135cc86-8def-4671-aa6d-4543cfa1121c","b36ec292-79c3-4d96-b448-f875d9f9f913","e3ad4d90-90aa-4cf6-b23e-4c0e171f92a7","59cd8f40-03b7-461a-9793-d32ba646f83d","8ee8bb48-b7e8-46e9-ba79-9bcf1f1d07fb","76ab7a7c-97b3-440f-82b8-a4a304dd3bf9","0d9cb5a2-a101-40bd-bc90-6c374e4ca591","4972fba3-6753-4476-ac37-783fe9f3101a","5fe085ea-4c91-4b2a-8e31-2ec35b7649cd","06e2ae5e-8339-4721-8498-361817fc115b","1086d588-3aa8-46e4-b64a-b74646e2e045","87afafc9-ff29-42d1-85c7-3e2c64682a36","48c4d285-4c86-48f7-9897-9951477b1c22","f82aaa5d-c171-48d9-8d4a-ec6632a456db","903f37cd-4b54-4fbe-9403-507309293c95","084466f7-3d3a-4341-a424-5318d7a63e3e","fb461c3d-1778-4eed-9877-140233a44ac0","c1e9ad62-1902-4067-a0f5-1ff53f097199","b0ff761d-2b81-4b3c-83aa-672fa6a763d9","c4e82ec5-35ee-4784-958f-f677dec2fa81","0ab0a9b1-34db-4af3-b153-bdd21d6bd9ea","a700815b-9d11-471f-b3ce-bb6b06eab3ae","23988f85-3051-40f7-8feb-115143b93ac7","a66b8cb5-8aef-442b-bdf5-6b017a9450f8","02eed48c-425c-4a66-9153-fb64f8724304","c544d13c-191d-43c9-87c1-29cdb700c357","31d22d23-61ae-4f79-b52a-4c81d46904a1","53ac92ab-95a7-426c-876f-53e6ca2b063e","260e56a5-a94e-4551-9793-4dac5cea089e","14ad86d7-521f-4936-9a7b-9fb5bd363084","cf6dadff-1dd1-4add-984c-291d557d1124","f4026aec-db04-430b-b30c-9329c7328963","f8912669-cb7e-4be0-94fa-82750080b87b","c710934e-2623-4020-9c5e-7933741b6a2b","734588bc-ce4e-4384-a916-26352f9d2313","1eac67bf-cce3-4440-aedc-dbb527df9489","47485391-1f60-4844-b5b0-243ee3112385","849c79ca-2195-4205-9c27-4f57885aef85","306ff28f-9ad0-49f0-8b35-b64bca64f5da"],"referer":null,"mapProject":null,"canCreateMapsSomewhere":false,"widgets":[],"sources":[],"isAdmin":false,"felt_version":"43","editableByCurrentSession":false,"amplitudeAnalyticsId":"d01926b917624c9f002b79f6016c7213","commonAnalyticsProperties":{"feature_flags":["ff_all_column_index","ff_editable_layers","ff_growth_enterprise_trials","ff_mobile","ff_new_source_inspection","ff_new_sql_preview","ff_server_side_filtering","ff_vector_buckets"],"workspace_id":"ccb99e8e-6539-4b08-bc41-758726f777fa","workspace_plan":"enterprise_unlimited","workspace_name":"Example Maps","workspace_age_days":266},"isOwner":false,"constraints":{"bounds":null,"maxZoom":null,"minZoom":null},"comments":{"threads":[],"users":{}},"mapBackgrounds":[],"resyncTimeoutMs":5250,"curated_layers_enabled":true,"defaultZoom":14.0,"teamAdminEmails":[],"isVirtualKarta":false,"maxTileURLLength":2000,"buildSha":"367f108c9bade31f0e53bf7729566260b0bf85e5","embedConfig":null,"mapTitle":"Store performance analysis","scripts":[{"id":"8bb59f04-5a05-487e-b879-66e7dbffa91f","name":"Custom extension 2","threads":[],"content":"console.log(\"Starting spatial analysis extension with stores with demographics...\");\n\nconst STORES_LAYER_ID = \"RcZPdrYkT9COdMODQ0kZwRA\"; // Stores with demographics\nconst COMPETITOR_LAYER_ID = \"o3r9BEW3LQiiZQVCYPLIi5B\";\n\nlet currentPanelId = null;\nlet isAnalyzing = false;\nlet currentDrawnElement = null;\nlet highlightedElements = [];\nlet currentTopIndex = -1;\nlet currentBottomIndex = -1;\nlet currentStateFilter = null;\nlet currentStoreData = null;\n\n// Helper function to safely create a new panel\nasync function createNewPanel() {\n try {\n currentPanelId = await felt.createPanelId();\n console.log(\"Created new panel ID:\", currentPanelId);\n return true;\n } catch (error) {\n console.error(\"Failed to create panel:\", error);\n currentPanelId = null;\n return false;\n }\n}\n\n// Helper function to safely update panel with validation\nasync function safeUpdatePanel(panelConfig) {\n if (!currentPanelId) {\n console.log(\"No panel ID available for update\");\n return false;\n }\n \n try {\n await felt.createOrUpdatePanel({\n panel: {\n id: currentPanelId,\n ...panelConfig,\n onClickClose: async function() {\n console.log(\"Panel closed by user - cleaning up all filters and elements\");\n \n try {\n // Clear all filters and elements when panel is closed\n await clearAllFiltersAndElements();\n } catch (cleanupError) {\n console.error(\"Error during cleanup on panel close:\", cleanupError);\n }\n \n // Delete the panel\n felt.deletePanel(currentPanelId);\n currentPanelId = null;\n }\n }\n });\n return true;\n } catch (error) {\n console.log(\"Panel update failed:\", error.message);\n currentPanelId = null;\n return false;\n }\n}\n\n// Helper function to safely delete panel\nfunction safeDeletePanel() {\n if (currentPanelId) {\n try {\n felt.deletePanel(currentPanelId);\n console.log(\"Panel deleted successfully\");\n } catch (error) {\n console.log(\"Panel deletion failed, likely already deleted:\", error.message);\n }\n }\n // Always reset the panel ID regardless of deletion success\n currentPanelId = null;\n}\n\n// Function to download filtered data as CSV\nasync function downloadFilteredDataAsCSV() {\n try {\n console.log(\"Starting CSV download of filtered data...\");\n \n if (!currentStoreData || !currentStoreData.features) {\n console.error(\"No store data available for download\");\n return;\n }\n \n // Show loading indicator\n await safeUpdatePanel({\n title: \"Downloading Data...\",\n body: [\n {\n type: \"Text\",\n content: \"Preparing CSV file for download...\"\n }\n ]\n });\n \n // Get all filtered features (not just the sample)\n let allFilteredFeatures = [];\n let filters = null;\n let boundary = null;\n \n // Determine current filters\n if (currentStateFilter) {\n filters = [\"store_state\", \"eq\", currentStateFilter];\n }\n \n // Check if we have a drawn element for spatial filtering\n if (currentDrawnElement) {\n boundary = await felt.getElementGeometry(currentDrawnElement.id);\n }\n \n // Fetch all pages of filtered data\n let nextPage = null;\n let pageCount = 0;\n \n do {\n pageCount++;\n console.log(\"Fetching page\", pageCount, \"for CSV export...\");\n \n const params = {\n layerId: STORES_LAYER_ID,\n pagination: nextPage\n };\n \n if (filters) {\n params.filters = filters;\n }\n \n if (boundary) {\n params.boundary = boundary;\n }\n \n const storeData = await felt.getFeatures(params);\n \n allFilteredFeatures = allFilteredFeatures.concat(storeData.features);\n nextPage = storeData.nextPage;\n \n console.log(\"Page\", pageCount, \"returned\", storeData.features.length, \"features. Total so far:\", allFilteredFeatures.length);\n \n } while (nextPage && pageCount < 100); // Safety limit\n \n console.log(\"Retrieved\", allFilteredFeatures.length, \"total features for CSV export\");\n \n if (allFilteredFeatures.length === 0) {\n console.log(\"No data to export\");\n // Return to previous panel state\n await updatePanelWithToggleState(false);\n return;\n }\n \n // Convert to CSV format\n const csvContent = convertFeaturesToCSV(allFilteredFeatures);\n \n // Generate filename based on current filters with proper null checks\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');\n let filename = \"stores_analysis_\" + timestamp + \".csv\";\n \n // Safely handle filter description for filename\n if (currentStoreData && currentStoreData.filterDescription) {\n try {\n const cleanDescription = currentStoreData.filterDescription\n .replace(/[^a-zA-Z0-9\\s]/g, '') // Remove special characters\n .replace(/\\s+/g, '_') // Replace spaces with underscores\n .toLowerCase();\n \n if (cleanDescription && cleanDescription.length > 0) {\n filename = \"stores_\" + cleanDescription + \"_\" + timestamp + \".csv\";\n }\n } catch (descError) {\n console.log(\"Error processing filter description for filename:\", descError);\n // Fall back to default filename\n }\n }\n \n // Create download using data URI\n const dataUri = \"data:text/csv;charset=utf-8,\" + encodeURIComponent(csvContent);\n \n // Show download instructions with easy-to-copy link\n await safeUpdatePanel({\n title: \"📊 CSV Ready for Download\",\n body: [\n {\n type: \"Text\",\n content: \"**Your CSV file is ready!**\\n\\n\" + \n \"**Records:** \" + allFilteredFeatures.length + \" \\n\" +\n \"**Filename:** `\" + filename + \"`\\n\\n\" +\n \"**To download:**\\n\" +\n \"1. Select all text in the box below (triple-click or Ctrl+A)\\n\" +\n \"2. Copy it (Ctrl+C or Cmd+C)\\n\" +\n \"3. Open a new browser tab\\n\" +\n \"4. Paste into the address bar and press Enter\"\n },\n {\n type: \"TextInput\", \n label: \"Download Link (select all and copy)\",\n value: dataUri\n },\n {\n type: \"Text\",\n content: \"💡 **Tip:** Triple-click inside the text box above to select the entire download link quickly.\"\n },\n {\n type: \"ButtonRow\",\n items: [\n {\n type: \"Button\",\n label: \"Back to Results\",\n variant: \"filled\",\n onClick: async function() {\n await updatePanelWithToggleState(false);\n }\n }\n ]\n }\n ]\n });\n \n console.log(\"CSV download link created:\", filename);\n \n } catch (error) {\n console.error(\"Error downloading CSV:\", error);\n \n // Show error and return to previous state\n await safeUpdatePanel({\n title: \"Download Error\",\n body: [\n {\n type: \"Text\",\n content: \"An error occurred while preparing the CSV download: \" + error.message + \"\\n\\nPlease try again.\"\n },\n {\n type: \"Button\",\n label: \"Back to Results\",\n variant: \"filled\",\n onClick: async function() {\n await updatePanelWithToggleState(false);\n }\n }\n ]\n });\n }\n}\n\n// Function to convert feature array to CSV string\nfunction convertFeaturesToCSV(features) {\n if (features.length === 0) {\n return \"\";\n }\n \n // Get all unique property keys from all features\n const allKeys = new Set();\n features.forEach(function(feature) {\n if (feature.properties) {\n Object.keys(feature.properties).forEach(function(key) {\n allKeys.add(key);\n });\n }\n });\n \n // Convert to sorted array for consistent column order\n const headers = Array.from(allKeys).sort();\n \n // Add feature ID as first column\n const csvHeaders = [\"feature_id\"].concat(headers);\n \n // Create CSV content\n let csvContent = csvHeaders.join(\",\") + \"\\n\";\n \n features.forEach(function(feature) {\n const row = [];\n \n // Add feature ID\n row.push(escapeCSVValue(feature.id));\n \n // Add property values in header order\n headers.forEach(function(header) {\n const value = feature.properties && feature.properties[header] !== undefined \n ? feature.properties[header] \n : \"\";\n row.push(escapeCSVValue(value));\n });\n \n csvContent += row.join(\",\") + \"\\n\";\n });\n \n return csvContent;\n}\n\n// Function to properly escape CSV values\nfunction escapeCSVValue(value) {\n if (value === null || value === undefined) {\n return \"\";\n }\n \n // Convert to string\n const stringValue = String(value);\n \n // If the value contains comma, quote, or newline, wrap in quotes and escape internal quotes\n if (stringValue.includes(\",\") || stringValue.includes('\"') || stringValue.includes(\"\\n\")) {\n return '\"' + stringValue.replace(/\"/g, '\"\"') + '\"';\n }\n \n return stringValue;\n}\n\n// Create action trigger for analysis options\nasync function setupAnalysisTool() {\n try {\n console.log(\"Setting up analysis tool...\");\n \n const result = await felt.createActionTrigger({\n actionTrigger: {\n label: \"Performance insights\",\n onTrigger: async function() {\n console.log(\">>> ACTION TRIGGER CLICKED! Starting analysis...\");\n \n try {\n // Clear any existing filters and elements\n console.log(\"Clearing previous state...\");\n await clearAllFiltersAndElements();\n \n // Ensure previous panel is properly closed\n safeDeletePanel();\n \n console.log(\"Creating new panel...\");\n const panelCreated = await createNewPanel();\n if (!panelCreated) {\n console.error(\"Could not create panel for analysis\");\n return;\n }\n \n console.log(\"Panel created successfully, getting states...\");\n \n // Use aggregation to get unique states faster\n const stateCategories = await felt.getCategoryData({\n layerId: STORES_LAYER_ID,\n attribute: \"store_state\"\n });\n \n console.log(\"Found\", stateCategories.length, \"unique states via aggregation\");\n \n // Convert to sorted options\n const stateOptions = stateCategories\n .map(function(category) {\n return { label: category.key, value: category.key };\n })\n .sort(function(a, b) {\n return a.label.localeCompare(b.label);\n });\n \n console.log(\"Creating panel with\", stateOptions.length, \"state options\");\n \n const panelUpdated = await safeUpdatePanel({\n title: \"Store Performance Analysis\",\n body: [\n {\n type: \"Text\",\n content: \"**Choose your analysis method:**\\n\\nFilter by state, draw a custom area, or combine both methods.\"\n },\n {\n type: \"Select\",\n label: \"Filter by State\",\n placeholder: \"Type or select a state to analyze...\",\n search: true,\n options: stateOptions,\n onChange: async function(args) {\n console.log(\"State selected:\", args.value);\n await analyzeByState(args.value);\n }\n },\n {\n type: \"Divider\"\n },\n {\n type: \"Select\",\n label: \"Draw Analysis Area\",\n placeholder: \"Choose drawing mode...\",\n options: [\n { label: \"Circle\", value: \"circle\" },\n { label: \"Polygon\", value: \"polygon\" }\n ],\n onChange: function(args) {\n console.log(\"Starting drawing mode:\", args.value);\n startDrawingMode(args.value);\n }\n }\n ]\n });\n \n if (panelUpdated) {\n console.log(\">>> Panel created and updated successfully!\");\n } else {\n console.error(\">>> Panel creation succeeded but update failed\");\n }\n \n } catch (triggerError) {\n console.error(\">>> ERROR in onTrigger function:\", triggerError);\n }\n },\n onCreate: function(args) {\n console.log(\"Action trigger created with ID:\", args.id);\n },\n onDestroy: function(args) {\n console.log(\"Action trigger destroyed with ID:\", args.id);\n }\n }\n });\n \n console.log(\">>> Action trigger setup completed successfully:\", result);\n return result;\n \n } catch (error) {\n console.error(\">>> ERROR setting up analysis tool:\", error);\n throw error;\n }\n}\n\n// Function to start new analysis\nasync function startNewAnalysis() {\n console.log(\"Starting new analysis - closing previous panel\");\n try {\n // Clear everything first\n await clearAllFiltersAndElements();\n \n // Ensure previous panel is properly closed\n safeDeletePanel();\n \n const panelCreated = await createNewPanel();\n if (!panelCreated) {\n console.error(\"Could not create panel for new analysis\");\n return;\n }\n \n // Use fast aggregation instead of iterating through features\n console.log(\"Getting states via aggregation...\");\n const stateCategories = await felt.getCategoryData({\n layerId: STORES_LAYER_ID,\n attribute: \"store_state\"\n });\n \n console.log(\"Found states via aggregation:\", stateCategories.length);\n \n // Convert to sorted options\n const stateOptions = stateCategories\n .map(function(category) {\n return { label: category.key, value: category.key };\n })\n .sort(function(a, b) {\n return a.label.localeCompare(b.label);\n });\n \n const success = await safeUpdatePanel({\n title: \"Store Performance Analysis\",\n body: [\n {\n type: \"Text\",\n content: \"**Choose your analysis method:**\\n\\nFilter by state, draw a custom area, or combine both methods.\"\n },\n {\n type: \"Divider\"\n },\n {\n type: \"Select\",\n label: \"Filter by State\",\n placeholder: \"Type or select a state to analyze...\",\n search: true,\n options: stateOptions,\n onChange: async function(args) {\n console.log(\"State selected:\", args.value);\n await analyzeByState(args.value);\n }\n },\n \n {\n type: \"Select\",\n label: \"Draw Analysis Area\",\n placeholder: \"Choose drawing mode...\",\n options: [\n { label: \"Circle\", value: \"circle\" },\n { label: \"Polygon\", value: \"polygon\" }\n ],\n onChange: function(args) {\n console.log(\"Starting drawing mode:\", args.value);\n startDrawingMode(args.value);\n }\n }\n ]\n });\n \n if (success) {\n console.log(\"New analysis panel created successfully\");\n } else {\n console.error(\"Failed to create new analysis panel\");\n }\n \n } catch (error) {\n console.error(\"Error starting new analysis:\", error);\n }\n}\n\n// Function to clear all filters and elements\nasync function clearAllFiltersAndElements() {\n try {\n console.log(\"Clearing all filters and elements...\");\n \n // Clear layer filters\n await felt.setLayerFilters({\n layerId: STORES_LAYER_ID,\n filters: null\n });\n \n await felt.setLayerFilters({\n layerId: COMPETITOR_LAYER_ID,\n filters: null\n });\n \n // Clear spatial boundaries\n await felt.setLayerBoundary({\n layerIds: [STORES_LAYER_ID, COMPETITOR_LAYER_ID],\n boundary: null\n });\n \n // Clear highlights and drawn elements\n await clearHighlights();\n if (currentDrawnElement) {\n await felt.deleteElement(currentDrawnElement.id);\n currentDrawnElement = null;\n }\n \n // Reset state\n currentStateFilter = null;\n currentTopIndex = -1;\n currentBottomIndex = -1;\n currentStoreData = null;\n \n console.log(\"All filters and elements cleared successfully\");\n \n } catch (error) {\n console.error(\"Error clearing filters and elements:\", error);\n }\n}\n\n// Function to calculate bounding box from features with very generous padding for full state view\nfunction calculateFullStateBoundingBox(features) {\n if (features.length === 0) return null;\n \n let minLng = Infinity;\n let maxLng = -Infinity;\n let minLat = Infinity;\n let maxLat = -Infinity;\n \n features.forEach(function(feature) {\n if (feature.bbox && feature.bbox.length >= 4) {\n // Use existing bbox if available\n minLng = Math.min(minLng, feature.bbox[0]);\n minLat = Math.min(minLat, feature.bbox[1]);\n maxLng = Math.max(maxLng, feature.bbox[2]);\n maxLat = Math.max(maxLat, feature.bbox[3]);\n }\n });\n \n if (minLng === Infinity) return null;\n \n // Add very generous padding (50% of the range on each side) to show full state context\n const lngRange = maxLng - minLng;\n const latRange = maxLat - minLat;\n const lngPadding = lngRange * 0.5;\n const latPadding = latRange * 0.5;\n \n return [\n minLng - lngPadding, // west\n minLat - latPadding, // south\n maxLng + lngPadding, // east\n maxLat + latPadding // north\n ];\n}\n\n// Function to calculate bounding box from geometry\nfunction calculateGeometryBoundingBox(geometry) {\n if (!geometry || !geometry.coordinates) return null;\n \n let minLng = Infinity;\n let maxLng = -Infinity;\n let minLat = Infinity;\n let maxLat = -Infinity;\n \n function processCoordinates(coords) {\n if (typeof coords[0] === 'number') {\n // Single coordinate pair\n minLng = Math.min(minLng, coords[0]);\n maxLng = Math.max(maxLng, coords[0]);\n minLat = Math.min(minLat, coords[1]);\n maxLat = Math.max(maxLat, coords[1]);\n } else {\n // Array of coordinates\n coords.forEach(processCoordinates);\n }\n }\n \n if (geometry.type === \"Circle\") {\n // For circles, calculate bounding box from center and radius\n const center = geometry.center || [0, 0];\n const radius = geometry.radius || 1000;\n const radiusDegrees = radius / 111320; // approximate meters per degree at equator\n \n return [\n center[0] - radiusDegrees, // west\n center[1] - radiusDegrees, // south\n center[0] + radiusDegrees, // east\n center[1] + radiusDegrees // north\n ];\n } else {\n // For polygons and other geometries\n processCoordinates(geometry.coordinates);\n \n if (minLng === Infinity) return null;\n \n // Add 20% padding\n const lngRange = maxLng - minLng;\n const latRange = maxLat - minLat;\n const lngPadding = lngRange * 0.2;\n const latPadding = latRange * 0.2;\n \n return [\n minLng - lngPadding, // west\n minLat - latPadding, // south\n maxLng + lngPadding, // east\n maxLat + latPadding // north\n ];\n }\n}\n\n// Function to get comprehensive territory data using aggregation\nasync function getTerritoriesWithAggregation(filters, boundary) {\n try {\n console.log(\"Getting territory data using aggregation...\");\n \n const params = {\n layerId: STORES_LAYER_ID,\n attribute: \"territory\"\n };\n \n // Only add filters and boundary if they exist and are not null\n if (filters) {\n params.filters = filters;\n }\n \n if (boundary) {\n params.boundary = boundary;\n }\n \n const territoryData = await felt.getCategoryData(params);\n \n console.log(\"Territory data from aggregation:\", territoryData);\n return territoryData;\n \n } catch (error) {\n console.error(\"Error getting territory data:\", error);\n return [];\n }\n}\n\n// Optimized function to analyze by state - much faster now\nasync function analyzeByState(selectedState) {\n try {\n console.log(\"Analyzing state:\", selectedState);\n \n // Clear existing drawn elements but keep state filter\n if (currentDrawnElement) {\n await felt.deleteElement(currentDrawnElement.id);\n currentDrawnElement = null;\n }\n \n await clearHighlights();\n \n // Reset indices\n currentTopIndex = -1;\n currentBottomIndex = -1;\n currentStateFilter = selectedState;\n \n // IMMEDIATE ACTIONS - Apply filters first for instant feedback\n console.log(\"Applying filters immediately...\");\n \n const stateFilter = [\"store_state\", \"eq\", selectedState];\n \n // Apply filters immediately (no await to prevent blocking)\n felt.setLayerFilters({\n layerId: STORES_LAYER_ID,\n filters: stateFilter\n });\n \n felt.setLayerFilters({\n layerId: COMPETITOR_LAYER_ID,\n filters: [\"addr_state\", \"eq\", selectedState]\n });\n \n // Show loading panel immediately\n safeUpdatePanel({\n title: \"State Analysis\",\n body: [\n {\n type: \"Text\",\n content: \"Analyzing store data for \" + selectedState + \"...\"\n }\n ]\n });\n \n // OPTIMIZED: Use parallel aggregation calls instead of fetching all features\n console.log(\"Running parallel aggregations...\");\n \n const [storeCountData, territoryData, revenueData, ordersData] = await Promise.all([\n // Get store count\n felt.getAggregates({\n layerId: STORES_LAYER_ID,\n aggregation: { methods: [\"count\"] },\n filters: stateFilter\n }),\n // Get territory distribution\n felt.getCategoryData({\n layerId: STORES_LAYER_ID,\n attribute: \"territory\",\n filters: stateFilter\n }),\n // Get total revenue\n felt.getAggregates({\n layerId: STORES_LAYER_ID,\n aggregation: { methods: [\"sum\"], attribute: \"total_revenue\" },\n filters: stateFilter\n }),\n // Get total orders\n felt.getAggregates({\n layerId: STORES_LAYER_ID,\n aggregation: { methods: [\"sum\"], attribute: \"orders\" },\n filters: stateFilter\n })\n ]);\n \n const actualStoreCount = storeCountData.count || 0;\n const totalRevenue = revenueData.sum || 0;\n const totalOrders = ordersData.sum || 0;\n \n console.log(\"Aggregations complete:\", {\n stores: actualStoreCount,\n revenue: totalRevenue,\n orders: totalOrders,\n territories: territoryData.length\n });\n \n // Get a small sample of stores for performance analysis only\n console.log(\"Getting sample stores for performance analysis...\");\n const sampleStores = await felt.getFeatures({\n layerId: STORES_LAYER_ID,\n filters: stateFilter\n });\n \n // Quick zoom using the sample (much faster than full dataset)\n if (sampleStores.features.length > 0) {\n const bounds = calculateFullStateBoundingBox(sampleStores.features);\n if (bounds) {\n console.log(\"Quick zoom to state view:\", bounds);\n felt.fitViewportToBounds({ bounds: bounds });\n }\n }\n \n // Process and display results with aggregated data\n await processAndDisplayAnalysisOptimized(\n sampleStores.features, \n selectedState, \n true, \n true, \n actualStoreCount,\n territoryData,\n totalRevenue,\n totalOrders\n );\n \n } catch (error) {\n console.error(\"Error analyzing by state:\", error);\n \n // Try to show error but don't fail if we can't\n await safeUpdatePanel({\n title: \"Error\",\n body: [\n {\n type: \"Text\",\n content: \"An error occurred during state analysis. Please try again.\"\n }\n ]\n });\n }\n}\n\n// Function to start drawing mode (circle or polygon)\nfunction startDrawingMode(drawingType) {\n felt.setTool(drawingType);\n \n // Update panel for drawing mode - if it fails, drawing still works\n safeUpdatePanel({\n title: \"Draw Analysis \" + (drawingType === \"circle\" ? \"Circle\" : \"Polygon\"),\n body: [\n {\n type: \"Text\",\n content: currentStateFilter \n ? \"**Draw a \" + drawingType + \"** to further filter within \" + currentStateFilter + \".\\n\\nThe \" + drawingType + \" will be combined with the current state filter.\"\n : \"**Draw a \" + drawingType + \" on the map** to analyze store data within that area.\"\n },\n {\n type: \"Divider\"\n },\n {\n type: \"Button\",\n label: \"Cancel Drawing\",\n variant: \"outlined\",\n onClick: function() {\n felt.setTool(null);\n if (currentStateFilter) {\n // Return to state analysis if we have a state filter\n analyzeByState(currentStateFilter);\n } else {\n // Return to main options\n startNewAnalysis();\n }\n }\n }\n ]\n });\n}\n\n// Function to clear existing highlights\nasync function clearHighlights() {\n if (highlightedElements.length > 0) {\n for (const elementId of highlightedElements) {\n try {\n await felt.deleteElement(elementId);\n } catch (error) {\n console.log(\"Element already deleted:\", elementId);\n }\n }\n highlightedElements = [];\n }\n}\n\n// Function to create place highlight and show popup with context preservation\nasync function highlightAndShowPopup(store, performanceType) {\n try {\n // Clear previous highlights\n await clearHighlights();\n \n // Get the GeoJSON feature to get coordinates\n const geoJsonFeature = await felt.getGeoJsonFeature({\n id: store.id,\n layerId: STORES_LAYER_ID\n });\n \n if (geoJsonFeature && geoJsonFeature.geometry) {\n let coords;\n if (geoJsonFeature.geometry.type === \"Point\") {\n coords = geoJsonFeature.geometry.coordinates;\n } else if (geoJsonFeature.geometry.type === \"MultiPoint\") {\n coords = geoJsonFeature.geometry.coordinates[0];\n }\n \n if (coords && coords.length >= 2) {\n // Different colors for top vs bottom performers, same star icon\n const isTopPerformer = performanceType === \"top\";\n const color = isTopPerformer ? \"#FFDC60\" : \"#DC143C\"; // Gold for top, light lavender for bottom (better contrast with black icons)\n \n // Create a place element with star symbol and appropriate color\n const highlightElement = await felt.createElement({\n type: \"Place\",\n coordinates: coords,\n color: color,\n symbol: \"star\",\n frame: \"frame-square\",\n name: \"$\" + store.calculatedRevenue.toLocaleString(),\n description: \"Revenue: $\" + store.calculatedRevenue.toLocaleString() + \"\\nCategory: \" + (store.properties.product_category || \"Unknown\") + \"\\nSales Rep: \" + (store.properties.sales_rep || \"Unknown\"),\n interaction: \"locked\"\n });\n \n highlightedElements.push(highlightElement.id);\n \n if (currentDrawnElement) {\n // For drawn areas: only pan to center the store, maintain current zoom level\n felt.setViewport({\n center: {\n latitude: coords[1],\n longitude: coords[0]\n }\n // No zoom parameter - keeps current zoom level to maintain drawn area context\n });\n console.log(\"Store highlighted and map panned to store location (maintaining drawn area zoom)\");\n } else {\n // For state-only analysis: use state-scale zoom level\n felt.setViewport({\n center: {\n latitude: coords[1],\n longitude: coords[0]\n },\n zoom: 8\n });\n console.log(\"Store highlighted and map panned with state-scale zoom\");\n }\n \n // Select feature and show popup (without additional viewport fitting)\n await felt.selectFeature({\n id: store.id,\n layerId: STORES_LAYER_ID,\n showPopup: true,\n fitViewport: false\n });\n }\n }\n } catch (error) {\n console.error(\"Error highlighting store:\", error);\n }\n}\n\n// Optimized function to process and display analysis results\nasync function processAndDisplayAnalysisOptimized(storeFeatures, filterDescription, isStateFilter, allowDrawing, actualStoreCount, territoryData, totalRevenue, totalOrders) {\n // Store the current store data\n currentStoreData = {\n features: storeFeatures,\n filterDescription: filterDescription,\n isStateFilter: isStateFilter,\n allowDrawing: allowDrawing,\n actualStoreCount: actualStoreCount\n };\n \n // Process store data for performance analysis (only the sample)\n const storesWithRevenue = storeFeatures.map(function(record) {\n const revenue = parseFloat(record.properties.total_revenue) || 0;\n const orders = parseFloat(record.properties.orders) || 0;\n \n return {\n ...record,\n calculatedRevenue: revenue,\n calculatedOrders: orders\n };\n }).sort(function(a, b) {\n return b.calculatedRevenue - a.calculatedRevenue;\n });\n \n // Get top and bottom performers without overlap\n let topStores = [];\n let bottomStores = [];\n \n if (storesWithRevenue.length > 0) {\n if (storesWithRevenue.length <= 10) {\n // If 10 or fewer stores, split them evenly\n const halfCount = Math.ceil(storesWithRevenue.length / 2);\n topStores = storesWithRevenue.slice(0, halfCount);\n bottomStores = storesWithRevenue.slice(-Math.floor(storesWithRevenue.length / 2)).reverse();\n } else {\n // If more than 10 stores, take top 5 and bottom 5\n topStores = storesWithRevenue.slice(0, 5);\n bottomStores = storesWithRevenue.slice(-5).reverse();\n }\n }\n \n // Convert aggregated territory data to objects for compatibility\n const territories = {};\n \n if (territoryData && territoryData.length > 0) {\n territoryData.forEach(function(territory) {\n territories[territory.key] = territory.value || 0;\n });\n }\n \n // Store data for navigation - using aggregated totals\n currentStoreData.topStores = topStores;\n currentStoreData.bottomStores = bottomStores;\n currentStoreData.totalRevenue = totalRevenue;\n currentStoreData.totalOrders = totalOrders;\n currentStoreData.storeCount = actualStoreCount;\n currentStoreData.territories = territories;\n \n // Build the complete panel initially with stores exploration disabled\n await updatePanelWithToggleState(false);\n}\n\n// Keep the original processAndDisplayAnalysis for spatial analysis\nasync function processAndDisplayAnalysis(storeFeatures, filterDescription, isStateFilter, allowDrawing, actualStoreCount, territoryData) {\n // Use the actual store count if provided, otherwise use the sample size\n const storeCount = actualStoreCount !== undefined ? actualStoreCount : storeFeatures.length;\n \n // Store the current store data\n currentStoreData = {\n features: storeFeatures,\n filterDescription: filterDescription,\n isStateFilter: isStateFilter,\n allowDrawing: allowDrawing,\n actualStoreCount: storeCount\n };\n \n // Calculate store statistics from the sample for revenue/orders totals\n let totalRevenue = 0;\n let totalOrders = 0;\n \n // Process store data and sort by revenue\n const storesWithRevenue = storeFeatures.map(function(record) {\n const revenue = parseFloat(record.properties.total_revenue) || 0;\n const orders = parseFloat(record.properties.orders) || 0;\n \n totalRevenue += revenue;\n totalOrders += orders;\n \n return {\n ...record,\n calculatedRevenue: revenue,\n calculatedOrders: orders\n };\n }).sort(function(a, b) {\n return b.calculatedRevenue - a.calculatedRevenue;\n });\n \n // Get top and bottom performers without overlap\n let topStores = [];\n let bottomStores = [];\n \n if (storesWithRevenue.length > 0) {\n if (storesWithRevenue.length <= 10) {\n // If 10 or fewer stores, split them evenly\n const halfCount = Math.ceil(storesWithRevenue.length / 2);\n topStores = storesWithRevenue.slice(0, halfCount);\n bottomStores = storesWithRevenue.slice(-Math.floor(storesWithRevenue.length / 2)).reverse();\n } else {\n // If more than 10 stores, take top 5 and bottom 5\n topStores = storesWithRevenue.slice(0, 5);\n bottomStores = storesWithRevenue.slice(-5).reverse();\n }\n }\n \n // Convert aggregated territory data to objects for compatibility\n const territories = {};\n \n if (territoryData && territoryData.length > 0) {\n territoryData.forEach(function(territory) {\n territories[territory.key] = territory.value || 0;\n });\n }\n \n // Store data for navigation\n currentStoreData.topStores = topStores;\n currentStoreData.bottomStores = bottomStores;\n currentStoreData.totalRevenue = totalRevenue;\n currentStoreData.totalOrders = totalOrders;\n currentStoreData.storeCount = storeCount;\n currentStoreData.territories = territories;\n \n // Build the complete panel initially with stores exploration disabled\n await updatePanelWithToggleState(false);\n}\n\n// Function to navigate stores without rebuilding entire panel - with drawn area context preservation\nasync function navigateStore(isTop, direction) {\n const stores = isTop ? currentStoreData.topStores : currentStoreData.bottomStores;\n const performanceType = isTop ? \"top\" : \"bottom\";\n \n if (isTop) {\n if (direction === \"next\") {\n currentTopIndex = (currentTopIndex + 1) % stores.length;\n } else {\n currentTopIndex = (currentTopIndex - 1 + stores.length) % stores.length;\n }\n await highlightAndShowPopup(stores[currentTopIndex], performanceType);\n } else {\n if (direction === \"next\") {\n currentBottomIndex = (currentBottomIndex + 1) % stores.length;\n } else {\n currentBottomIndex = (currentBottomIndex - 1 + stores.length) % stores.length;\n }\n await highlightAndShowPopup(stores[currentBottomIndex], performanceType);\n }\n \n // Update the panel to reflect the new selection\n await updatePanelWithToggleState(true);\n}\n\n// Function to update panel based on toggle state\nasync function updatePanelWithToggleState(exploreStoresEnabled) {\n if (!currentStoreData) return;\n \n const {\n filterDescription,\n allowDrawing,\n storeCount,\n totalRevenue,\n totalOrders,\n territories,\n topStores,\n bottomStores\n } = currentStoreData;\n \n // Build navigation function for stores\n function buildStoreTable(stores, isTop, storeType) {\n if (stores.length === 0) return [];\n \n const currentIndex = isTop ? currentTopIndex : currentBottomIndex;\n const store = stores[currentIndex];\n \n if (!store) return [];\n \n const revenue = store.calculatedRevenue;\n const category = store.properties.product_category || \"Unknown\";\n const salesRep = store.properties.sales_rep || \"Unknown\";\n const city = store.properties.store_city || \"Unknown\";\n const state = store.properties.store_state || \"Unknown\";\n \n const content = [];\n \n // Store type header\n content.push({\n type: \"Text\",\n content: \"**\" + storeType + \" Performers**\"\n });\n \n // Store details in table format\n content.push({\n type: \"Text\",\n content: \"| Metric | Value |\\n|--------|--------|\\n| **Revenue** | $\" + revenue.toLocaleString() + \" |\\n| **Category** | \" + category + \" |\\n| **Sales Rep** | \" + salesRep + \" |\\n| **Location** | \" + city + \", \" + state + \" |\"\n });\n \n // Navigation with store counter between buttons\n if (stores.length > 1) {\n content.push({\n type: \"ButtonRow\",\n items: [\n {\n type: \"Button\",\n label: \"← Previous\",\n variant: \"outlined\",\n onClick: async function() {\n await navigateStore(isTop, \"previous\");\n }\n },\n {\n type: \"Button\",\n label: (currentIndex + 1) + \" / \" + stores.length,\n variant: \"transparent\",\n disabled: true,\n onClick: function() {\n // No action - this is just for display\n }\n },\n {\n type: \"Button\",\n label: \"Next →\",\n variant: \"outlined\",\n onClick: async function() {\n await navigateStore(isTop, \"next\");\n }\n }\n ]\n });\n } else {\n // For single store, just show the number\n content.push({\n type: \"Text\",\n content: \"**1 / 1**\"\n });\n }\n \n return content;\n }\n \n // Build panel content with professional filter display\n const newPanelContent = [];\n \n // Create professional filter description with safe access\n const safeFilterDescription = filterDescription || \"Unknown\";\n \n if (safeFilterDescription.includes(\"Circle:\") && safeFilterDescription.includes(\"within\")) {\n // Handle circle with state filter\n const parts = safeFilterDescription.split(\" within \");\n newPanelContent.push({\n type: \"Text\",\n content: \"### Analysis Scope\\n**Geographic Area:** \" + parts[0] + \" \\n**State Filter:** \" + parts[1]\n });\n } else if (safeFilterDescription.includes(\"Custom polygon area\") && safeFilterDescription.includes(\"within\")) {\n // Handle polygon with state filter\n const parts = safeFilterDescription.split(\" within \");\n newPanelContent.push({\n type: \"Text\",\n content: \"### Analysis Scope\\n**Geographic Area:** \" + parts[0] + \" \\n**State Filter:** \" + parts[1]\n });\n } else if (safeFilterDescription.includes(\"Circle:\")) {\n // Handle circle only\n newPanelContent.push({\n type: \"Text\",\n content: \"### Analysis Scope\\n**Geographic Filter:** \" + safeFilterDescription\n });\n } else if (safeFilterDescription.includes(\"Custom polygon area\")) {\n // Handle polygon only\n newPanelContent.push({\n type: \"Text\",\n content: \"### Analysis Scope\\n**Geographic Filter:** \" + safeFilterDescription\n });\n } else {\n // Handle state only or other filters\n newPanelContent.push({\n type: \"Text\",\n content: \"### Analysis Scope\\n**State:** \" + safeFilterDescription\n });\n }\n \n // Add drawing options if we're in state mode\n if (allowDrawing) {\n newPanelContent.push({\n type: \"Select\",\n label: \"Draw to Further Filter\",\n placeholder: \"Choose drawing mode...\",\n options: [\n { label: \"Circle\", value: \"circle\" },\n { label: \"Polygon\", value: \"polygon\" }\n ],\n onChange: function(args) {\n startDrawingMode(args.value);\n }\n });\n }\n \n newPanelContent.push({\n type: \"Divider\"\n });\n newPanelContent.push({\n type: \"Text\",\n content: \"### Summary Statistics\\n\\n| Metric | Value |\\n|--------|-------|\\n| Store Count | \" + storeCount + \" |\\n| Total Revenue | $\" + totalRevenue.toLocaleString() + \" |\\n| Total Orders | \" + totalOrders.toLocaleString() + \" |\"\n });\n \n // Add territory distribution table - only if we have data and multiple territories\n if (storeCount > 0 && Object.keys(territories).length > 1) {\n newPanelContent.push({\n type: \"Divider\"\n });\n \n let territoryTable = \"### Territory Distribution\\n\\n| Territory | Stores | Share |\\n|----------|--------|-------|\\n\";\n const sortedTerritories = Object.entries(territories)\n .sort(function(a, b) { return b[1] - a[1]; });\n \n // Calculate total for percentage calculation\n const totalTerritoryStores = Object.values(territories).reduce(function(sum, count) { return sum + count; }, 0);\n \n sortedTerritories.forEach(function(territory) {\n const percentage = totalTerritoryStores > 0 ? ((territory[1] / totalTerritoryStores) * 100).toFixed(1) : \"0.0\";\n territoryTable += \"| \" + territory[0] + \" | \" + territory[1] + \" | \" + percentage + \"% |\\n\";\n });\n \n newPanelContent.push({\n type: \"Text\",\n content: territoryTable\n });\n }\n \n // Add Performance section\n newPanelContent.push({\n type: \"Divider\"\n });\n \n newPanelContent.push({\n type: \"Text\",\n content: \"### Performance\"\n });\n \n // Single toggle for exploring stores\n newPanelContent.push({\n type: \"ToggleGroup\",\n options: [{ label: \"Explore stores\", value: \"explore\" }],\n value: exploreStoresEnabled ? [\"explore\"] : [],\n onChange: async function(args) {\n const newExploreEnabled = args.value.includes(\"explore\");\n \n if (newExploreEnabled) {\n // Initialize both indices when enabling exploration\n if (topStores.length > 0) {\n currentTopIndex = 0;\n }\n if (bottomStores.length > 0) {\n currentBottomIndex = 0;\n }\n \n // Highlight the first top performer if available, otherwise the first bottom performer\n if (topStores.length > 0) {\n await highlightAndShowPopup(topStores[0], \"top\");\n } else if (bottomStores.length > 0) {\n await highlightAndShowPopup(bottomStores[0], \"bottom\");\n }\n \n // Update panel to show the store details AFTER highlighting\n setTimeout(async function() {\n await updatePanelWithToggleState(true);\n }, 100);\n \n return; // Prevent the recursive call at the end\n } else {\n // Clear highlights when disabling exploration\n currentTopIndex = -1;\n currentBottomIndex = -1;\n await clearHighlights();\n }\n \n await updatePanelWithToggleState(newExploreEnabled);\n }\n });\n \n // Show both performance tables when exploration is enabled\n if (exploreStoresEnabled && (topStores.length > 0 || bottomStores.length > 0)) {\n const gridItems = [];\n \n // Add top performers table if we have top stores and valid index\n if (topStores.length > 0 && currentTopIndex >= 0 && currentTopIndex < topStores.length) {\n gridItems.push(...buildStoreTable(topStores, true, \"Top\"));\n }\n \n // Add bottom performers table if we have bottom stores and valid index\n if (bottomStores.length > 0 && currentBottomIndex >= 0 && currentBottomIndex < bottomStores.length) {\n // Add divider between tables if we have both\n if (gridItems.length > 0) {\n gridItems.push({ type: \"Divider\" });\n }\n gridItems.push(...buildStoreTable(bottomStores, false, \"Bottom\"));\n }\n \n if (gridItems.length > 0) {\n newPanelContent.push({\n type: \"Grid\",\n grid: \"1fr\",\n items: gridItems\n });\n }\n }\n \n // Control buttons\n newPanelContent.push({\n type: \"Divider\"\n });\n \n // Add CSV download button with improved user experience\n newPanelContent.push({\n type: \"ButtonRow\",\n items: [\n {\n type: \"Button\",\n label: \"Download CSV\",\n variant: \"outlined\",\n onClick: async function() {\n await downloadFilteredDataAsCSV();\n }\n },\n {\n type: \"Button\",\n label: \"New Analysis\",\n variant: \"filled\",\n tint: \"primary\",\n onClick: async function() {\n await startNewAnalysis();\n }\n }\n ]\n });\n \n // Update panel with error handling - don't fail if panel update fails\n const success = await safeUpdatePanel({\n title: \"Store Performance Analysis Results\",\n body: newPanelContent\n });\n \n if (!success) {\n console.log(\"Panel update failed but analysis continues\");\n }\n}\n\n// Listen for element creation (both circles and polygons) - with enhanced error handling and proper spatial filtering\nfelt.onElementCreateEnd({\n handler: async function(params) {\n console.log(\">>> Element creation event fired with params:\", params);\n \n try {\n // Validate params structure\n if (!params) {\n console.error(\"No params received in element creation handler\");\n return;\n }\n \n if (!params.element) {\n console.error(\"No element in params:\", params);\n return;\n }\n \n const element = params.element;\n console.log(\"Element received:\", element);\n \n // Validate element structure\n if (!element.type) {\n console.error(\"Element has no type property:\", element);\n return;\n }\n \n if (element.type !== \"Circle\" && element.type !== \"Polygon\") {\n console.log(\"Ignoring element of type:\", element.type);\n return;\n }\n \n if (isAnalyzing) {\n console.log(\"Already analyzing, ignoring new element creation\");\n return;\n }\n \n console.log(element.type + \" created for analysis\");\n isAnalyzing = true;\n \n // Update the element properties with blue color\n let updatedElement;\n if (element.type === \"Circle\") {\n updatedElement = await felt.updateElement({\n id: element.id,\n type: \"Circle\",\n fillOpacity: 0,\n strokeOpacity: 0.8,\n color: \"#F04574\",\n strokeWidth: 2,\n radiusMarker: false,\n name: \"\",\n interaction: \"locked\"\n });\n } else { // Polygon\n updatedElement = await felt.updateElement({\n id: element.id,\n type: \"Polygon\",\n fillOpacity: 0,\n strokeOpacity: 0.8,\n strokeWidth: 2,\n color: \"#F04574\",\n areaMarker: false,\n name: \"\",\n interaction: \"locked\"\n });\n }\n \n currentDrawnElement = updatedElement;\n currentTopIndex = -1;\n currentBottomIndex = -1;\n \n await clearHighlights();\n \n // Get element geometry for spatial filtering\n console.log(\"Getting element geometry for spatial filtering...\");\n const elementGeometry = await felt.getElementGeometry(element.id);\n console.log(\"Element geometry retrieved:\", elementGeometry);\n \n // Calculate bounding box and zoom to the drawn area\n const elementBounds = calculateGeometryBoundingBox(elementGeometry);\n if (elementBounds) {\n console.log(\"Zooming to drawn element area:\", elementBounds);\n felt.fitViewportToBounds({\n bounds: elementBounds\n });\n }\n \n // Show loading - continue even if panel update fails\n await safeUpdatePanel({\n title: element.type + \" Analysis\",\n body: [\n {\n type: \"Text\",\n content: currentStateFilter \n ? \"Applying \" + element.type.toLowerCase() + \" filter within \" + currentStateFilter + \"...\"\n : \"Applying spatial filters and analyzing store data...\"\n }\n ]\n });\n \n // Apply spatial boundaries for visualization\n console.log(\"Applying spatial boundaries to layers...\");\n await felt.setLayerBoundary({\n layerIds: [STORES_LAYER_ID, COMPETITOR_LAYER_ID],\n boundary: elementGeometry\n });\n \n // Get filters for data retrieval\n let filters = null;\n if (currentStateFilter) {\n filters = [\"store_state\", \"eq\", currentStateFilter];\n }\n \n // Get filtered data with spatial boundary - THIS IS THE KEY FIX\n console.log(\"Getting spatially filtered store data...\");\n console.log(\"Filters:\", filters);\n console.log(\"Boundary:\", elementGeometry);\n \n const storeData = await felt.getFeatures({\n layerId: STORES_LAYER_ID,\n filters: filters,\n boundary: elementGeometry\n });\n \n console.log(\"Spatially filtered stores retrieved:\", storeData.count, \"stores found\");\n console.log(\"Store data:\", storeData);\n \n // Get comprehensive territory data for the drawn area\n const territoryData = await getTerritoriesWithAggregation(filters, elementGeometry);\n \n // Build description based on active filters\n let filterDescription;\n if (element.type === \"Circle\") {\n const radiusMiles = (element.radius / 1609.34).toFixed(2);\n filterDescription = \"Circle: \" + radiusMiles + \" mile radius\";\n } else {\n filterDescription = \"Custom polygon area\";\n }\n \n if (currentStateFilter) {\n filterDescription += \" within \" + currentStateFilter;\n }\n \n // Process and display results\n await processAndDisplayAnalysis(\n storeData.features, \n filterDescription, \n false,\n false, // No additional drawing when already have a drawn element\n storeData.count, // Use the actual count from the response\n territoryData\n );\n \n // Turn off drawing tool\n felt.setTool(null);\n \n } catch (error) {\n console.error(\"Error during element analysis:\", error);\n console.error(\"Error stack:\", error.stack);\n } finally {\n isAnalyzing = false;\n }\n }\n});\n\n// Initialize the extension with proper error handling and detailed logging\nasync function initializeExtension() {\n try {\n console.log(\">>> STARTING EXTENSION INITIALIZATION <<<\");\n \n // First, let's test basic SDK connectivity\n console.log(\"Testing basic SDK connectivity...\");\n const mapDetails = await felt.getMapDetails();\n console.log(\"Map details retrieved successfully:\", mapDetails);\n \n // Now set up the analysis tool\n console.log(\"Setting up analysis tool...\");\n await setupAnalysisTool();\n \n console.log(\">>> EXTENSION INITIALIZED SUCCESSFULLY <<<\");\n console.log(\"The action button should now be visible in the left sidebar.\");\n \n } catch (error) {\n console.error(\">>> FAILED TO INITIALIZE EXTENSION <<<\", error);\n \n // Retry after a short delay\n setTimeout(async function() {\n try {\n console.log(\">>> RETRYING EXTENSION INITIALIZATION <<<\");\n await setupAnalysisTool();\n console.log(\">>> EXTENSION INITIALIZED SUCCESSFULLY ON RETRY <<<\");\n } catch (retryError) {\n console.error(\">>> FAILED TO INITIALIZE EXTENSION ON RETRY <<<\", retryError);\n }\n }, 2000);\n }\n}\n\n// Start initialization immediately\ninitializeExtension();\n\nconsole.log(\">>> EXTENSION SCRIPT LOADED - Store Performance Analysis with popup fix <<<\");","insertedAt":"2025-10-09T21:34:58","zOrder":0}],"mapbox_api_token":"pk.eyJ1IjoiZmVsdG1hcHMiLCJhIjoiY20wZndoenl1MTFrYzJxb2czemdpNGFvZCJ9.y7NzVOMNQNTC487xOYTk7Q","socketTokenTTLSeconds":1209600,"defaultCoordinates":{"lat":37.807,"lng":-122.271},"actions":[],"featureFlags":{"ff_extensions_use_next_sdk":false,"ff_new_source_inspection":true,"felt_is_up":true,"ff_copy_paste_styles":false,"ff_create_layers":false,"ff_editable_layers":true,"ff_growth_enterprise_trials":true,"ff_isr":false,"ff_mobile":true,"ff_mobile_alpha":false,"ff_more_components":false,"ff_photos_in_comments":false,"ff_server_side_filtering":true,"ff_use_our_tiles":false,"ff_wherobots":false},"onboardingNeeded":[],"isAdminView":false,"mapFolderId":null,"satelliteMode":false,"mapImages":[],"updatedAtUnixMs":1760045758000,"loadedAt":1764853988,"shareUrl":"https://felt.com/map/Store-performance-analysis-Y9AdtJQWLQhm3YkhP9BIo3IB","authors":{"2d8f0d35-09e0-4bed-9323-bc2695c2272e":{"name":"Mamata Akella"}},"workspaceBilling":{"plan_limits":{"processing_bytes":50000000000,"map_views":100000,"api_calls":20000,"storage_bytes":250000000000,"editor_limit":25,"member_limit":250},"limits_enforced":true,"next_monthly_cycle_start":1767225600000,"plan_data_usage":{"data_processing":64938760,"data_storage":194294170379},"featureGroups":{"enterprise_geocoding":false,"dashboards":true,"cloud_sources":true,"app_development":true,"raster_infrastructure":true},"inGracePeriod":false},"createMapParams":null,"mapId":"63d76d25-058b-4219-b762-484ff88a3721","isTrainingKarta":false,"selectedBackgroundId":null,"individualPermissions":[],"teams":[],"flashMessages":[],"customViewport":null,"mapDescription":null,"customIcons":[],"current_user":{},"checkerboardTiles":false,"defaultControlsMode":null,"accessControl2":{"view":{"canDownloadLayers":false,"canDuplicate":true},"edit":{"canChangePublicSharingSettings":false,"canEditDuplicateSetting":false,"canInviteUsers":false,"canMove":false},"comments":{"canAttachPhotosToComments":false,"canCreateThread":false,"canDeleteAnyComment":false,"canDeleteOwnComment":false,"canEditOwnComment":false,"canExportComments":false,"canReplyToThread":false,"canResolveAnyComment":false,"canResolveOwnComment":false,"canTrackReadStatus":false,"canViewAllThreads":false},"teams":{"canAddSources":false,"canDeletePublishedLayer":false,"canPublishLayer":false},"kartaTeamMemberIds":[]},"urls":{"api":"/api","settings":"/users/settings","dashboard":"/maps","marketing":"/","newMap":"/map/new","newWorkspace":"/join","placeholderImage":"/images/placeholder-39c4259c68cef774b08a1a06e6626c95.png?vsn=d","catchup":"/map/Store-performance-analysis-Y9AdtJQWLQhm3YkhP9BIo3IB/catchup","canonicalPath":"/map/Store-performance-analysis-Y9AdtJQWLQhm3YkhP9BIo3IB","spritesBaseUrl":"https://app-assets.felt.com/sprites","spritesManifest":{"/sprites/msdf.json":"/sprites/msdf-0ed695ece9e09531d79ea4daa8255599.json","/sprites/msdf.png":"/sprites/msdf-235245f7cf195ff0eb39cf7a7b8fa6e9.png","/sprites/msdf@2x.json":"/sprites/msdf@2x-1e78f109cb6438c7e87da1f75344a7b7.json","/sprites/msdf@2x.png":"/sprites/msdf@2x-73817a584887afef4c1e0188642592ab.png","/sprites/sprite.json":"/sprites/sprite-964569eef729aa4773a354b47cee5d86.json","/sprites/sprite.png":"/sprites/sprite-819511448f6f2499deff86603ff78215.png","/sprites/sprite@2x.json":"/sprites/sprite@2x-24e211e07ce576bf48740474ed7d6547.json","/sprites/sprite@2x.png":"/sprites/sprite@2x-87cb20c28d14435142f4e32339e093dd.png"},"workspaceUsage":null},"currentTeamBannerAcknowledgement":{"plan":null,"plan_name":null,"acknowledged":true},"showBasemapLabels":true,"kartaChannel":"anonymous_map:63d76d25-058b-4219-b762-484ff88a3721","validMapImageMimeTypes":["application/pdf","application/postscript","application/x-photoshop","image/apng","image/avif","image/bmp","image/heic","image/jp2","image/jpeg","image/jpm","image/jpx","image/jxr","image/pict","image/pjpeg","image/png","image/svg+xml","image/vnd.adobe.photoshop","image/vnd.microsoft.icon","image/webp","image/x-icns","image/x-icon","image/x-pict"],"allowedFeatures":{"sources":true,"filters":true,"extensions":true,"h3":true,"embeds":true,"sdk":true,"widgets":true,"transformations":true,"mapActions":true,"customIcons":true,"bucketSources":true,"iframePopups":true,"stacSource":true,"streamCogs":true,"liveLayers":true,"createLayerFromColumns":true,"advancedExports":true,"basicExports":true,"basicPipelineExports":true,"commentsAttachedData":true,"commentsAttachedMedia":true,"editableLayers":true,"embedActionCustomization":true,"embedTokens":true,"enterprisePipelineExports":true,"sameDomainJoin":true,"streamVectorTiles":true,"updateEnterpriseSettings":true,"uploadLayer":true,"viewerExportData":true,"workspaceLibrary":true,"enterpriseExports":true},"elements":[],"layerAttrConstraints":{"maxNameLength":300},"folderTree":[],"mapColorPalette":[],"mapUrls":{"export":"https://felt.com/map/export/63d76d25-058b-4219-b762-484ff88a3721","embed":"https://felt.com/embed/map/Store-performance-analysis-Y9AdtJQWLQhm3YkhP9BIo3IB","login":"/login/map/63d76d25-058b-4219-b762-484ff88a3721","appHomeForKarta":"/maps/contains/63d76d25-058b-4219-b762-484ff88a3721","dataTroubleshooting":"https://help.felt.com/upload-anything/troubleshooting","exportComments":"https://felt.com/map/export/63d76d25-058b-4219-b762-484ff88a3721/comments","signup":"/signup/map/63d76d25-058b-4219-b762-484ff88a3721","supportedFormats":"https://help.felt.com/upload-anything/files"},"selectedDefaultBackgroundMode":"dark","settings":{"path":"/map/Store-performance-analysis-Y9AdtJQWLQhm3YkhP9BIo3IB","url":"https://felt.com/map/Store-performance-analysis-Y9AdtJQWLQhm3YkhP9BIo3IB","mapDescription":null,"mapTitle":"Store performance analysis","viewers":{"allowExport":false,"dataTable":true,"defaultTableLayerId":null,"duplicateMap":true,"seeMapPresence":false}}}