Metadata-Version: 2.4
Name: iniamet
Version: 0.3.0
Summary: Unofficial Python library for accessing Chilean INIA agrometeorological station data
Author-email: René Sepúlveda <rsepulveda2016@udec.cl>
Maintainer-email: René Sepúlveda <rsepulveda2016@udec.cl>
License: MIT
Project-URL: Homepage, https://github.com/reneignacio/iniamet-library
Project-URL: Documentation, https://github.com/reneignacio/iniamet-library#readme
Project-URL: Repository, https://github.com/reneignacio/iniamet-library
Project-URL: Bug Tracker, https://github.com/reneignacio/iniamet-library/issues
Project-URL: Changelog, https://github.com/reneignacio/iniamet-library/blob/main/CHANGELOG.md
Project-URL: API Reference, https://agromet.inia.cl/api/v4
Keywords: inia,climate,weather,agrometeorological,chile,api,meteorology,unofficial
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Requires-Dist: pandas>=1.5.0
Requires-Dist: numpy>=1.23.0
Provides-Extra: viz
Requires-Dist: folium>=0.14.0; extra == "viz"
Requires-Dist: ipython>=7.0.0; extra == "viz"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: black>=22.0.0; extra == "dev"
Requires-Dist: flake8>=5.0.0; extra == "dev"
Requires-Dist: mypy>=0.990; extra == "dev"
Provides-Extra: all
Requires-Dist: folium>=0.14.0; extra == "all"
Requires-Dist: ipython>=7.0.0; extra == "all"
Dynamic: license-file

# INIAMET - Chilean INIA Agrometeorological Data Library

