Idiomas disponibles:

Zena.food: Usé Machine Learning para Descubrir las Joyas Ocultas de Génova

Existe un artículo de Lauren Leek llamado How Google Maps quietly allocates survival across London’s restaurants. Lo leí ocho meses después de mudarme a Génova y no pude soltarlo. La premisa es precisa y un poco incómoda: la forma en que Google Maps clasifica restaurantes no tiene casi nada que ver con la calidad de la comida. Tiene que ver con la prominencia algorítmica, una combinación de número de reseñas, velocidad de acumulación, presencia en la web y señales de interacción. Los locales que acumulan más reseñas aparecen más arriba. Mayor posición trae más visitantes. Más visitantes traen más reseñas. Al bucle no le importa si la comida es buena.

Terminé de leer y el pensamiento fue inmediato. Vivo en Génova, debería hacer esto para Génova. No porque me costara encontrar buenos restaurantes, y no porque nadie me lo hubiera sugerido. Solo porque seguía explorando una ciudad a la que me había mudado ocho meses antes, y esta era una forma interesante de entenderla mejor. La ciudad sería el dataset. La pregunta era si los datos confirmarían lo que había empezado a sospechar caminando por sus calles.

El resultado es zena.food, un mapa interactivo del panorama gastronómico de Génova que elimina el sesgo estructural y saca a la luz los lugares que el algoritmo está subestimando sistemáticamente. Y también en la dirección contraria.

Zena.Food Logo

Qué es Génova en realidad

Génova no es lo que la gente imagina cuando piensa en Italia. No es Florencia. No es Roma, gracias a Dios. Es una ciudad portuaria y obrera apilada verticalmente contra una ladera de una manera que no tiene ningún sentido geográfico obvio, con una red medieval de callejones, los Caruggi, que los turistas encuentran intimidante y los residentes recorren de memoria. Los callejones del casco antiguo, declarados Patrimonio de la Humanidad por la UNESCO, son tan estrechos que dos personas con bolsas de la compra no pueden cruzarse sin que una de ellas haga el baile de la acera. Las señales GPS rebotan en edificios medievales de seis pisos y te meten en callejones sin salida con la seguridad de alguien que nunca ha estado aquí. Esto no es una queja. Es la personalidad de la ciudad.

El turismo que recibe Génova es casi enteramente el de los cruceros. Los barcos llegan a Porto Antico. Los pasajeros tienen entre cuatro y seis horas. Caminan hasta el Acquario, que es el acuario más grande de Italia y merece genuinamente su reputación, comen en algún sitio dentro de distancia a pie del terminal, y vuelven al barco. Dejan reseñas. Las reseñas describen la experiencia de ser turista en Génova una tarde. No tienen casi nada que ver con la ciudad en la que realmente viven sus residentes. Esto genera un efecto predecible: el paseo marítimo y las zonas adyacentes a los focos turísticos acumulan reseñas en un volumen que no tiene nada que ver con la calidad de la comida y todo que ver con el tráfico de personas. Un restaurante en una zona elevada de la ciudad, accesible en funicular y frecuentado casi exclusivamente por gente que vive aquí, puede tener una décima parte de las reseñas de un local frente al mar. Las puntuaciones parecen comparables en el mapa. Están midiendo cosas completamente distintas.

La red de callejones antiguos empeora esto. Pequeñas trattorias llevan a veces tres generaciones operando sin página web, sin cuenta de Instagram, sin un perfil en Google Business que nadie gestione activamente. Acumulan reseñas como las cosas genuinamente buenas acumulan reconocimiento: despacio, de parte de gente que estaba buscando de verdad. El algoritmo lee esto como baja confianza. Es baja exposición, que es algo completamente distinto. Google Maps trata ambos casos como restaurantes con valoraciones. El contexto estructural es invisible para el algoritmo, y esa invisibilidad no es neutral.

El Tourist Index

Zena.food está construido para personas que viven en Génova, no para quienes pasan un día de crucero. Lo que me parece genuinamente molesto de los restaurantes para turistas no es que existan. Es que el algoritmo los promociona activamente a costa de todo lo demás, porque las señales que el algoritmo usa para medir calidad son las mismas señales que los locales con mucho turismo acumulan como efecto secundario estructural de su ubicación. Para corregir eso había que medirlo primero.

El Tourist Index (TI) es una puntuación compuesta de 0.0 a 1.0 que mide cuánto de la valoración de un local se explica probablemente por el tráfico turístico y no por la calidad local. Tres pilares.

