---
name: pp-mufap
description: "Pakistan's mutual fund industry as a dated local panel: two decades of daily NAVs, monthly PKR asset allocation, and a market-implied short rate no other tool derives. Trigger phrases: `pakistan mutual fund nav`, `mufap fund returns`, `money market fund yields pakistan`, `mutual fund asset allocation pakistan`, `pakistan fund industry aum`, `use mufap`, `run mufap`."
author: "qazmataz"
license: "Apache-2.0"
argument-hint: "<command> [args] | install cli|mcp"
allowed-tools: "Read Bash"
metadata:
  openclaw:
    requires:
      bins:
        - mufap-pp-cli
    install:
      - kind: go
        bins: [mufap-pp-cli]
        module: github.com/mvanhorn/printing-press-library/library/payments/mufap/cmd/mufap-pp-cli
---
<!-- GENERATED FILE — DO NOT EDIT.
     This file is a verbatim mirror of library/payments/mufap/SKILL.md,
     regenerated post-merge by tools/generate-skills/. Hand-edits here are
     silently overwritten on the next regen. Edit the library/ source instead.
     See the repository agent guide, section "Generated artifacts: registry.json, cli-skills/". -->

# MUFAP — Printing Press CLI

## Prerequisites: Install the CLI

This skill drives the `mufap-pp-cli` binary. **You must verify the CLI is installed before invoking any command from this skill.** If it is missing, install it first:

1. Install via the Printing Press installer. It defaults binaries to `$HOME/.local/bin` on macOS/Linux and `%LOCALAPPDATA%\Programs\PrintingPress\bin` on Windows:
   ```bash
   npx -y @mvanhorn/printing-press-library install mufap --cli-only
   ```
2. Verify: `mufap-pp-cli --version`
3. Ensure the reported install directory is on `$PATH` for the agent/runtime that will invoke this skill.

If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.6 or newer). This installs into `$GOPATH/bin` (default `$HOME/go/bin`), so add that directory to `$PATH` instead:

```bash
go install github.com/mvanhorn/printing-press-library/library/payments/mufap/cmd/mufap-pp-cli@latest
```

If `--version` reports "command not found" after install, the runtime cannot see the binary directory on `$PATH`. Do not proceed with skill commands until verification succeeds.

MUFAP publishes one HTML page per date and nothing else. This CLI walks those dates into a local SQLite panel with an observed_at stamp on every row, then derives series the site never shows: a daily short-rate proxy from money-market fund yields, industry equity exposure in rupees, and cross-sectional return dispersion. It records which dates returned zero rows, so a gap is never mistaken for a zero.

## Prerequisites: Populate the local mirror

Six of the ten headline capabilities — `rates`, `panel`, `dispersion`, `universe`, `coverage`
and `dump` — read **only** the local SQLite mirror. They never call MUFAP. Against an empty or
thin mirror they return an empty result **at exit code 0**:

```bash
mufap-pp-cli rates --from 2026-09-01 --to 2026-09-04 --agent
# {"meta":{"source":"local"},"results":[]}       exit 0
```

The `run: mufap-pp-cli backfill daily --from <date> --to <date>` hint is written to **stderr
only**, which `--agent` JSON consumers routinely discard. So: an empty `results` from one of
those six commands means the mirror is thin for that range, not that the industry was quiet.
Backfill the range first, then check `coverage`, then derive.

Canonical first run:

```bash
mufap-pp-cli doctor                                                        # config + reachability
mufap-pp-cli amcs --json                                              # 27 AMC GUIDs
mufap-pp-cli backfill daily --from 2026-09-01 --to 2026-09-04              # one request per date
mufap-pp-cli coverage --resource daily-returns --from 2026-09-01 --to 2026-09-04 --agent
mufap-pp-cli rates --from 2026-09-01 --to 2026-09-04 --agent               # now non-empty
```

`backfill` has three subcommands mirroring three separate resources, and running one does not
populate the others: `backfill daily` (a daily tab), `backfill monthly` (the monthly net-assets
panel — where **industry AUM** lives), `backfill allocation` (per-fund monthly asset
allocation). It is resumable: each date commits on its own and dates already in the coverage
ledger are skipped unless `--force` is passed.

The two commands that do **not** read the mirror, `exposure` and `verify allocation`, fetch live
from MUFAP on every run and fan out over the whole industry. Read their entries below before
running either.

## When to Use This CLI

Reach for this CLI when you need Pakistani mutual fund data as a time series rather than a single lookup: building a dated panel of fund NAVs, deriving a short-rate proxy, measuring industry asset allocation in rupees, or checking how many funds actually reported on a given date. It is built for research pipelines that care about publication timing and about telling a real zero apart from a missing observation.

