# IILMP Django Architecture — Full Structure

> **Stack:** Django 5 · HTMX · Tailwind CSS · Alpine.js · DRF (API layer)
>
> **Pattern:** Monolithic fullstack — Django renders HTML templates server-side.
> HTMX replaces most JavaScript by swapping partial HTML fragments over HTTP.
> Alpine.js handles lightweight client-side interactivity (dropdowns, modals, toggling).
> Tailwind CSS compiled via Node/npm pipeline, served via WhiteNoise in production.
> DRF coexists under `api/v1/` for any mobile clients or third-party integrations.

---

## Frontend Stack Decision

| Concern | Tool | Reason |
|---|---|---|
| Markup & pages | Django templates (`.html`) | Server-side rendering, SEO-friendly, tight Python integration |
| Styling | Tailwind CSS + `@tailwindcss/forms` + `@tailwindcss/typography` | Utility-first, no context switching, dark mode via `dark:` variants |
| Dynamic UI | HTMX | Replaces 90% of SPA behaviour with HTML attributes — `hx-get`, `hx-post`, `hx-swap` |
| Client-side state | Alpine.js | 15 attributes — dropdowns, modals, tabs, form toggles |
| Static pipeline | Tailwind CLI + WhiteNoise | Compiles `input.css` → `output.css` at build time |
| File uploads | HTMX `hx-encoding="multipart/form-data"` | No separate JS upload library |
| Real-time | Django Channels + HTMX SSE (`hx-ext="sse"`) | Notifications, live updates |

---

## Architecture Layers

| Layer | Responsibilities |
|---|---|
| **Presentation** | Django templates + HTMX partials · Tailwind CSS · Alpine.js · DRF ViewSets (API) |
| **Application / Service** | `services.py` per module · business rules · workflows · Celery tasks · signals |
| **Domain** | Django ORM models · managers · querysets · validators · enums |
| **Infrastructure** | PostgreSQL · Redis · Celery · django-storages · SMTP / SMS |
| **Cross-cutting** | `core/`: auth, permissions, audit, notifications, AI client, storage utility |
| **DevOps** | Docker · docker-compose · Nginx · Gunicorn · WhiteNoise · pytest |

---

## Root Project Layout

```
iilmp/
├── apps/
│   ├── core/
│   ├── rims/
│   ├── rep/
│   ├── repository/
│   ├── mel/
│   ├── alumni/
│   └── smehub/
├── api/                                    ← DRF versioned API (mobile / integrations)
│   └── v1/
│       ├── __init__.py
│       ├── urls.py
│       └── router.py
├── config/
│   ├── settings/
│   │   ├── base.py
│   │   ├── development.py
│   │   ├── staging.py
│   │   └── production.py
│   ├── urls.py
│   ├── wsgi.py
│   ├── asgi.py                             ← Django Channels for WebSocket / SSE
│   └── celery.py
├── templates/                              ← GLOBAL templates — base layouts + components
│   ├── base.html                           ← root HTML: loads Tailwind, HTMX, Alpine.js
│   ├── layouts/
│   │   ├── dashboard.html                  ← authenticated: sidebar + topbar
│   │   ├── auth.html                       ← login/register centred card
│   │   └── public.html                     ← public landing pages
│   ├── components/                         ← reusable Tailwind + Alpine.js UI pieces
│   │   ├── navbar.html
│   │   ├── sidebar.html
│   │   ├── breadcrumbs.html
│   │   ├── page_header.html
│   │   ├── empty_state.html
│   │   ├── modals/
│   │   │   ├── confirm_delete.html
│   │   │   └── base_modal.html
│   │   ├── tables/
│   │   │   ├── base_table.html
│   │   │   ├── table_row_actions.html
│   │   │   └── sortable_header.html
│   │   ├── forms/
│   │   │   ├── field.html                  ← label + input + error message
│   │   │   ├── file_upload.html            ← drag-and-drop upload widget
│   │   │   └── search_input.html
│   │   └── alerts/
│   │       ├── success.html
│   │       ├── error.html
│   │       └── warning.html
│   └── partials/                           ← HTMX response fragments (no full layout)
│       ├── toast.html                      ← notification toast via HTMX OOB swap
│       ├── loader.html                     ← spinner shown during HTMX request
│       └── pagination.html
├── static/
│   ├── css/
│   │   ├── input.css                       ← @tailwind base/components/utilities + custom
│   │   └── output.css                      ← compiled (gitignored in dev)
│   ├── js/
│   │   ├── htmx.min.js
│   │   ├── alpine.min.js
│   │   └── app.js                          ← CSRF setup, toast init, global helpers
│   └── images/
│       └── logo.svg
├── uploads/                                ← all user file uploads (see storage utility)
│   ├── rims/
│   ├── rep/
│   ├── repository/
│   ├── mel/
│   ├── alumni/
│   └── smehub/
├── docker/
│   ├── Dockerfile
│   ├── Dockerfile.celery
│   └── nginx.conf
├── docs/
│   ├── architecture.md
│   ├── api_reference.md
│   └── adr/
├── requirements/
│   ├── base.txt
│   ├── development.txt
│   ├── staging.txt
│   └── production.txt
├── scripts/
│   ├── migrate_legacy.py
│   └── create_superuser.sh
├── tests/
│   └── conftest.py
├── .env.example
├── .gitignore
├── docker-compose.yml
├── docker-compose.prod.yml
├── manage.py
├── package.json                            ← tailwindcss, @tailwindcss/forms, typography
├── tailwind.config.js
└── pyproject.toml
```