S_i, proximidad espacial (peso 40%). Decaimiento por distancia desde cinco polos turísticos: el Acquario, Piazza De Ferrari, Via Garibaldi, Stazione Principe y Boccadasse. Fórmula: max(0, 1 - d_min / 500). A 50 metros del Acquario, S_i ronda 0.90. Los locales en zonas elevadas de la ciudad puntúan cerca de 0. Los cinco polos se eligieron mirando un mapa de Génova y preguntándose adónde van realmente los turistas de crucero. La respuesta no era complicada.

V_i, vocabulario semántico (peso 40%). Gemini 2.5 Flash lee el texto de las reseñas de cada local y puntúa cuán orientado al turismo está el lenguaje. Marcadores turísticos: “justo al lado del acuario”, reseñas en varios idiomas, referencias a cruceros, “perfecto para una parada rápida antes de embarcar.” Marcadores locales: términos en dialecto, referencias a platos genoveses específicos por su nombre real (pansoti col tocco, trofie al pesto, farinata, cima alla genovese), el vocabulario que usan los italianos cuando escriben para otros italianos y no para cualquiera que pueda estar leyendo. Un 4.5 de alguien que escribe “amazing focaccia!!!! 😍😍” y un 4.5 de alguien que escribe “posto buonissimo, prezzi onesti” y nada más contienen información distinta. Este pilar intenta codificar esa diferencia.

E_i, accesibilidad topográfica (peso 20%). Elevación de OpenTopoData a resolución SRTM de 30m, con una corrección geoidal de +46m específica para Liguria, porque los datos satelitales de elevación tienen un desplazamiento sistemático en esta región que dejaba técnicamente varios restaurantes genoveses bajo el agua sin ella. Normalizado de modo que el nivel del mar puntúa 1.0 y 50 metros de altitud puntúan 0.0. Por debajo de 50 metros, un turista puede caminar desde el paseo marítimo. Por encima, necesita saber adónde va.

El compuesto: TI = 0.40·S_i + 0.40·V_i + 0.20·E_i

Un pilar quedó fuera. El diseño inicial incluía el ratio lingüístico de las reseñas: la proporción de reseñas en italiano frente a otros idiomas. Parecía una señal turística limpia. El problema es que la Google Places API devuelve solo cinco reseñas seleccionadas algorítmicamente por local, y esa selección está sesgada hacia contenido de alta interacción, que se correlaciona con los turistas, lo que significa que para locales con mucho turismo la API ya estaba filtrando hacia reseñas en inglés antes de que yo las viera. Usar eso como señal turística era circular. Habría medido el sesgo de la propia API, no la composición real de visitantes del restaurante. Se eliminó.

Hidden Gems

El Tourist Index le dice al modelo cuánta exposición turística estructural tiene un local. El modelo usa eso, más todo lo que sabe sobre el local, para predecir qué valoración debería tener. Un Hidden Gem es lo que ocurre cuando la predicción está equivocada en una dirección específica: el local lo está haciendo notablemente mejor de lo que todo lo que juega en su contra sugeriría.

Piensa en lo que eso significa concretamente. Una trattoria en el fondo de la red de callejones tiene un Tourist Index bajo, pocas reseñas, ninguna presencia en la web, y está en una zona donde el techo algorítmico es bajo. El modelo analiza todo eso y predice una valoración modesta. Si la valoración real es sustancialmente más alta, la diferencia es el residuo. Los locales en el 15% superior de residuos son Hidden Gems. La desventaja estructural era real. El local la superó de todas formas. Esa diferencia es el proxy más limpio de calidad que puede producir este dataset.

En el mapa, las joyas brillan. El anillo animado alrededor de un marcador es la señal visual de que hay algo ahí que vale la pena investigar. Hacer clic abre la ficha del local con el desglose del Tourist Index por pilar y una explicación SHAP en lenguaje sencillo: qué factores influyeron más en la predicción del modelo y en qué dirección. “La elevación alta contribuyó positivamente a la clasificación como gema” significa que el modelo esperaba una valoración más baja por la visibilidad reducida de la ubicación, y el local superó esa expectativa. No es una garantía de una buena comida. Es una sugerencia bien razonada.

A Hidden Gem

Hay una categoría secundaria que merece mención: los slow accumulators. Son locales con una valoración alta en relación con su número de reseñas dentro de su grupo de cocina. Tienen menos reseñas que lugares similares en zonas similares, pero su valoración se mantiene o supera lo que se esperaría con ese volumen de reseñas. El modelo no sabe por qué tienen pocas reseñas. Solo nota que están rindiendo por encima de su peso estructural. Suelen ser lugares más nuevos, o lugares que los locales han encontrado en silencio antes de que la maquinaria de reseñas los haya alcanzado. La etiqueta existe porque una joya con 300 reseñas significa algo distinto a una joya con 18 reseñas y un 4.6. Las dos son interesantes. La segunda es más rara.

