# Philippine Hazards: PAGASA Weather + PHIVOLCS Earthquakes (`a-g-d/ph-hazards`) Actor

Structured JSON from Philippine government hazard bulletins: PAGASA daily weather, tropical cyclone status and PHIVOLCS earthquake reports.

- **URL**: https://apify.com/a-g-d/ph-hazards.md
- **Developed by:** [Aloever Dulay](https://apify.com/a-g-d) (community)
- **Categories:** News, Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 earthquake records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Philippine Hazards Actor (PAGASA + PHIVOLCS)

Structured JSON from Philippine government hazard bulletins. One run gives you the
PAGASA daily weather outlook, the current tropical cyclone situation, and the latest
PHIVOLCS earthquake reports, all as typed dataset items you can pipe into alerts,
dashboards, models or agents.

### Who needs this

- **Disaster-risk and emergency teams** who want machine-readable cyclone position,
  wind and forecast impacts instead of copying them off a web page.
- **Insurers and reinsurers** tracking parametric triggers (magnitude, wind speed,
  location) for the Philippine market.
- **Logistics and shipping operators** watching coastal water conditions and
  typhoon tracks around Luzon, Visayas and Mindanao.
- **Newsrooms** that need the official numbers fast, with a source URL per item.
- **Researchers** building time series of PHIVOLCS events or PAGASA forecasts.
- **AI agents and automations** that need a stable schema rather than raw HTML.

### Output

The actor writes to **two datasets** so each record type keeps a clean, self-describing
shape (Apify dataset views can pick columns, but not filter rows, so unrelated records
belong in separate datasets). Both are documented by dataset schemas in `.actor/`, and the
actor's [output schema](.actor/output_schema.json) points integrations and AI agents at
each one:

| Dataset           | Source   | One item per                           |
| ----------------- | -------- | -------------------------------------- |
| `default`         | PHIVOLCS | earthquake matching the input filters  |
| `weather` (alias) | PAGASA   | run (a single nested weather snapshot) |

#### `default` dataset — earthquakes

One item per PHIVOLCS earthquake that matches the filters. Each item carries `source`,
`type`, `fetchedAt` (UTC, when the page was fetched) and `sourceUrl`, then the earthquake
fields. Timestamps from the bulletins are ISO-8601 in Manila time (`+08:00`).

```json
{
  "source": "phivolcs",
  "type": "earthquake",
  "fetchedAt": "2026-09-22T22:14:52.426Z",
  "sourceUrl": "https://earthquake.phivolcs.dost.gov.ph/",
  "occurredAt": "2026-09-23T05:32:00+08:00",
  "latitude": 5.56,
  "longitude": 125.34,
  "depthKm": 25,
  "magnitude": 1.6,
  "location": "018 km N 57° W of Sarangani Island (Municipality Of Sarangani) (Davao Occidental)",
  "detailUrl": "https://earthquake.phivolcs.dost.gov.ph/2026_Earthquake_Information/September/2026_0922_2132_B1F.html"
}
```

#### `weather` dataset — one snapshot per run

Exactly one item per run when PAGASA is selected. Provenance (`source`, `fetchedAt`,
`sourceUrl`) is kept **once** at the top level; the PAGASA parts are nested rather than
spread across many rows:

- `bulletin` — Severe Weather Bulletin status: `active` flag, `headline`,
  `bulletinPdfUrl` and its own `sourceUrl` (a different page).
- `tropicalCyclones` — every cyclone under "TC Information" (empty when none).
- `forecasts` — regional rows from "Forecast Weather Conditions".
- `windCoastal` — rows from "Forecast Wind and Coastal Water Conditions".
- `temperatureHumidity` — 24-hour temperature and humidity extremes, or `null`.
- `tidesAstronomy` — Metro Manila sun/moon times and Manila Bay tides, or `null`.
- `issuedAt`, `synopsis` — the outlook's issuance time and synopsis.

```json
{
  "source": "pagasa",
  "fetchedAt": "2026-09-22T22:14:52.024Z",
  "sourceUrl": "https://www.pagasa.dost.gov.ph/weather",
  "issuedAt": "2026-09-23T04:00:00+08:00",
  "synopsis": "Trough of Tropical Depression (TD) affecting the eastern sections of Visayas and Mindanao.",
  "bulletin": {
    "sourceUrl": "https://www.pagasa.dost.gov.ph/tropical-cyclone/severe-weather-bulletin",
    "active": false,
    "headline": "No Active Tropical Cyclone within the Philippine Area of Responsibility",
    "bulletinPdfUrl": null
  },
  "tropicalCyclones": [
    {
      "headline": "TROPICAL CYCLONE OUTSIDE PAR AS OF 3:00 AM TODAY",
      "name": null,
      "category": "TROPICAL DEPRESSION",
      "insidePar": false,
      "latitude": 14.7,
      "longitude": 139.5,
      "maxSustainedWindsKph": 55
    }
  ],
  "forecasts": [
    {
      "place": "Eastern Visayas, Caraga, Davao Oriental, Misamis Oriental, and Camiguin",
      "weatherCondition": "Cloudy skies with scattered rains and thunderstorms",
      "causedBy": "Trough of TD",
      "impacts": "Possible flash floods or landslides due to moderate to at times heavy rains"
    }
  ],
  "temperatureHumidity": {
    "temperatureC": {
      "max": { "value": 31.9, "time": "2:00 PM" },
      "min": { "value": 25.7, "time": "5:00 AM" }
    }
  }
}
```

The item shapes are defined once as zod schemas in [`src/schema.ts`](src/schema.ts) (with
the exported TypeScript types `EarthquakeItem`, `WeatherSnapshot`, ...). The committed
`.actor/*_dataset_schema.json` and `.actor/output_schema.json` are generated from those
schemas with `pnpm run schemas:generate`; a test fails if they drift.

### Input

| Field            | Type     | Default                  | Meaning                                                               |
| ---------------- | -------- | ------------------------ | --------------------------------------------------------------------- |
| `sources`        | string\[] | `["pagasa", "phivolcs"]` | Which sources to fetch.                                               |
| `minMagnitude`   | number   | `0`                      | Keep earthquakes with magnitude at or above this value.               |
| `sinceHours`     | integer  | `24`                     | Keep earthquakes that occurred within this many hours before the run. |
| `maxEarthquakes` | integer  | `200`                    | Cap on earthquake items, most recent first.                           |

The magnitude filter accepts fractional values through the API; the Console form
offers whole numbers because Apify input schemas only have an `integer` type.

### Pricing

The actor uses Apify pay-per-event pricing. Users pay only for these events, never for
platform usage:

| Event               | Charged                         | Why this unit                                                              |
| ------------------- | ------------------------------- | -------------------------------------------------------------------------- |
| `apify-actor-start` | once per run, by Apify          | Apify's synthetic start event; Apify also covers the first 5 s of compute. |
| `weather-snapshot`  | once per stored PAGASA snapshot | A weather run always yields one snapshot, so it has one price.             |
| `earthquake-record` | per stored earthquake item      | Volume varies with the filters, so the user pays per record.               |

Every item is charged only after it is stored, so a user never pays for data they cannot
see. Before fetching a source, the actor checks that the user's spending limit covers at
least one of its items and skips the source otherwise. If the limit covers only some of
the earthquakes found, the actor stores and bills the most recent ones that fit. Charging
is skipped entirely when the run is not billed per event (local runs, or a different
pricing model), so the actor works unchanged in development.

Apify's generic per-item event, `apify-default-dataset-item`, is not used: each
earthquake is billed once, as `earthquake-record`.

### Data sources and public-data basis

- **PAGASA** (Philippine Atmospheric, Geophysical and Astronomical Services
  Administration), `https://www.pagasa.dost.gov.ph/weather` and
  `/tropical-cyclone/severe-weather-bulletin`. Official public advisories from a
  Department of Science and Technology agency; the site publishes no robots.txt and
  the pages carry no personal data.
- **PHIVOLCS** (Philippine Institute of Volcanology and Seismology),
  `https://earthquake.phivolcs.dost.gov.ph/`. Official public earthquake bulletins;
  likewise a DOST agency and no personal data.

Each run makes three requests in total (two to PAGASA, one to PHIVOLCS) with the
User-Agent `ph-hazards-actor/0.1 (+https://github.com/hidden-claw/ph-hazards-actor)`.
Please schedule runs at a sensible cadence; the bulletins change a few times a day.

#### TLS note for PHIVOLCS

The PHIVOLCS server presents a valid GlobalSign certificate but does not send the
intermediate CA in the handshake, so a default Node.js client rejects it. The actor
bundles the public "GlobalSign RSA OV SSL CA 2018" intermediate
(`src/certs/`, with its source URL and fingerprint in the file header) and adds it to
the trust store next to Node's default roots. Certificate verification stays fully
enabled.

### Limitations

- **Severe Weather Bulletins are not parsed yet.** When a cyclone is active PAGASA
  embeds the bulletin as a PDF; the `tc_bulletin_status` item reports `active: true`
  and the PDF URL, but signal levels and the forecast track are not extracted.
- **PHIVOLCS lists the current month only.** `sinceHours` cannot reach further back
  than the front page shows, and detail pages are not fetched.
- **Cyclone summary table shape is inferred.** On the capture day the multi-column
  table under "TC Information" was present but empty; its data rows are mapped by
  header name and may need adjustment once a cyclone inside PAR is observed live.
- Tide predictions cover Manila Bay only, as published by PAGASA.

### Why not X

- **Why not `pagasa-parser`?** It is Apache-2.0 and well made, but it models Severe
  Weather Bulletin PDFs (via `@pagasa-parser/source-pdf`), not the daily weather page
  or PHIVOLCS. It is the natural dependency for the PDF milestone above.
- **Why not USGS for earthquakes?** USGS covers the region but with different
  magnitudes, locations and latency than the official Philippine bulletin, and it
  misses many small local events PHIVOLCS reports.
- **Why not an RSS feed?** Neither agency publishes a structured feed for these pages.

### Development

```bash
pnpm install
pnpm run build
APIFY_LOCAL_STORAGE_DIR=$PWD/storage node dist/main.js
```

Put an input file at `storage/key_value_stores/default/INPUT.json`; earthquakes land in
`storage/datasets/default/` and the weather snapshot in `storage/datasets/weather/`. To
exercise pay-per-event charging locally, set `ACTOR_TEST_PAY_PER_EVENT=true` and inspect
the `charging-log` dataset.

After editing `src/schema.ts`, run `pnpm run schemas:generate` to refresh the committed
dataset and output schemas in `.actor/` (a test fails if they are stale).

Quality gate (also enforced by CI and the pre-commit hook):

```bash
pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm test && pnpm run build
```

Tests run against saved HTML fixtures in `test/fixtures/` (see the README there for
which are real captures and which are synthetic), so the suite never touches the
live sites.

### License

MIT, see [LICENSE](LICENSE).

# Changelog

This Actor's version history is a separate document: https://apify.com/a-g-d/ph-hazards/changelog.md

# Actor input Schema

## `sources` (type: `array`):

Which government sources to fetch. PAGASA = daily weather, tropical cyclone status and bulletin state. PHIVOLCS = earthquake reports for the current month.

## `minMagnitude` (type: `integer`):

Only emit earthquakes with magnitude greater than or equal to this value (0 keeps everything).

## `sinceHours` (type: `integer`):

Only emit earthquakes that occurred within this many hours before the run. PHIVOLCS only lists the current month, so values above ~744 have no extra effect.

## `maxEarthquakes` (type: `integer`):

Upper bound on the number of earthquake items pushed (most recent first).

## Actor input object example

```json
{
  "sources": [
    "pagasa",
    "phivolcs"
  ],
  "minMagnitude": 0,
  "sinceHours": 24,
  "maxEarthquakes": 200
}
```

# Actor output Schema

## `earthquakes` (type: `string`):

PHIVOLCS earthquake records, one item per earthquake, matching the input filters. Read the default dataset items to list recent Philippine earthquakes with time, magnitude, depth and coordinates.

## `weather` (type: `string`):

PAGASA weather snapshots, one nested item per run, covering the tropical cyclone bulletin status, forecasts, wind/coastal conditions, temperature/humidity and tides/astronomy. Read the weather dataset items for the latest outlook.

# 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 = {
    "sources": [
        "pagasa",
        "phivolcs"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("a-g-d/ph-hazards").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 = { "sources": [
        "pagasa",
        "phivolcs",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("a-g-d/ph-hazards").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 '{
  "sources": [
    "pagasa",
    "phivolcs"
  ]
}' |
apify call a-g-d/ph-hazards --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,a-g-d/ph-hazards"
        }
    }
}
```

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/aSGZOhKADwJiaJPQZ/builds/n1mnKncWDWf2YZxut/openapi.json
