18 KiB
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 carryxp/streakDays/achievements[]),MOCK_PERSONNEL(p1 Jesus, p2 Sarah —sensitiveData/compliance),MOCK_SUBCONTRACTORS(sub_001–005 —tradeType/rating),MOCK_OWNERS(company dashboards). Noprofile,skillScores,bio,equipment,pronouns,nicknameon anyone; noSKILL_CATALOG. - Self-edit page: only
CustomerProfile.jsx(/portal/profile, CUSTOMER only) edits name/email/phone/address viaupdateProfile()inAuthContext. No User Details page for employee/owner/admin/contractor/subcontractor. - Owner person view:
PeopleDirectory.jsx(/owner/people) lists onlypersonnel(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.jsxshowsMOCK_VENDORS(v1–v6, separate from subs). - Gamification:
GamificationContext.jsxis a single globalINITIAL_STATE(xp 3360, level 12, streak 4 …) — NOT per-person. Field agents'xp/streakDays/achievementsin MOCK_USERS are static + disconnected.LEADERBOARD_MOCKhas 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.jsxshows factor bars + aiInsight + reasons chips).skillMatchis a static integer, not derived.DISPATCH_LEADS_INITIALhas fakecanvassedBy: {agentId:'U-05', name:'Marcus Webb'}/'U-07' Priya Nair.DISPATCH_WEEKLY_STATS.repBreakdownkeyed by REP-##. - Skills: only
DISPATCH_REPS[].skills = ['insurance_claim','retail_estimate',…](plain strings). No catalog, no scores. - Routes: no
/profile//settings//user-detailsfor non-customer roles; room for a universal profile route and a/owner/people/:personIddetail.
Schemas (target — per Appendix A.3)
Org-level skill catalog (in mockStore.jsx):
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):
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_CATALOGconstant (above) near the other exported constants. Expose it on the store context value asskillCatalog. - Step 2: Add a
profileobject (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 lighterself+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), andcompany.skillScoresreferencingSKILL_CATALOGids 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 getlicense/insurance/w9Status. Addcompany.statusTags(most 'preferred'; one 'probation' for variety),payTerms,hireDate,managerId. - Step 3: Populate
profile.achievementsfor the field agents from their existingMOCK_USERSfields —{ 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'sprofile(merged onto their base identity fromresolvePerson), or a sensible empty default.updateOwnProfile(personId, selfPatch)— shallow-merges intoprofile.self(used by the self-service page).setSkillScore(personId, skillId, score)— upserts acompany.skillScoresentry.addCatalogSkill(skill)/updateCatalogSkill(id, patch)— manageSKILL_CATALOG(owner). Follow the existingupdateProject-stylesetX(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. aprofilesstate map keyed by personId, seeded from the people, so edits don't require mutating four different arrays). Exposeprofiles+ the mutators.
- Step 5 (verify):
npm run build→✓ built, 0 duplicate-key warnings.git grep -c "skillScores" -- src/data/mockStore.jsx≥ 12;SKILL_CATALOGexported;resolveProfile/updateOwnProfile/setSkillScoreon 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_REPSto canonical people. For each REP-##, setpersonIdto 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 setname/initialsto that person; replace theskills: [...]string array with the catalog skill ids the person scores highest on (from theirprofile.company.skillScores). Keep lat/lng/capacity/rating/closeRate. (Keepid: 'REP-##'if changing it is risky, but addpersonId; OR switchidto 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.repBreakdownkeys → re-key to the canonical person/rep ids used in Step 1. - Step 3 — ground
DISPATCH_RECOMMENDATIONS: rewrite each recommendation'sreasons[],stormReasons[],aiInsight, andfactors.skillMatchso they cite the cited rep's real profile facts:factors.skillMatch= the rep's actualskillScorefor the skill the lead needs (e.g. an insurance-claim lead → the rep'ssk_insurance_claimscore).reasons[]include a fact-citing chip, e.g."Insurance Claim 92/100 — top-rated for carrier work","Carries a tarp + drone on the truck"(fromprofile.self.equipment).aiInsightreferences 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 OWNprofile.self. PullresolveProfile(user.id)+updateOwnProfilefrom 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, callupdateOwnProfile(user.id, patch). Show the read-onlyachievements(level/xp/streak/badges) if present. Do NOT show thecompanylayer (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) →/profileshows 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
selfprofile (bio, contact, equipment, specialties, availability, achievements badges) AND thecompanylayer (internalNotes, statusTags, payTerms, hireDate, manager) AND a Skill Scores table — eachSKILL_CATALOGskill 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 editinternalNotes/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_MOCKnames (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 theirMOCK_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 buildclean, 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; theskillMatchvalue and any skill/equipment named inreasons/aiInsightEXIST in the cited rep'sprofile.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
profilewith skillScores;SKILL_CATALOGexported + editable in Org Settings. - Self page: a field agent can open
/profile, edit their self layer (nickname/equipment), save, and see it persist; thecompanylayer 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-layerprofile(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.