---

## Global Base Templates

### `templates/base.html`

```html
{% load static %}
<!DOCTYPE html>
<html lang="en" class="h-full bg-gray-50">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{% block title %}IILMP — RUFORUM{% endblock %}</title>
  <link rel="stylesheet" href="{% static 'css/output.css' %}">
  <script src="{% static 'js/htmx.min.js' %}"></script>
  <script src="{% static 'js/alpine.min.js' %}" defer></script>
  {% block extra_head %}{% endblock %}
</head>
<body class="h-full" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
  {% block body %}{% endblock %}
  <div id="toast-container" class="fixed bottom-4 right-4 z-50 space-y-2"
       hx-swap-oob="true"></div>
  {% block extra_js %}{% endblock %}
</body>
</html>
```

### `templates/layouts/dashboard.html`

```html
{% extends "base.html" %}
{% block body %}
<div class="flex h-full" x-data="{ sidebarOpen: false }">
  {% include "components/sidebar.html" %}
  <div class="flex flex-col flex-1 overflow-hidden">
    {% include "components/navbar.html" %}
    <main class="flex-1 overflow-y-auto p-6">
      {% include "components/breadcrumbs.html" %}
      {% if messages %}
        {% for message in messages %}
          {% include "components/alerts/"|add:message.tags|add:".html" %}
        {% endfor %}
      {% endif %}
      {% block content %}{% endblock %}
    </main>
  </div>
</div>
{% endblock %}
```

### `tailwind.config.js`

```js
module.exports = {
  content: [
    './templates/**/*.html',
    './apps/**/templates/**/*.html',
    './static/js/**/*.js',
  ],
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        ruforum: { 50: '#f0fdf4', 500: '#16a34a', 600: '#15803d', 700: '#166534' },
      },
      fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'] },
    },
  },
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
  ],
}
```

---

## HTMX View Pattern Convention

Every HTMX-aware view returns a partial when called by HTMX, a full page otherwise:

```python
# grants/views.py
from django_htmx.http import trigger_client_event

def grant_list(request):
    grants = Grant.objects.filter(...)
    template = "grants/partials/filter_results.html" \
               if request.htmx else "grants/call_list.html"
    return render(request, template, {"grants": grants})
```

---

## apps/core/ — Shared Foundation

