---
name: ph_leaderboard
description: >-
  Scrapes Product Hunt Kitty Points people leaderboards (all_time, year, month, week),
  dedupes by profile, and exports company, title, kitten points, @handle, profile URL,
  and rank. Use when the user asks for PH kitty points leaderboard, KP leaderboard,
  community points ranking, or parsing kitty-points URLs—not the product yearly leaderboard.
---

# Product Hunt Kitty Points leaderboards (`ph_leaderboard`)

## Purpose

Collect **ranked people** from Product Hunt **Kitty Points** leaderboards across time scopes, merge runs, **dedupe** by identity, and save a structured table (CSV/JSON). This is **not** the product launch yearly leaderboard (`/leaderboard/yearly/...`).

## Sources (fetch all four)

`period=` is required. `ref=header_nav` may be dropped if redirects normalize.

- `https://www.producthunt.com/kitty-points?ref=header_nav&period=all_time`
- `https://www.producthunt.com/kitty-points?ref=header_nav&period=year`
- `https://www.producthunt.com/kitty-points?ref=header_nav&period=month`
- `https://www.producthunt.com/kitty-points?ref=header_nav&period=week`

## Tooling (mandatory order)

1. **Firecrawl browser** — These pages behave like **infinite-scroll** lists. Open each URL, scroll until **no new rows** appear (same discipline as PH followers/following). Extract row data from the stabilized view or exported snapshot.
2. If Firecrawl browser is blocked, empty, or incomplete after retries → **agent-browser** (headed; user can clear Cloudflare once, then continue in-session).
3. **Firecrawl scrape** on a leaderboard URL **only** if a 1–3 URL pilot proves the **full** list is visible without scroll (uncommon).

Log `firecrawl_method` / fallback in run notes.

## Dedupe

After merging the four period runs:

- Dedupe key: canonical **`https://www.producthunt.com/@<slug>`** or normalized **`@slug`** (single `@`).
- Document merge policy with the user before the long run, for example:
  - **Wide row:** one row per person; columns like `kitten_points_all_time`, `leaderboard_rank_all_time`, … per `period`.
  - **Long format:** rows keyed by `(profile_url, period)`; optional second pass to collapse.

## Required output fields

Map from the live card/UI into columns (empty string when missing):

| Field | Meaning |
| --- | --- |
| `company_name` | Employer / company as shown on the card |
| `job_title` | Role or headline/tagline used as occupation line |
| `kitten_points` | Points string or number for that row’s **period** (preserve `K`/`KC`/`KP` suffixes when ambiguous) |
| `ph_username` | Handle **without** duplicate `@` (store `@handle` if the rest of the pipeline expects it—stay consistent in one file) |
| `ph_profile_url` | `https://www.producthunt.com/@<handle>` |
| `leaderboard_rank` | **1-based** rank on **that period’s** page at scrape time |
| `leaderboard_period` | `all_time` \| `year` \| `month` \| `week` (required in long format; optional in wide) |

## Credits and checkpoints

- Append **Firecrawl** ledger: timestamp, endpoint (`browser` / `scrape`), URL/period, estimate/actual credits, run cumulative total.
- Checkpoint partial CSV/JSON every **50–100** rows or after each period URL completes.
- Do not re-fetch the same period URL in the same run without reason (prior failure or confirmed UI change).

## Validation before delivery

- Row counts plausible vs visible UI.
- No duplicate `ph_profile_url` in the chosen dedupe mode (unless long format intentionally repeats profile across periods).
- UTF-8 CSV; headers aligned with agreed schema.
