// APM mock data — Earlens portfolio (a fictional hearing-tech company).
// Single source of truth, mutated by useReducer in app.jsx.

// ── Roles ────────────────────────────────────────────────────────────
const APM_ROLES = {
  admin:        { label: 'Admin',           desc: 'Manages users, roles, taxonomies, sensitivity tags.' },
  owner:        { label: 'Project Owner',   desc: 'Drives projects through phase gates.' },
  approver:     { label: 'Stage Approver',  desc: 'Signs off on phase-gate reviews.' },
  manager:      { label: 'Portfolio Manager', desc: 'Plans initiatives, allocates, manages capacity.' },
  raqa:         { label: 'RA/QA',           desc: 'Reviews regulated workflows, signs HIPAA/RA gates.' },
  helpdesk:     { label: 'Helpdesk',        desc: 'Triages tickets, runs Help board.' },
  devops:       { label: 'DevOps',          desc: 'Manages components catalog, change control, releases.' },
  exec:         { label: 'Executive',       desc: 'Reviews briefings, approves big-bet initiatives.' },
  member:       { label: 'Team Member',     desc: 'Works tickets, submits ideas, contributes to projects.' },
};

// ── 9 personas ───────────────────────────────────────────────────────
const APM_PEOPLE = [
  { id: 'p_alex',   name: 'Alex Chen',       role: 'admin',    title: 'Platform Admin',          team: 'IT Platform',     initials: 'AC' },
  { id: 'p_priya',  name: 'Priya Natarajan', role: 'owner',    title: 'Sr. Project Owner',       team: 'Clinical Apps',   initials: 'PN' },
  { id: 'p_sam',    name: 'Sam Okafor',      role: 'approver', title: 'Director, IT Security',   team: 'Security',        initials: 'SO' },
  { id: 'p_marisa', name: 'Marisa Vidal',    role: 'manager',  title: 'Portfolio Manager',       team: 'PMO',             initials: 'MV' },
  { id: 'p_jenna',  name: 'Jenna Rowe',      role: 'raqa',     title: 'RA/QA Lead',              team: 'Regulatory',      initials: 'JR' },
  { id: 'p_diego',  name: 'Diego Salas',     role: 'helpdesk', title: 'Helpdesk Lead',           team: 'IT Support',      initials: 'DS' },
  { id: 'p_kira',   name: 'Kira Lindgren',   role: 'devops',   title: 'Sr. DevOps Engineer',     team: 'Platform',        initials: 'KL' },
  { id: 'p_dr_ito', name: 'Dr. Naoko Ito',   role: 'exec',     title: 'Chief Medical Officer',   team: 'Executive',       initials: 'NI' },
  { id: 'p_tomas',  name: 'Tomás Beltrán',   role: 'member',   title: 'Audiology Lead',          team: 'Clinical Ops',    initials: 'TB' },
];

const PERSON_BY_ID = Object.fromEntries(APM_PEOPLE.map(p => [p.id, p]));