```
apps/core/
├── authentication/
│   ├── models.py                           ← CustomUser, UserProfile, MFADevice
│   ├── managers.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── backends.py                         ← SSO / OAuth2
│   ├── forms.py                            ← LoginForm, RegisterForm, MFAForm
│   ├── templates/
│   │   └── authentication/
│   │       ├── login.html                  ← extends layouts/auth.html
│   │       ├── register.html
│   │       ├── password_reset.html
│   │       ├── password_reset_confirm.html
│   │       ├── mfa_verify.html
│   │       └── profile.html
│   └── tests/
├── permissions/
│   ├── roles.py
│   ├── policies.py
│   └── mixins.py
├── notifications/
│   ├── models.py
│   ├── services.py
│   ├── channels.py
│   ├── tasks.py
│   └── templates/
│       ├── notifications/
│       │   ├── inbox.html
│       │   └── partials/
│       │       └── notification_item.html  ← HTMX partial: single notification row
│       └── emails/
│           ├── base_email.html
│           ├── grant_awarded.html
│           └── course_completed.html
├── audit/
│   ├── models.py
│   ├── middleware.py
│   ├── mixins.py
│   └── templates/
│       └── audit/
│           └── log_list.html
├── ai/
│   └── client.py                          ← shared LangChain wrapper (recommend / summarise)
│   # NOTE: classifiers.py / recommendations.py removed in v1 — reintroduce when a
│   # concrete cross-module callsite emerges. Live AI today lives in
│   # apps/repository/analytics/ai_insights.py and apps/rep/personalisation/embeddings.py.
├── storage/                                ← see dedicated storage utility file
│   ├── __init__.py
│   ├── handler.py                          ← UploadHandler — main utility class
│   ├── validators.py                       ← FileValidator, MagicBytesValidator
│   ├── paths.py                            ← upload_to() path generators per module
│   ├── registry.py                         ← FILE_TYPE_REGISTRY: ext → category/config
│   └── tests/
│       └── test_handler.py
├── pagination/
│   └── paginators.py
├── exceptions/
│   └── handlers.py
├── utils/
│   ├── validators.py
│   ├── formatters.py
│   ├── pdf.py
│   └── export.py
├── seeders/
│   ├── __init__.py
│   └── core_seeder.py
├── management/
│   └── commands/
│       ├── seed_roles.py
│       └── send_pending_notifications.py
├── templates/
│   └── core/
│       ├── dashboard_home.html
│       └── 403.html
├── apps.py
└── admin.py
```

---

## apps/rims/ — RUFORUM Information Management

