Architecture
Architecture
Technical overview of the CPD platform stack and how the pieces fit together.
High-level
Stack
| Layer | Tech | Notes |
|---|---|---|
| CMS | PageMotor (Chris Pearson) | PHP 8.3, MariaDB 10, nginx, FPM |
| VPS | Ubuntu 24.04 on 154.66.198.194 | Shared with s2l.online; isolated system users per site |
| SSL | Let’s Encrypt via certbot | Auto-renewing cron |
| AI | Claude Sonnet 4.5 (claude-sonnet-4-5-20250929) | Anthropic API, key stored in admin |
| Video | Vimeo API + iframe embeds | All MLUCT content, domain-whitelisted for cpd.medilearn.africa |
| Frontend | Vanilla HTML/CSS/JS | No 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 ( No course specified. No course selected. Browse our coursesEP_Vimeo_API class), viewer UI, landing pages via , viewer via , 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
- User lands on
/interview/. Chat UI loads, JS kicks offinterview_start. - Server creates a new session row, calls Claude with the system prompt, returns Claude’s opening question.
- User replies. JS calls
interview_messagewith the session ID and text. - Server appends the message, calls Claude with full history + system prompt, returns response.
- Server scans the response for
<profile>...</profile>. If found, JSON is extracted and stored, status set tocompleted, redirect URL returned. - User redirected to
/my-pathway/?session=....
Data flow: the pathway
- Request hits
/my-pathway/?session=.... [cpd-pathway]shortcode validates the session, reads the completed interview row, decodes the profile JSON.- Plugin reads
/var/www/cpd/user-content/inventory-taxonomy.json(the 300-video catalogue, grouped by specialty). - For each primary specialty in the profile, pulls top 4 newest lectures; for each secondary, top 2. Builds HTML.
- 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_Coursesoptions JSON. - Database credentials —
/var/www/cpd/config.php(mode 640, readable bycpduser and root only). - Server access — SSH key-only on
s2lalias (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.