// ── 4 initiatives ────────────────────────────────────────────────────
const APM_INITIATIVES = [
  {
    id: 'i_hearwell',
    code: 'INI-001',
    name: 'HearWell Cloud Migration',
    summary: 'Move clinical fitting platform to a HIPAA-eligible AWS environment with SOC 2 controls.',
    owner: 'p_marisa',
    sponsor: 'p_dr_ito',
    target: 'FY26 Q3',
    status: 'on-track',
    health: 84,
    budget: 4_200_000,
    spent: 1_780_000,
    projects: ['proj_fit_cloud', 'proj_id_sso', 'proj_audit_pipe', 'proj_telem_v2'],
    okrs: [
      { id: 'o1', text: 'Decommission on-prem fitting servers', target: 'Q3', progress: 35 },
      { id: 'o2', text: 'Achieve SOC 2 Type II readiness',     target: 'Q4', progress: 62 },
      { id: 'o3', text: 'Cut clinic-side latency by 40%',       target: 'Q3', progress: 48 },
    ],
  },
  {
    id: 'i_pearl',
    code: 'INI-002',
    name: 'PEARL — AI Fit Coach',
    summary: 'Embed an AI assistant in the fitting flow that recommends adjustments from clinic-anonymized telemetry.',
    owner: 'p_priya',
    sponsor: 'p_dr_ito',
    target: 'FY26 Q4',
    status: 'at-risk',
    health: 58,
    budget: 2_800_000,
    spent: 1_410_000,
    projects: ['proj_fitcoach_mvp', 'proj_telem_v2', 'proj_consent'],
    okrs: [
      { id: 'o1', text: 'Ship MVP fit-coach to 5 pilot clinics', target: 'Q3', progress: 70 },
      { id: 'o2', text: 'Reach +15pp first-fit acceptance',      target: 'Q4', progress: 22 },
      { id: 'o3', text: 'Pass IRB & HIPAA review',               target: 'Q3', progress: 90 },
    ],
  },
  {
    id: 'i_clinic',
    code: 'INI-003',
    name: 'Clinic Workbench Refresh',
    summary: 'Rebuild the day-of-clinic experience for audiologists: scheduling, in-visit notes, follow-up.',
    owner: 'p_marisa',
    sponsor: 'p_tomas',
    target: 'FY27 Q1',
    status: 'on-track',
    health: 76,
    budget: 1_900_000,
    spent: 460_000,
    projects: ['proj_sched_v2', 'proj_visit_notes', 'proj_followup'],
    okrs: [
      { id: 'o1', text: 'Cut visit charting time 30%', target: 'Q4', progress: 18 },
      { id: 'o2', text: 'Double follow-up adherence',  target: 'Q1', progress: 12 },
    ],
  },
  {
    id: 'i_compliance',
    code: 'INI-004',
    name: 'Quality & RA Foundations',
    summary: 'Modernize document control, training records, and post-market surveillance to align with FDA QSR + EU MDR.',
    owner: 'p_jenna',
    sponsor: 'p_dr_ito',
    target: 'FY26 Q4',
    status: 'on-track',
    health: 81,
    budget: 1_350_000,
    spent: 720_000,
    projects: ['proj_doctrl', 'proj_pms', 'proj_training'],
    okrs: [
      { id: 'o1', text: 'Replace legacy QMS', target: 'Q4', progress: 55 },
      { id: 'o2', text: 'PMS dashboard live', target: 'Q3', progress: 80 },
    ],
  },
];

const INI_BY_ID = Object.fromEntries(APM_INITIATIVES.map(i => [i.id, i]));

// ── Phases (canonical pipeline) ─────────────────────────────────────
const APM_PHASES = [
  'Intake', 'Design', 'IT Review', 'Security', 'HIPAA/PII',
  'Cost & Ops', 'RA/QA', 'UAT', 'Go-Live', 'Active', 'Archived'
];

// Sensitivity tags
const APM_SENSITIVITY = [
  { id: 'phi',     label: 'PHI',       desc: 'Protected Health Information' },
  { id: 'pii',     label: 'PII',       desc: 'Personally Identifiable Info' },
  { id: 'srcdata', label: 'Source data', desc: 'Clinical-trial source records' },
  { id: 'finint',  label: 'Fin/Internal', desc: 'Confidential financial' },
  { id: 'public',  label: 'Public',    desc: 'No restrictions' },
];