```
apps/rims/
├── __init__.py
├── grants/
│   ├── models.py
│   ├── managers.py
│   ├── services.py
│   ├── workflows.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py                            ← GrantCallForm, ApplicationForm, ReviewForm
│   ├── tasks.py
│   ├── signals.py
│   ├── constants.py
│   ├── validators.py
│   ├── admin.py
│   ├── apps.py
│   ├── templates/
│   │   └── grants/
│   │       ├── call_list.html
│   │       ├── call_detail.html
│   │       ├── call_create.html
│   │       ├── call_edit.html
│   │       ├── application_list.html
│   │       ├── application_detail.html     ← full application + reviewer comments
│   │       ├── application_create.html     ← multi-step form
│   │       ├── review_form.html            ← reviewer scoring sheet
│   │       ├── award_dashboard.html
│   │       ├── closeout_form.html
│   │       └── partials/
│   │           ├── call_row.html           ← HTMX: single table row
│   │           ├── application_row.html
│   │           ├── status_badge.html       ← HTMX OOB swap
│   │           ├── review_score_row.html
│   │           └── filter_results.html     ← HTMX search/filter response
│   ├── seeders/
│   │   └── grants_seeder.py
│   ├── migrations/
│   └── tests/
├── projects/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── tasks.py
│   ├── apps.py
│   ├── templates/
│   │   └── projects/
│   │       ├── project_list.html
│   │       ├── project_detail.html         ← milestones timeline + budget tracker
│   │       ├── project_create.html
│   │       ├── milestone_form.html
│   │       └── partials/
│   │           ├── milestone_row.html
│   │           ├── budget_progress.html    ← HTMX: live budget gauge
│   │           └── risk_flag_row.html
│   ├── seeders/
│   │   └── projects_seeder.py
│   ├── migrations/
│   └── tests/
├── scholarships/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── signals.py
│   ├── apps.py
│   ├── templates/
│   │   └── scholarships/
│   │       ├── scholarship_list.html
│   │       ├── scholar_list.html
│   │       ├── scholar_detail.html
│   │       ├── enrolment_form.html
│   │       ├── progress_report_form.html
│   │       ├── stipend_list.html
│   │       └── partials/
│   │           ├── scholar_row.html
│   │           └── stipend_row.html
│   ├── seeders/
│   │   └── scholarships_seeder.py
│   ├── migrations/
│   └── tests/
├── operations/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── operations/
│   │       ├── partner_list.html
│   │       ├── partner_detail.html
│   │       ├── institution_list.html
│   │       ├── mou_list.html
│   │       └── partials/
│   │           ├── partner_row.html
│   │           └── mou_row.html
│   ├── seeders/
│   │   └── operations_seeder.py
│   ├── migrations/
│   └── tests/
└── finance/
    ├── models.py
    ├── services.py
    ├── serializers.py
    ├── views.py
    ├── urls.py
    ├── forms.py
    ├── apps.py
    ├── templates/
    │   └── finance/
    │       ├── budget_overview.html
    │       ├── budget_detail.html
    │       ├── expenditure_list.html
    │       ├── disbursement_list.html
    │       ├── disbursement_form.html
    │       └── partials/
    │           ├── expenditure_row.html
    │           ├── budget_line_row.html
    │           └── cashflow_chart.html     ← HTMX-loaded chart partial
    ├── seeders/
    │   └── finance_seeder.py
    ├── migrations/
    └── tests/
```

---

## apps/rep/ — Regional E-Learning Platform

```
apps/rep/
├── __init__.py
├── courses/
│   ├── models.py
│   ├── managers.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── signals.py
│   ├── apps.py
│   ├── templates/
│   │   └── courses/
│   │       ├── catalogue.html              ← browsable catalogue with filters
│   │       ├── course_detail.html          ← syllabus, modules, enrol CTA
│   │       ├── course_create.html
│   │       ├── course_edit.html
│   │       ├── my_courses.html             ← learner dashboard
│   │       ├── programme_list.html
│   │       ├── programme_detail.html
│   │       ├── certificate.html            ← printable certificate
│   │       └── partials/
│   │           ├── course_card.html
│   │           ├── progress_bar.html
│   │           ├── enrolment_status.html   ← HTMX OOB: button → status badge
│   │           └── catalogue_results.html  ← HTMX filter response
│   ├── seeders/
│   │   └── courses_seeder.py
│   ├── migrations/
│   └── tests/
├── content/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── content/
│   │       ├── lesson_view.html            ← lesson player: video/SCORM/H5P embed
│   │       ├── lesson_create.html
│   │       ├── scorm_embed.html
│   │       ├── oer_browser.html            ← Repository OER search + link
│   │       └── partials/
│   │           ├── lesson_sidebar.html     ← module nav tree
│   │           └── oer_search_results.html ← HTMX live OER search
│   ├── seeders/
│   │   └── content_seeder.py
│   ├── migrations/
│   └── tests/
├── assessments/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── assessments/
│   │       ├── quiz_list.html
│   │       ├── quiz_attempt.html           ← question-by-question HTMX navigation
│   │       ├── quiz_result.html
│   │       ├── gradebook.html
│   │       ├── question_bank.html
│   │       └── partials/
│   │           ├── question.html           ← HTMX: load next question
│   │           └── result_row.html
│   ├── seeders/
│   │   └── assessments_seeder.py
│   ├── migrations/
│   └── tests/
├── forums/
│   ├── models.py
│   ├── services.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── forums/
│   │       ├── forum_list.html
│   │       ├── thread_list.html
│   │       ├── thread_detail.html          ← threaded posts with HTMX reply form
│   │       ├── thread_create.html
│   │       └── partials/
│   │           ├── post.html
│   │           └── reply_form.html         ← HTMX inline reply
│   ├── seeders/
│   │   └── forums_seeder.py
│   ├── migrations/
│   └── tests/
├── career/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── career/
│   │       ├── job_board.html
│   │       ├── job_detail.html
│   │       ├── job_create.html
│   │       ├── cv_builder.html             ← multi-section CV editor (Alpine.js)
│   │       ├── cv_preview.html             ← print-ready layout
│   │       ├── my_applications.html
│   │       └── partials/
│   │           ├── job_card.html
│   │           ├── cv_section.html         ← Alpine.js toggled editor section
│   │           └── application_status.html ← HTMX OOB status update
│   ├── seeders/
│   │   └── career_seeder.py
│   ├── migrations/
│   └── tests/
└── personalisation/
    ├── models.py
    ├── engine.py
    ├── services.py
    ├── tasks.py
    ├── serializers.py
    ├── views.py
    ├── apps.py
    ├── templates/
    │   └── personalisation/
    │       ├── recommended_courses.html
    │       └── partials/
    │           └── recommendation_cards.html ← HTMX-loaded on dashboard
    ├── seeders/
    │   └── personalisation_seeder.py
    ├── migrations/
    └── tests/
```

