Files
LynkedUpPro_CRM/docs/superpowers/plans/2026-05-29-plan7-profiles-skills-dispatch.md
T

155 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan 7 — Profiles, Skill Catalog & AI Dispatch Context
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). Follow the execution protocol (`2026-05-29-execution-protocol.md`) — sequential on shared files, parallel only where lanes are disjoint.
**Goal:** Give every person a **two-layer profile** (self-edited + company-private), an **org skill catalog** with per-person 0100 scores, surface **ProCanvas achievements**, and **ground LynkDispatch** — remap the fake `DISPATCH_REPS` to canonical people and rewrite each recommendation's `reasons`/`aiInsight`/`skillMatch` to **cite real profile facts** (equipment + skill scores), so "the AI read their profile" is demonstrably true on screen.
**Architecture:** Add a `profile` object (`self` / `company` / `achievements`) to the canonical people in `mockStore.jsx`, plus an org-level `SKILL_CATALOG` and per-person `skillScores`, exposed via the store (a `resolveProfile(id)` selector + `updateOwnProfile`/`setSkillScore`/`addCatalogSkill` mutators). Then remap dispatch reps to canonical field agents and rewrite the pre-authored recommendations to reference those people's real profile facts. UI: a self-service **User Details** page (all non-customer roles edit their `self` layer), an owner **Person Details** view (sees `self` + `company` + skill scores + achievements, edits scores/notes), and a **Skill Catalog editor** in Org Settings. Plan 8 (ABAC) later governs who can see the `company` layer; Plan 7 gates it with a simple role check.
**Tech stack:** React 18, Vite 7, mock React-context store, pnpm. No test harness → verify via `npm run build` + grep + dev-run (a dispatch recommendation's rationale must quote a real skill/equipment value from the cited rep's profile).
**Spec/master:** `docs/superpowers/2026-05-29-MASTER-PLAN.md` §13 (profiles/skills/AI dispatch), Appendix A.3 (person & profile), A.7 (dispatch), Appendix G Plan 7. Builds on the canonical roster (Plan 1).
---
## Current state (verified)
- **People** split across: `MOCK_USERS` (login; field agents e1e5 carry `xp`/`streakDays`/`achievements[]`), `MOCK_PERSONNEL` (p1 Jesus, p2 Sarah — `sensitiveData`/`compliance`), `MOCK_SUBCONTRACTORS` (sub_001005 — `tradeType`/`rating`), `MOCK_OWNERS` (company dashboards). **No `profile`, `skillScores`, `bio`, `equipment`, `pronouns`, `nickname` on anyone; no `SKILL_CATALOG`.**
- **Self-edit page:** only `CustomerProfile.jsx` (`/portal/profile`, CUSTOMER only) edits name/email/phone/address via `updateProfile()` in `AuthContext`. **No User Details page for employee/owner/admin/contractor/subcontractor.**
- **Owner person view:** `PeopleDirectory.jsx` (`/owner/people`) lists only `personnel` (p1/p2) + `vendors`**field agents e1e5 and subs sub_001005 don't appear**. Detail panel shows email/phone/masked SSN/compliance; "Edit Profile"/"Request Document" are **stubs**. No skill scores / internal notes anywhere. `VendorDirectory.jsx` shows `MOCK_VENDORS` (v1v6, separate from subs).
- **Gamification:** `GamificationContext.jsx` is a single **global** `INITIAL_STATE` (xp 3360, level 12, streak 4 …) — NOT per-person. Field agents' `xp`/`streakDays`/`achievements` in MOCK_USERS are static + disconnected. `LEADERBOARD_MOCK` has fake names (Alex M., Jessica R., …).
- **LynkDispatch:** `DISPATCH_REPS` (mockStore ~line 4645) = **REP-01…05 with FAKE names** (Carlos Mendez, Aisha Kumar, Tom Bradley, Nina Patel, Derek Walsh) — **zero link to canonical people**. `DISPATCH_RECOMMENDATIONS` (~4842) have `{repId, score, slotStart/End, distanceMi, factors{proximity,driveTime,calendarFit,weather,skillMatch}, reasons[], stormReasons[], aiInsight, conflictFlag, routePath}` — rendered fully on screen (`DispatchRecommendationDrawer.jsx` shows factor bars + aiInsight + reasons chips). `skillMatch` is a static integer, not derived. `DISPATCH_LEADS_INITIAL` has fake `canvassedBy: {agentId:'U-05', name:'Marcus Webb'}` / `'U-07' Priya Nair`. `DISPATCH_WEEKLY_STATS.repBreakdown` keyed by REP-##.
- **Skills:** only `DISPATCH_REPS[].skills = ['insurance_claim','retail_estimate',…]` (plain strings). No catalog, no scores.
- **Routes:** no `/profile`/`/settings`/`/user-details` for non-customer roles; room for a universal profile route and a `/owner/people/:personId` detail.
---
## Schemas (target — per Appendix A.3)
Org-level skill catalog (in `mockStore.jsx`):
```js
export const SKILL_CATALOG = [
{ id: 'sk_insurance_claim', name: 'Insurance Claim Handling', description: 'Files & negotiates carrier claims/supplements.' },
{ id: 'sk_roof_inspection', name: 'Roof Inspection', description: 'Damage assessment, measurements, documentation.' },
{ id: 'sk_emergency_tarp', name: 'Emergency Tarp', description: 'Rapid storm tarp / water mitigation.' },
{ id: 'sk_retail_estimate', name: 'Retail Estimate', description: 'Cash/retail bid preparation & close.' },
{ id: 'sk_electrical', name: 'Electrical', description: 'Panel/wiring/EV per NEC.' },
{ id: 'sk_drone_survey', name: 'Drone Survey', description: 'Aerial roof imaging & measurement.' },
{ id: 'sk_customer_comms', name: 'Customer Communication', description: 'Homeowner rapport & expectation-setting.' },
];
```
Per-person `profile` (added to the canonical people):
```js
profile: {
self: { // person edits; owner can view
preferredName, photo, phone, altContact, address, emergencyContact,
nickname, pronouns, gender, sex, // all optional
bio, yearsExperience, languages: [], specialties: [], equipment: [],
availability, serviceAreas: [],
// subcontractors also:
license, insurance: { coi, expiry }, w9Status,
},
company: { // owner/admin only
internalNotes, statusTags: [], // 'preferred' | 'probation' | 'do-not-use' | ...
skillScores: [{ skillId, score }], // score = 0..100 or 'N/A'
payTerms, hireDate, managerId,
},
achievements: { level, xp, streak, badges: [], tiers: [] }, // employees; from MOCK_USERS gamification
}
```
---
## Lane / ownership map
| Task | Lane (exclusive files) | Phase |
|------|------------------------|-------|
| T1 SKILL_CATALOG + profiles + store API | `src/data/mockStore.jsx` | A (seq) |
| T2 Dispatch remap + ground recommendations | `src/data/mockStore.jsx` | A (seq, after T1) |
| T3 Self-service User Details page | new `src/pages/UserDetailsPage.jsx`, `src/App.jsx` | B (parallel) |
| T4 Owner Person Details view | `src/pages/owner/PeopleDirectory.jsx` | B (parallel) |
| T5 Skill Catalog editor + leaderboard names | `src/pages/owner/OrgSettings.jsx`, `src/context/GamificationContext.jsx` | B (parallel) |
| T6 Verify | (read-only) | C |
**Phase A (T1, T2) sequential** — both edit `mockStore.jsx` (T2's dispatch grounding cites T1's profile data). **Phase B (T3, T4, T5) parallel** — disjoint files (T3 owns App.jsx + the new page). **T6** last.
---
## Task 1: Skill catalog + profiles + store API
**Files:** `src/data/mockStore.jsx` only.
- [ ] **Step 1:** Add the `SKILL_CATALOG` constant (above) near the other exported constants. Expose it on the store context value as `skillCatalog`.
- [ ] **Step 2:** Add a `profile` object (per the schema above) to each canonical person who needs one — at minimum the **5 field agents (e1e5)**, the **2 reps (p1, p2)**, and the **5 subcontractors (sub_001005)**; owners (own_001/002) and admins (a1a3) get a lighter `self`+`company` (no skillScores required but allowed). Author realistic values: bios, `yearsExperience`, `languages`, `equipment` (e.g. Cody Tatum: `['ladder rack','drone','tarp kit']`; Carlos Mendoza: `['tarp','panel tester','bucket truck']`), `serviceAreas` (Plano zips), and `company.skillScores` referencing `SKILL_CATALOG` ids with 0100 (or `'N/A'`) — make the scores tell a story (e.g. Cody 92 emergency_tarp + 88 insurance_claim; Maya Patel high roof_inspection). Subs get `license`/`insurance`/`w9Status`. Add `company.statusTags` (most 'preferred'; one 'probation' for variety), `payTerms`, `hireDate`, `managerId`.
- [ ] **Step 3:** Populate `profile.achievements` for the field agents from their existing `MOCK_USERS` fields — `{ level: <derive from xp>, xp, streak: streakDays, badges: achievements, tiers: [] }`. (Use the GamificationContext level-title thresholds for a level number, or store xp and let the UI derive.)
- [ ] **Step 4 — store API:** add to the context value:
- `resolveProfile(personId)` — returns the person's `profile` (merged onto their base identity from `resolvePerson`), or a sensible empty default.
- `updateOwnProfile(personId, selfPatch)` — shallow-merges into `profile.self` (used by the self-service page).
- `setSkillScore(personId, skillId, score)` — upserts a `company.skillScores` entry.
- `addCatalogSkill(skill)` / `updateCatalogSkill(id, patch)` — manage `SKILL_CATALOG` (owner). Follow the existing `updateProject`-style `setX(prev => …)` pattern; store people in a state array if not already (profiles live on the existing people arrays — pick ONE canonical place to store the editable profile, e.g. a `profiles` state map keyed by personId, seeded from the people, so edits don't require mutating four different arrays). Expose `profiles` + the mutators.
- [ ] **Step 5 (verify):** `npm run build``✓ built`, 0 duplicate-key warnings. `git grep -c "skillScores" -- src/data/mockStore.jsx` ≥ 12; `SKILL_CATALOG` exported; `resolveProfile`/`updateOwnProfile`/`setSkillScore` on the context value.
- [ ] **Step 6:** commit `git commit -am "feat(data): add org skill catalog + two-layer person profiles + profile store API"`.
## Task 2: Remap dispatch reps + ground recommendations in profile facts
**Files:** `src/data/mockStore.jsx` only. **After T1.**
- [ ] **Step 1 — remap `DISPATCH_REPS`** to canonical people. For each REP-##, set `personId` to a field agent (REP-01→e1 Cody Tatum, REP-02→e2 Hannah Reyes, REP-03→e3 Travis Boone, REP-04→e4 Shelby Greer, REP-05→e5 Dalton Pruitt) and set `name`/`initials` to that person; replace the `skills: [...]` string array with the catalog skill ids the person scores highest on (from their `profile.company.skillScores`). Keep lat/lng/capacity/rating/closeRate. (Keep `id: 'REP-##'` if changing it is risky, but add `personId`; OR switch `id` to the person id — your call, just make recommendations resolve.)
- [ ] **Step 2 — fix other fake ids:** `DISPATCH_LEADS_INITIAL.canvassedBy` (`U-05 Marcus Webb`, `U-07 Priya Nair`) → canonical field agents (e.g. e1 Cody Tatum, e2 Hannah Reyes). `DISPATCH_WEEKLY_STATS.repBreakdown` keys → re-key to the canonical person/rep ids used in Step 1.
- [ ] **Step 3 — ground `DISPATCH_RECOMMENDATIONS`:** rewrite each recommendation's `reasons[]`, `stormReasons[]`, `aiInsight`, and `factors.skillMatch` so they **cite the cited rep's real profile facts**:
- `factors.skillMatch` = the rep's actual `skillScore` for the skill the lead needs (e.g. an insurance-claim lead → the rep's `sk_insurance_claim` score).
- `reasons[]` include a fact-citing chip, e.g. `"Insurance Claim 92/100 — top-rated for carrier work"`, `"Carries a tarp + drone on the truck"` (from `profile.self.equipment`).
- `aiInsight` references a concrete fact: e.g. `"Cody scores 92 on Emergency Tarp and carries a tarp kit — best fit for a storm-window job."`
Each recommendation must be traceable: the number/equipment it cites must EXIST in the cited rep's profile (this is the acceptance test).
- [ ] **Step 4 (verify):** `npm run build``✓ built`. `git grep -n "Nina Patel\|Derek Walsh\|Carlos Mendez\|Marcus Webb\|Priya Nair" -- src/data/mockStore.jsx`**none** (fake names gone). Spot-check 2 recommendations: the skill score / equipment cited matches the rep's profile.
- [ ] **Step 5:** commit `git commit -am "feat(dispatch): map dispatch reps to canonical people; ground recommendations in real profile facts"`.
## Task 3: Self-service User Details page
**Files:** Create `src/pages/UserDetailsPage.jsx`; edit `src/App.jsx`. **Phase B (parallel).**
- [ ] **Step 1:** Build `UserDetailsPage.jsx` — the logged-in person (`useAuth().user`) edits THEIR OWN `profile.self`. Pull `resolveProfile(user.id)` + `updateOwnProfile` from the store. Sections (editable): **Identity** (preferredName, nickname, pronouns, gender, sex — all optional, clearly labelled optional; photo as a URL/objectURL upload), **Contact** (phone, altContact, address, emergencyContact), **About** (bio, yearsExperience, languages, specialties, equipment — equipment as add/remove chips), **Availability** (availability, serviceAreas). For subcontractors also show **Credentials** (license, insurance COI + expiry, w9Status). On save, call `updateOwnProfile(user.id, patch)`. Show the **read-only** `achievements` (level/xp/streak/badges) if present. Do NOT show the `company` layer (that's owner-only). Match app dark styling.
- [ ] **Step 2 (routes):** import the page; add a universal route `/profile``UserDetailsPage`, gated to all internal roles `['OWNER','ADMIN','FIELD_AGENT','CONTRACTOR','SUBCONTRACTOR']` (customers keep `/portal/profile`). Add a nav/menu entry if there's an obvious place (e.g. the user dropdown) — optional.
- [ ] **Step 3 (verify):** `npm run build``✓ built`. Dev-run as a field agent (login a field agent) → `/profile` shows their profile; editing nickname/equipment and saving persists in session.
- [ ] **Step 4:** commit `git commit -am "feat(profile): self-service User Details page for all internal roles"`.
## Task 4: Owner Person Details view
**Files:** `src/pages/owner/PeopleDirectory.jsx` only. **Phase B (parallel).**
- [ ] **Step 1:** Expand the directory's people source to include the **field agents (e1e5)** and **subcontractors (sub_001005)** alongside `personnel` — pull from the store (the people arrays / `users`) so the owner sees the whole roster, grouped or filterable by role.
- [ ] **Step 2:** Build/extend the detail panel to a full **Person Details** view showing BOTH layers (owner is authorised): the `self` profile (bio, contact, equipment, specialties, availability, achievements badges) AND the `company` layer (internalNotes, statusTags, payTerms, hireDate, manager) AND a **Skill Scores** table — each `SKILL_CATALOG` skill with the person's score (0100 bar or 'N/A').
- [ ] **Step 3 (owner editing):** let the owner **set skill scores** (a number input / slider per catalog skill → `setSkillScore(personId, skillId, value)`) and edit `internalNotes`/`statusTags`. Wire the previously-stub "Edit"/notes affordances. (Skill catalog DEFINITION lives in Org Settings — T5; here the owner just scores people against the catalog.)
- [ ] **Step 4 (verify):** `npm run build``✓ built`. Dev-run as owner → People shows e1e5 + subs; opening a person shows self+company+skill scores+achievements; setting a score persists and is reflected (and would feed dispatch).
- [ ] **Step 5:** commit `git commit -am "feat(owner): Person Details view — full roster, two-layer profile, skill scoring"`.
## Task 5: Skill Catalog editor + canonical leaderboard
**Files:** `src/pages/owner/OrgSettings.jsx`, `src/context/GamificationContext.jsx`. **Phase B (parallel).**
- [ ] **Step 1 (OrgSettings):** add a **Skill Catalog** section where the owner manages `skillCatalog` — list each skill (name + description), add a new skill (`addCatalogSkill({ id, name, description })`), and edit existing (`updateCatalogSkill`). Match the OrgSettings styling/section pattern.
- [ ] **Step 2 (GamificationContext):** replace the fake `LEADERBOARD_MOCK` names (Alex M., Jessica R., …) with the canonical field agents (Cody Tatum, Hannah Reyes, Travis Boone, Shelby Greer, Dalton Pruitt) and plausible XP values consistent with their `MOCK_USERS.xp`. Keep the logged-in-user substitution behavior.
- [ ] **Step 3 (verify):** `npm run build``✓ built`. Owner can add a skill in Org Settings (appears in the catalog + as a scorable row on Person Details). Leaderboard shows canonical names.
- [ ] **Step 4:** commit `git commit -am "feat(skills): org skill-catalog editor; canonicalize ProCanvas leaderboard names"`.
## Task 6: Verify (read-only)
- [ ] `npm run build` clean, 0 duplicate-key warnings.
- [ ] **No fake people left in dispatch/gamification:** `git grep -nE "Nina Patel|Derek Walsh|Carlos Mendez|Aisha Kumar|Tom Bradley|Marcus Webb|Priya Nair|Alex M\.|Jessica R\." -- src` → none.
- [ ] **Dispatch grounded (the headline acceptance):** pick 2 `DISPATCH_RECOMMENDATIONS`; the `skillMatch` value and any skill/equipment named in `reasons`/`aiInsight` EXIST in the cited rep's `profile.company.skillScores` / `profile.self.equipment`. Dev-run: open LynkDispatch → a recommendation card shows a canonical rep + a rationale that quotes a real number ("Emergency Tarp 92/100 · carries a tarp").
- [ ] **Profiles:** ≥ 12 people have `profile` with skillScores; `SKILL_CATALOG` exported + editable in Org Settings.
- [ ] **Self page:** a field agent can open `/profile`, edit their self layer (nickname/equipment), save, and see it persist; the `company` layer is NOT shown to them.
- [ ] **Owner view:** owner Person Details shows self+company+skill scores+achievements for e1e5 and subs; setting a skill score persists.
---
## Self-review checklist
- [ ] `SKILL_CATALOG` + per-person two-layer `profile` (self/company/achievements) added; store API (`resolveProfile`/`updateOwnProfile`/`setSkillScore`/`addCatalogSkill`) exposed.
- [ ] Dispatch reps mapped to canonical field agents; recommendations cite real skill scores + equipment; all fake names gone (dispatch, canvassedBy, weekly stats, leaderboard).
- [ ] Self-service User Details page (all internal roles) edits the self layer only; achievements read-only; company layer hidden.
- [ ] Owner Person Details shows the full roster + both layers + skill scoring; Org Settings edits the catalog.
- [ ] Build clean; a dispatch rationale demonstrably quotes a value that exists in the cited rep's profile.
## Notes for Plan 8
Plan 8 (ABAC) replaces the role checks that gate the `company` profile layer + skill scoring with policies (`can(subject, 'view', profile, {field:'company'})`), and the coarse route guards with the policy engine. The `resolveProfile` self/company split here is exactly the field-level visibility ABAC will formalise. Org Settings (skill catalog editor here) becomes the policy/attribute editor too.