// ── 12 projects ──────────────────────────────────────────────────────
const APM_PROJECTS = [
  {
    id: 'proj_fitcoach_mvp', code: 'PRJ-2041',
    name: 'PEARL Fit-Coach MVP',
    initiative: 'i_pearl', owner: 'p_priya', raqa: 'p_jenna',
    phase: 'RA/QA', health: 'at-risk', progress: 64,
    sensitivity: ['phi','srcdata'],
    target: '2026-04-30', updated: '2 hours ago',
    summary: 'Recommend fitting-curve adjustments to audiologists in real time.',
    components: ['comp_telem','comp_audio_ml','comp_consent','comp_sso'],
    spent: 482_000, budget: 700_000,
    risks: 2, openTickets: 7, blockers: 1,
  },
  {
    id: 'proj_telem_v2', code: 'PRJ-2042',
    name: 'Clinic Telemetry v2',
    initiative: 'i_pearl', owner: 'p_kira', raqa: 'p_jenna',
    phase: 'IT Review', health: 'on-track', progress: 38,
    sensitivity: ['phi'],
    target: '2026-06-15', updated: 'yesterday',
    summary: 'Streaming telemetry with consent-aware redaction.',
    components: ['comp_telem','comp_consent','comp_kafka'],
    spent: 145_000, budget: 520_000,
    risks: 1, openTickets: 3, blockers: 0,
  },
  {
    id: 'proj_consent', code: 'PRJ-2043',
    name: 'Patient Consent Service',
    initiative: 'i_pearl', owner: 'p_priya', raqa: 'p_jenna',
    phase: 'HIPAA/PII', health: 'on-track', progress: 71,
    sensitivity: ['phi','pii'],
    target: '2026-03-12', updated: '4 hours ago',
    summary: 'Centralized consent ledger consumed by every clinic surface.',
    components: ['comp_consent','comp_audit'],
    spent: 198_000, budget: 280_000,
    risks: 0, openTickets: 2, blockers: 0,
  },
  {
    id: 'proj_fit_cloud', code: 'PRJ-2044',
    name: 'Fitting Platform — Cloud',
    initiative: 'i_hearwell', owner: 'p_kira', raqa: 'p_jenna',
    phase: 'UAT', health: 'on-track', progress: 88,
    sensitivity: ['phi'],
    target: '2026-02-28', updated: '15 minutes ago',
    summary: 'Lift-and-reshape of legacy fitting servers onto AWS.',
    components: ['comp_fit_core','comp_sso','comp_audit','comp_kafka'],
    spent: 1_120_000, budget: 1_400_000,
    risks: 1, openTickets: 9, blockers: 0,
  },
  {
    id: 'proj_id_sso', code: 'PRJ-2045',
    name: 'Workforce SSO + MFA',
    initiative: 'i_hearwell', owner: 'p_alex', raqa: null,
    phase: 'Active', health: 'on-track', progress: 100,
    sensitivity: ['pii'],
    target: '2025-11-04', updated: 'Nov 4',
    summary: 'OIDC-based SSO with mandatory phishing-resistant MFA.',
    components: ['comp_sso'],
    spent: 312_000, budget: 320_000,
    risks: 0, openTickets: 0, blockers: 0,
  },
  {
    id: 'proj_audit_pipe', code: 'PRJ-2046',
    name: 'Audit Event Pipeline',
    initiative: 'i_hearwell', owner: 'p_kira', raqa: 'p_jenna',
    phase: 'Cost & Ops', health: 'at-risk', progress: 52,
    sensitivity: ['phi','finint'],
    target: '2026-05-20', updated: '3 days ago',
    summary: 'Append-only audit stream + retention policy engine.',
    components: ['comp_audit','comp_kafka'],
    spent: 248_000, budget: 410_000,
    risks: 3, openTickets: 5, blockers: 1,
  },
  {
    id: 'proj_sched_v2', code: 'PRJ-2047',
    name: 'Clinic Scheduling v2',
    initiative: 'i_clinic', owner: 'p_priya', raqa: null,
    phase: 'Design', health: 'on-track', progress: 22,
    sensitivity: ['pii'],
    target: '2026-09-10', updated: 'today',
    summary: 'Rebuild scheduling around audiologist availability + room constraints.',
    components: ['comp_sched','comp_sso'],
    spent: 76_000, budget: 480_000,
    risks: 0, openTickets: 2, blockers: 0,
  },
  {
    id: 'proj_visit_notes', code: 'PRJ-2048',
    name: 'In-Visit Charting',
    initiative: 'i_clinic', owner: 'p_priya', raqa: 'p_jenna',
    phase: 'Intake', health: 'on-track', progress: 8,
    sensitivity: ['phi','pii'],
    target: '2026-12-01', updated: 'today',
    summary: 'Voice + structured charting during the audiology visit.',
    components: [],
    spent: 0, budget: 360_000,
    risks: 0, openTickets: 0, blockers: 0,
  },
  {
    id: 'proj_followup', code: 'PRJ-2049',
    name: 'Patient Follow-up Loop',
    initiative: 'i_clinic', owner: 'p_tomas', raqa: 'p_jenna',
    phase: 'Intake', health: 'on-track', progress: 4,
    sensitivity: ['phi','pii'],
    target: '2027-02-01', updated: 'yesterday',
    summary: '14- and 30-day follow-up nudges with response capture.',
    components: [],
    spent: 0, budget: 240_000,
    risks: 0, openTickets: 0, blockers: 0,
  },
  {
    id: 'proj_doctrl', code: 'PRJ-2050',
    name: 'Document Control Replacement',
    initiative: 'i_compliance', owner: 'p_jenna', raqa: 'p_jenna',
    phase: 'Security', health: 'on-track', progress: 44,
    sensitivity: ['finint','srcdata'],
    target: '2026-08-30', updated: '5 hours ago',
    summary: 'Replace legacy QMS document control with a regulated SaaS.',
    components: ['comp_sso','comp_audit'],
    spent: 220_000, budget: 600_000,
    risks: 1, openTickets: 4, blockers: 0,
  },
  {
    id: 'proj_pms', code: 'PRJ-2051',
    name: 'Post-Market Surveillance Dash',
    initiative: 'i_compliance', owner: 'p_jenna', raqa: 'p_jenna',
    phase: 'Go-Live', health: 'on-track', progress: 96,
    sensitivity: ['phi','srcdata'],
    target: '2026-01-22', updated: '1 hour ago',
    summary: 'Adverse-event signal detection across markets.',
    components: ['comp_telem','comp_audit'],
    spent: 392_000, budget: 410_000,
    risks: 0, openTickets: 1, blockers: 0,
  },
  {
    id: 'proj_training', code: 'PRJ-2052',
    name: 'Training Records Modernization',
    initiative: 'i_compliance', owner: 'p_alex', raqa: 'p_jenna',
    phase: 'Active', health: 'on-track', progress: 100,
    sensitivity: ['pii'],
    target: '2025-09-30', updated: 'Sep 30',
    summary: 'Centralized training records tied to job code.',
    components: ['comp_sso'],
    spent: 108_000, budget: 110_000,
    risks: 0, openTickets: 0, blockers: 0,
  },
];

