Roadmap P0 — platform safety & ops
- Merge duplicate encoach.student.attempt/answer models into encoach_scoring
and drop the stale encoach_exam_template copies.
- Remove duplicate /api/exam/* routes; canonicalize on one controller tree.
- Gate raw-SQL seeds in seed_demo_data.py behind an explicit env flag.
- Add /api/health and /api/health/ready (DB + LLM reachability) endpoints.
- Fix docker-compose + ship odoo-docker.conf for container-local runs.
- Enforce OpenAI request_timeout=30s and @jwt_required on all AI/coach routes.
- Promote canonical cefr_mapper to encoach_ai.services.cefr_mapper.
- JWT cache TTL=30s + invalidation hook on user mutation.
Roadmap P1 — exam correctness & data provenance
- Wire QualityChecker + IeltsValidator into exam submit with a
pending_review gate (encoach_ai.services.question_validator).
- Populate RAG metadata (course_id, subject_id, entity_id, taxonomy) on
encoach_vector embeddings and add a chunking pipeline (>2000 chars).
- Add provenance fields on encoach.question (model, prompt_hash, log_id)
and validate LLM output with schema before DB insert.
- Unify response envelope to {items,total,page,size}.
- Approval reject rollback with savepoint atomicity.
- Ticket notifications on status/assignee change.
Roadmap P2 — performance & observability
- Reports: replace Python loops with SQL read_group aggregations.
- X-Request-ID middleware + structured JSON logs.
- In-process/Prometheus counters and openapi.py controller exporting a
spec by scanning @http.route decorators.
- Paymob real checkout + HMAC-SHA512 webhook verification, backed by a
new encoach.paymob.order model and ir.config_parameter credentials.
- JWT refresh tokens + revocation table.
- Composite DB indexes on hot report/ticket/attempt paths.
Roadmap P3 — human-in-the-loop & compliance
- Human-in-the-loop exam review workflow (pending_review → publish) with
new review controller and status transitions.
- encoach.ai.prompt model + versioning + admin editor endpoints (one
active version per key, render-preview dry run).
- Student feedback loop → encoach.ai.feedback (upsert per user/subject,
admin triage + resolve endpoints).
- GDPR export (/api/gdpr/export) and right-to-erasure (/api/gdpr/delete)
with anonymization, tombstone record, and admin-self-erasure guard.
- HttpCase smoke tests for /api/health and /api/health/ready.
Made-with: Cursor
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""Platform health probes (P0.4).
|
|
|
|
Exposes unauthenticated ``/api/health`` (quick liveness) and ``/api/health/ready``
|
|
(deep readiness: DB + JWT secret + OpenAI key). Designed to be consumed by
|
|
uptime monitors, load-balancer health checks and container orchestrators.
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
|
|
from odoo import http
|
|
from odoo.http import request
|
|
from odoo.release import version as odoo_version
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
_STARTED_AT = time.time()
|
|
|
|
|
|
class HealthController(http.Controller):
|
|
"""Platform health probes — safe to expose publicly."""
|
|
|
|
@http.route("/api/health", type="http", auth="none", methods=["GET"], csrf=False)
|
|
def health(self, **_kw):
|
|
"""Liveness probe: the process is up and able to handle HTTP."""
|
|
return request.make_json_response({
|
|
"status": "ok",
|
|
"service": "encoach-backend",
|
|
"odoo_version": odoo_version,
|
|
"uptime_seconds": int(time.time() - _STARTED_AT),
|
|
})
|
|
|
|
@http.route("/api/health/ready", type="http", auth="none", methods=["GET"], csrf=False)
|
|
def ready(self, **_kw):
|
|
"""Readiness probe: DB reachable, JWT secret configured, AI key known."""
|
|
checks = {}
|
|
ok = True
|
|
|
|
try:
|
|
request.env.cr.execute("SELECT 1")
|
|
checks["database"] = "ok"
|
|
except Exception as exc:
|
|
ok = False
|
|
checks["database"] = f"error: {exc}"
|
|
|
|
try:
|
|
IrParam = request.env["ir.config_parameter"].sudo()
|
|
checks["jwt_secret"] = "ok" if IrParam.get_param("encoach.jwt_secret") else "missing"
|
|
if checks["jwt_secret"] == "missing":
|
|
ok = False
|
|
ai_key = IrParam.get_param("encoach_ai.openai_api_key")
|
|
import os
|
|
if not ai_key:
|
|
ai_key = os.environ.get("OPENAI_API_KEY", "")
|
|
checks["openai_key"] = "ok" if ai_key else "missing"
|
|
except Exception as exc:
|
|
ok = False
|
|
checks["config"] = f"error: {exc}"
|
|
|
|
status = 200 if ok else 503
|
|
return request.make_json_response({
|
|
"status": "ok" if ok else "degraded",
|
|
"checks": checks,
|
|
"uptime_seconds": int(time.time() - _STARTED_AT),
|
|
}, status=status)
|