# EnCoach — Work Report
**Period covered:** Recent hardening & delivery sprint (Phases 0 → 3 of the
improvement roadmap).
**Repository of record:** `https://git.albousalh.com/yamen/full_encoach_platform`
**Primary branch:** `main` (= current `v4` tip, commit `93c530ee`).
This report summarizes what was built, fixed, and documented in the last few
days. It's organized by phase so each block maps 1:1 to the roadmap items and
the commits that shipped them.
---
## Table of contents
- [Executive summary](#executive-summary)
- [What shipped — by phase](#what-shipped--by-phase)
- [Phase 0 — Platform safety & operations](#phase-0--platform-safety--operations)
- [Phase 1 — Exam correctness & data provenance](#phase-1--exam-correctness--data-provenance)
- [Phase 2 — Performance & observability](#phase-2--performance--observability)
- [Phase 3 — Human-in-the-loop, compliance, i18n, a11y, CI](#phase-3--human-in-the-loop-compliance-i18n-a11y-ci)
- [Branding update](#branding-update)
- [Repository migration](#repository-migration)
- [Commits landed in this sprint](#commits-landed-in-this-sprint)
- [Verification results](#verification-results)
- [Known gaps & deferred items](#known-gaps--deferred-items)
- [Operator checklist for production](#operator-checklist-for-production)
---
## Executive summary
| Area | Before | After |
| -------------------------- | -------------------------------------------- | ----------------------------------------------------------------------- |
| Duplicate exam models | Two `student.attempt` models in two modules | Single canonical `encoach.student.attempt` in `encoach_scoring` |
| Auth on AI routes | Public in places | `@jwt_required` on every AI / coach endpoint |
| Health check | None | `GET /api/health` + `GET /api/health/ready` (DB + LLM probes) |
| LLM resilience | Hangs possible | `request_timeout=30s` on OpenAI calls |
| Exam submit | Straight to graded | QualityChecker + IeltsValidator → `pending_review` gate |
| RAG metadata | Missing entity/course/subject | Full provenance on every embedding + chunking for large passages |
| Question provenance | Anonymous | `model`, `prompt_hash`, `log_id` stamped on every `encoach.question` |
| LLM output validation | Raw JSON hope-for-the-best | Schema validation before DB insert |
| Response envelope | Inconsistent across controllers | `{items,total,page,size}` everywhere |
| Reports | Python loops over thousands of rows | SQL `read_group` aggregates |
| Payments | Mocked Paymob | Real Accept flow + HMAC-SHA512 webhook verification |
| JWT | Single access token only | Refresh tokens + revocation table |
| Frontend bundle | Monolithic | `React.lazy` + Vite `manualChunks` (radix/charts/icons/forms split) |
| TypeScript errors | 181 | 0 |
| i18n | Hard-coded English | `i18next` + en/ar locales + RTL auto-switch + LanguageToggle |
| Exam review | No human review step | `pending_review → publish` workflow + admin UI |
| AI prompts | Buried in Python constants | `encoach.ai.prompt` versioned templates + admin editor + render preview |
| Student feedback on AI | None | Thumbs up/down → `encoach.ai.feedback` + admin triage |
| GDPR | None | `/api/gdpr/export` + `/api/gdpr/delete` + Privacy Center page |
| Dark mode | Partial | Full `ThemeProvider` + toggle + chart tokens |
| Accessibility | Dialog warnings | `DialogDescription` on every `DialogContent` |
| CI | None | GitHub Actions: frontend (tsc, lint, build, Playwright) + backend Odoo |
| Documentation | Scattered | §21 Hardening Release in `PROJECT_SUMMARY.md` + 4 ADRs |
---
## What shipped — by phase
### Phase 0 — Platform safety & operations
Goal: make the platform *safe to run* before adding features.
1. **Merged duplicate exam models.** `encoach_scoring` is now the single home
for `encoach.student.attempt` and `encoach.student.answer`; the stale
copies in `encoach_exam_template` were deleted along with the duplicate
`/api/exam/*` controllers.
2. **Added `@jwt_required` to every AI / coach route** so no AI endpoint is
reachable without an authenticated token.
3. **Added health endpoints**
- `GET /api/health` — liveness.
- `GET /api/health/ready` — readiness (DB ping + LLM reachability).
4. **Hardened OpenAI calls** with `request_timeout=30s` to prevent hung
workers on flaky provider responses.
5. **Gated `seed_demo_data.py` raw SQL** behind an explicit
`ENCOACH_SEED_DEMO=1` env flag so a stray run in production cannot
clobber real data.
6. **Fixed `docker-compose.yml`** and shipped `odoo-docker.conf` so the
container-local run works out of the box.
7. **Published `/logo.svg`** as a deployable asset (later superseded by the
project-manager PNG — see [Branding update](#branding-update)).
8. **Promoted a canonical `cefr_mapper`** to `encoach_ai.services.cefr_mapper`
(no more two copies drifting).
9. **JWT cache TTL = 30s** with an invalidation hook on user mutations so
role/entity changes propagate within half a minute.
### Phase 1 — Exam correctness & data provenance
Goal: make every AI-generated item *trustworthy and traceable*.
1. **QualityChecker + IeltsValidator wired into exam submit.** Submitted
attempts now pass through a validation layer and are parked in
`pending_review` if anything fails IELTS rubric / quality rules, instead
of being scored blind.
2. **Populated RAG metadata** on every `encoach.vector.embedding` record:
`course_id`, `subject_id`, `entity_id`, and taxonomy tags. Queries can
now be scoped per tenant / per skill.
3. **Chunking pipeline** for content > 2000 chars keeps embeddings within
model limits and preserves passage locality.
4. **Provenance fields on `encoach.question`**: `ai_model`, `prompt_hash`,
`log_id` — so any item can be traced back to the exact prompt + run
that produced it.
5. **Schema validation of LLM output** before DB insert (jsonschema-style).
Malformed JSON no longer reaches storage.
6. **Response envelope unified** to `{items, total, page, size}` across all
controllers.
7. **Approval reject rollback** now uses a PG savepoint so a partial
failure during rejection leaves no orphaned rows.
8. **Ticket notifications** fire on status and assignee change.
9. **`new_project/` retired.** `backend/` is declared canonical;
`new_project/DEPRECATED.md` carries the redirect notice.
### Phase 2 — Performance & observability
Goal: make the platform *fast and measurable*.
1. **Reports use SQL `read_group`** instead of Python loops. The admin
Reports pages no longer scan tens of thousands of attempt rows in
Python.
2. **`X-Request-ID` middleware + structured JSON logs** so a single
request can be traced end-to-end across controllers.
3. **Prometheus-style counters** (basic in-process) exposed through the new
`encoach_api` `openapi.py` controller, which also exports a live
OpenAPI spec by scanning `@http.route` decorators at runtime.
4. **In-process caching** for hot reports and the AI narrative endpoint,
with a Redis-ready seam.
5. **Paymob Accept integration** — the full 3-step flow (auth → order →
payment key) plus an HMAC-SHA512-verified webhook endpoint that
updates `encoach.paymob.order` atomically. Credentials come from
`ir.config_parameter`.
6. **JWT refresh tokens** with a revocation table and auto-refresh on the
frontend API client.
7. **Composite DB indexes** on the hottest report / ticket / attempt paths.
8. **React.lazy + Vite `manualChunks`** split `vendor-radix`,
`vendor-charts`, `vendor-icons`, `vendor-forms`, `vendor-react`, and
`vendor-query` into their own bundles.
9. **Fixed all 181 TypeScript errors.** `npx tsc --noEmit -p
tsconfig.app.json` now passes cleanly.
### Phase 3 — Human-in-the-loop, compliance, i18n, a11y, CI
Goal: make the platform *professional and defensible*.
1. **i18n.** `i18next` + language detector + en/ar locales + RTL
auto-switch + `LanguageToggle` component wired into both role and
admin layouts.
2. **GDPR compliance.** `/api/gdpr/export` returns a JSON snapshot of the
caller's personal data. `/api/gdpr/delete` anonymizes PII, nulls
logs, deactivates the account, creates an
`encoach.gdpr.erasure.request` tombstone, and refuses admin
self-erasure. A new `PrivacyCenter` page in the student / teacher /
admin portals exposes both actions.
3. **Human-in-the-loop exam review.** `pending_review → publish`
workflow, backed by a new `review_workflow.py` controller and two new
admin pages: `ExamReviewQueue` and `ExamReviewDetail`.
4. **AI prompt management.** New `encoach.ai.prompt` model with
versioning ("one active row per key"), REST endpoints for list /
retrieve / create / activate / render-preview, and the
`AIPromptEditor` admin page.
5. **Student AI feedback loop.** `encoach.ai.feedback` with upsert
semantics per `(user, subject_type, subject_id)`, reusable
`AIFeedbackButtons` component, and the `AIFeedbackTriage` admin page
with status filters and resolve actions.
6. **Dark mode** fully wired: `ThemeProvider` from `next-themes`,
`ThemeToggle`, and chart color tokens that follow the theme.
7. **Accessibility.** `DialogDescription` added to every
`DialogContent`, fixing the Radix a11y warning across the app.
8. **CI scaffolding.** `.github/workflows/ci.yml` runs two jobs on every
push: *frontend* (tsc → lint → build → Playwright smoke) and
*backend* (Postgres 16 + `odoo:19 --test-enable --test-tags
encoach_api`).
9. **Onboarding consolidated.** Single root `README.md` plus four ADRs:
- ADR-0001 canonical directory layout
- ADR-0002 JWT refresh token flow
- ADR-0003 paginated response envelope
- ADR-0004 RAG metadata + chunking
10. **Dead code removed.** `ExamPage` marketing, `Index`, `ProfilePage`
import, and other legacy routes excised.
---
## Branding update
The project-manager-supplied `EnCoach` logo (icon + wordmark + tagline
"Unlock your potential with AI powered platform") now appears across:
- **Login page** — shown at `h-36 w-auto` so the wordmark and tagline are
legible.
- **Student / teacher sidebar** — small rounded icon when collapsed, full
wordmark at `h-14` when expanded.
- **Admin sidebar** — same collapsed / expanded behavior.
- **`og:image` meta** (social cards) — already pointed at
`/logo-icon.png`, so it picks up the new asset automatically.
The duplicated inline `EnCoach` text was removed from every
sidebar since the wordmark is now baked into the image.
Commit: `47d09a3c chore(branding): adopt project-manager EnCoach logo
across login and sidebars`.
---
## Repository migration
To consolidate the codebase into a single canonical repo for future work,
the entire monorepo was migrated to:
- **`https://git.albousalh.com/yamen/full_encoach_platform`**
Migration steps performed:
1. Added the new repo as the `origin` remote.
2. Pushed the current `v4` tip as `main` (default branch).
3. Pushed all six local branches: `v4`, `v3`, `frontend-v3`,
`full_stack_dev`, `feature/full-backend-v1`,
`feature/generation-page-ai-workflows`.
4. Pushed all tags (none existed locally).
5. Rebound `v4` upstream to `origin/v4`.
6. **Removed all seven prior remotes** from `.git/config`:
`backend-v3`, `frontend`, `frontend-v3`, `ip-origin`,
`mirror-monorepo`, `origin-backend`, `origin-frontend`.
The local checkout now has exactly one remote — `origin` → the new URL —
so any future `git push` / `git pull` goes there with no further config.
---
## Commits landed in this sprint
```
93c530ee chore(ci,docs): GitHub Actions, ADRs, README overhaul, §21 Hardening Release
e70a2854 feat(frontend): Phase 2/3 hardening release
dcf5ea69 feat(backend): Phase 2/3 hardening release
47d09a3c chore(branding): adopt project-manager EnCoach logo across login and sidebars
c016a522 feat(reports): replace mock Reports pages with real backend aggregates
d940db07 fix(config): align Configuration pages with platform logic
7f23127e chore(remotes,docs): rename remotes to match source-of-truth doctrine
7737f6de docs(summary): declare encoach_backend_v4 + encoach_frontend_v4 as repos of record
```
File-churn totals for the four hardening commits:
| Commit | Files changed | Lines + | Lines − |
| ----------------------- | ------------- | ------- | ------- |
| branding (logo) | 4 | +42 | −18 |
| backend hardening | 70 | +4 221 | −716 |
| frontend hardening | 75 | +3 722 | −546 |
| CI + docs + ADRs | 10 | +740 | −96 |
---
## Verification results
Ran before each commit and again after the final commit:
| Check | Result |
| ------------------------------------------------------ | ----------------------------------------- |
| `python -m compileall backend/custom_addons/encoach_*` | Pass, no syntax errors |
| `npx tsc --noEmit -p tsconfig.app.json` | Pass (0 errors, down from 181) |
| `npm run build` | Pass — 3.63 s, bundles split as expected |
| `python -m compileall seed_demo_data.py` | Pass |
| Playwright smoke (`test:e2e`) | 2 / 2 tests pass (login renders, root→/login) |
Frontend bundle profile after `manualChunks`:
```
vendor-charts 422.80 kB │ gzip: 112.73 kB
vendor-radix 318.60 kB │ gzip: 99.50 kB
index (app shell) 219.02 kB │ gzip: 65.36 kB
vendor-forms 88.31 kB │ gzip: 24.40 kB
vendor-icons 53.77 kB │ gzip: 9.83 kB
vendor-query 41.17 kB │ gzip: 12.23 kB
vendor-react 23.33 kB │ gzip: 8.60 kB
```
---
## Known gaps & deferred items
These were intentionally **not** addressed in this sprint to keep scope
bounded. Each has a clear next owner / next step.
- **P1.2 — `ir.rule` entity isolation + drop blanket `sudo()`.** Requires
a full `sudo()` audit across every module + coordinated frontend
changes. Deferred to a follow-up ticket.
- **AIFeedbackButtons inside `AiStudyCoach`.** The coach's response
currently lacks the `ai_log_id` that the feedback model keys on. The
reusable component ships, but wiring it into the coach is gated on a
small service change.
- **Redis cache backend.** The in-process cache ships; wiring it to Redis
is a config + deployment-time step and was deferred.
- **Paymob credentials.** `payment.paymob.api_key`,
`payment.paymob.integration_id`, `payment.paymob.hmac_secret`, and
`payment.paymob.iframe_id` must be set in `ir.config_parameter`
before checkout works. Left unset on purpose — no secrets in git.
- **Arabic translation coverage.** Core UI (nav, auth, privacy,
feedback, theme, common) is translated; deeper exam-builder strings
still fall through to English.
---
## Operator checklist for production
Before promoting to production, an operator should:
1. **Set Paymob credentials** in `ir.config_parameter`:
- `payment.paymob.api_key`
- `payment.paymob.integration_id`
- `payment.paymob.hmac_secret`
- `payment.paymob.iframe_id`
2. **Set the OpenAI key** in `ir.config_parameter` as `openai.api_key`
(or the matching env var on the container).
3. **Run migrations** for the new modules / models:
`encoach.ai.prompt`, `encoach.ai.feedback`,
`encoach.gdpr.erasure.request`, `encoach.paymob.order`,
`encoach.jwt.token` (refresh revocation).
4. **Verify health** from outside the container:
`curl https:///api/health` → 200, and
`curl https:///api/health/ready` → 200 or 503 with structured
reason.
5. **Pin the default-branch protection** on
`git.albousalh.com/yamen/full_encoach_platform` (`main`).
6. **Schedule the CI workflow** — first run happens automatically on the
next push.
7. **Smoke Playwright locally** one more time: `cd frontend && npm run
test:e2e` (requires browsers via `npm run test:e2e:install`).