# Data Dictionary — Apartments (Buy & Rent)
# Diccionario de Datos — Pisos (Venta y Alquiler)

> **EN** — This document describes every column of the two CSV exports produced by the HouseFlipper data pipeline: `apartments_buy_*.csv` (sale listings) and `apartments_rent_*.csv` (rental listings). Both files share the **exact same 65-column schema**.
>
> **ES** — Este documento describe cada columna de los dos ficheros CSV generados por el pipeline de datos de HouseFlipper: `apartments_buy_*.csv` (anuncios de venta) y `apartments_rent_*.csv` (anuncios de alquiler). Ambos ficheros comparten **exactamente el mismo esquema de 65 columnas**.

## Overview / Resumen

| | |
|---|---|
| **Files / Ficheros** | `apartments_buy_202606251439.csv`, `apartments_rent_202606251443.csv` |
| **Rows / Filas (approx.)** | ≈ 119,675 (buy / venta), ≈ 126,018 (rent / alquiler) |
| **Columns / Columnas** | 65 (identical schema / esquema idéntico) |
| **Source / Origen** | Idealista listings extracted from ClickHouse, enriched with OpenStreetMap POIs and scored by a CatBoost model / Anuncios de Idealista extraídos de ClickHouse, enriquecidos con POIs de OpenStreetMap y valorados por un modelo CatBoost |
| **Encoding** | UTF-8, comma-separated, quoted text fields / UTF-8, separado por comas, campos de texto entrecomillados |

**Key difference between files / Diferencia clave entre ficheros:**
- **EN** — In `apartments_buy`, `price` and all `price_*` columns are the **sale price in euros (€)**. In `apartments_rent`, they are the **monthly rent in euros per month (€/month)**. Every other column has identical meaning across both files.
- **ES** — En `apartments_buy`, `price` y todas las columnas `price_*` son el **precio de venta en euros (€)**. En `apartments_rent`, son la **renta mensual en euros al mes (€/mes)**. El resto de columnas tienen idéntico significado en ambos ficheros.

---

## 1. Identification & Listing / Identificación y Anuncio

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `url` | string | — | Listing URL. Primary key (one row per property). | URL del anuncio. Clave primaria (una fila por inmueble). |
| `website` | string | — | Source listing domain (always `idealista.com`). | Dominio del portal de origen (siempre `idealista.com`). |
| `title` | string | — | Listing headline/title. | Título del anuncio. |
| `description` | string | — | Full free-text property description. Rows with NULL descriptions are filtered out upstream. | Descripción completa del inmueble en texto libre. Las filas con descripción NULL se filtran aguas arriba. |

## 2. Price & Listing History / Precio e Histórico del Anuncio

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `price` | integer | € (buy) / €/month (rent) | Current listed price (sale) or monthly rent. Filtered to a valid range (apartments buy: €50k–€900k; rent: €400–€6,000/mo). | Precio actual del anuncio (venta) o renta mensual (alquiler). Filtrado a un rango válido (venta: 50.000–900.000 €; alquiler: 400–6.000 €/mes). |
| `last_date` | datetime | ISO 8601 | Date the property was last seen in the data (`max(insert_date)`). | Fecha en que el inmueble se vio por última vez en los datos (`max(insert_date)`). |
| `price_change_date` | datetime | ISO 8601 | Date of the most recent price change; falls back to `first_date` if no change. | Fecha del último cambio de precio; usa `first_date` si no hubo cambios. |
| `first_date` | datetime | ISO 8601 | Date the property first appeared in the data (`min(insert_date)`). | Fecha en que el inmueble apareció por primera vez (`min(insert_date)`). |
| `days_active` | integer | days / días | Total days on market, from `first_date` to `last_date`. | Días totales en el mercado, de `first_date` a `last_date`. |
| `days_active_last_price` | integer | days / días | Days on market at the current price (from `price_change_date` to `last_date`). | Días en el mercado al precio actual (de `price_change_date` a `last_date`). |
| `n_price_change` | integer | count / recuento | Number of distinct price changes recorded for this listing. | Número de cambios de precio distintos registrados para este anuncio. |
| `n_records` | integer | count / recuento | Number of historical snapshots captured for this listing. | Número de instantáneas históricas capturadas para este anuncio. |
| `price_down_from` | float | € | Original price before the most recent reduction; NULL if never reduced. Signals price-drop opportunities. | Precio original antes de la última rebaja; NULL si no se rebajó nunca. Señala oportunidades por bajada de precio. |