Routing the common asks: daily NAVs and returns -> `backfill daily` then `panel`; money-market yields -> `rates`; **industry AUM / net assets** -> `backfill monthly` then `dump monthly` (AUM is the monthly net-assets panel, not the daily NAV panel and not `exposure`, which covers listed equities only); PKR asset allocation -> `backfill allocation` then `dump allocation`, or `exposure` for the industry equity total.

## Anti-triggers

Do not use this CLI for:
- Do not use this CLI for live PSX equity prices or index levels; it holds fund NAVs, not stock quotes.
- Do not use it to buy, sell, or redeem fund units; MUFAP is an industry association site and exposes no transaction surface.
- Do not use it for daily fund AUM; net assets and asset allocation are published monthly, and only NAV and returns are daily.
- Do not use it as an official policy-rate source; the rates command is a market-implied proxy derived from fund yields, not a State Bank publication.

## Unique Capabilities

These capabilities aren't available in any other tool for this API.

### Series only a local panel can produce
- **`rates`** — Derive a daily short-term interest rate series from the cross-section of money-market fund yields.

  _This is the only daily, backfillable PKR short-rate proxy obtainable without a blocked government source._

  ```bash
  mufap-pp-cli rates --from 2026-09-01 --to 2026-09-04 --agent
  ```
- **`exposure`** — Total PKR the mutual fund industry holds in listed equities, by month, from per-fund asset allocation.

  _It is the asset-side counterpart to a mutual-fund net-flow series, enabling a cross-source join no single publisher offers._

  ```bash
  mufap-pp-cli exposure --from 2026-07 --to 2026-07 --max-amcs 1 --agent
  ```
- **`dispersion`** — Cross-sectional spread of fund returns within a category on each date.

  _Dispersion is a daily breadth measure that a single-fund page cannot express._

  ```bash
  mufap-pp-cli dispersion --category Equity --from 2026-09-01 --to 2026-09-04 --agent
  ```

### Local panel construction
- **`backfill`** — Fetch the daily fund panel or monthly allocation across a date range into the local store.

  _Publication timing is what makes a variable admissible as ex-ante, so every row records when it was actually observed._

  ```bash
  mufap-pp-cli backfill daily --from 2026-09-03 --to 2026-09-03
  ```
- **`panel`** — Query the stored daily NAV and return panel by date, fund, category or sector.

  _Turns 21 years of single-date HTML pages into one queryable table._

  ```bash
  mufap-pp-cli panel --category Equity --from 2026-09-01 --to 2026-09-04 --agent --select date,fund,nav
  ```
- **`coverage`** — Show which dates were fetched, which returned zero rows, and which were never attempted.

  _Distinguishes fetched-and-empty from never-fetched, so a gap is never mistaken for a zero._

  ```bash
  mufap-pp-cli coverage --resource daily-returns --from 2026-09-01 --to 2026-09-04 --agent
  ```
- **`dump`** — Dump the stored panel, allocation or coverage tables as JSONL for piping, a single JSON array for agents, or CSV.

  _Lets an analysis pipeline consume the panel directly instead of scraping._

  ```bash
  mufap-pp-cli dump daily-returns --from 2026-09-01 --to 2026-09-04 --format json --agent
  ```

### Trust the numbers
- **`verify allocation`** — Check that fund asset-class percentages net to 100 and flag months whose percent columns are unpopulated.

  _A month that fails the invariant is corrupt input, not a weak signal, and must be excluded before modelling._

  ```bash
  mufap-pp-cli verify allocation --month 2026-07 --max-amcs 1 --agent
  ```
- **`universe`** — Report how many funds reported on each date, by sector and category.

  _A silently narrowing universe fakes verdicts, so width is printed alongside every cross-sectional result._

  ```bash
  mufap-pp-cli universe --from 2026-09-01 --to 2026-09-04 --agent
  ```
- **`freshness`** — Detect funds whose published NAV validity date lags the requested date.

  _Differencing the live view without this check manufactures returns that never happened._

  ```bash
  mufap-pp-cli freshness --agent
  ```

## Command Reference

**allocation** — Per-fund monthly asset allocation in PKR millions and percent

- `mufap-pp-cli allocation --fund-code <int> --month <M-YYYY>` — Asset allocation for one fund in one month.

  `--fund-code` takes the **integer** `fund` field from `funds by-amc`, never the FundID GUID (a GUID returns HTTP 500). `--month` is required and is M-YYYY: not zero-padded, not ISO.

  ```bash
  mufap-pp-cli allocation --fund-code 12766 --month 7-2026 --json
  ```

**amcs** — Asset management companies (AMCs) registered with MUFAP

- `mufap-pp-cli amcs` — List the 27 asset management companies MUFAP tracks.

**dates** — Reporting periods MUFAP has published

- `mufap-pp-cli dates` — List the Year/Month periods MUFAP has published industry statistics for.

