import Fuse from 'fuse.js' import { documents } from '@/data/documents' import { investigations } from '@/data/investigations' import { skills } from '@/data/skills' import { kpis } from '@/data/kpis' import { timelineConsultations } from '@/data/timeline' import { getAchievementEntries, getEducationEntries, getSearchQuickActions, } from '@/lib/profile-content' import type { DetailPanelContent } from '@/types/pmr' export type PaletteSection = 'Experience' | 'Core Skills' | 'Significant Interventions' | 'Achievements' | 'Education' | 'Quick Actions' export type PaletteAction = | { type: 'scroll'; tileId: string } | { type: 'expand'; tileId: string; itemId: string } | { type: 'link'; url: string } | { type: 'download' } | { type: 'panel'; panelContent: DetailPanelContent } export type IconColorVariant = 'teal' | 'green' | 'amber' | 'purple' export interface PaletteItem { id: string title: string subtitle: string section: PaletteSection iconVariant: IconColorVariant iconType: 'role' | 'skill' | 'project' | 'achievement' | 'edu' | 'action' keywords: string action: PaletteAction } // Build the full palette dataset matching the concept HTML structure export function buildPaletteData(): PaletteItem[] { const items: PaletteItem[] = [] // Experience — all 4 roles from consultations.ts, open detail panel on select timelineConsultations.forEach((c) => { items.push({ id: `exp-${c.id}`, title: c.role, subtitle: `${c.organization} \u00b7 ${c.duration}`, section: 'Experience', iconVariant: 'teal', iconType: 'role', keywords: `${c.role.toLowerCase()} ${c.organization.toLowerCase()} ${c.duration.toLowerCase()}`, action: { type: 'panel', panelContent: { type: 'career-role', consultation: c } }, }) }) // Core Skills — all ~21 skills from skills.ts, opening detail panel on select skills.forEach((skill) => { items.push({ id: `skill-${skill.id}`, title: skill.name, subtitle: `${skill.frequency} \u00b7 Since ${skill.startYear} \u00b7 ${skill.category}`, section: 'Core Skills', iconVariant: 'green', iconType: 'skill', keywords: `${skill.name.toLowerCase()} ${skill.frequency.toLowerCase()} ${skill.category.toLowerCase()}`, action: { type: 'panel', panelContent: { type: 'skill', skill } }, }) }) // Significant Interventions — all 5 investigations from investigations.ts investigations.forEach((inv) => { items.push({ id: `proj-${inv.id}`, title: inv.name, subtitle: `${inv.methodology.split('.')[0]} \u00b7 ${inv.requestedYear}`, section: 'Significant Interventions', iconVariant: 'amber', iconType: 'project', keywords: `${inv.name.toLowerCase()} ${inv.methodology.toLowerCase()} ${inv.techStack.join(' ').toLowerCase()} ${inv.requestedYear}`, action: { type: 'panel', panelContent: { type: 'project', investigation: inv } }, }) }) // Achievements — open corresponding KPI detail panel getAchievementEntries().forEach((entry, i) => { const kpi = kpis.find(k => k.id === entry.kpiId) items.push({ id: `ach-${i}`, title: entry.title, subtitle: entry.subtitle, section: 'Achievements', iconVariant: 'amber', iconType: 'achievement', keywords: entry.keywords, action: kpi ? { type: 'panel', panelContent: { type: 'kpi', kpi } } : { type: 'scroll', tileId: 'patient-summary' }, }) }) // Education — matching actual entries in EducationSubsection getEducationEntries().forEach((entry, i) => { items.push({ id: `edu-${i}`, title: entry.title, subtitle: entry.subtitle, section: 'Education', iconVariant: 'purple', iconType: 'edu', keywords: entry.keywords, action: { type: 'scroll', tileId: 'patient-pathway' }, }) }) // Quick Actions getSearchQuickActions().forEach((entry, i) => { const action: PaletteAction = entry.type === 'download' ? { type: 'download' } : { type: 'link', url: entry.url } items.push({ id: `action-${i}`, title: entry.title, subtitle: entry.subtitle, section: 'Quick Actions', iconVariant: 'teal', iconType: 'action', keywords: entry.keywords, action, }) }) return items } // Build a fuse.js search index from palette items export function buildSearchIndex(items: PaletteItem[]): Fuse { return new Fuse(items, { keys: [ { name: 'title', weight: 2 }, { name: 'subtitle', weight: 1 }, { name: 'keywords', weight: 1.5 }, ], threshold: 0.3, includeScore: true, minMatchCharLength: 2, }) } // Section ordering for grouped display const SECTION_ORDER: PaletteSection[] = [ 'Experience', 'Core Skills', 'Significant Interventions', 'Achievements', 'Education', 'Quick Actions', ] // Group palette items by section, maintaining defined order export function groupBySection(items: PaletteItem[]): Array<{ section: PaletteSection; items: PaletteItem[] }> { const groups = new Map() for (const item of items) { const existing = groups.get(item.section) if (existing) { existing.push(item) } else { groups.set(item.section, [item]) } } return SECTION_ORDER .filter(section => groups.has(section)) .map(section => ({ section, items: groups.get(section)! })) } // Build rich natural-language text representations for semantic embedding. // IDs match PaletteItem IDs so embeddings can be correlated back to palette entries. export function buildEmbeddingTexts(): Array<{ id: string; text: string }> { const texts: Array<{ id: string; text: string }> = [] // Consultations (Experience) — enriched with plan outcomes, employer classification, clinical specialties timelineConsultations.forEach((c) => { const isNHS = c.organization.includes('NHS') || c.organization.includes('ICB') const employer = isNHS ? `NHS employer: ${c.organization}` : `Private sector employer: ${c.organization} (not NHS)` const examBullets = c.examination.join('. ') const planOutcomes = c.plan.join('. ') const codedDescriptions = c.codedEntries.map(e => e.description).join('. ') // Role-specific enrichment for clinical specialties and methodology let roleContext = '' if (c.id === 'high-cost-drugs-2022') { roleContext = ' Clinical specialties covered: rheumatology, ophthalmology (wet AMD, DMO, RVO), dermatology, gastroenterology, neurology, and migraine. Wrote most of the system\'s high-cost drug pathways, implementing NICE technology appraisals while balancing legal requirements against financial costs and local clinical preferences.' } else if (c.id === 'deputy-head-2024') { roleContext = ' Created dm+d medicines data table integrating all dictionary of medicines and devices products with standardised strengths, morphine equivalents, and Anticholinergic Burden scoring — single source of truth for all medicines analytics. Supported tirzepatide commissioning (NICE TA1026) with financial projections and authored executive paper advocating primary care model, driving system shift to GP-led delivery.' } else if (c.id === 'interim-head-2025') { roleContext = ' Built Python switching algorithm using real-world GP prescribing data to identify patients eligible for cost-effective alternatives — compressed months of manual analysis into 3 days. Created novel GP payment system linking incentive rewards to prescribing savings.' } else if (c.id === 'pharmacy-manager-2017') { roleContext = ' Community pharmacy role at Tesco PLC, a private sector employer. Served as Local Pharmaceutical Committee (LPC) representative for Norfolk. Full HR responsibilities including recruitment, performance management, grievances.' } texts.push({ id: `exp-${c.id}`, text: `${c.role} at ${c.organization}, ${c.duration}. ${employer}. ${c.history} Key achievements: ${examBullets}. Outcomes: ${planOutcomes}. ${codedDescriptions}.${roleContext}`, }) }) // Skills — enriched with role context and practical application const skillContextMap: Record = { 'data-analysis': 'Applied across NHS medicines optimisation, identifying £14.6M efficiency programme. Used for prescribing pattern analysis, budget forecasting, and population health analytics serving 1.2M people.', 'python': 'Used to build switching algorithms (14,000 patients, £2.6M savings), controlled drug monitoring systems, Blueteq form automation, and Sankey chart visualisation tools. Self-taught.', 'sql': 'Core tool for patient-level analytics, dm+d data integration, and transforming practice-level data to patient-level SQL analysis. Used across all NHS data roles.', 'power-bi': 'Built PharMetrics interactive dashboard tracking £220M prescribing budget. Created dashboards used by 200+ clinicians and commissioners across Norfolk & Waveney ICB.', 'javascript-typescript': 'Used for web development including this portfolio website. Built with React, TypeScript, and Vite.', 'excel': 'Used for financial modelling, data validation, and ad-hoc analysis. Foundational tool across all roles from community pharmacy to NHS ICB.', 'algorithm-design': 'Designed patient switching algorithm and automated incentive scheme analysis. Applied to real-world GP prescribing data at population scale.', 'data-pipelines': 'Built automated data processing pipelines for medicines analytics, enabling self-serve models for team data fluency.', 'medicines-optimisation': 'Core domain expertise spanning community pharmacy through to NHS ICB-level population health. Led efficiency programmes worth £14.6M+.', 'population-health': 'Leading population health analytics for 1.2M people across Norfolk & Waveney ICS. Developing patient-level datasets from real-world GP prescribing data.', 'nice-ta': 'Led NICE technology appraisal implementation across high-cost drug pathways. Covered rheumatology, ophthalmology, dermatology, gastroenterology, neurology, and migraine.', 'health-economics': 'Financial modelling for DOAC switching programmes, tirzepatide commissioning, and pharmaceutical rebate negotiations.', 'clinical-pathways': 'Wrote most of the Norfolk & Waveney ICB high-cost drug pathways. Created Sankey chart tool for patient pathway visualisation and trust compliance auditing.', 'controlled-drugs': 'Built Python-based population-scale monitoring system calculating oral morphine equivalents (OME) across all opioid prescriptions. Enables high-risk patient identification and potential diversion detection.', 'budget-management': 'Managed £220M NHS prescribing budget with sophisticated forecasting models, variance analysis, and monthly financial reporting to the ICB executive team.', 'stakeholder-engagement': 'Presented to Chief Medical Officer bimonthly. Engaged with GP practices, trusts, commissioners, and pharmaceutical companies across the integrated care system.', 'pharma-negotiation': 'Renegotiated pharmaceutical rebate terms ahead of patent expiry, securing improved commercial position for the ICB.', 'team-development': 'Improved team data fluency through training and documentation. Supervised staff through NVQ3 to pharmacy technician registration. Created national induction training at Tesco.', 'change-management': 'Completed NHS Mary Seacole Programme (2018, 78%). Led transformation to patient-level SQL analytics and self-serve analytical models.', 'financial-modelling': 'Built interactive DOAC switching dashboard with rebate mechanics, workforce constraints, and patent expiry timelines. Financial projections for tirzepatide commissioning.', 'executive-comms': 'Authored executive papers for ICB board including tirzepatide commissioning advocacy. Presented evidence-based recommendations to CMO bimonthly.', } skills.forEach((skill) => { const context = skillContextMap[skill.id] ?? '' texts.push({ id: `skill-${skill.id}`, text: `${skill.name} is a ${skill.category.toLowerCase()} skill used ${skill.frequency.toLowerCase()}, with ${skill.yearsOfExperience} years of experience since ${skill.startYear}. ${context}`, }) }) // KPI-backed Achievements — enriched with full story context and outcomes getAchievementEntries().forEach((entry, index) => { const id = `ach-${index}` const kpi = kpis.find(k => k.id === entry.kpiId) const explanation = kpi?.explanation ?? '' const storyParts: string[] = [] if (kpi?.story) { storyParts.push(kpi.story.context) storyParts.push(kpi.story.role) if (kpi.story.period) storyParts.push(`Period: ${kpi.story.period}.`) storyParts.push(`Outcomes: ${kpi.story.outcomes.join('. ')}.`) } texts.push({ id, text: `Achievement: ${entry.title}. ${entry.subtitle}. ${explanation} ${storyParts.join(' ')}`, }) }) // Investigations (Significant Interventions) — enriched with role context and cross-references const projectContextMap: Record = { 'inv-pharmetrics': 'Built during Deputy Head role at NHS Norfolk & Waveney ICB. Provides self-serve analytics for budget holders across the integrated care system. Live at medicines.charlwood.xyz.', 'inv-switching-algorithm': 'Built during Interim Head role at NHS Norfolk & Waveney ICB. Uses real-world GP prescribing data to auto-identify patients on expensive drugs suitable for cost-effective alternatives. Compressed months of manual analysis into 3 days. Includes novel GP payment system linking incentive rewards to prescribing savings.', 'inv-blueteq-gen': 'Built during High-Cost Drugs & Interface Pharmacist role at NHS Norfolk & Waveney ICB. Automates prior approval form creation for high-cost drug pathways spanning rheumatology, ophthalmology, dermatology, gastroenterology, neurology, and migraine.', 'inv-cd-monitoring': 'Built during Deputy Head role at NHS Norfolk & Waveney ICB. Calculates oral morphine equivalents (OME) across all opioid prescriptions at population scale. Enables previously impossible population-level controlled drug analysis. Related to controlled drugs skill.', 'inv-sankey-tool': 'Built during High-Cost Drugs & Interface Pharmacist role at NHS Norfolk & Waveney ICB. Visualises patient journeys through high-cost drug pathways. Enables trust-level compliance auditing across multiple clinical specialties.', } investigations.forEach((inv) => { const techList = inv.techStack.join(', ') const resultList = inv.results.join('. ') const context = projectContextMap[inv.id] ?? '' texts.push({ id: `proj-${inv.id}`, text: `Project: ${inv.name} (${inv.status}, ${inv.requestedYear}). ${inv.methodology} Tech stack: ${techList}. Results: ${resultList}. ${context}`, }) }) // Education — enriched with research grades and specific subject details const educationItems: Array<{ id: string; docId: string }> = [ { id: 'edu-0', docId: 'doc-mary-seacole' }, { id: 'edu-1', docId: 'doc-mpharm' }, { id: 'edu-2', docId: 'doc-alevels' }, { id: 'edu-3', docId: 'doc-gphc' }, ] educationItems.forEach((entry, index) => { const fallback = getEducationEntries()[index] const doc = documents.find(d => d.id === entry.docId) if (doc) { const research = doc.researchDetail ? ` Research: ${doc.researchDetail}.` : '' const researchGrade = doc.researchGrade ? ` Research grade: ${doc.researchGrade}.` : '' const classification = doc.classification ? ` Classification: ${doc.classification}.` : '' texts.push({ id: entry.id, text: `Education: ${doc.title}. ${doc.type} from ${doc.institution ?? doc.source}, ${doc.duration ?? doc.date}.${classification}${research}${researchGrade} ${doc.notes ?? ''}`, }) } else { texts.push({ id: entry.id, text: `Education: ${fallback?.title ?? ''}. ${fallback?.subtitle ?? ''}.`, }) } }) // Quick Actions getSearchQuickActions().forEach((entry, index) => { texts.push({ id: `action-${index}`, text: `${entry.title}. ${entry.subtitle}.`, }) }) return texts }