El modelo

El Tourist Index describe la exposición estructural. El modelo predice la valoración base estructural: dado todo lo que se puede saber sobre la ubicación, la prominencia, la cocina y la exposición de un local, ¿qué valoración esperaría el algoritmo?

Regresión XGBoost, entrenada sobre 935 locales genoveses con al menos 30 reseñas y en funcionamiento actualmente. La matriz de características incluye el log del número de reseñas (no el recuento bruto, porque la distribución tiene una cola derecha muy pronunciada y un puñado de locales del paseo marítimo tienen totales de reseñas de cinco cifras), el nivel de precio, la densidad de celdas hexagonales H3, la elevación, una bandera de casco antiguo, la distancia al nodo de transporte público más cercano, el Tourist Index, la categoría de cocina clasificada por Gemini, una bandera de cadena, la diversidad de cocinas por zona, y un tipo de zona derivado del clustering PCA de agregados H3. Ese clustering agrupa áreas por carácter compuesto: valoración media, volumen total de reseñas, concentración de cadenas, diversidad de cocinas, produciendo cuatro tipos. Una joya en una zona residencial ordinaria es una señal más fuerte que el mismo residuo en una zona turística del paseo marítimo, porque el techo estructural es más bajo en el primer caso.

Optuna ejecutó 100 pruebas para ajustar los hiperparámetros. RMSE de validación cruzada en 5 pliegues, estratificada por zona H3: 0.2809. El objetivo era 0.25. Las valoraciones de Google en Génova se agrupan estrechamente entre 3.8 y 4.8 para la mayoría de los locales en funcionamiento. Nadie que le da un 2.0 a un restaurante estaba en un crucero pasando una tarde agradable. La distribución está comprimida por diseño, lo cual explica por qué una desviación estándar de residuos de 0.28 sobre una escala de 5 puntos es manejable aunque no se alcanzó el objetivo. No quedó donde el plan decía, pero lo suficientemente cerca para ser útil.

El resultado es un residuo por local: valoración real menos la predicha. El 15% superior de residuos son joyas. El 15% inferior son trampas. Las cadenas están excluidas de la clasificación como joyas independientemente del residuo, porque el reconocimiento de marca infla la velocidad de acumulación de reseñas de formas no relacionadas con la calidad de la comida. Las 20 cadenas detectadas en Génova no sorprendieron a nadie.

Recuento final: 1.578 locales. 141 joyas, 140 trampas. 143 marcados como slow accumulators. El dataset completo cubre restaurantes, bares y cafés de toda la ciudad.

Cómo llevarse bien con Google

Hubo una cosa en la que invertí una cantidad desproporcionada de tiempo: asegurarme de que este proyecto no crea problemas con Google. Esa frase suena defensiva. No lo es. Es simplemente precisa. Construir un producto sobre una API requiere leer los Términos de Servicio, entender lo que realmente dicen, y estructurar la implementación técnica para cumplirlos. Hice las tres cosas, en parte porque lo prefiero así y en parte porque una carta de cese y desistimiento de Google terminaría el proyecto más rápido que un mal RMSE.

Los datos provienen de la Google Places API (New), una interfaz pública, documentada y explícitamente autorizada. El proyecto no hace scraping de Google Maps. No rastrea páginas. Cada petición pasa por la API oficial con una clave válida, con máscaras de campo para solicitar solo lo necesario, con seguimiento de costes para mantenerse dentro del presupuesto. Los Términos de Servicio de Google Maps Platform exigen que los datos en caché se actualicen en 30 días. El pipeline se ejecuta trimestralmente y gestiona esto. Los datos brutos de la API, incluyendo el texto de las reseñas, se eliminan tras el procesamiento. Lo que Zena.food almacena permanentemente es solo el resultado derivado: la valoración predicha, el residuo, la etiqueta de joya, la explicación SHAP. Son los propios cálculos del modelo, no el contenido de Google. El único dato bruto de la API almacenado indefinidamente es el place_id, el identificador único que Google permite expresamente archivar permanentemente, y lo único necesario para enlazar de vuelta a la página oficial de Google Maps del local, que hace cada ficha de local.

El frontend muestra la atribución “Powered by Google”. Enlaza a Google Maps para todos los detalles a nivel de local. El proyecto no aloja contenido de reseñas. No compite con Google Maps. Apunta hacia él. La documentación legal y de privacidad completa está en zena.food/legale para quien quiera los detalles.