**funds** — Funds managed by an AMC, with category and pricing mechanism

- `mufap-pp-cli funds` — List funds for one AMC (AMCId is the GUID from `amcs list`)

**payouts** — Announced fund payouts and distributions

- `mufap-pp-cli payouts` — List announced fund payouts and distributions, with the per-unit amount and the ex-NAV the payout is struck against.

**unitholders** — Unit-holder pattern by investor type and sector

- `mufap-pp-cli unitholders --year <YYYY>` — Unit-holder pattern for one calendar year

**netsales** — Monthly industry flow, headline and by investor class

- `mufap-pp-cli netsales monthly --month <1-12> --year <YYYY>` — Sales, redemptions and net sales by sector and category in PKR millions, with the sheet's Total row returned separately as an invariant and reconciled under a rounding-aware bound.
- `mufap-pp-cli netsales investor --month <1-12> --year <YYYY>` — The same month across the nine investor classes. **Partial slice: no net column, and it failed to reconcile against the headline total in 19 of 25 measured months. Never apportion the industry total with it.**

**vps** — Voluntary Pension Scheme breakdowns

- `mufap-pp-cli vps age-wise` — VPS allocation broken down by contributor age band
- `mufap-pp-cli vps retired-cash` — Break down Voluntary Pension Scheme assets held as retired cash.
- `mufap-pp-cli vps withdrawals` — Report cash withdrawn from Voluntary Pension Scheme funds.

**Do not use `import`.** It appears in `mufap-pp-cli --help` as "Import data from JSONL file via API create/upsert calls", but MUFAP publishes no write surface: it is inert generator scaffolding whose own examples are placeholders (`import <resource> --input data.jsonl`). There is no endpoint behind it.


### Finding the right command

When you know what you want to do but not which command does it, ask the CLI directly:

```bash
mufap-pp-cli which "<capability in your own words>"
```

`which` scores a natural-language capability query against this CLI's curated feature index by term overlap. Exit code `0` means at least one entry shared a term with the query; exit code `2` means nothing matched — fall back to `--help` or use a narrower query. **There is no confidence floor**, so exit 0 is not proof the CLI serves the question: `which "book a flight to paris"` exits 0 with `exposure` at score 2. Read the returned `score` and the entry's description, and confirm the command actually answers the ask before acting on it. `--json` (and other machine formats) keep the exit-2 contract and write `{"matches":[]}` on stdout so agents can inspect the envelope without treating a miss as success.

## Recipes

### Build the short-rate series

```bash
mufap-pp-cli rates --from 2026-09-01 --to 2026-09-04 --agent --select date,median_yield,fund_count
```

Returns one row per date with the cross-sectional median money-market yield and the number of funds behind it, so a thin date is visible rather than silently averaged.

### Industry equity exposure by month

```bash
mufap-pp-cli exposure --from 2026-07 --to 2026-07 --max-amcs 1 --agent
```

Sums the PKR stocks-and-equities column across every fund, giving the asset-side series to pair against mutual-fund net flows.

### Audit a month before trusting it

```bash
mufap-pp-cli verify allocation --month 2026-07 --max-amcs 1 --agent
```

Flags funds whose asset-class percentages do not net to 100 after subtracting liabilities, which the site's own 100% label hides.

### Check universe width before any cross-section

```bash
mufap-pp-cli universe --from 2026-09-01 --to 2026-09-04 --agent --select date,fund_count
```

Prints how many funds reported per date so a narrowing universe cannot fake a result.

### Export the panel for external analysis

```bash
mufap-pp-cli dump daily-returns --from 2026-09-01 --to 2026-09-04 --format jsonl
```

Streams the stored panel as newline-delimited JSON for loading into a research database without re-fetching.

## Data model gotchas

Properties of MUFAP's published tables, each one measured during discovery rather than assumed.
Do not simplify them away.