const PROJ_BY_ID = Object.fromEntries(APM_PROJECTS.map(p => [p.id, p]));

// ── Components catalog ──────────────────────────────────────────────
const APM_COMPONENTS = [
  { id: 'comp_sso',      name: 'Workforce SSO',         owner: 'p_alex',   version: '2.4.1', stability: 'GA',
    dependents: ['proj_fit_cloud','proj_id_sso','proj_sched_v2','proj_doctrl','proj_training','proj_fitcoach_mvp'],
    sensitivity: ['pii'], summary: 'OIDC IdP + MFA gateway used by every employee surface.' },
  { id: 'comp_consent',  name: 'Consent Ledger',        owner: 'p_priya',  version: '1.2.0', stability: 'Beta',
    dependents: ['proj_fitcoach_mvp','proj_telem_v2','proj_consent'],
    sensitivity: ['phi','pii'], summary: 'Append-only consent records consumed by clinic + telemetry surfaces.' },
  { id: 'comp_audit',    name: 'Audit Event Bus',       owner: 'p_kira',   version: '0.9.3', stability: 'Beta',
    dependents: ['proj_fit_cloud','proj_audit_pipe','proj_pms','proj_doctrl','proj_consent'],
    sensitivity: ['phi','finint'], summary: 'Append-only audit stream feeding the long-term retention store.' },
  { id: 'comp_kafka',    name: 'Streaming Backbone',    owner: 'p_kira',   version: '3.1.0', stability: 'GA',
    dependents: ['proj_telem_v2','proj_audit_pipe','proj_fit_cloud'],
    sensitivity: [], summary: 'Internal Kafka cluster + schema registry.' },
  { id: 'comp_telem',    name: 'Telemetry SDK',         owner: 'p_kira',   version: '4.0.0-rc.2', stability: 'RC',
    dependents: ['proj_fitcoach_mvp','proj_telem_v2','proj_pms'],
    sensitivity: ['phi'], summary: 'Embedded SDK for fitting devices and clinic apps.' },
  { id: 'comp_audio_ml', name: 'Audio ML Service',      owner: 'p_priya',  version: '0.4.0', stability: 'Alpha',
    dependents: ['proj_fitcoach_mvp'],
    sensitivity: ['phi','srcdata'], summary: 'Recommends fitting-curve adjustments from anonymized telemetry.' },
  { id: 'comp_fit_core', name: 'Fitting Core Engine',   owner: 'p_kira',   version: '6.7.2', stability: 'GA',
    dependents: ['proj_fit_cloud'],
    sensitivity: ['phi'], summary: 'Legacy fitting algorithms wrapped as a service.' },
  { id: 'comp_sched',    name: 'Scheduling Service',    owner: 'p_priya',  version: '0.3.0', stability: 'Alpha',
    dependents: ['proj_sched_v2'],
    sensitivity: ['pii'], summary: 'Resource-constrained scheduling for clinics.' },
];

const COMP_BY_ID = Object.fromEntries(APM_COMPONENTS.map(c => [c.id, c]));

