Architecture

kennjordan
← All sections

Architecture

Technical overview of the CPD platform stack and how the pieces fit together.

High-level

┌────────────────────────────────────────────────────┐ │ cpd.medilearn.africa (MobiLearn CPD, public) │ │ │ │ / Home + AI interview CTA │ │ /interview/ Claude chat UI │ │ /my-pathway/ Personalised video curriculum │ │ /watch/ Full course viewer │ │ /admin/ PageMotor admin │ └────────────────────────────────────────────────────┘ │ │ │ Claude API │ Vimeo API ▼ ▼ ┌──────────┐ ┌────────────┐ │ Anthropic│ │ Vimeo │ │ (Sonnet │ │ (Mobilearn │ │ 4.5) │ │ account) │ └──────────┘ └────────────┘

Stack

LayerTechNotes
CMSPageMotor (Chris Pearson)PHP 8.3, MariaDB 10, nginx, FPM
VPSUbuntu 24.04 on 154.66.198.194Shared with s2l.online; isolated system users per site
SSLLet’s Encrypt via certbotAuto-renewing cron
AIClaude Sonnet 4.5 (claude-sonnet-4-5-20250929)Anthropic API, key stored in admin
VideoVimeo API + iframe embedsAll MLUCT content, domain-whitelisted for cpd.medilearn.africa
FrontendVanilla HTML/CSS/JSNo framework; JS scoped via IIFEs; CSS scoped via page-level classes

Plugins on cpd.medilearn.africa

ep-courses

The core courses engine, copied from s2l.online. Handles course/lesson data model, Vimeo integration helpers (EP_Vimeo_API class), viewer UI, landing pages via

No course specified.

, viewer via

No course selected. Browse our courses

, activity tracking, and admin UI for managing courses.

ep-cpd-brand

Site chrome. Renders the MobiLearn CPD header and footer via [cpd-shell-start] and [cpd-shell-end] shortcodes wrapping every page’s content. Hides the underlying s2l theme chrome via body:has(.mcpd-site) CSS selectors. Colour palette and typography live here.

ep-cpd-home

The landing page, rendered via [cpd-home]. Hero, AI interview CTA card, two featured pathway cards, UCT partnership footer note. Reads featured courses directly from the database by slug.

ep-cpd-interview

The AI interview and personalised pathway. Provides two shortcodes:

  • [cpd-interview] — the chat UI at /interview/. External JS file handles fetch calls.
  • [cpd-pathway] — the pathway page at /my-pathway/?session=.... Reads the stored interview profile, matches against the taxonomy, renders grouped video cards with an inline modal player.

Two AJAX actions: interview_start and interview_message. DB table pm_ep_cpd_interviews tracks session, conversation log, profile, IP, and status. Rate limited to 10 interviews per IP per day.

Data flow: the interview

  1. User lands on /interview/. Chat UI loads, JS kicks off interview_start.
  2. Server creates a new session row, calls Claude with the system prompt, returns Claude’s opening question.
  3. User replies. JS calls interview_message with the session ID and text.
  4. Server appends the message, calls Claude with full history + system prompt, returns response.
  5. Server scans the response for <profile>...</profile>. If found, JSON is extracted and stored, status set to completed, redirect URL returned.
  6. User redirected to /my-pathway/?session=....

Data flow: the pathway

  1. Request hits /my-pathway/?session=....
  2. [cpd-pathway] shortcode validates the session, reads the completed interview row, decodes the profile JSON.
  3. Plugin reads /var/www/cpd/user-content/inventory-taxonomy.json (the 300-video catalogue, grouped by specialty).
  4. For each primary specialty in the profile, pulls top 4 newest lectures; for each secondary, top 2. Builds HTML.
  5. Renders cards with Vimeo ID + hash as data attributes. Click triggers the modal player with https://player.vimeo.com/video/ID?h=HASH&autoplay=1.

Secrets and configuration

  • Anthropic API key — admin UI at /admin/ai/ (Architect Claude Settings). Stored as a JSON option in pm_options.
  • Vimeo API token — admin UI at /admin/plugins → EP Courses → Integrations. Stored in EP_Courses options JSON.
  • Database credentials/var/www/cpd/config.php (mode 640, readable by cpd user and root only).
  • Server access — SSH key-only on s2l alias (see s2l_server memory on the build machine).

Re-running the inventory audit

The taxonomy file at /var/www/cpd/user-content/inventory-taxonomy.json is a static snapshot. When new MLUCT content is added to Vimeo, the audit script needs to be re-run on Kenn’s Mac:

cd "~/Library/Mobile Documents/com~apple~CloudDocs/Claude Workspace/MobiLearn CPD/inventory"
# (run the audit python script, see runbooks)
# Then scp the new taxonomy.json to the server:
scp taxonomy.json s2l:/var/www/cpd/user-content/inventory-taxonomy.json

Scaling considerations

Current scale is prototype-level. Capacity estimates for production:

  • Claude API: trivial — each interview is ~2k tokens, Anthropic’s rate limits allow 1000s/hour.
  • FPM workers: CPD pool set to 8 max children, handles ~50 concurrent requests.
  • MariaDB: comfortably handles 100s of interviews/day.
  • Disk: 90 GB free on VPS; no concerns.
  • Vimeo bandwidth: served by Vimeo, not us.

Bottleneck at scale will be Anthropic cost per interview. At £0.02 each, 10,000 interviews = £200.