- **Negatives are written in accounting notation, never with a minus sign.** `"(4.97)"` is -4.97. On 2026-09-04, tab=returns, 96 of 388 rows (24.7%) carried a parenthesised YTD and **zero** rows carried a leading minus. `rates`, `dispersion`, `panel`, `exposure` and `dump` decode it; `--raw-values` on `panel`/`dump` keeps MUFAP's text verbatim. A parser that skips unparseable cells removes exactly the left tail: before this was decoded, equity YTD dispersion on 2026-09-03 read median **+3.15** over 17 funds instead of **-3.70** over 91 — the sign of the market was inverted.
- **The row key is `Sector | Category | Fund Name`, not the fund name.** Fund names are not unique within a date: 49 of 388 rows on 2026-09-04 (12.6%) collide on name alone, mostly because VPS pension funds legitimately repeat one name across their Money Market, Debt and Equity sub-fund series. `dump` emits the composite as `row_key`; keying your own table on fund name collapses the pension universe with no error raised anywhere.
- **Columns differ per tab.** The fund-name column is `Fund Name` on `--tab returns` and `Fund` on all four other tabs. `--tab payout` has **no** `Validity Date` column at all — its date column is `Payout Date`, so `--from`/`--to` select on a different field there. `--tab pricing` and `--tab ter` are current reference data rather than a dated panel: they return 551 rows regardless of the date requested, so their row counts are not a universe width.
- **Percent columns are 0.0 for every month before roughly 2024** while the PKR amount columns stay correct. Derive percentages as amount/Total — which is what `exposure` does — and read `verify allocation`'s UNPOPULATED verdict as "no percentages published", not as a failure.
- **`TotalPercentage` is the literal string `"100%"`**, not a computed check; MUFAP displays a passing invariant it never performs. `verify allocation` computes it.
- **`message: "No data found"` appears even when `data` is fully populated.** Never gate on it; gate on the parsed row count.
- **The net-sales Total row is an invariant, not data.** `netsales monthly` returns SectorId 100 / Sector `Total` separately as `total` and excludes it from `rows`; summing it alongside the others double-counts the whole month.
- **The same VPS figures appear under two pension sector labels.** MUFAP renders identical voluntary-pension rows under both `Pension Funds (Open-End Funds)` and `Employer Pension Funds`. Summing both overstates pension flow by about 13% — 4,680 rows before de-duplication versus 4,072 after. `netsales monthly` drops the duplicates and counts them in `vps_duplicates_dropped`.
- **On the net-sales pages a dash means MISSING, not zero — the opposite of the daily tables' convention and of CDC's.** A category showing `-` did not report; it is not a category that reported zero. Those figures come back as `null`, never `0`. For 2026-05, 33 of 40 rows are non-reporting while the month's total flow is 9,458m.
- **Reconciling net sales needs a rounding-aware tolerance, not a fixed one.** MUFAP renders whole PKR millions, so summing n rows against a rounded total carries up to ±0.5·(n+1) of rounding error. Across the 25 months that carry a Total row every residual is ≤ 2.0 while the bound ranges 3.0–7.5, so **all 25 reconcile**; a fixed ±1.5 tolerance falsely fails three of them (2025-12, 2026-01, 2026-04). Both the sales and the redemption residual must be checked — 2026-04 is exact on sales and off by 2.0 on redemptions.
- **The investor-class feed is a partial slice.** It carries no net column (derive net as sales − redemptions, only where both are present) and its class totals failed to reconcile against the headline month total in 19 of 25 measured months. Use it as a coverage-matched comparison; never to apportion the industry total.
- **Most net-sales months are legitimately empty, and a challenge is not an empty month.** MUFAP serves a rendered page for every (Month, Year) whether or not it published data — 79 of the 104 months from 2018-01 to 2026-08 are empty this way, with a Total row whose cells are all dashes. Cloudflare also challenges these two paths intermittently (3 of 8 sequential requests when measured); that is retried with backoff and then reported as a challenge, never as an empty month.
- **Four date encodings.** `YYYY-MM-DD` for the daily and monthly range flags, `M-YYYY` (not zero-padded, not ISO) for `allocation --month`, `YYYY` for `unitholders --year`, and `Mon DD, YYYY` as displayed in the table. A wrong encoding returns HTTP 200 with an empty table, or HTTP 500 — never an informative error.

## Auth Setup

No credentials. MUFAP sits behind Cloudflare, so the CLI ships a Chrome-fingerprint HTTP transport that clears the challenge without a browser, a clearance cookie, or any login.

Run `mufap-pp-cli doctor` to verify setup.

## Agent Mode

Add `--agent` to any command. Expands to: `--json --compact --no-input --no-color`.

Global format flags share one contract on promoted, novel, sync, and `--deliver` paths:

- `--json` — one JSON document on stdout (sync progress events go to stderr)
- `--compact` — keep identity/status/timestamp fields; does not change the document vs stream shape
- `--csv` / `--plain` — tabular rows (collection envelopes unwrap to the row array)
- `--quiet` — one identity value per row, no envelope

- **Pipeable** — JSON on stdout, errors on stderr
- **Filterable** — `--select` keeps a subset of fields. Dotted paths descend into nested structures; arrays traverse element-wise. Critical for keeping context small on verbose APIs:

  ```bash
  mufap-pp-cli rates --from 2026-09-01 --to 2026-09-04 --agent --select date,median_yield,fund_count
  mufap-pp-cli panel --from 2026-09-01 --to 2026-09-04 --agent --select date,fund,NAV
  ```
- **Previewable** — `--dry-run` shows the request without sending
- **Non-interactive** — never prompts, every input is a flag
- **Read-only** — MUFAP exposes no write 