---
name: match_pois_to_interests
kind: function
lang: kt
domain: core
version: "1.0.0"
purity: pure
signature: "fun matchPoisToInterests(pois: List<Map<String, Any>>, interests: List<Interest>, maxResults: Int = 10): List<ScoredPOI>"
description: "Filtra y rankea una lista de POIs segun un perfil de intereses del usuario. Calcula un score por categoria (0.5), nombre (0.3) y tags (0.2), multiplicado por el weight del interes. Retorna los top maxResults POIs con score > 0, enriquecidos con score y matchedInterests."
tags: [poi, scoring, ranking, geolocation, interests, voice-guide, osm]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "kotlin/functions/core/match_pois_to_interests.kt"
params:
  - name: pois
    desc: "Lista de POIs parseados de JSON. Cada mapa contiene: id (Long), lat (Double), lon (Double), name (String), category (String), tags (Map<String,String>)."
  - name: interests
    desc: "Perfil de intereses del usuario. Cada Interest tiene name, keywords (lista de terminos de busqueda) y weight (multiplicador de score, default 1.0)."
  - name: maxResults
    desc: "Numero maximo de POIs a retornar, ordenados por score descendente. Default 10."
output: "Lista de ScoredPOI con score > 0, ordenados por score descendente, hasta maxResults elementos. Lista vacia si ningun POI coincide con algun interes."
---

## Ejemplo

```kotlin
val interests = listOf(
    Interest(name = "gastronomia", keywords = listOf("restaurant", "cafe", "bar"), weight = 1.5),
    Interest(name = "cultura", keywords = listOf("museum", "historic", "monument"), weight = 1.0)
)

val pois = listOf(
    mapOf(
        "id" to 1L,
        "lat" to 40.416775,
        "lon" to -3.703790,
        "name" to "Museo del Prado",
        "category" to "museum",
        "tags" to mapOf("tourism" to "museum", "name" to "Museo Nacional del Prado")
    ),
    mapOf(
        "id" to 2L,
        "lat" to 40.415,
        "lon" to -3.700,
        "name" to "Cafe Central",
        "category" to "cafe",
        "tags" to mapOf("amenity" to "cafe")
    )
)

val results = matchPoisToInterests(pois, interests, maxResults = 5)
// results[0].name == "Museo del Prado", score == 1.0  (categoria 0.5 + tags 0.2 = 0.7 × 1.0, name "museo" no matchea "museum" como substring de "museo del prado"... wait: "museum" no esta en "museo del prado")
// results[0].matchedInterests == ["cultura"]
// results[1].name == "Cafe Central", score == 1.2  (categoria 0.5 × 1.5)
```

## Algoritmo de scoring

Para cada POI, por cada interés:

1. **Category match** (+0.5): `poi.category.lowercase() == keyword` (comparación exacta)
2. **Name match** (+0.3): `keyword in poi.name.lowercase()` (substring, case-insensitive)
3. **Tags match** (+0.2): `keyword in tagValues.lowercase()` donde `tagValues = tags.values.joinToString(" ")`

El subtotal del interés se multiplica por `interest.weight` y se acumula en `totalScore`.

Solo se incluye el interés en `matchedInterests` si su `interestScore > 0`.

## Notas

- Puerto exacto de `match_pois_to_interests_py_core` (Python) al stack Kotlin.
- Los POIs se reciben como `List<Map<String, Any>>` porque provienen de JSON parseado (sin schema fijo).
- El id se casteá con fallback `Int -> Long` para compatibilidad con parsers JSON que usan Int por defecto.
- El score se redondea a 6 decimales para paridad con `round(score, 6)` de Python.
- Funcion pura: no hace I/O, no muta los inputs, determinista.
- Los tipos `Interest` y `ScoredPOI` se definen en el mismo archivo para mantener el paquete `core` autocontenido.
