# FEMA Public Assistance Funding & Obligation Monitor (`nintendo424/fema-public-assistance-obligation-monitor`) Actor

Monitor official OpenFEMA Public Assistance funded projects for disaster recovery funding changes. Get decision-ready obligation increases, deobligations, mitigation, process-step, and status events instead of raw FEMA API rows.

- **URL**: https://apify.com/nintendo424/fema-public-assistance-obligation-monitor.md
- **Developed by:** [Blake Panter](https://apify.com/nintendo424) (community)
- **Categories:** Business, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 0.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

## FEMA Public Assistance Funding & Obligation Monitor

Track **FEMA Public Assistance** disaster recovery funding without rebuilding OpenFEMA snapshots yourself. This Actor monitors the official **OpenFEMA Public Assistance Funded Projects Details** feed and turns funded-project updates into decision-ready events for obligation increases, deobligations, mitigation amounts, process steps, and project status.

Run it on a schedule to compare each project with its previous normalized snapshot and emit only the configured changes. This is a funding-monitoring product—not an open-bid feed—and it preserves FEMA's Public Assistance terminology rather than labeling every obligation a grant.

### Built for

- Disaster recovery consultants tracking funded projects and obligation changes
- State, local, tribal, and territorial emergency management teams overseeing Public Assistance recovery
- Infrastructure, resilience, and government affairs teams monitoring disaster recovery funding
- Analysts and data teams that need structured OpenFEMA change events for alerts, reporting, or downstream workflows

### Why this instead of a raw FEMA scraper

- **Change detection across runs:** see what changed instead of repeatedly receiving the same OpenFEMA rows.
- **Decision-ready funding signals:** get typed obligation increases, deobligations, mitigation changes, process-step changes, and status changes with previous, current, and delta values.
- **Traceable, automation-ready output:** every event includes project and geography context, official-source evidence, detection time, and a stable `dedupeKey`.
- **Reliable recurring delivery:** persistent state and a retryable pending outbox protect unpaid change events when a run is interrupted.

### Signals

- `initial-obligation` — first observation in the selected baseline (opt in with `includeInitial`)
- `obligation-increase` — current total obligated increased
- `deobligation` — current total obligated decreased
- `mitigation-change` — mitigation amount changed
- `process-step-change` — FEMA project processing step changed
- `status-change` — project status changed
- `unchanged` — no configured signal type met its emission criteria; changes excluded by a non-empty `signalTypes` allowlist count as unchanged (opt in with `emitUnchanged`)

Every result includes project identity and geography, previous/current/delta monetary values, old/new status and process fields, an evidence description, official source URL, detection time, and a stable `dedupeKey`. Results are charged as the PPE event `fema-project-funding-change`.

### Example input

```json
{
  "states": ["LA", "NC"],
  "disasterNumbers": [],
  "damageCategoryCodes": ["E"],
  "minimumTotalObligated": 100000,
  "minimumAbsoluteChange": 1000,
  "signalTypes": ["obligation-increase", "deobligation", "status-change"],
  "includeInitial": false,
  "emitUnchanged": false,
  "maxSourceRecords": 5000,
  "maxItems": 100,
  "pageSize": 500,
  "requestTimeoutSecs": 30,
  "baselineStoreName": "fema-pa-obligation-monitor-baseline"
}
```

### Stateful behavior

The Actor opens the deliberately named KV store and saves one legal key such as `project-552422` per normalized project. Each record contains the current snapshot plus a per-signal pending outbox and bounded acknowledgement history. The pending outbox is capped at 256 signals per project. Whenever a project already has pending signals, the Actor emits and acknowledges all of that older work before staging the newly fetched observation, even when the outbox has spare capacity. If a failed push, `maxItems`, or a PPE charge limit interrupts that drain, the newly fetched snapshot remains unstaged and retryable; the Actor neither evicts pending work nor advances the snapshot. One observation can atomically stage at most four signals, and the internal outbox capacity is never allowed below that batch size, preventing capacity deadlock. The Actor **persists a newly accepted snapshot and all of its payable signals before any dataset/PPE emission**, then acknowledges each signal after its push succeeds. Delivery is at least once, not exactly once: if the dataset/PPE push succeeds but acknowledgement persistence fails, that event can be delivered and charged again on retry. Consumers should make processing idempotent by deduplicating on the stable `dedupeKey`. Unpaid signals remain pending for a later run; signals whose acknowledgements were durably saved are not pushed again. Use the same store name for recurring runs; use a fresh name to intentionally re-baseline. Existing snapshot-only records are migrated when next observed.

Project/geography/category and `minimumTotalObligated` filters are applied before baseline comparison and charging. `minimumAbsoluteChange` is measured against the immediately previous stored observation; sub-threshold changes are stored and do not accumulate across runs. `maxItems` is checked before another project is persisted, and both `maxItems` and PPE charge limits stop output without discarding already-pending signals. `maxSourceRecords` bounds source retrieval.

### Coverage and caveats

- Source: `GET https://www.fema.gov/api/open/v2/PublicAssistanceFundedProjectsDetails`, using documented `$top`/`$skip` pagination and deterministic `gmProjectId asc` ordering.
- Every response body is streamed with a strict 10 MiB UTF-8 byte cap, even when `Content-Length` is absent or incorrect. Each page has a monotonic wall-time deadline through UTF-8 decoding and JSON parsing: async body reads are aborted, while synchronous decode/parse cannot be preempted but the page is rejected if either stage returns after the deadline.
- This monitors records observed within the bounded source window. Narrow filters are useful, but filtering is local and does not expand the source window.
- A project absent from a later bounded response is **not** emitted as resolved, closed, or deobligated. Only explicit fields in a returned record produce signals.
- Null or blank monetary fields remain `null`, never synthetic zeroes. A null-to-number transition is not classified as a monetary increase because there is no numeric prior amount.
- `detectedAt` is the Actor observation time; FEMA's `lastRefresh` and obligation dates are included separately.
- FEMA may revise, delay, or correct source data. Verify consequential decisions against the linked official record.
- Multiple simultaneous changes can emit multiple events for one project, each with its own signal type and dedupe key.

### Local development

```bash
bun install
bun test
bun run typecheck
npx --yes apify-cli validate-schema
apify actor:run
```

For a first smoke run, set `includeInitial: true`, a fresh `baselineStoreName`, and small `maxSourceRecords`/`maxItems`. Repeat with the same baseline and `includeInitial: false` to verify unchanged suppression.

# Actor input Schema

## `states` (type: `array`):

Optional two-letter state or territory abbreviations; matching is case-insensitive.

## `disasterNumbers` (type: `array`):

Optional FEMA disaster declaration numbers from 1 through 99999.

## `damageCategoryCodes` (type: `array`):

Optional single-letter Public Assistance category codes A through G or Z; matching is case-insensitive.

## `minimumTotalObligated` (type: `number`):

Only track projects whose current total obligated amount meets this USD floor.

## `minimumAbsoluteChange` (type: `number`):

Run-to-run USD threshold for obligation, deobligation, and mitigation signals. Status and process signals are unaffected.

## `signalTypes` (type: `array`):

Optional allowlist: initial-obligation, obligation-increase, deobligation, mitigation-change, process-step-change, status-change, or unchanged. Empty includes all eligible signals, subject to includeInitial and emitUnchanged.

## `includeInitial` (type: `boolean`):

Emit initial-obligation when a project has no stored baseline. Disabled by default for alert-oriented runs.

## `emitUnchanged` (type: `boolean`):

Emit when no configured signal type met its criteria. Changes excluded by a non-empty signalTypes allowlist count as unchanged. Disabled by default.

## `maxSourceRecords` (type: `integer`):

Hard bound on records fetched from OpenFEMA before local filtering.

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

Hard cap checked before every paid dataset push.

## `pageSize` (type: `integer`):

Number of records requested per OpenFEMA page.

## `requestTimeoutSecs` (type: `integer`):

Wall-time deadline in seconds for each page request through body decoding and JSON parsing. Async reads are aborted; synchronous decode/parse cannot be preempted, but the page is rejected if either returns after the deadline.

## `baselineStoreName` (type: `string`):

Deliberately named persistent key-value store used across runs. Use a fresh legal name to establish a new baseline.

## Actor input object example

```json
{
  "states": [],
  "disasterNumbers": [],
  "damageCategoryCodes": [],
  "minimumTotalObligated": 0,
  "minimumAbsoluteChange": 0,
  "signalTypes": [],
  "includeInitial": false,
  "emitUnchanged": false,
  "maxSourceRecords": 1000,
  "maxItems": 100,
  "pageSize": 100,
  "requestTimeoutSecs": 30,
  "baselineStoreName": "fema-pa-obligation-monitor-baseline"
}
```

# Actor output Schema

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

Dataset items emitted by this run.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("nintendo424/fema-public-assistance-obligation-monitor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("nintendo424/fema-public-assistance-obligation-monitor").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 '{}' |
apify call nintendo424/fema-public-assistance-obligation-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nintendo424/fema-public-assistance-obligation-monitor"
        }
    }
}

```

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/Ah77FeHKM7R5cm9Zz/builds/10OZ1WoR6MqeUL6Fy/openapi.json