## 3. Location & Address / Localización y Dirección

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `latitude` | float | decimal degrees / grados decimales | WGS84 latitude. | Latitud WGS84. |
| `longitude` | float | decimal degrees / grados decimales | WGS84 longitude. | Longitud WGS84. |
| `approximate_location` | boolean | true/false | Whether coordinates are approximate (vs. precise street-level). | Indica si las coordenadas son aproximadas (frente a nivel de calle exacto). |
| `state` | string | — | Autonomous community (e.g. "Comunidad de Madrid"). | Comunidad autónoma (p. ej. "Comunidad de Madrid"). |
| `province` | string | — | Province (e.g. "Madrid"). Used to filter by location. | Provincia (p. ej. "Madrid"). Se usa para filtrar por localización. |
| `city` | string | — | Municipality (e.g. "Leganés", "Madrid"). | Municipio (p. ej. "Leganés", "Madrid"). |
| `postcode` | string | — | Postal code. | Código postal. |
| `district` | string | — | City district / barrio. | Distrito / barrio de la ciudad. |
| `quarter` | string | — | Quarter / sub-district. | Zona / subdistrito. |
| `street` | string | — | Street name. | Nombre de la calle. |
| `number` | string | — | Street number (may be empty). | Número de la calle (puede estar vacío). |
| `location` | string (category) | — | Geographic location key from the project registry (e.g. `madrid`, `sevilla`). Used as a categorical model feature and DB namespacing key. **Distinct from `city`/`province`.** | Clave de localización del registro del proyecto (p. ej. `madrid`, `sevilla`). Se usa como variable categórica del modelo y clave de partición en BD. **Distinta de `city`/`province`.** |

## 4. Physical Attributes / Características Físicas

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `area` | integer | m² | Usable floor area. Filtered to 25–150 m² for apartments. | Superficie útil. Filtrada a 25–150 m² para pisos. |
| `floor` | integer (nullable) | floor number / nº de planta | Floor: 0 = ground, -1 = basement, 1+ = upper floors. Apartments with floor > 3 **and** no elevator are filtered out. | Planta: 0 = bajo, -1 = sótano, 1+ = plantas superiores. Los pisos con planta > 3 **y** sin ascensor se descartan. |
| `n_rooms` | integer (nullable) | count / recuento | Number of bedrooms. Filtered to 0–5 for apartments. | Número de dormitorios. Filtrado a 0–5 para pisos. |
| `n_baths` | integer (nullable) | count / recuento | Number of bathrooms. Filtered to 1–3 for apartments. | Número de baños. Filtrado a 1–3 para pisos. |
| `is_exterior` | boolean | true/false | Exterior-facing (vs. interior courtyard). | Exterior (frente a interior / patio). |
| `has_elevator` | boolean | true/false | Building has an elevator. | El edificio tiene ascensor. |
| `has_terrace` | boolean | true/false | Has a terrace/balcony. | Tiene terraza/balcón. |
| `has_swimming_pool` | boolean | true/false | Has a swimming pool. | Tiene piscina. |
| `has_parking` | boolean | true/false | Has parking. | Tiene plaza de garaje. |
| `has_garden` | boolean | true/false | Has a garden. | Tiene jardín. |
| `property_state` | string | — | Condition declared in the listing attributes (e.g. "Buen estado"); may be "unknown". | Estado declarado en los atributos del anuncio (p. ej. "Buen estado"); puede ser "unknown". |
| `energy_certificate` | string | — | Energy rating (A–G) or consumption rating; "unknown" if absent. | Calificación energética (A–G) o de consumo; "unknown" si no consta. |

## 5. Text-Derived Features / Variables Derivadas del Texto