---

## apps/repository/ — Digital Repository

```
apps/repository/
├── __init__.py
├── documents/
│   ├── models.py
│   ├── managers.py
│   ├── services.py
│   ├── ingestion.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── tasks.py
│   ├── apps.py
│   ├── templates/
│   │   └── documents/
│   │       ├── browse.html                 ← collection browser with filters
│   │       ├── document_detail.html        ← metadata, abstract, download, citations
│   │       ├── document_submit.html        ← submission form with drag-drop upload
│   │       ├── qa_queue.html               ← documents pending QA review
│   │       ├── qa_review.html              ← single document QA form
│   │       ├── collection_list.html
│   │       ├── collection_detail.html
│   │       └── partials/
│   │           ├── document_row.html
│   │           ├── search_results.html     ← HTMX live search response
│   │           └── metadata_panel.html     ← HTMX collapsible metadata sidebar
│   ├── seeders/
│   │   └── documents_seeder.py
│   ├── migrations/
│   └── tests/
├── search/
│   ├── indexes.py
│   ├── services.py
│   ├── serializers.py
│   └── views.py
├── analytics/
│   ├── models.py
│   ├── services.py
│   ├── ai_insights.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── tasks.py
│   ├── apps.py
│   ├── templates/
│   │   └── analytics/
│   │       ├── usage_dashboard.html
│   │       ├── ai_insights.html
│   │       └── partials/
│   │           └── stats_widget.html       ← HTMX-loaded stats cards
│   ├── seeders/
│   │   └── analytics_seeder.py
│   ├── migrations/
│   └── tests/
└── integration/
    ├── rims_api.py
    ├── mel_feed.py
    ├── rep_link.py
    └── smehub_search.py
```

### REP → M&EL Integration Pathways (MEL-FEED-01)

Repository and REP both write to M&EL through **two intentional pathways**. Both run in production; neither is being deprecated.

| Pathway | Code | Use case | Failure mode |
|---|---|---|---|
| **In-process Django signal** | `apps/mel/tracking/receivers.py:36-43` `on_document_published` (Repository) → `apps/mel/tracking/services.py:378` `record_document_published` | Same-transaction writes that must be idempotent and visible immediately (indicator records, activity counters). Runs inside the publishing request — no network call, no retry needed. | If the receiver raises, the publish transaction rolls back. Acceptable because M&EL and Repository share the same database. |
| **HTTP outbox** | `apps/repository/integration/mel_feed.py:108` `deliver(outbox_row)` and `apps/rep/courses/tasks.py:42` `_mel_push_one` (drained by `process_rep_integration_outbox` at `:120`) | Cross-system pushes that need retry, observability, a kill switch, and resilience to MEL downtime. Used when MEL is treated as a remote service (post-split) or for long-running batch indicators. | Failures are retried with exponential backoff; after `IntegrationOutbox.MAX_RETRIES` rows are flagged FAILED and `_alert_staff_repeated_mel_failure` notifies operators (`tasks.py:83-117`). |

