# Pricing Data Ops

This prototype publishes pricing as static data files. OpenRouter rows come from the public Models API snapshot pipeline. Official provider prices are curated manually in V1 with source URLs and review timestamps.

## Local Commands

Run from `LLMApiPricing原型图-v1`:

```bash
npm run pricing:fetch
npm run pricing:build
npm run pricing:update
npm test
```

`npm run pricing:fetch` saves a timestamped OpenRouter response under `data/snapshots/openrouter/`.

`npm run pricing:build` normalizes the newest snapshot, merges `data/manual/official-prices.json`, writes public outputs, and appends price-change events.

`npm run pricing:update` runs fetch then build.

The fetch step uses a request timeout and one retry for transient OpenRouter failures. The build step refuses to overwrite public JSON when the newest snapshot has too few valid OpenRouter rows.

## Review Checklist

Review these generated files before publishing:

- `data/models.json`: normalized model rows, prices, `sourceUrl`, `observedAt`, confidence, optional `validUntil`, and frontend fields.
- `data/providers.json`: provider metadata used by the comparison tables.
- `data/meta.json`: `generatedAt`, model/provider counts, `skippedModelCount`, and `sourceSnapshot`.
- `data/changes.jsonl`: append-only price changes; large percentage moves may include `needsReview: true`.

## Data Semantics

`needsReview: true` means the change-log entry crossed the review threshold and should be checked before treating it as reliable market signal.

`validUntil` marks an official curated price as time-limited or needing review after that timestamp. Use it for promotional prices, announced pricing windows, or provider docs that include a validity date.

Skipped sentinel rows are OpenRouter rows with negative prompt or completion prices, such as routing-only placeholders. They are counted in `data/meta.json` as `skippedModelCount` and are not published to `data/models.json`.

Source URLs are required for official curated rows and are carried into model price rows so the UI can link directly to the evidence. OpenRouter rows use OpenRouter model URLs with `sourceType: "openrouter-api"`.

Manual official rows must match a generated OpenRouter model ID. A typo or model removal fails the build instead of silently dropping the official price.

Set `active: false` on a manual official row only after review confirms the OpenRouter model has been retired or removed. Inactive rows are kept for audit history but skipped by the automatic merge so one retired model does not block daily OpenRouter updates.

Snapshot files under `data/snapshots/openrouter/` are raw API inputs. Keep them when auditing a build or investigating why `sourceSnapshot` changed.

## Scheduled Run

`.github/workflows/pricing-data.yml` runs the same update sequence twice daily, at 00:17 UTC and 08:17 UTC, and on manual dispatch:

```bash
npm install --package-lock=false
npm run pricing:update
npm test
```

If generated data changed, the workflow commits `data/models.json`, `data/providers.json`, `data/meta.json`, `data/changes.jsonl`, the matching `public/data` copies, and new OpenRouter snapshots back to the repository with `GITHUB_TOKEN`.

The Cloudflare Pages project is a Direct Upload project with no Git connection. To publish generated data automatically, create a Cloudflare API token with `Cloudflare Pages: Edit` access for account `81ba8678d5a02947825b7f7f1195c12f`, then save it as the GitHub Actions secret `CLOUDFLARE_API_TOKEN`. When this secret exists, the workflow runs `npm run build` and deploys `web/out` to the `llmapipricing` Pages project with Wrangler.

Do not auto-publish official provider price changes until the manual rows and source URLs have been reviewed.
