Metadata-Version: 2.5
Name: alpin-api
Version: 0.5.0
Summary: Deterministic forum scraping for Alpine ski-resort updates
Author: Michael Medek
License: MIT License
        
        Copyright (c) 2026 Michael Medek
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: curl-cffi>=0.13
Requires-Dist: requests>=2.32
Requires-Dist: tqdm>=4.67
Provides-Extra: tracker
Requires-Dist: groq>=0.31; extra == 'tracker'
Description-Content-Type: text/markdown

# alpin-api

Every night, this project turns posts from dedicated Alpine ski-resort forum topics into a public GeoJSON map of the latest construction and infrastructure progress.

[View the live interactive map](https://michaelmedek.github.io/alpin-api/)

## Install the package for developers

Requires Python 3.11+, Docker, internet, and a Funiforum account. macOS setup with [Homebrew](https://brew.sh):

```shell
# Install the required tools and start the local Docker environment.
brew install python@3.11 colima docker
colima start --vm-type vz --cpu 2 --memory 4 --disk 20

# Create and activate a virtual environment, then install alpin-api.
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install alpin-api
```

### Start the forum services

Alpinforum uses HTTP; Alpinfans and Remontées Mécaniques need FlareSolverr (:8191), Funiforum the browser solver (:8194).

```shell
# Stop the service setup immediately if any command fails.
set -e

# Start FlareSolverr for the forums that require browser-aware HTTP requests.
docker run -d --name flaresolverr --restart unless-stopped -p 127.0.0.1:8191:8191 -e BROWSER_WAIT_TIMEOUT=5 ghcr.io/flaresolverr/flaresolverr@sha256:c80ae007ce2ccdcd217a12426e4f039ef763ff90738c808d38810c3e59323767

# Export the credentials required by the Funiforum solver and Python client.
export FUNIFORUM_USER='your-forum-user'
export FUNIFORUM_PASSWORD='your-forum-password'

# Build and start the authenticated Funiforum browser solver.
docker build -t alpin-api-funisolver:local "$(alpin-api-funisolver-context)"
docker run -d --name funisolver --restart unless-stopped --cap-drop=ALL --security-opt no-new-privileges:true --shm-size=2g -p 127.0.0.1:8194:8194 -e FUNIFORUM_USER -e FUNIFORUM_PASSWORD alpin-api-funisolver:local
```

Wait until `docker logs funisolver` shows `Running on`, then verify; health waits for authentication:

```shell
# Stop the verification immediately if any check fails.
set -e

# Verify that both local forum services are ready.
curl -fsS --max-time 5 http://localhost:8191/
curl -fsS --max-time 125 http://localhost:8194/health
```

Keep credentials exported in Python and Docker. Initialization warns and retains unavailable forums. Skip unavailable sources; return remaining sources without extra warnings, or an empty list and one resort warning if none remain. Invalid resort/forum assignments fail before traffic. Restore prerequisites and recreate `AlpinAPI`.

After reboot: `colima start`, `docker start flaresolverr funisolver`, `source .venv/bin/activate`; export both credentials. Recreate Funiforum's container if credentials change. Repeat the checks.

### Use the API

Fetch a registered resort, optionally filtered by forum:

<!-- BEGIN DOCSTRING EXAMPLE: AlpinAPI.fetch_posts -->

```python
from alpin_api import AlpinAPI, Forum, SKI_RESORTS

# List all registered resorts and their names
print("\n".join(f"{resort_id}: {resort.name}" for resort_id, resort in SKI_RESORTS.items()))

posts_by_resort = AlpinAPI().fetch_posts(
    resort_ids=["hintertuxer_gletscher", "alpe_d_huez"],  # None fetches all registered resorts
    hours_old_max=24.0,
    forum=Forum.ALPINFORUM,  # None fetches all registered forums
    latest_topic_limit=50,  # Must be a positive discovery-pruning limit
)

hintertux_posts = posts_by_resort["hintertuxer_gletscher"]
```

<!-- END DOCSTRING EXAMPLE: AlpinAPI.fetch_posts -->

Discover topics:

<!-- BEGIN DOCSTRING EXAMPLE: AlpinAPI.fetch_latest_topics -->

```python
from alpin_api import AlpinAPI, Forum

latest_topics = AlpinAPI().fetch_latest_topics(
    forum=Forum.ALPINFORUM,
    topic_limit=50,
    topic_hours_old_max=24.0,
)

for fetched_topic in latest_topics:
    print(fetched_topic.topic.topic_id, fetched_topic.topic.title, fetched_topic.latest_post_datetime)
```

<!-- END DOCSTRING EXAMPLE: AlpinAPI.fetch_latest_topics -->

Fetch a known topic:

<!-- BEGIN DOCSTRING EXAMPLE: AlpinAPI.fetch_topic_posts -->

```python
from alpin_api import AlpinAPI, Forum

ischgl_posts = AlpinAPI().fetch_topic_posts(
    forum=Forum.ALPINFORUM,
    topic_identifier=285,
    hours_old_max=72.0,  # enough time to cover some news
)
```

<!-- END DOCSTRING EXAMPLE: AlpinAPI.fetch_topic_posts -->

All normalized datetimes are aware UTC values. `hours_old_max=24.0` covers exactly 24 rolling hours; retrieval reads at most the newest two pages. Discovery returns globally newest-first topics with their latest-post datetimes, and its age filter is applied after `topic_limit`. `latest_topic_limit` lets the aggregate call fetch only registered topics that remain active in that bounded discovery result. Explicit forum filters require a registered topic for every selected resort.

## Install and test the repo from source

On macOS with [Homebrew](https://brew.sh), install tools and clone:

```shell
# Install the development tools and clone the repository.
brew install git uv node colima docker
git clone https://github.com/MichaelMedek/alpin-api.git

# Enter the checkout, install locked dependencies, and enable pre-commit.
cd alpin-api
uv sync --frozen --extra tracker
uv run --frozen --extra tracker pre-commit install

# Start the local Docker environment.
colima start --vm-type vz --cpu 2 --memory 4 --disk 20
```

Select `.venv/bin/python` in VS Code. Configure and start services:

```shell
# Export the API key and forum credentials required by the full test suite.
export GROQ_API_KEY='your-groq-key'
export FUNIFORUM_USER='your-forum-user'
export FUNIFORUM_PASSWORD='your-forum-password'

# Start FlareSolverr for its live forum regressions.
docker run -d --name flaresolverr --restart unless-stopped -p 127.0.0.1:8191:8191 -e BROWSER_WAIT_TIMEOUT=5 ghcr.io/flaresolverr/flaresolverr@sha256:c80ae007ce2ccdcd217a12426e4f039ef763ff90738c808d38810c3e59323767

# Build and start the authenticated Funiforum browser solver.
docker build -t alpin-api-funisolver:local src/alpin_api/_funisolver
docker run -d --name funisolver --restart unless-stopped --cap-drop=ALL --security-opt no-new-privileges:true --shm-size=2g -p 127.0.0.1:8194:8194 -e FUNIFORUM_USER -e FUNIFORUM_PASSWORD alpin-api-funisolver:local
```

Wait until `docker logs funisolver` shows `Running on`, then verify; health waits for authentication:

```shell
# Stop the validation immediately if any command fails.
set -e

# Verify that both local forum services are ready.
curl -fsS --max-time 5 http://localhost:8191/
curl -fsS --max-time 125 http://localhost:8194/health

# Check the lock file and run the complete test suite.
uv lock --check
uv run --frozen --extra tracker pytest --exitfirst

# Run repository checks and build the release distributions.
uv run --frozen --extra tracker pre-commit run --all-files
uv build

# Run the live tracker locally to create a run directory and rewrite map.geojson.
uv run --frozen --extra tracker --no-dev python main.py
```

A passing full run covers all four forums and Groq without skips. After reboot, start Colima and both containers, export all three credentials, and repeat the health checks.