**Why both?** The signal handler keeps in-app M&EL views correct in real time without depending on Celery being healthy. The outbox provides durable, observable cross-system delivery — needed if MEL is ever moved to a separate database or remote service. Routing the same event through both paths is intentional: the signal updates internal counters; the outbox row is the **system of record** for cross-system audit and retry.

**Status surface.** Operators monitor delivery via `apps/repository/analytics/views.py:88-105` rendered through `analytics/integration_status.html`, with per-target tiles (MEL / REP / RIMS / SME-Hub) showing pending / sent / failed counts and the last-100 outbox rows. Direct database access via `IntegrationOutboxAdmin` (`apps/repository/documents/admin.py:144-148`).

**Consolidating is risk-without-gain.** Removing the signal would break in-app M&EL freshness whenever Celery lags; removing the outbox would lose the cross-system audit trail and retry semantics.

---

## apps/mel/ — Monitoring, Evaluation & Learning

```
apps/mel/
├── __init__.py
├── indicators/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── indicators/
│   │       ├── indicator_list.html
│   │       ├── indicator_detail.html       ← trend chart + data points
│   │       ├── logframe.html               ← logical framework matrix
│   │       ├── data_entry_form.html
│   │       └── partials/
│   │           ├── indicator_row.html
│   │           ├── progress_gauge.html     ← Alpine.js animated gauge
│   │           └── trend_chart.html        ← HTMX-loaded chart partial
│   ├── seeders/
│   │   └── indicators_seeder.py
│   ├── migrations/
│   └── tests/
├── reports/
│   ├── models.py
│   ├── services.py
│   ├── builders.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── tasks.py
│   ├── apps.py
│   ├── templates/
│   │   └── reports/
│   │       ├── report_list.html
│   │       ├── report_builder.html         ← configurator (Alpine.js)
│   │       ├── report_view.html            ← rendered report with export buttons
│   │       ├── scheduled_reports.html
│   │       └── partials/
│   │           ├── report_row.html
│   │           └── report_section.html     ← HTMX lazy-loaded section
│   ├── seeders/
│   │   └── reports_seeder.py
│   ├── migrations/
│   └── tests/
├── feedback/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── feedback/
│   │       ├── survey_list.html
│   │       ├── survey_take.html            ← HTMX page-by-page survey
│   │       ├── survey_results.html
│   │       ├── survey_create.html          ← Alpine.js dynamic field builder
│   │       └── partials/
│   │           ├── question_item.html
│   │           └── response_chart.html
│   ├── seeders/
│   │   └── feedback_seeder.py
│   ├── migrations/
│   └── tests/
└── tracking/
    ├── models.py
    ├── services.py
    ├── receivers.py                        ← listens to signals from RIMS, REP, SME-Hub
    ├── serializers.py
    ├── views.py
    ├── urls.py
    ├── apps.py
    ├── templates/
    │   └── tracking/
    │       ├── tracking_dashboard.html
    │       ├── participant_record.html     ← individual tracking timeline
    │       └── partials/
    │           ├── timeline_event.html
    │           └── impact_scorecard.html
    ├── seeders/
    │   └── tracking_seeder.py
    ├── migrations/
    └── tests/
```

---

## apps/alumni/ — Alumni Management