> **EN** — Extracted by regex from the title + description (`text_features.py`).
> **ES** — Extraídas mediante expresiones regulares del título + descripción (`text_features.py`).

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `has_ac` | boolean | true/false | Mentions air conditioning / climate control ("aire acondicionado", "climatización", "split"…). | Menciona aire acondicionado / climatización ("aire acondicionado", "climatización", "split"…). |
| `is_bank_property` | boolean | true/false | Bank-owned/REO asset (mentions servicers: Haya, Solvia, Sareb… or "activo bancario"). | Inmueble de banco/REO (menciona servicers: Haya, Solvia, Sareb… o "activo bancario"). |
| `is_luxury` | boolean | true/false | Marketed as high-end ("lujo", "alto standing", "señorial", "domótica"…). | Anunciado como alta gama ("lujo", "alto standing", "señorial", "domótica"…). |
| `neighborhood_risk` | boolean | true/false | In a known distressed neighborhood (hardcoded list, e.g. Polígono Sur, Entrevías…). | En un barrio conflictivo conocido (lista fija, p. ej. Polígono Sur, Entrevías…). |
| `renovation_state` | string (category) | — | Inferred renovation condition: `recently_renovated`, `renovated`, `good_condition`, `needs_partial_renovation`, `needs_full_renovation`, or `unknown`. | Estado de reforma inferido: `recently_renovated`, `renovated`, `good_condition`, `needs_partial_renovation`, `needs_full_renovation` o `unknown`. |
| `seller_type` | string (category) | — | Seller type: `bank`, `particular`, `agency`, or `unknown`. | Tipo de vendedor: `bank`, `particular`, `agency` o `unknown`. |
| `heating_type` | string (category) | — | Heating system: `radiant_floor`, `heat_pump`, `central`, `individual`, `gas`, `electric`, or `unknown`. | Sistema de calefacción: `radiant_floor`, `heat_pump`, `central`, `individual`, `gas`, `electric` o `unknown`. |
| `has_storage_desc` | boolean | true/false | Mentions a storage room ("trastero"). | Menciona trastero. |
| `has_wardrobes` | boolean | true/false | Mentions built-in wardrobes ("armarios empotrados"). | Menciona armarios empotrados. |
| `has_portero` | boolean | true/false | Mentions a doorman/concierge ("portero", "conserje"). | Menciona portero / conserje. |
| `desc_len` | integer | characters / caracteres | Length of the description in characters. | Longitud de la descripción en caracteres. |

## 6. Points-of-Interest Enrichment / Enriquecimiento con Puntos de Interés

> **EN** — Counts of OpenStreetMap POIs within **500 m** of the property, computed via a BallTree spatial search on a metric projection (default EPSG:32630, UTM 30N). `dist_to_center` is the distance to the location's center point.
> **ES** — Recuento de POIs de OpenStreetMap a menos de **500 m** del inmueble, calculado con una búsqueda espacial BallTree sobre una proyección métrica (por defecto EPSG:32630, UTM 30N). `dist_to_center` es la distancia al punto central de la localización.

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `railway_station_count` | integer | count / recuento | Railway/metro stations within 500 m. | Estaciones de tren/metro a menos de 500 m. |
| `bus_stop_count` | integer | count / recuento | Bus stops within 500 m. | Paradas de autobús a menos de 500 m. |
| `supermarket_count` | integer | count / recuento | Supermarkets within 500 m. | Supermercados a menos de 500 m. |
| `restaurant_count` | integer | count / recuento | Restaurants within 500 m. | Restaurantes a menos de 500 m. |
| `school_count` | integer | count / recuento | Schools within 500 m. | Colegios a menos de 500 m. |
| `pharmacy_count` | integer | count / recuento | Pharmacies within 500 m. | Farmacias a menos de 500 m. |
| `kindergarten_count` | integer | count / recuento | Kindergartens within 500 m. | Guarderías a menos de 500 m. |
| `bank_count` | integer | count / recuento | Banks within 500 m. | Bancos a menos de 500 m. |
| `dist_to_center` | float | meters / metros | Distance to the location's center point (Madrid → Puerta del Sol; Sevilla → Plaza Nueva). NULL if the location defines no center. | Distancia al punto central de la localización (Madrid → Puerta del Sol; Sevilla → Plaza Nueva). NULL si la localización no define centro. |