En cuanto a la parte financiera: Gemini 2.5 Flash a través de AI Studio es gratuito con 1.500 peticiones al día, y una ejecución completa del pipeline de Génova cabe dentro de ese límite. Los datos de elevación y transporte son de dominio público y ODbL respectivamente. La Google Places API no es ninguna de esas cosas. Lo que gasté en ella es asunto mío, de Google Cloud y de un panel de facturación que he decidido dejar de abrir. Hay una razón por la que el pipeline se ejecuta trimestralmente.

El stack

El pipeline completo corre en Python, que no necesita justificación para nada que implique spatial joins, archivos Parquet y gradient boosting. El indexado espacial usa la rejilla hexagonal H3 de Uber, que divide el espacio más uniformemente que los cuadrados y ofrece cálculos de densidad consistentes tanto en un bloque de callejones denso como en una terraza escarpada donde tres trattorias comparten lo que una vez fue el jardín de alguien.

Gemini 2.5 Flash gestiona la clasificación de cocinas y la puntuación semántica de las reseñas. La elección fue en parte el nivel gratuito y en su mayor parte la salida JSON estructurada con validación Pydantic. Un pipeline que falla silenciosamente cuando un LLM devuelve prosa creativa en lugar de un esquema válido es un pipeline al que nunca volverás a confiar. Pydantic hace que los fallos sean ruidosos.

Go es mi lenguaje principal. FastAPI con Pydantic v2 es lo más cerca que llega Python a la disciplina de tipos de Go: esquemas explícitos, validación rápida, comportamiento predecible. La API carga el archivo Parquet puntuado al inicio con una caché LRU y filtra por petición. No hay base de datos en tiempo de consulta porque no hace falta. El dataset final es un único archivo. El pipeline lo produce, la API lo sirve, y esa es toda la arquitectura.

El frontend es React con deck.gl para el renderizado del mapa. Con 1.500 locales y anillos de brillo animados para las joyas, Leaflet se degrada. deck.gl corre sobre WebGL y no lo hace. MapLibre proporciona la capa de teselas base sin licencia de Google Maps JavaScript. Zustand gestiona el estado de los filtros. TanStack Query gestiona la obtención de datos. No hay Redux porque no hay ningún problema aquí que Redux resuelva y Zustand no resuelva en una décima parte de las líneas.

Page Filter

Qué salió

El sitio está en vivo en zena.food. El mapa muestra los 1.578 locales, filtrables por cocina, precio, zona, elevación y estado de joya. Hacer clic en un local abre un panel con el desglose del Tourist Index por pilar, una explicación SHAP de qué impulsó la predicción del modelo, y un enlace al local en Google Maps.

Las joyas se agrupan en dos sitios: la red de callejones antiguos y las zonas elevadas de la ciudad. Las trampas se agrupan en el paseo marítimo turístico. Ninguna de las dos cosas sorprende en retrospectiva. El hallazgo más interesante tiene que ver con las cocinas de minorías étnicas. Los locales clasificados por Gemini como Street Food e International aparecen en el nivel de joyas a una tasa superior a su cuota del dataset total. Las cocinas de minorías étnicas representan alrededor del 22% de todos los locales. Su tasa de joyas es notablemente más alta. Lauren Leek encontró el mismo patrón en Londres. La explicación parcial es la ubicación: estos locales tienden a agruparse en zonas con baja exposición turística, lo que les da bases estructurales más bajas que son más fáciles de superar. Pero después de controlar por ubicación y prominencia, todavía queda señal. Lo que representa es una pregunta abierta genuina, y una que me parece más interesante que el mapa en sí.

La etiqueta de slow accumulator produjo la validación más satisfactoria. Varios de los 143 locales marcados como emergentes eran sitios que los locales ya me habían mencionado. El modelo los encontró de forma independiente, usando solo el recuento de reseñas relativo a sus pares de cocina y la valoración actual. No sabe por qué esos locales tienen pocas reseñas. Solo nota que están rindiendo por encima de su peso estructural.

Zena.food image.

Zena.food resultó ser un excelente proyecto de fin de semana, en el sentido de que llevó considerablemente más que un fin de semana y me alegra mucho haberlo construido de todas formas. Es la mejor guía de restaurantes, bares y cafés que he encontrado para Génova, lo cual es un listón bajo dado que la competencia es Google Maps con sus sesgos conocidos. Pero el listón quedó superado, y mi mujer, que tiene opiniones firmes sobre las cenas románticas y una sana desconfianza hacia las trampas para turistas, ahora está genuinamente contenta de dejar que la app sugiera nuestra próxima salida.