# CZ Zoning Plan Amendment Watcher (`atlas-data/cz-zoning-amendment-watcher`) Actor

Watches Czech municipal notice boards (úřední desky) for zoning postings: zoning plan amendments, new plans, studies, public consultations and planning decisions. Classifies notices, links cadastral areas and parcels, matches your watchlist. Informational only — not legal advice.

- **URL**: https://apify.com/atlas-data/cz-zoning-amendment-watcher.md
- **Developed by:** [Atlas](https://apify.com/atlas-data) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## CZ Zoning Plan Amendment Watcher

Marketplace-native Apify Actor that watches **Czech municipal official notice boards
(úřední desky)** for zoning-relevant postings — zoning plan amendments (změna územního plánu),
new zoning plans, urbanistic studies, public consultations and planning decisions — classifies
them with a transparent rule-based Czech lexicon, links them to **cadastral areas (katastrální
území)** and **parcel numbers**, matches them against your watchlist, and emits provenance-linked
dataset records with deep links to the official postings.

### Who it is for

- **Land scouts / akviziteři** monitoring 10–50 cadastral areas across dozens of municipalities
  who cannot manually re-check boards and miss short statutory comment windows.
- **Small-to-mid property developers** needing early warning of upzoning/downzoning around owned
  or optioned land.
- **Architects / planning consultants** tracking proceedings that touch active commissions.

### Sources and legal basis

Municipal notice-board publication is legally mandated by **§ 67 of Act No. 128/2000 Sb.**
(including electronic form). This Actor reads only publicly accessible listing pages:

| Board ID | Municipality | Source |
| --- | --- | --- |
| `praha` | Hlavní město Praha | `eud.praha.eu` electronic board (official) |
| `ostrava` | Statutární město Ostrava | `ostrava.cz` úřední deska |
| `liberec` | Město Liberec | `liberec.cz/deska/` |

Posture:

- Honest `User-Agent`, low request rate (one listing request per board per run), politeness delay
  between boards, robots.txt respected (Liberec disallows `/files/…` asset paths — the Actor never
  downloads files; detail links are surfaced for you to open yourself).
- No authentication circumvention, no paywall bypass, no CAPTCHA evasion — ever.
- Delivery model is **excerpt + deep link** to the official posting; third-party copyrighted
  annexes are never republished wholesale.
- eDesky.cz offers a national aggregation but its API requires a user account key; this Actor
  therefore reads municipal boards directly. Built-in coverage is deliberately limited to the
  boards above plus any custom boards you add — see *Coverage honesty* below.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | `incremental \| full` | `incremental` | `incremental`: emit notices new since the previous run (first run establishes a baseline and emits the current backlog flagged `baselineEstablished`). `full`: re-emit everything currently posted within the lookback window. |
| `boards` | array of IDs or `{name,url}` | all three built-ins | Built-in IDs: `praha`, `ostrava`, `liberec`. Custom boards: `{"name": "Obec X", "url": "https://…"}` parsed by a conservative generic parser (marked `parserKind: "generic"`, slightly damped confidence). Max 25 per run. |
| `cadastralWatchlist` | string\[] | `[]` | Katastr names you care about (`Strašnice`, `Hrušov`…). Matching folds Czech diacritics and respects word boundaries. Hits are tagged and prioritized in output ordering. Max 200 entries. |
| `daysBack` | integer 1–90 | 14 | Skip notices officially posted older than this (bounds first-run backlog). Undatable notices are always kept. |
| `maxItems` | integer 1–10000 | 500 | Hard cap on `zoning_notice` records per run. Records cut by the cap stay pending and are retried next run (at-least-once delivery). |

### Output (default dataset)

One record stream, deterministic under identical inputs:

1. **`run_summary`** (always first): mode, baseline flag, board success/failure counts, raw vs
   zoning-relevant counts, emission counts, legal-basis note and disclaimer.
2. **`zoning_notice`**: classified planning notice with:
   - `noticeClass`: one of `zmena_up`, `novy_up`, `uzemni_studie`, `verejne_projednani`,
     `uzemni_rozhodnuti`, `other_planning`;
   - `classificationConfidence` (title match > body match; damped when families compete),
     `matchedKeywords` for auditability;
   - `impactHint` (`upzoning` / `downzoning` / `neutral`) with hard-capped LOW confidence —
     keyword-based impact guessing is explicitly not overpromised;
   - `affectedCadastralAreas`, `parcelNumbers` (best-effort extraction);
   - `watchlistMatches`; stable `recordId` (SHA-1); full provenance with deep links.
3. **`board_health`** (one per requested board): fetch/parse status, raw and zoning counts,
   latency, error text — so partial outages and layout breakage are visible instead of silent.

Incremental state lives in the key-value store under key `watch-state`: a single bounded document
(hard cap 5,000 notices, oldest evicted first) with per-entry integrity fingerprints. Corrupt or
tampered state degrades to an explicit baseline rather than silently suppressing diffs.

### Coverage honesty

- Coverage equals exactly the enabled boards. Notices posted on physical boards only are
  invisible to any electronic monitor; this Actor never claims CZ-wide completeness.
- **Liberec reachability:** `www.liberec.cz` has been observed dropping connections from some
  datacenter IP ranges (including parts of the Apify platform), while working normally from
  residential/EU networks. When unreachable, the run still succeeds with the remaining boards
  and the failure is reported verbatim in that run's `board_health` row — never silently.
- A board that responds but yields zero parseable items is flagged in logs and visible via
  `board_health` across runs — layout changes surface as data, not as silent gaps.
- Classification is heuristic (rule-based lexicon with reported confidence), not a legal
  characterization. Always verify against the linked official posting.

### Legal & attribution

- Source attribution on every record: *"Czech municipal official notice boards (úřední desky)"*,
  with municipality, board URL and notice deep link.
- Informational monitoring aid only — **not legal or planning advice**. Users remain responsible
  for verifying deadlines and consequences against the official sources.
- No personal-data enrichment: applicant names inside notices are neither extracted nor stored.

### Pricing

Free / pay-per-usage safe. This Actor performs no pay-per-event charging and stores no billing
markers, so enabling PPE later requires an explicit pricing migration, never implicit charges.

### Development

```bash
npm install
npm run lint          # ESLint
npm run typecheck     # tsc --noEmit
npm run build         # dist/
npm test              # unit + integration + adversarial + schema suites (offline fixtures)
npm run test:smoke    # live-source smoke against real boards (SMOKE=1)
npm run smoke:local   # end-to-end Actor run with local Apify storage
```

Node 20+. Tests use captured live fixtures plus clearly-marked synthetic zoning notices so flows
are deterministic regardless of what each board publishes on a given day.

# Actor input Schema

## `mode` (type: `string`):

incremental: emits zoning-relevant notices that appeared since the previous run (the very first run establishes the baseline and emits the current backlog flagged baselineEstablished=true). full: re-emits every classified notice currently visible on the boards regardless of state.

## `boards` (type: `array`):

Array of built-in board IDs ("praha", "ostrava", "liberec") and/or custom objects {"name": "Municipality", "url": "https://…official board page…"} parsed by a conservative generic parser. Maximum 25 boards per run.

## `cadastralWatchlist` (type: `array`):

Katastrální území names you care about (e.g. Strašnice, Hrušov). Notices mentioning any watched name are prioritized and tagged with watchlistMatches. Matching folds Czech diacritics and respects word boundaries. Leave empty to simply receive all classified planning notices.

## `daysBack` (type: `integer`):

Notices whose official posting date is older than this many days are skipped (they are already archived on the board). Notices without an extractable date are always kept. This bounds the first-run backlog.

## `maxItems` (type: `integer`):

Hard cap on zoning\_notice dataset records per run (run summary and per-board health rows always fit as well). Anything cut by this cap stays pending and is retried on the next run — no silent data loss.

## Actor input object example

```json
{
  "mode": "incremental",
  "boards": [
    "praha",
    "ostrava",
    "liberec"
  ],
  "cadastralWatchlist": [],
  "daysBack": 30,
  "maxItems": 250
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `state` (type: `string`):

No description

# 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 = {
    "mode": "incremental",
    "boards": [
        "praha",
        "ostrava",
        "liberec"
    ],
    "cadastralWatchlist": [],
    "daysBack": 30,
    "maxItems": 250
};

// Run the Actor and wait for it to finish
const run = await client.actor("atlas-data/cz-zoning-amendment-watcher").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 = {
    "mode": "incremental",
    "boards": [
        "praha",
        "ostrava",
        "liberec",
    ],
    "cadastralWatchlist": [],
    "daysBack": 30,
    "maxItems": 250,
}

# Run the Actor and wait for it to finish
run = client.actor("atlas-data/cz-zoning-amendment-watcher").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 '{
  "mode": "incremental",
  "boards": [
    "praha",
    "ostrava",
    "liberec"
  ],
  "cadastralWatchlist": [],
  "daysBack": 30,
  "maxItems": 250
}' |
apify call atlas-data/cz-zoning-amendment-watcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atlas-data/cz-zoning-amendment-watcher"
        }
    }
}

```

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/vOypyQkRZghMfBLvA/builds/aXfWJyRFRMW7z81eI/openapi.json