```
apps/alumni/
├── __init__.py
├── profiles/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── profiles/
│   │       ├── directory.html              ← searchable alumni directory
│   │       ├── profile_detail.html         ← public profile page
│   │       ├── profile_edit.html
│   │       └── partials/
│   │           ├── alumni_card.html
│   │           └── directory_results.html  ← HTMX search response
│   ├── seeders/
│   │   └── profiles_seeder.py
│   ├── migrations/
│   └── tests/
├── tracking/
│   ├── models.py
│   ├── services.py
│   ├── tasks.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── apps.py
│   ├── templates/
│   │   └── tracking/
│   │       ├── milestone_list.html
│   │       ├── milestone_create.html
│   │       ├── impact_report.html
│   │       └── partials/
│   │           └── milestone_row.html
│   ├── seeders/
│   │   └── tracking_seeder.py
│   ├── migrations/
│   └── tests/
└── engagement/
    ├── models.py
    ├── services.py
    ├── serializers.py
    ├── views.py
    ├── urls.py
    ├── forms.py
    ├── apps.py
    ├── templates/
    │   └── engagement/
    │       ├── mentorship_list.html
    │       ├── event_list.html
    │       ├── event_detail.html
    │       ├── network_groups.html
    │       └── partials/
    │           ├── event_card.html
    │           └── mentor_card.html
    ├── seeders/
    │   └── engagement_seeder.py
    ├── migrations/
    └── tests/
```

---

## apps/smehub/ — SME Hub

```
apps/smehub/
├── __init__.py
├── onboarding/
│   ├── models.py
│   ├── services.py
│   ├── workflows.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── onboarding/
│   │       ├── innovation_list.html
│   │       ├── innovation_detail.html
│   │       ├── submit_innovation.html      ← multi-step wizard (HTMX steps)
│   │       ├── vetting_queue.html
│   │       ├── vetting_form.html
│   │       └── partials/
│   │           ├── innovation_card.html
│   │           ├── vetting_row.html
│   │           ├── wizard_step.html        ← HTMX: step N → step N+1
│   │           └── workflow_status.html
│   ├── seeders/
│   │   └── onboarding_seeder.py
│   ├── migrations/
│   └── tests/
├── incubation/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── signals.py
│   ├── apps.py
│   ├── templates/
│   │   └── incubation/
│   │       ├── cohort_list.html
│   │       ├── cohort_detail.html
│   │       ├── cohort_create.html
│   │       ├── mentor_session_form.html
│   │       ├── my_programme.html           ← entrepreneur programme dashboard
│   │       └── partials/
│   │           ├── cohort_card.html
│   │           ├── session_row.html
│   │           └── programme_progress.html
│   ├── seeders/
│   │   └── incubation_seeder.py
│   ├── migrations/
│   └── tests/
├── marketplace/
│   ├── models.py
│   ├── services.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── apps.py
│   ├── templates/
│   │   └── marketplace/
│   │       ├── product_catalogue.html
│   │       ├── product_detail.html
│   │       ├── product_list_form.html
│   │       ├── buyer_registry.html
│   │       └── partials/
│   │           ├── product_card.html
│   │           ├── buyer_row.html
│   │           └── catalogue_results.html
│   ├── seeders/
│   │   └── marketplace_seeder.py
│   ├── migrations/
│   └── tests/
├── linkage/
│   ├── models.py
│   ├── services.py
│   ├── ai_matchmaking.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── forms.py
│   ├── tasks.py
│   ├── apps.py
│   ├── templates/
│   │   └── linkage/
│   │       ├── partner_directory.html
│   │       ├── partner_detail.html
│   │       ├── service_provider_list.html
│   │       ├── my_connections.html
│   │       ├── connection_request_form.html
│   │       ├── ai_recommendations.html
│   │       └── partials/
│   │           ├── partner_card.html
│   │           ├── connection_row.html
│   │           └── recommendation_card.html ← HTMX AI recommendation
│   ├── seeders/
│   │   └── linkage_seeder.py
│   ├── migrations/
│   └── tests/
└── investment/
    ├── models.py
    ├── services.py
    ├── serializers.py
    ├── views.py
    ├── urls.py
    ├── forms.py
    ├── apps.py
    ├── templates/
    │   └── investment/
    │       ├── investor_directory.html
    │       ├── funding_calls.html
    │       ├── funding_call_detail.html
    │       ├── match_list.html
    │       └── partials/
    │           ├── investor_card.html
    │           └── funding_call_card.html
    ├── seeders/
    │   └── investment_seeder.py
    ├── migrations/
    └── tests/
```

