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

18 KiB
Raw Permalink Blame History

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) + vendorsfield 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):

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_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.jsxnone (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 /profileUserDetailsPage, 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.