diff --git a/kazeia_central/app.py b/kazeia_central/app.py index c08d5cc..dcdc19f 100644 --- a/kazeia_central/app.py +++ b/kazeia_central/app.py @@ -22,10 +22,10 @@ from .core.adb import Adb from .core.context import VERSION, Ctx from .core.operator_router import make_router as operator_router from .core.store import Store -from .features.fleet.router import make_router as fleet_router -from .features.voices.autosync import VoiceAutoSync -from .features.voices.bridge import VoiceBridge -from .features.voices.router import make_router as voices_router +from .features.technical.fleet.router import make_router as fleet_router +from .features.technical.voices.autosync import VoiceAutoSync +from .features.technical.voices.bridge import VoiceBridge +from .features.technical.voices.router import make_router as voices_router _WEB_DIR = Path(__file__).resolve().parent / "web" _STORE_PATH = Path(os.environ.get("KAZEIA_STORE", Path.cwd() / "data" / "central.db")) diff --git a/kazeia_central/features/fleet/__init__.py b/kazeia_central/features/medical/__init__.py similarity index 100% rename from kazeia_central/features/fleet/__init__.py rename to kazeia_central/features/medical/__init__.py diff --git a/kazeia_central/features/technical/__init__.py b/kazeia_central/features/technical/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kazeia_central/features/technical/fleet/__init__.py b/kazeia_central/features/technical/fleet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kazeia_central/features/fleet/router.py b/kazeia_central/features/technical/fleet/router.py similarity index 98% rename from kazeia_central/features/fleet/router.py rename to kazeia_central/features/technical/fleet/router.py index 242bd9a..cbc1834 100644 --- a/kazeia_central/features/fleet/router.py +++ b/kazeia_central/features/technical/fleet/router.py @@ -6,7 +6,7 @@ from __future__ import annotations from fastapi import APIRouter from pydantic import BaseModel -from ...core.context import Ctx +from kazeia_central.core.context import Ctx from .service import device_overview, fan_out diff --git a/kazeia_central/features/fleet/service.py b/kazeia_central/features/technical/fleet/service.py similarity index 96% rename from kazeia_central/features/fleet/service.py rename to kazeia_central/features/technical/fleet/service.py index dad10e9..ef182d8 100644 --- a/kazeia_central/features/fleet/service.py +++ b/kazeia_central/features/technical/fleet/service.py @@ -13,8 +13,8 @@ from __future__ import annotations from typing import Any, Callable -from ...core.adb import Adb -from ...core.provider import ProviderClient +from kazeia_central.core.adb import Adb +from kazeia_central.core.provider import ProviderClient def fan_out(adb: Adb, fn: Callable[[str], Any], *, diff --git a/kazeia_central/features/voices/__init__.py b/kazeia_central/features/technical/voices/__init__.py similarity index 100% rename from kazeia_central/features/voices/__init__.py rename to kazeia_central/features/technical/voices/__init__.py diff --git a/kazeia_central/features/voices/admin_ingest.py b/kazeia_central/features/technical/voices/admin_ingest.py similarity index 98% rename from kazeia_central/features/voices/admin_ingest.py rename to kazeia_central/features/technical/voices/admin_ingest.py index f1e8e3c..c16e44d 100644 --- a/kazeia_central/features/voices/admin_ingest.py +++ b/kazeia_central/features/technical/voices/admin_ingest.py @@ -20,7 +20,7 @@ import posixpath import tempfile from typing import Any -from ...core.adb import Adb +from kazeia_central.core.adb import Adb # getExternalFilesDir("voix") de com.kazeia.admin. /sdcard = /storage/emulated/0. ADMIN_VOIX_DIR = os.environ.get( diff --git a/kazeia_central/features/voices/autosync.py b/kazeia_central/features/technical/voices/autosync.py similarity index 100% rename from kazeia_central/features/voices/autosync.py rename to kazeia_central/features/technical/voices/autosync.py diff --git a/kazeia_central/features/voices/bridge.py b/kazeia_central/features/technical/voices/bridge.py similarity index 97% rename from kazeia_central/features/voices/bridge.py rename to kazeia_central/features/technical/voices/bridge.py index 291f1f9..2463a8b 100644 --- a/kazeia_central/features/voices/bridge.py +++ b/kazeia_central/features/technical/voices/bridge.py @@ -45,7 +45,7 @@ class VoiceBridge: f"(provisionner ov_venv, ou définir OV_PYTHON)") env = dict(os.environ, PYTHONPATH=str(_REPO), PYTHONUNBUFFERED="1") self._proc = subprocess.Popen( - [self.ov_python, "-m", "kazeia_central.features.voices.worker"], + [self.ov_python, "-m", "kazeia_central.features.technical.voices.worker"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, # stderr → console text=True, env=env, bufsize=1, ) diff --git a/kazeia_central/features/voices/enroll.py b/kazeia_central/features/technical/voices/enroll.py similarity index 100% rename from kazeia_central/features/voices/enroll.py rename to kazeia_central/features/technical/voices/enroll.py diff --git a/kazeia_central/features/voices/orchestrator.py b/kazeia_central/features/technical/voices/orchestrator.py similarity index 98% rename from kazeia_central/features/voices/orchestrator.py rename to kazeia_central/features/technical/voices/orchestrator.py index 7c4b5af..8edd1f2 100644 --- a/kazeia_central/features/voices/orchestrator.py +++ b/kazeia_central/features/technical/voices/orchestrator.py @@ -18,9 +18,9 @@ import posixpath import tempfile from typing import Any -from ...core.adb import Adb -from ...core.provider import ProviderClient -from ...core.store import Store +from kazeia_central.core.adb import Adb +from kazeia_central.core.provider import ProviderClient +from kazeia_central.core.store import Store from . import admin_ingest, transfer from .bridge import VoiceBridge diff --git a/kazeia_central/features/voices/router.py b/kazeia_central/features/technical/voices/router.py similarity index 98% rename from kazeia_central/features/voices/router.py rename to kazeia_central/features/technical/voices/router.py index d3f7c1a..1034e91 100644 --- a/kazeia_central/features/voices/router.py +++ b/kazeia_central/features/technical/voices/router.py @@ -6,7 +6,7 @@ from __future__ import annotations from fastapi import APIRouter from pydantic import BaseModel -from ...core.context import Ctx +from kazeia_central.core.context import Ctx from . import orchestrator as vo diff --git a/kazeia_central/features/voices/transcribe.py b/kazeia_central/features/technical/voices/transcribe.py similarity index 100% rename from kazeia_central/features/voices/transcribe.py rename to kazeia_central/features/technical/voices/transcribe.py diff --git a/kazeia_central/features/voices/transfer.py b/kazeia_central/features/technical/voices/transfer.py similarity index 98% rename from kazeia_central/features/voices/transfer.py rename to kazeia_central/features/technical/voices/transfer.py index 6a4ed69..4e566e4 100644 --- a/kazeia_central/features/voices/transfer.py +++ b/kazeia_central/features/technical/voices/transfer.py @@ -13,7 +13,7 @@ from __future__ import annotations import posixpath -from ...core.adb import Adb, AdbError +from kazeia_central.core.adb import Adb, AdbError # Le provider expose VOICE_WAV_DIR = "${MODELS_DIR}/../voix/voix" → ce marqueur # permet d'extraire MODELS_DIR depuis un wav_path, donc le dossier voix OmniVoice cible. diff --git a/kazeia_central/features/voices/worker.py b/kazeia_central/features/technical/voices/worker.py similarity index 97% rename from kazeia_central/features/voices/worker.py rename to kazeia_central/features/technical/voices/worker.py index 81707de..213a09c 100644 --- a/kazeia_central/features/voices/worker.py +++ b/kazeia_central/features/technical/voices/worker.py @@ -1,7 +1,7 @@ """Worker d'enrôlement vocal — process persistant sous ov_venv. Lancé par `voice/bridge.py` (côté API py3.14) via : - ov_venv/bin/python -m kazeia_central.features.voices.worker + ov_venv/bin/python -m kazeia_central.features.technical.voices.worker Garde le modèle OmniVoice (enrôlement + ASR de réf) **chaud**, et sert une file de jobs via un protocole JSON-lines sur stdin/stdout. diff --git a/kazeia_central/web/app.js b/kazeia_central/web/app.js index d95e852..1f16cb4 100644 --- a/kazeia_central/web/app.js +++ b/kazeia_central/web/app.js @@ -25,15 +25,17 @@ async function withBusy(btn, fn) { const o = btn.textContent; btn.disabled = tru // ---- sections ------------------------------------------------------------- const SECTIONS = [ + { group: "🔧 Technique", domain: "tech" }, { id: "flotte", ic: "🛰", label: "Flotte", render: renderFlotte }, { id: "voix", ic: "🎙", label: "Voix", render: renderVoix }, { id: "updates", ic: "⬆", label: "Mises à jour", render: renderUpdates }, + { id: "config", ic: "⚙", label: "Config & moteur", render: () => stub("Config & moteur", "Config sampling (température, presets…) et prompts moteur, avec push par tablette. Dépend des méthodes call() base64-JSON côté app patiente (PROVIDER_RPC_SPEC).") }, { id: "audit", ic: "📋", label: "Audit", render: renderAudit }, - { sep: "Parité admin (à venir)" }, - { id: "profils", ic: "👤", label: "Profils", render: () => stub("Profils & fiche patient", "Fiche clinique chiffrée côté Kazeia-central (identité, contact, clinique) + langue par patient. La fiche est constructible maintenant ; la propagation de la langue passe par l'app patiente (PROFILE_LANGUAGE_SPEC livrée).") }, - { id: "conversations", ic: "💬", label: "Conversations", render: () => stub("Conversations cliniques", "Rapatriement chiffré des conversations + export PDF/JSON + purge + collecteur de fond. Dépend de conversations_export côté app patiente.") }, - { id: "config", ic: "⚙", label: "Config & presets", render: () => stub("Config & presets", "Édition de la config sampling + presets et push. Dépend des méthodes call() base64-JSON côté app patiente (PROVIDER_RPC_SPEC).") }, - { id: "rag", ic: "📚", label: "RAG", render: () => stub("Corpus RAG", "Authoring du corpus + test retrieval + publication OTA. Dépend de rag_upsert_json côté app patiente.") }, + { group: "⚕️ Médical / clinique", domain: "med" }, + { id: "patients", ic: "👤", label: "Patients & profils", render: () => stub("Patients & fiche patient", "Dossier patient chiffré côté Kazeia-central (identité, contact, clinique) + profils on-device + langue par patient. La fiche est constructible maintenant ; la propagation de la langue passe par l'app patiente (PROFILE_LANGUAGE_SPEC livrée).") }, + { id: "conversations", ic: "💬", label: "Conversations", render: () => stub("Conversations cliniques", "Rapatriement chiffré des conversations + export PDF/JSON + purge + collecteur de fond. Cœur RGPD. Dépend de conversations_export côté app patiente.") }, + { id: "rag", ic: "📚", label: "RAG thérapeutique", render: () => stub("Corpus RAG thérapeutique", "Authoring du corpus clinique + test retrieval + publication OTA. Dépend de rag_upsert_json côté app patiente.") }, + { id: "questionnaires", ic: "📝", label: "Questionnaires", render: () => stub("Questionnaires cliniques", "Passation d'échelles/questionnaires cliniques au patient et suivi des scores dans le temps. À concevoir avec toi.") }, ]; // ---- boot + shell --------------------------------------------------------- @@ -73,8 +75,8 @@ async function renderShell() {
`; - $("#sidebar").innerHTML = SECTIONS.map(s => s.sep - ? `` + $("#sidebar").innerHTML = SECTIONS.map(s => s.group + ? `` : ``).join(""); $("#sidebar").querySelectorAll(".nav-item").forEach(n => n.onclick = () => { S.section = n.dataset.id; renderSection(); }); await loadDevices(); @@ -94,7 +96,7 @@ async function loadDevices() { function renderSection() { document.querySelectorAll(".nav-item").forEach(n => n.classList.toggle("active", n.dataset.id === S.section)); - const sec = SECTIONS.find(s => s.id === S.section) || SECTIONS[0]; + const sec = SECTIONS.find(s => s.id === S.section) || SECTIONS.find(s => s.render); const c = $("#content"); c.innerHTML = `
`; Promise.resolve(sec.render(c)).catch(e => { c.innerHTML = `
${esc(e.message)}
`; }); } diff --git a/kazeia_central/web/styles.css b/kazeia_central/web/styles.css index 1dc9f4f..37e0fa1 100644 --- a/kazeia_central/web/styles.css +++ b/kazeia_central/web/styles.css @@ -64,7 +64,10 @@ h1,h2,h3 { margin: 0; font-weight: 650; } .nav-item.active { background: var(--primary-soft); color: var(--primary-d); font-weight: 650; } .nav-sep { height: 1px; background: var(--line); margin: 10px 6px; } .nav-label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); - padding: 6px 12px 4px; } + padding: 12px 12px 5px; font-weight: 700; } +.nav-label.dom-tech { color: var(--primary-d); border-top: 1px solid var(--line); margin-top: 4px; } +.nav-label.dom-med { color: var(--ok); border-top: 1px solid var(--line); margin-top: 10px; } +.nav-label.dom-tech:first-child { border-top: 0; margin-top: 0; } main { margin: var(--top) 0 0 var(--side); padding: 22px 26px; min-height: calc(100vh - var(--top)); } .page-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } diff --git a/tests/test_fleet.py b/tests/test_fleet.py index 615881d..06ed29f 100644 --- a/tests/test_fleet.py +++ b/tests/test_fleet.py @@ -6,7 +6,7 @@ from fastapi.testclient import TestClient from kazeia_central.core.adb import Adb, AdbError from kazeia_central.core.adb.client import AdbDevice from kazeia_central.app import create_app -from kazeia_central.features.fleet.service import device_overview, fan_out +from kazeia_central.features.technical.fleet.service import device_overview, fan_out from kazeia_central.core.store import Store diff --git a/tests/test_voice.py b/tests/test_voice.py index 13b3482..ef39919 100644 --- a/tests/test_voice.py +++ b/tests/test_voice.py @@ -13,9 +13,9 @@ import nacl.pwhash from kazeia_central.core.adb import Adb from kazeia_central.core.adb.client import AdbDevice from kazeia_central.core.store import Store -from kazeia_central.features.voices import admin_ingest -from kazeia_central.features.voices import orchestrator as vo -from kazeia_central.features.voices import transfer +from kazeia_central.features.technical.voices import admin_ingest +from kazeia_central.features.technical.voices import orchestrator as vo +from kazeia_central.features.technical.voices import transfer _OPS = nacl.pwhash.argon2id.OPSLIMIT_MIN _MEM = nacl.pwhash.argon2id.MEMLIMIT_MIN @@ -249,14 +249,14 @@ def test_sync_admin_delete_cleans(tmp_path): # ---- déclencheur auto ----------------------------------------------------- def test_autosync_blocked_when_locked(tmp_path): - from kazeia_central.features.voices.autosync import VoiceAutoSync + from kazeia_central.features.technical.voices.autosync import VoiceAutoSync locked = Store(tmp_path / "l.db", kdf_ops=_OPS, kdf_mem=_MEM) # pas unlock a = VoiceAutoSync(_Adb([_manifest("paul", "global")]), locked, _Bridge()) assert a.sync_now("D1")["state"] == "store_verrouille" def test_autosync_enabled_ingests(tmp_path): - from kazeia_central.features.voices.autosync import VoiceAutoSync + from kazeia_central.features.technical.voices.autosync import VoiceAutoSync s = _store(tmp_path) a = VoiceAutoSync(_Adb([_manifest("paul", "global")]), s, _Bridge()) a.set_enabled(True, delete_source=False)