# Weather History Scraper (`datalayer/climate-anomaly-checker`) Actor

Every weather actor returns a forecast. This answers the question buyers actually ask: is this normal for here, at this time of year? Thirty years of history per location, giving the anomaly in degrees, the rank among those years, and frost, heat and heavy-rain days against their own normals.

- **URL**: https://apify.com/datalayer/climate-anomaly-checker.md
- **Developed by:** [Datalayer](https://apify.com/datalayer) (community)
- **Categories:** Other, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.14 / 1,000 daily records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Weather History Scraper — Climate Anomaly

Historical weather for any location on earth, compared against **that location's
own 30-year baseline**.

Every weather actor on the store returns a forecast. A forecast answers "how hot
will it be on Thursday" — which is free everywhere and which nobody pays for.
The question an agronomist, an underwriter or a logistics planner actually asks
is different:

> **Is this normal for here, at this time of year?**

Answering that needs decades of history for the same calendar window. This does
it in one request per location.

### What it tells you

For the last 30 days at each location, against the same 30 days in each of the
previous 30 years:

- **Temperature anomaly** — this window minus the normal, in °C
- **Rank** — 1 means the warmest such window in 30 years. Measured, not asserted
- **Rainfall as a percentage of normal** — 45% is a drought signal; 210% is a
  harvest problem
- **Threshold days against their own normal** — frost, hot, very hot, heavy
  rain, dry. Not just "6 hot days", but "6 hot days where 2.8 is normal"

A live run on 2026-08-16:

| Location | Mean temp | Normal | Anomaly | Rank | Hot days (normal) |
|---|---|---|---|---|---|
| Napa Valley, US | 23.65°C | 22.86°C | **+0.79°C** | 10 / 31 | **28** (21.7) |
| Berlin, DE | 20.45°C | 20.05°C | +0.40°C | 13 / 31 | 5 (2.8) |
| Sydney, AU | 12.44°C | 12.38°C | +0.06°C | 13 / 31 | 0 (0) |

Napa's temperature anomaly is unremarkable. Its **hot-day count is 29% above
normal**, which for a vineyard is the number that matters. That gap is the
reason threshold days are reported against their own baseline rather than on
their own.

### Why one request per location

Open-Meteo's ERA5 archive reaches back to 1940, and — verified 2026-08-16 — will
return **30 years of daily records in a single call** (10,987 days in the live
run above). So the whole comparison costs one HTTP request. That is what makes
this an actor rather than a research project.

### Who uses this

- **Agriculture** — growing degree days, frost risk, heat stress against a real
  local baseline rather than a regional average.
- **Insurance and reinsurance** — was this event unusual for this postcode?
- **Logistics and energy** — demand and disruption planning.
- **ESG and climate reporting** — site-level trend evidence.
- **Events and hospitality** — what the weather actually does there in
  late September, over 30 years, not one.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `locations` | array | `["Napa Valley"]` | Place names or `"lat,lon"`. Coordinates skip geocoding and are exact |
| `windowDays` | integer | `30` | Length of the analysis window |
| `baselineYears` | integer | `30` | Years to compare against. Archive reaches 1940 |
| `hotDayC` | integer | `30` | Hot-day threshold |
| `veryHotDayC` | integer | `35` | Second heat threshold, for crop and livestock limits |
| `heavyRainMm` | integer | `20` | Heavy-rain threshold |
| `includeDaily` | boolean | `false` | Also return every day in the window |

### Output

**`climate_summary`** — one row per location: `meanTemp`, `normalTemp`,
`tempAnomaly`, `tempRank`, `isWarmestOnRecord`, `tempPercentile`,
`totalRainfall`, `normalRainfall`, `rainfallPercentOfNormal`, `rainRank`,
`frostDays` + `normalFrostDays`, `hotDays` + `normalHotDays`, `veryHotDays`,
`heavyRainDays`, `dryDays`, `highestTemp`, `lowestTemp`, `wettestDay`,
`maxWindGust`, `elevationM`, `baselineYearsUsed`.

**`daily`** — day-by-day temperature, rainfall and wind. Off by default because
it multiplies the row count by the window length.

### Things worth knowing

- **The window ends 3 days ago, not today.** The ERA5 archive lags real time by
  about 2 days (measured). Including today would return nulls and drag every
  mean down.
- **Check `resolvedName`.** The geocoder matches "Napa Valley" to *Bothe-Napa
  Valley State Park*. Close, but if you need a specific vineyard, pass
  coordinates.
- **Check `baselineYearsUsed`.** Asking for 30 years does not guarantee 30 years
  of coverage. A year with a gap in the archive is excluded rather than allowed
  to distort the normal, and the count tells you what the figures rest on.
- **A tie is not a record.** `isWarmestOnRecord` requires beating every baseline
  year outright, not merely ranking first.
- **`elevationM`** explains a lot of surprises — the model grid cell for a
  mountain valley reads colder than the town it is named after.

### Pricing

Pay per event. $0.05 per climate summary, $0.0002 per daily record.

# Actor input Schema

## `locations` (type: `array`):

Place names or coordinates. "Napa Valley", "Berlin", or "38.54,-122.53" all work. Coordinates skip the geocoding step and are exact.

## `windowDays` (type: `integer`):

How many recent days to analyse. The window ends 3 days ago because the ERA5 archive lags real time by about 2 days.

## `baselineYears` (type: `integer`):

How many past years to compare against, using the same calendar window in each. The archive reaches back to 1940, and 30 years is the standard climate normal.

## `hotDayC` (type: `integer`):

A day counts as hot when the maximum reaches this.

## `veryHotDayC` (type: `integer`):

A second, higher heat threshold — useful for crop and livestock stress limits.

## `heavyRainMm` (type: `integer`):

A day counts as heavy rain at or above this daily total.

## `includeSummary` (type: `boolean`):

One row per location with the anomaly, the rank and the threshold-day counts. This is the point of the actor.

## `includeDaily` (type: `boolean`):

Also return every day in the window. Off by default because it multiplies the row count by the window length.

## `proxyConfiguration` (type: `object`):

Optional. Open-Meteo is a public API and does not require a proxy.

## Actor input object example

```json
{
  "locations": [
    "Napa Valley",
    "Berlin"
  ],
  "windowDays": 30,
  "baselineYears": 30,
  "hotDayC": 30,
  "veryHotDayC": 35,
  "heavyRainMm": 20,
  "includeSummary": true,
  "includeDaily": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `summary` (type: `string`):

One row per location: anomaly against the baseline, rank among the years, threshold days.

## `daily` (type: `string`):

Day-by-day temperature, rainfall and wind across the window.

## `csv` (type: `string`):

Everything as a CSV download.

## `runSummary` (type: `string`):

The window used, the baseline depth, and any location that could not be resolved.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "locations": [
        "Napa Valley",
        "Berlin"
    ],
    "windowDays": 30,
    "baselineYears": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("datalayer/climate-anomaly-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "locations": [
        "Napa Valley",
        "Berlin",
    ],
    "windowDays": 30,
    "baselineYears": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("datalayer/climate-anomaly-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "locations": [
    "Napa Valley",
    "Berlin"
  ],
  "windowDays": 30,
  "baselineYears": 30
}' |
apify call datalayer/climate-anomaly-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datalayer/climate-anomaly-checker"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/PJWuiqQwgLcGq83Vj/builds/SB4cecEEgh2x97AYq/openapi.json
