---
title: "Specialist match"
description: "/n9/specialists scores public practitioner entities from charts_by_system only. L3-only, deterministic, no LLM, no vault text."
locale: "en"
---
# Specialist match

`/n9/specialists` is a clone-only page under `app/[locale]/n9/layout.tsx` (ModuleShell, `LoginGateCta` like the vault). It is **not** an L1 opportunity type and not the AI matcher. There is no LLM.

## Flow

1. Member writes a need and optionally chips preferred systems.
2. `POST /api/n9life/match` (`auth()` + `connection()`) runs `detectCrisisKeywords`. Crisis → **422** `crisis_pause` with the ethics response. The vault is not read.
3. `deriveSignals(profile)` builds labeled facts from **`charts_by_system` only**: `life_path`, `matrix_center`, `hall_id`, `hd_type`, `hd_authority`, `ready_systems`.
4. `findSpecialists` loads public entities with `db().queryDocs` on collection `entities`, types from `NUMEROLOGY_CATEGORY_ENTITY_TYPES` **`.id` values** (`numerologist`, `human_design_analyst`, …), `visibility == public`. Using `Object.keys` of that preset would send `NUMEROLOGIST` and match nothing.
5. Score is a weighted sum from `features/n9life/specialist-match-weights.json`, overridable by `ring-config.n9life.specialistMatch.weights`.
6. Top **N** (`topN` default 8) return with keyed reason codes. Rows persist on `n9life_match_requests` (owner scoped). `GET` lists own requests (limit 50). `DELETE` closes.

## Weights

| Key | Default | Meaning |
|-----|---------|---------|
| `systemFit` | 0.5 | Catalog type fits ready or selected systems |
| `keywordOverlap` | 0.3 | Need-text tokens vs entity tags, services, short description |
| `locationOrRemote` | 0.1 | Remote/online or stated location, weak tie-break |
| `verifiedOrMembership` | 0.1 | Verified listing or active membership, not a ranking of the person |

`systemToEntityType` examples: numerology → `numerologist`, `astrologer`, `spiritual_coach`; Human Design → `human_design_analyst`, `spiritual_coach`.

Reason codes: `system_fit`, `keyword_overlap`, `location_or_remote`, `verified_or_membership`. The results list links with next-intl `{ pathname: '/entities/[id]', params: { id } }` (a template string `/entities/${id}` fails the typed `Link` href).

## What you will see empty

Match returns no hits until public practitioner entities exist in those types. That is data, not a bug in the scorer.

## Owner notify (skipped)

`notifyThreshold` `0.75` is in config. L1 `createNotification` expects `CreateNotificationRequest` plus a `NotificationType`. There is no specialist-match type. Member-side results ship without notifying the entity owner.

> **Warning**
> Need text is the member's request, not the vault. `specialist-match.ts` must not import aspirations. Signals JSON stored on the request is chart-derived.

  

  
- [n9life/cosmic-mirror](/docs/n9life/cosmic-mirror.md) — Same-workflow: panel CTA Find a specialist

  
- [n9life/chart-and-inputs](/docs/n9life/chart-and-inputs.md) — Depends-on: ready systems on the profile

  
- [n9life/vault-and-privacy](/docs/n9life/vault-and-privacy.md) — Depends-on: vault is never a signal