## 7. Engineered Features / Variables de Ingeniería

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `days_from_global_start` | integer | days / días | Days from a global anchor date (**2024-01-15**) to `price_change_date`. A stable time axis comparable across all locations. | Días desde una fecha ancla global (**15-01-2024**) hasta `price_change_date`. Eje temporal estable y comparable entre localizaciones. |
| `area_per_room` | float | m²/room (m²/hab.) | Floor area divided by number of rooms (0 rooms → NA). Space/livability proxy. | Superficie dividida entre el número de habitaciones (0 hab. → NA). Indicador de amplitud/habitabilidad. |
| `rooms_baths` | float | ratio | Bedrooms-to-bathrooms ratio (0 baths → NA). Apartment-specific. | Ratio dormitorios/baños (0 baños → NA). Específico de pisos. |
| `high_floor_no_elevator` | integer (0/1/2) | category / categoría | High-floor-without-elevator flag: 0 = has elevator or floor ≤ 2; 1 = floor 1–2 without elevator; 2 = floor > 2 without elevator. Desirability penalty. | Indicador de planta alta sin ascensor: 0 = con ascensor o planta ≤ 2; 1 = planta 1–2 sin ascensor; 2 = planta > 2 sin ascensor. Penalización de atractivo. |

## 8. Model Predictions / Predicciones del Modelo

> **EN** — Predicted price quantiles from a CatBoost multi-quantile regression, one model per `property_type × operation` trained across all locations. For **buy** files these are predicted **sale prices (€)**; for **rent** files, predicted **monthly rents (€/month)**. Values are sorted per row so quantiles are monotonically increasing. The gap between `price_q10` and `price_q90` represents the model's 80% uncertainty band.
> **ES** — Cuantiles de precio predichos por una regresión multicuantil CatBoost, un modelo por `property_type × operation` entrenado con todas las localizaciones. En los ficheros de **venta** son **precios de venta predichos (€)**; en los de **alquiler**, **rentas mensuales predichas (€/mes)**. Los valores se ordenan por fila para que los cuantiles sean monótonos crecientes. La diferencia entre `price_q10` y `price_q90` representa la banda de incertidumbre del 80% del modelo.

| Column / Columna | Type / Tipo | Units / Unidad | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
| `price_q10` | float | € / €·mes | 10th-percentile predicted price (low-end estimate; lower bound of the 80% band). | Precio predicho en el percentil 10 (estimación baja; límite inferior de la banda del 80%). |
| `price_q25` | float | € / €·mes | 25th-percentile predicted price. Conservative estimate used as the gate for undervaluation. | Precio predicho en el percentil 25. Estimación conservadora usada como umbral de infravaloración. |
| `price_model` | float | € / €·mes | 50th-percentile (median) predicted price. The central/headline estimate. | Precio predicho en el percentil 50 (mediana). Estimación central / de referencia. |
| `price_q75` | float | € / €·mes | 75th-percentile predicted price (optimistic estimate). | Precio predicho en el percentil 75 (estimación optimista). |
| `price_q90` | float | € / €·mes | 90th-percentile predicted price (high-end estimate; upper bound of the 80% band). | Precio predicho en el percentil 90 (estimación alta; límite superior de la banda del 80%). |

---

## Notes / Notas

- **EN** — Boolean columns are serialized as the strings `true` / `false`. Categorical text columns use the sentinel `unknown` when a value could not be determined. Numeric columns may contain empty/NaN values (the model handles them natively). Rows for very low-volume cities, and listings matching exclusion patterns (occupied, auction, old-rent), are filtered out during ETL.
- **ES** — Las columnas booleanas se serializan como las cadenas `true` / `false`. Las columnas categóricas de texto usan el centinela `unknown` cuando no se pudo determinar el valor. Las columnas numéricas pueden contener vacíos/NaN (el modelo los gestiona de forma nativa). Las filas de ciudades con muy pocos registros y los anuncios que coinciden con patrones de exclusión (okupas, subastas, renta antigua) se descartan durante el ETL.

*Generated 2026-06-25 from the HouseFlipper data pipeline source. / Generado el 25-06-2026 a partir del código del pipeline de datos de HouseFlipper.*