---

## API Layer & Config

```
api/v1/
├── __init__.py
├── router.py
└── urls.py

config/settings/base.py  — key values:
  AUTH_USER_MODEL = 'core.CustomUser'
  TEMPLATES[0]['DIRS'] = [BASE_DIR / 'templates']
  TEMPLATES[0]['APP_DIRS'] = True          ← auto-discovers apps/<app>/templates/
  STATICFILES_DIRS = [BASE_DIR / 'static']
  STATIC_ROOT = BASE_DIR / 'staticfiles'  ← WhiteNoise serves from here
  MEDIA_ROOT = BASE_DIR / 'uploads'
  MEDIA_URL = '/uploads/'
  INSTALLED_APPS += ['django_htmx', 'django_browser_reload']  ← dev only
```

---

## Operations Runbook

### Encryption at rest (FRREP-AC008)

Application code does **not** encrypt files itself — encryption-at-rest is delegated to the storage backend per `setup/storage_utility.md` "Security & Operations".

When `USE_S3=true`, `config/settings/production.py` writes every uploaded object with `ServerSideEncryption` set from the `AWS_S3_ENCRYPTION` env var:

| Mode | Env value | Use when |
|---|---|---|
| SSE-KMS (default) | `AWS_S3_ENCRYPTION=aws:kms` | Production. Supports CloudTrail audit + key-rotation policies. Set `AWS_S3_KMS_KEY_ID` for a customer-managed CMK; leave empty to use the AWS-managed `aws/s3` key. |
| SSE-S3 | `AWS_S3_ENCRYPTION=AES256` | Acceptable if KMS isn't available. AES-256 with bucket-default keys. |

Same scheme must apply to off-site backups. On-prem deployments rely on LUKS or equivalent at the volume level. Postgres-level encryption is a DBA concern (TDE / cloud-provider managed encryption).

---

## Recommended Packages

| Package | Purpose |
|---|---|
| `django-htmx` | `request.htmx` detection, HTMX response helpers |
| `django-tailwind` | `python manage.py tailwind start` dev watcher |
| `whitenoise` | Compressed static file serving in production |
| `django-browser-reload` | Live reload in development |
| `django-crispy-forms` + `crispy-tailwind` | Auto-render forms with Tailwind classes |
| `djangorestframework` | REST API layer |
| `drf-spectacular` | OpenAPI 3.0 schema + Swagger UI |
| `djangorestframework-simplejwt` | JWT auth for API clients |
| `django-guardian` | Object-level permissions |
| `celery` + `django-celery-beat` | Async + scheduled tasks |
| `django-celery-results` | Store task results in PostgreSQL |
| `psycopg2-binary` | PostgreSQL adapter |
| `django-redis` | Redis cache + sessions |
| `django-filter` | DRF querystring filtering |
| `django-cors-headers` | CORS for API clients |
| `django-storages` + `boto3` | S3/MinIO for production uploads |
| `python-magic` | True MIME type detection from file bytes |
| `django-haystack` + `elasticsearch-dsl` | Full-text search |
| `django-import-export` | CSV/Excel data migration |
| `django-allauth` | SSO, social auth, email verification |
| `channels` + `daphne` | WebSockets / SSE real-time |
| `reportlab` + `weasyprint` | PDF generation for certificates, reports, CVs |
| `openpyxl` | Excel report generation for M&EL |
| `django-fsm` | State machine workflows |
| `factory_boy` + `pytest-django` | Test factories + pytest integration |
| `pytest-cov` | Coverage reporting |
| `ruff` + `black` | Linting + formatting |
