From e56853c415192590a803d3ba793d6d44c35c267a Mon Sep 17 00:00:00 2001 From: Satyam Rastogi Date: Fri, 29 May 2026 20:15:39 +0530 Subject: [PATCH] docs: add Plan 7 (profiles, skill catalog & AI dispatch context) --- docs/superpowers/2026-05-29-MASTER-PLAN.md | 3 +- ...26-05-29-plan7-profiles-skills-dispatch.md | 154 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-05-29-plan7-profiles-skills-dispatch.md diff --git a/docs/superpowers/2026-05-29-MASTER-PLAN.md b/docs/superpowers/2026-05-29-MASTER-PLAN.md index 8bb661f..2b64f1d 100644 --- a/docs/superpowers/2026-05-29-MASTER-PLAN.md +++ b/docs/superpowers/2026-05-29-MASTER-PLAN.md @@ -527,7 +527,8 @@ Each plan = one phase, produces working/verifiable software on its own, and is e ### Plan 7 — Profiles, skill catalog & AI dispatch context - **What:** two-layer profiles (self + company) per person; the self-service User Details page (all roles); the owner Employee/Subcontractor Details view; the org skill catalog; ProCanvas achievements surfaced; `DISPATCH_REPS` mapped to canonical people; dispatch `reasons`/`aiInsight`/`skillMatch` authored to cite real profile facts. - **Why:** richer structured context yields more believable AI dispatch (investor wow), and profile depth is what a prospect expects from a mature CRM. -- **How:** add `profile`/`skillCatalog`/`skillScores` to people + dispatch-rep↔person mapping in `mockStore.jsx` (sequential); build the User Details page + owner Details view (new component files → parallel); rewrite the pre-authored `DISPATCH_RECOMMENDATIONS` to reference profile facts. Verify: a recommendation's rationale cites a real skill/equipment value from the cited rep's profile. +- **How:** add `profile`/`skillCatalog`/`skillScores` to people + dispatch-rep↔person mapping in `mockStore.jsx` (sequential); build the User Details page + owner Details view (new component files → parallel); rewrite the pre-authored `DISPATCH_RECOMMENDATIONS` to reference profile facts. Verify: a recommendation's rationale cites a real skill/equipment value from the cited rep's profile. (Concretely: the dispatch reps are currently FAKE — REP-01 "Carlos Mendez"/"Nina Patel"/"Derek Walsh", plus fake `canvassedBy` ids and leaderboard names — all remap to canonical people; profiles store a self/company split that Plan 8's ABAC later governs at field level.) +- **Plan file:** `docs/superpowers/plans/2026-05-29-plan7-profiles-skills-dispatch.md`. ### Plan 8 — ABAC access control + unified routing - **What:** an attribute-based policy engine (subject/resource/action/context attributes; ordered permit/deny; deny-wins; default-deny) generalizing the role/override data; `can(action, resource, ctx)` API; a permission-aware route guard; the Option-B unified route scheme (one human-readable route per feature, role landings only); nav hiding + styled Access Restricted page; Org Settings as policy/attribute editor. diff --git a/docs/superpowers/plans/2026-05-29-plan7-profiles-skills-dispatch.md b/docs/superpowers/plans/2026-05-29-plan7-profiles-skills-dispatch.md new file mode 100644 index 0000000..6179242 --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-plan7-profiles-skills-dispatch.md @@ -0,0 +1,154 @@ +# 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 0–100 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 e1–e5 carry `xp`/`streakDays`/`achievements[]`), `MOCK_PERSONNEL` (p1 Jesus, p2 Sarah — `sensitiveData`/`compliance`), `MOCK_SUBCONTRACTORS` (sub_001–005 — `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 e1–e5 and subs sub_001–005 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` (v1–v6, 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 (e1–e5)**, the **2 reps (p1, p2)**, and the **5 subcontractors (sub_001–005)**; owners (own_001/002) and admins (a1–a3) 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 0–100 (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: , 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 (e1–e5)** and **subcontractors (sub_001–005)** 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 (0–100 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 e1–e5 + 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 e1–e5 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.