// ── Component change requests ───────────────────────────────────────
const APM_CHANGES = [
  { id: 'chg_001', component: 'comp_telem', title: 'Add device-firmware version to event payload',
    submitter: 'p_kira', submitted: '3 days ago', breaking: false, status: 'pending',
    impact: ['proj_fitcoach_mvp','proj_telem_v2','proj_pms'],
    description: 'Append `fw_version` field to all telemetry events. Backwards compatible.', votes: 4 },
  { id: 'chg_002', component: 'comp_consent', title: 'Require granular consent for ML training',
    submitter: 'p_jenna', submitted: '1 day ago', breaking: true, status: 'pending',
    impact: ['proj_fitcoach_mvp','proj_telem_v2','proj_consent'],
    description: 'Split consent into clinical-care and ML-training scopes. Existing consents migrate to clinical-care only.', votes: 7 },
  { id: 'chg_003', component: 'comp_sso', title: 'Force MFA re-enrollment for clinical roles',
    submitter: 'p_alex', submitted: '6 hours ago', breaking: false, status: 'approved',
    impact: ['proj_fit_cloud','proj_doctrl'],
    description: 'Re-enroll any clinical role onto FIDO2 within 30 days.', votes: 3 },
];

// ── Ideas backlog ────────────────────────────────────────────────────
const APM_IDEAS = [
  { id: 'idea_001', title: 'Auto-summarize visit notes to the patient', submitter: 'p_tomas', submitted: '2 days ago',
    upvotes: 14, status: 'new', tags: ['clinic','ai','copy'],
    description: 'A 1-paragraph visit summary written at a 6th-grade reading level, reviewed by the audiologist before sending.' },
  { id: 'idea_002', title: 'Battery-life forecast in the fitting app', submitter: 'p_diego', submitted: '5 days ago',
    upvotes: 22, status: 'new', tags: ['device','ux'],
    description: 'Predict days-until-recharge based on observed usage and surface a gentle nudge in the patient app.' },
  { id: 'idea_003', title: 'Bring back the Q-shape preview in fittings', submitter: 'p_priya', submitted: '1 week ago',
    upvotes: 8, status: 'merged', mergedInto: 'proj_fitcoach_mvp', tags: ['clinic','ux'],
    description: 'Audiologists miss the legacy Q-curve preview; merge into Fit-Coach MVP scope.' },
  { id: 'idea_004', title: 'Public-facing API for partner audiology chains', submitter: 'p_alex', submitted: '3 days ago',
    upvotes: 11, status: 'new', tags: ['platform','partners'],
    description: 'A read-only API for partners to retrieve aggregated, de-identified outcomes.' },
  { id: 'idea_005', title: 'Replace 4-digit clinic PIN with passkeys', submitter: 'p_alex', submitted: 'today',
    upvotes: 5, status: 'promoted', promotedTo: 'proj_visit_notes', promotedAs: 'project', tags: ['security','clinic'],
    description: 'Phishing-resistant auth at the clinic terminal.' },
  { id: 'idea_006', title: 'Outcomes-based pricing pilot', submitter: 'p_dr_ito', submitted: 'today',
    upvotes: 3, status: 'new', tags: ['business','executive'],
    description: 'Pilot a results-based contract with one large hearing-aid retailer.' },
];

// ── Help board ───────────────────────────────────────────────────────
const APM_HELP = [
  { id: 'help_001', title: 'Can the EU clinics share consent records?', from: 'p_priya', topic: 'compliance', age: '20 min', status: 'open', answers: 0 },
  { id: 'help_002', title: 'How do I get a SOC 2 letter for a vendor?', from: 'p_alex', topic: 'security', age: '2 hr', status: 'answered', answers: 3, promoted: 'ticket' },
  { id: 'help_003', title: 'Telemetry SDK 4.0 install on M1 macs', from: 'p_tomas', topic: 'devops', age: '4 hr', status: 'answered', answers: 2 },
  { id: 'help_004', title: 'Where do I find the clinic PIN reset flow?', from: 'p_diego', topic: 'support', age: 'yesterday', status: 'open', answers: 0 },
];