[![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![PyPI version](https://badge.fury.io/py/iniamet.svg)](https://pypi.org/project/iniamet/)

**Python library for accessing Chilean INIA (Instituto de Investigaciones Agropecuarias) agrometeorological station data.**

> ⚠️ **DISCLAIMER**: This is an **unofficial, community-developed library**. It is **NOT officially affiliated with, endorsed by, or maintained by INIA**. It accesses publicly available data from INIA's agrometeorological API.

Access data from 400+ weather stations across Chile. Download temperature, precipitation, humidity, wind, radiation and more as pandas DataFrames.

## Features

- Simple, high-level API — get data in a few lines of code
- Named constants (`VAR_TEMPERATURA_MEDIA`, `VAR_PRECIPITACION`, etc.) instead of magic numbers
- Built-in temporal aggregation: raw (15 min), hourly, daily, weekly, monthly
- Regional bulk download: all stations in a region in one call
- Smart station filtering: by region code, name, or number
- Global (shared) disk cache — reused across projects; concurrency-safe; downloads only missing date ranges
- Automatic chunking of long date ranges and robust error handling (never caches an API error as "no data")
- Quality control (QC) module to detect sensor errors and outliers
- Full type hints and pandas integration

## Installation

```bash
pip install iniamet
```

Optional visualization features (interactive maps):

```bash
pip install iniamet[viz]
```

## API Key

You need an API key from [https://agromet.inia.cl/api/v4/](https://agromet.inia.cl/api/v4/).

### Option 1: Config file (recommended)

```bash
python -m iniamet.config set-key YOUR-API-KEY
python -m iniamet.config show   # verify
```

### Option 2: Environment variable

```bash
# Linux/Mac
export INIA_API_KEY='your-api-key'

# Windows CMD
set INIA_API_KEY=your-api-key

# Windows PowerShell
$env:INIA_API_KEY='your-api-key'
```

### Option 3: Pass directly in code

```python
client = INIAClient(api_key='your-api-key')
```

## Quick Start

```python
from iniamet import INIAClient, VAR_TEMPERATURA_MEDIA, VAR_PRECIPITACION

client = INIAClient()

# List all stations
stations = client.get_stations()
print(f"Total stations: {len(stations)}")

# Filter by region (accepts code, name, or number)
nuble = client.get_stations(region="R16")   # or "Ñuble" or "16"
inia_only = client.get_stations(region="R16", station_type="INIA")

# See what variables a station has
variables = client.get_variables("INIA-47")
print(variables[['variable_id', 'nombre', 'unidad']])

# Download data (raw, every 15 min)
df = client.get_data(
    station="INIA-47",
    variable=VAR_TEMPERATURA_MEDIA,
    start_date="2024-09-01",
    end_date="2024-09-30"
)

# Daily aggregation (min/max/mean for temperature)
df_daily = client.get_data(
    station="INIA-47",
    variable=VAR_TEMPERATURA_MEDIA,
    start_date="2024-09-01",
    end_date="2024-09-30",
    aggregation="diario"   # or "daily" or "D"
)
# Returns columns: tiempo, valor, valor_min, valor_max, valor_media
```

### Backward compatibility

Both syntaxes work identically:

```python
# Old syntax (v0.1.x) — still works
data = client.get_data('INIA-47', 2002, '2024-01-01', '2024-01-31')

# New syntax (v0.2.0+) — recommended
from iniamet import VAR_TEMPERATURA_MEDIA
data = client.get_data('INIA-47', VAR_TEMPERATURA_MEDIA, '2024-01-01', '2024-01-31')
```

## Variable Constants

Import any of these from `iniamet`:

| Constant | ID | Variable | Unit |
|---|---|---|---|
| `VAR_PRECIPITACION` | 2001 | Precipitation | mm |
| `VAR_TEMPERATURA_MEDIA` | 2002 | Air temperature | °C |
| `VAR_HUMEDAD_RELATIVA` | 2007 | Relative humidity | % |
| `VAR_VIENTO_DIRECCION` | 2012 | Wind direction | ° |
| `VAR_VIENTO_VELOCIDAD_MEDIA` | 2013 | Wind speed (mean) | m/s |
| `VAR_VIENTO_VELOCIDAD_MAXIMA` | 2014 | Wind speed (max) | m/s |
| `VAR_RADIACION_MEDIA` | 2022 | Solar radiation | W/m² |
| `VAR_BATERIA_VOLTAJE` | 2024 | Battery voltage | V |
| `VAR_TEMPERATURA_SUELO_10CM` | 2027 | Soil temperature 10 cm | °C |
| `VAR_TEMPERATURA_SUPERFICIE` | 2077 | Surface temperature | °C |
| `VAR_PRESION_ATMOSFERICA` | 2125 | Atmospheric pressure | mbar |

List all variables programmatically:

```python
from iniamet import list_all_variables
print(list_all_variables()[['variable_id', 'nombre', 'unidad']])
```

## Temporal Aggregation

The `aggregation` parameter in `get_data()` accepts:

| Value | Result |
|---|---|
| `None` / `"raw"` / `"crudo"` | Raw data every 15 min (default) |
| `"horario"` / `"hourly"` / `"H"` | Hourly |
| `"diario"` / `"daily"` / `"D"` | Daily |
| `"semanal"` / `"weekly"` / `"W"` | Weekly |
| `"mensual"` / `"monthly"` / `"M"` | Monthly |

For temperature variables, daily (and coarser) aggregation returns `valor_min`, `valor_max`, and `valor_media` in addition to `valor`.
For precipitation, it returns the accumulated sum.

## Bulk Download

```python
from iniamet import INIAClient, VAR_TEMPERATURA_MEDIA, VAR_PRECIPITACION

client = INIAClient()

results = client.bulk_download(
    stations=["INIA-47", "INIA-139", "INIA-211"],
    variables=[VAR_TEMPERATURA_MEDIA, VAR_PRECIPITACION],
    start_date="2024-09-01",
    end_date="2024-09-30"
)

# Results keyed as "station_variable"
df_temp_47 = results["INIA-47_2002"]
```

## Regional Download

Download and consolidate data from all stations in a region:

```python
from iniamet import RegionalDownloader

rd = RegionalDownloader(region="R16")   # or "Ñuble" or "16"

df = rd.download_climate_data(
    start_date="2024-09-01",
    end_date="2024-09-30",
    variables=["temperature", "precipitation"],   # english names
    aggregation="daily"
)

rd.save_to_csv(df, "nuble_sept2024.csv")
```

Available variable names for `RegionalDownloader`: `temperature`, `precipitation`, `humidity`, `wind_speed`, `wind_speed_max`, `wind_direction`, `radiation`, `pressure`, `soil_temperature`, `surface_temperature`, `battery_voltage`.

## Quality Control

```python
from iniamet.qc import apply_quality_control, get_qc_report

# Apply all QC checks and return only clean data
clean = apply_quality_control(df, variable_name='temperatura')

# Or keep all rows with QC flags
df_flagged = apply_quality_control(df, 'temperatura', return_clean_only=False)
print(get_qc_report(df_flagged))
```

QC checks applied: physically impossible values, extreme values (WMO fixed-range test), stuck sensor (persistence test), sudden changes (temporal consistency test), and consecutive zeros.

Fine-grained control:

```python
from iniamet.qc import QualityControl

qc = QualityControl()
df = qc.detect_impossible_values(df, 'temperatura')
df = qc.detect_stuck_sensor(df, min_repeats=4)
df = qc.detect_sudden_changes(df, 'temperatura')
```

## Advanced Client Options

```python
client = INIAClient(
    api_key="...",          # API key (or set via env/config file)
    cache=True,             # enable disk cache (default: True)
    cache_dir=None,         # None = shared GLOBAL cache (default); or pass a path
    timeout=30,             # HTTP timeout in seconds (default: 30)
    min_request_interval=0.5,  # seconds between API calls (default: 0.5)
    cache_only=False,       # if True, never call API — only use cached data
    max_retries=3,          # retries on transient (5xx/timeout) failures
    api_version="v4"        # API version (default: v4); accepts "v2", "v3", 4, ...
)
```

### API version

The library targets **API v4** by default (v2 was retired). You can pin a
version if needed, or point at a custom base URL:

```python
INIAClient(api_version="v2")                          # force a specific version
INIAClient(base_url="https://agromet.inia.cl/api/v4") # full override
# or set the env var:  INIAMET_API_VERSION=v4
```

v4 responses include extra station fields — `provincia`, `region_codigo`,
`comuna_codigo`, `intervalo`, `ultima_lectura`, `red_de_estaciones`,
`propietario` — plus `unidad_simbolo` / `ultimo_dato` on variables. All the
historical columns are unchanged, so existing code keeps working.

### All variables of a station at once (v4)

`get_station_data` uses v4's wide `muestras_row` endpoint to fetch several (or
all) base variables in a single request — far fewer API calls:

```python
# tiempo + one column per base variable (v2002=temp, v2007=humidity, v2001=precip, ...)
wide = client.get_station_data("INIA-78", "2025-03-01", "2025-03-31")

# only some variables
wide = client.get_station_data("INIA-78", "2025-03-01", "2025-03-31",
                               variables=[2002, 2001])

# Context manager (auto-closes HTTP connection)
with INIAClient() as client:
    df = client.get_data("INIA-47", VAR_TEMPERATURA_MEDIA, "2024-01-01", "2024-01-31")

# Force refresh from API, ignoring cache
stations = client.get_stations(region="R16", force_update=True)
variables = client.get_variables("INIA-47", force_update=True)
```

### Global (shared) cache

By default (`cache_dir=None`) the cache lives in **one shared location per
machine**, so every project and notebook reuses the same downloads instead of
re-downloading into a `./iniamet_cache` in each working folder:

| OS | Default location |
|----|------------------|
| Windows | `%LOCALAPPDATA%\iniamet\cache` |
| macOS | `~/Library/Caches/iniamet` |
| Linux | `$XDG_CACHE_HOME/iniamet` or `~/.cache/iniamet` |

```python
from iniamet import default_cache_dir
print(default_cache_dir())            # see the resolved location
```

- Override the location with the `INIAMET_CACHE_DIR` environment variable, or by
  passing an explicit `cache_dir="./iniamet_cache"` (per-folder cache, old behavior).
- The cache is **concurrency-safe**: atomic writes + per-key locks make it safe to
  share across threads (`bulk_download(max_workers=4)`) and processes.
- It records exactly which date ranges were fetched (a coverage manifest), so
  incremental queries only download what's missing — **including internal gaps** —
  and the current year is always refreshed.
- Purge "no data" metadata (to re-check empty periods, keeping downloaded data):
  `client.clear_nodata_cache()`.

```python
# Check if a variable is available for a station
if client.validate_station_variable("INIA-47", VAR_TEMPERATURA_MEDIA):
    df = client.get_data("INIA-47", VAR_TEMPERATURA_MEDIA, "2024-01-01", "2024-01-31")
```

## Error handling

> **The INIA server returns errors with HTTP 200**, wrapped as
> `{"response": "<message>"}` (e.g. a bad key comes back as
> `{"response": "credencial de acceso no encontrada."}`), and during outages it
> may reply `200` with an **empty body**. The library classifies these into
> **typed exceptions** instead of silently returning an empty list — so a bad
> key or a server outage can never masquerade as "no data".

```python
from iniamet import (
    INIAClient, IniametError, AuthenticationError,
    InvalidRequestError, UpstreamEmptyResponseError, UpstreamUnavailableError,
)

client = INIAClient()
try:
    df = client.get_data("INIA-78", 2002, "2025-03-01", "2025-03-31")
except AuthenticationError:
    ...   # API key missing/invalid
except UpstreamEmptyResponseError:
    ...   # server replied 200 with an empty body (outage) — NOT "no data"
except UpstreamUnavailableError:
    ...   # 5xx / timeout / network
except IniametError:
    ...   # any other library error
```

| Exception | When |
|---|---|
| `AuthenticationError` | API key missing, invalid, or not recognized |
| `InvalidRequestError` | bad parameters (e.g. `fechas incorrectas`), 4xx |
| `UpstreamEmptyResponseError` | HTTP 200 with an **empty body** (server outage) |
| `UpstreamUnavailableError` | 5xx, timeout, network error, invalid JSON |
| `INIAAPIError` | base class of all API errors (catches all of the above) |
| `IniametError` | base class of every library error |

An **empty DataFrame is now reserved for one meaning only**: "the query
succeeded and there are no samples in that range." Any failure raises.

### Health check

One call to tell you whether the library is usable right now — and if not, why:

```python
client.health()
# {'ok': True, 'api_reachable': True, 'key_valid': True,
#  'sample_rows': 480, 'api_version': 'v4', 'detail': 'OK — 480 estaciones...'}

# bad key:
# {'ok': False, 'api_reachable': True, 'key_valid': False, ...
#  'detail': 'API key inválida o ausente: credencial de acceso no encontrada.'}
```

### Bulk download failures

`bulk_download(report=True)` returns a result object exposing what failed and why
(the default still returns a plain dict for backward compatibility):

```python
res = client.bulk_download(stations, variables, start, end, report=True)
res.data        # {"INIA-47_2002": df, ...}  successful downloads
res.failures    # {"INIA-154_2002": "AuthenticationError: ...", ...}
res.ok          # False if anything failed
res.raise_if_all_failed()
```

## Region Codes

Accepts code (`"R16"`), name (`"Ñuble"`), or number (`"16"` / `16`).

| Code | Region |
|------|--------|
| R01  | Tarapacá |
| R02  | Antofagasta |
| R03  | Atacama |
| R04  | Coquimbo |
| R05  | Valparaíso |
| R06  | O'Higgins |
| R07  | Maule |
| R08  | Biobío |
| R09  | La Araucanía |
| R10  | Los Lagos |
| R11  | Aysén |
| R12  | Magallanes |
| R13  | Metropolitana |
| R14  | Los Ríos |
| R15  | Arica y Parinacota |
| R16  | Ñuble |

## Development

```bash
git clone https://github.com/reneignacio/iniamet-library
cd iniamet-library
pip install -e ".[dev]"
pytest
pytest --cov=iniamet --cov-report=html
```

## Links

- **PyPI**: [https://pypi.org/project/iniamet/](https://pypi.org/project/iniamet/)
- **Source**: [https://github.com/reneignacio/iniamet-library](https://github.com/reneignacio/iniamet-library)
- **INIA API**: [https://agromet.inia.cl/api/v4](https://agromet.inia.cl/api/v4)
- **Issues**: [GitHub Issues](https://github.com/reneignacio/iniamet-library/issues)

## License

MIT License. See [LICENSE](LICENSE) for details.

**This is an UNOFFICIAL library**, not affiliated with or endorsed by INIA. All data belongs to INIA — refer to their terms of service for usage policies.
