# Identify Provider Category

## Purpose

Pick **one** category from the known taxonomy for this provider. The category drives which Layer 2 static use-case file (`use-case-catalog/references/categories/<category>.yaml`) is loaded in Phase 2B.

## Known categories (12)

Aligned with StackOne's unified-API verticals. Example providers shown are real Falcon connectors.

| Category | Existing Falcon connector examples |
|---|---|
| `messaging` | Gmail, Attentive, Braze, Customer.io, Discord, GoogleMeet |
| `crm` | HubSpot, Pipedrive, Affinity, Apollo, Attio, Bullhorn, CapsuleCRM |
| `hris` | BambooHR, Deel, 7shifts, BreatheHR, CascadeHR, CezanneHR, HiBob, Personio |
| `ats` | Ashby, Greenhouse, Lever, BreezyHR, Eploy, Factorial, Workable |
| `ticketing` | Freshdesk, FreshService, Front, Aha, Asana, AzureDevOps, ClickUp |
| `payments` | Stripe, PayPal, Square, GoCardless, QuickbooksOnline |
| `scheduling` | Cal.com, Calendly, GoogleCalendar, OutlookCalendar |
| `marketing` | ActiveCampaign, Airship, BigCommerce, Bitly, CampaignMonitor, Customer.io |
| `documents` | Box, Dropbox, DocuSign, AdobePDFServices, Airtable, Confluence |
| `iam` | 1Password, Auth0, AzureDevOps, Contentful, Databricks |
| `lms` | 360Learning, AdobeLearningManager, Cornerstone, Coursera, Docebo, Go1 |
| `accounting` | ApprovalMax, Chargebee, FreshBooks, FreeAgent, Harvest, QuickbooksOnline, Plaid |

## How matching works

Two-tier approach: deterministic script first, LLM fallback if no match.

### Tier 1 — Deterministic script

```bash
node ${CLAUDE_PLUGIN_ROOT}/scripts/research/identify_category.js <provider> [docs_url]
```

The script applies a keyword-based scoring algorithm:

- Each category has 5–15 known keywords (provider names, domains, vocabulary)
- For each category, count keyword matches in the provider slug and docs URL hostname
- The category with the highest score wins, if score ≥ 2 → confidence `high`
- Score = 1 → confidence `medium`
- Score = 0 (no keyword match) → confidence `unmatched`, fall through to Tier 2

### Tier 2 — LLM fallback (if Tier 1 returns `unmatched`)

If the script reports `unmatched`, the SKILL falls back to the LLM:

1. Fetch the provider's docs landing page
2. Read the title, the first paragraph, and the navigation labels
3. Pick the closest match from the 12-category taxonomy
4. Set `confidence: low` so reviewers know this was an inference
5. If genuinely no category fits, set `category_new_suggestion: <proposed_name>` — the maintainer can add a new category file in a future PR

## Output file

**Path:** `.connector-dev/<provider>/research/provider_category.yaml`

```yaml
provider: gmail
category: messaging
confidence: high              # high | medium | low | unmatched
matched_signals:
  - "provider slug contains 'gmail'"
  - "docs URL hostname matches developers.google.com/workspace/gmail"
matched_static_catalog: messaging.yaml
category_new_suggestion: null
notes: null
investigated_at: <ISO timestamp>
```

## Field rules

- `provider` — slug used in all file paths
- `category` — one of the 12 known values
- `confidence` — `high` (≥2 keyword matches), `medium` (1 match), `low` (LLM inferred), `unmatched` (no category fits — only when proposing a new category)
- `matched_signals` — list of signals that contributed to the match; empty for LLM-only matches
- `matched_static_catalog` — filename of the Layer 2 catalog file to load (e.g. `messaging.yaml`)
- `category_new_suggestion` — set ONLY when `confidence: unmatched`; proposed snake_case category name
- `notes` — optional LLM commentary explaining the decision (Tier 2 only)

## When confidence is `unmatched`

Phase 2B (load static catalog) skips loading the Layer 2 file (none exists). Phase 2D (discover-dynamic-use-cases) compensates by producing a richer Layer 3 catalog from scratch. Phase 2E (classification) still works because the intrinsic role classifier doesn't depend on Layer 2 — APIs still get correctly tiered, just with fewer use-case mappings.

The `category_new_suggestion` is logged to `_index.md` so the maintainer can later create a static category file by PR.

## Anti-Patterns

- ❌ Pick a category outside the 12 known values without setting `category_new_suggestion`
- ❌ Set `confidence: high` when the script returned `unmatched` and the LLM made an inference
- ❌ Skip writing `provider_category.yaml` — downstream phases require it (even if `confidence: unmatched`)
- ❌ Try to assign multiple categories — single category per provider (v1 design constraint)