// ── Tickets ──────────────────────────────────────────────────────────
const APM_TICKETS = [
  { id: 't1001', title: 'Fitting platform 502 on EU clinic uploads', project: 'proj_fit_cloud', from: 'p_diego',
    age: '12 min', priority: 'P1', status: 'in-progress', assignee: 'p_kira' },
  { id: 't1002', title: 'Consent ledger drops "ml-training" scope on edit', project: 'proj_consent', from: 'p_priya',
    age: '38 min', priority: 'P2', status: 'open', assignee: null },
  { id: 't1003', title: 'SSO redirect loop for Okta + Edge users', project: 'proj_id_sso', from: 'p_diego',
    age: '1 hr', priority: 'P2', status: 'open', assignee: 'p_alex' },
  { id: 't1004', title: 'PMS dash showing stale signal for Brazil', project: 'proj_pms', from: 'p_jenna',
    age: '2 hr', priority: 'P2', status: 'open', assignee: 'p_jenna' },
  { id: 't1005', title: 'Audit pipeline lag spike during nightly batch', project: 'proj_audit_pipe', from: 'p_kira',
    age: '3 hr', priority: 'P3', status: 'in-progress', assignee: 'p_kira' },
  { id: 't1006', title: 'Fit-coach mis-suggests for low-frequency loss', project: 'proj_fitcoach_mvp', from: 'p_tomas',
    age: '5 hr', priority: 'P2', status: 'open', assignee: 'p_priya' },
  { id: 't1007', title: 'Doc-control: cannot upload 200MB CER PDF', project: 'proj_doctrl', from: 'p_jenna',
    age: 'yesterday', priority: 'P3', status: 'open', assignee: null },
];

// ── Briefings (executive feed) ──────────────────────────────────────
const APM_BRIEFINGS = [
  { id: 'b1', kind: 'risk',     when: 'Today, 9:14am', title: 'PEARL is at 58% health',
    body: 'First-fit acceptance OKR slipped from 31% to 22%. Owner is reviewing the model retrain schedule.' },
  { id: 'b2', kind: 'milestone',when: 'Today, 7:02am', title: 'PMS Dashboard cleared Go-Live',
    body: 'Post-Market Surveillance dashboard cleared Go-Live with no conditions. Live in 36 hours.' },
  { id: 'b3', kind: 'spend',    when: 'Yesterday',    title: 'HearWell at 42% of FY budget',
    body: 'Spend tracking on plan; cloud costs slightly under forecast.' },
  { id: 'b4', kind: 'decision', when: '2 days ago',   title: 'Decision needed: outcomes-based pricing pilot',
    body: 'Idea logged by Dr. Ito. Awaiting routing into an initiative or rejection.' },
];

// ── Activity feed (cross-cutting) ───────────────────────────────────
const APM_ACTIVITY = [
  { who: 'p_priya', verb: 'submitted', what: 'PEARL Fit-Coach MVP for RA/QA review', when: '12 min ago', kind: 'gate' },
  { who: 'p_jenna', verb: 'opened a change request on', what: 'Consent Ledger', when: '38 min ago', kind: 'change' },
  { who: 'p_alex',  verb: 'approved', what: 'comp_sso change "Force MFA re-enrollment"', when: '1 hr ago', kind: 'approve' },
  { who: 'p_kira',  verb: 'released', what: 'Telemetry SDK 4.0 RC2', when: '3 hr ago', kind: 'release' },
  { who: 'p_tomas', verb: 'logged an idea', what: '"Auto-summarize visit notes to the patient"', when: 'yesterday', kind: 'idea' },
  { who: 'p_marisa',verb: 'updated capacity for', what: 'HearWell Cloud Migration', when: 'yesterday', kind: 'plan' },
  { who: 'p_diego', verb: 'escalated ticket', what: 't1001 to P1', when: '2 days ago', kind: 'ticket' },
];

// ── Helpers ──────────────────────────────────────────────────────────
const projectsByInitiative = (iniId) => APM_PROJECTS.filter(p => p.initiative === iniId);
const projectsByOwner      = (pid)   => APM_PROJECTS.filter(p => p.owner === pid);
const ticketsByAssignee    = (pid)   => APM_TICKETS.filter(t => t.assignee === pid);
const componentsByOwner    = (pid)   => APM_COMPONENTS.filter(c => c.owner === pid);

const fmtMoney = (n) => '$' + Math.round(n / 1000).toLocaleString() + 'k';
const fmtMoneyFull = (n) => '$' + n.toLocaleString();
const pct = (a, b) => Math.round((a / b) * 100);

Object.assign(window, {
  APM_ROLES, APM_PEOPLE, PERSON_BY_ID,
  APM_INITIATIVES, INI_BY_ID,
  APM_PHASES, APM_SENSITIVITY,
  APM_PROJECTS, PROJ_BY_ID,
  APM_COMPONENTS, COMP_BY_ID, APM_CHANGES,
  APM_IDEAS, APM_HELP, APM_TICKETS, APM_BRIEFINGS, APM_ACTIVITY,
  projectsByInitiative, projectsByOwner, ticketsByAssignee, componentsByOwner,
  fmtMoney, fmtMoneyFull, pct,
});
