Przejdź do głównej zawartości

Visits

Na tej stronie
KomendaOpisTyp
create_visitTworzy nową wizytęwrite
get_visitPobiera wizytę po IDquery
get_all_visitsPobiera wszystkie wizytyquery
get_recent_visitsPobiera ostatnie N wizytquery
update_visitAktualizuje wizytę (SOAP, status)write
delete_visitUsuwa wizytęwrite
finalize_visitFinalizuje wizytęwrite
duplicate_visitDuplikuje wizytęwrite
export_visitsEksportuje wizytyquery
import_visitsImportuje wizytywrite

erDiagram
VISIT {
string visit_id PK
string patient_id FK
string user_id FK "Lekarz prowadzący"
string clinic_id FK
timestamp visit_date
string visit_type "consultation, follow-up, vaccination..."
string visit_status "draft, in_progress, completed, cancelled"
text soap_subjective "S - Wywiad"
text soap_objective "O - Badanie"
text soap_assessment "A - Rozpoznanie"
text soap_plan "P - Plan leczenia"
text internal_notes "Notatki wewnętrzne"
text ai_sugestie "AI suggestions JSON"
text transcript "Transkrypcja"
text raw_transcript "Raw transcript"
text pending_transcript "Fragment w trakcie"
string audio_source_url
string audio_source_type "live, upload"
string recording_id FK
string processing_status "idle, transcribing, generating"
timestamp created_at
timestamp updated_at
timestamp finalized_at
}
PATIENT ||--o{ VISIT : "has"
USER ||--o{ VISIT : "conducts"

stateDiagram-v2
[*] --> draft: Nowa wizyta
draft --> in_progress: Rozpoczęcie edycji
draft --> cancelled: Anulowanie
in_progress --> draft: Autosave
in_progress --> completed: Finalizacja
completed --> [*]: Zakończona
cancelled --> [*]: Anulowana
note right of draft: Można edytować
note right of in_progress: Autosave co 30s
note right of completed: Tylko podgląd

sequenceDiagram
participant User
participant UI as VisitsHubView
participant Editor as useVisitEditor
participant Tauri as Tauri Backend
participant DB as SQLite
participant AI as Unified AI
User->>UI: Nowa wizyta
UI->>Tauri: create_visit(patient_id, visit_type)
Tauri->>DB: INSERT INTO visits
DB-->>Tauri: visit_id
Tauri-->>UI: Visit
Note over UI,Editor: Edycja wizyty
loop Autosave (30s)
Editor->>Tauri: update_visit(changes)
Tauri->>DB: UPDATE visits SET ...
end
opt Transkrypcja audio
User->>UI: Nagrywa/uploaduje audio
UI->>Tauri: transcribe_and_save_to_visit
Tauri->>AI: STT
AI-->>Tauri: transcript
Tauri->>DB: UPDATE visits SET transcript = ...
end
opt AI SOAP
User->>UI: Generate SOAP
UI->>AI: unified_ai_generate_soap
AI-->>UI: SOAP sections
UI->>Tauri: update_visit(soap_*)
end
User->>UI: Finalizuj
UI->>Tauri: finalize_visit(visit_id)
Tauri->>DB: UPDATE visits SET visit_status = 'completed'
opt Auto AI Tasks
Tauri->>AI: unified_ai_generate_tasks
AI-->>Tauri: task suggestions
end
Tauri-->>UI: Success

Tworzy nową wizytę:

// Frontend (src/services/visitService.ts)
interface CreateVisitRequest {
patient_id: string;
visit_type?: string; // 'consultation', 'follow-up', 'vaccination', etc.
visit_date?: string; // ISO date, default: now
user_id?: string; // Lekarz, default: current user
}
const visit = await safeInvoke<Visit>(
'create_visit',
withSessionPayload({ visit_data: request })
);
// Response
interface Visit {
visit_id: string;
patient_id: string;
user_id: string;
visit_date: string;
visit_type: string;
visit_status: 'draft';
soap_subjective?: string;
soap_objective?: string;
soap_assessment?: string;
soap_plan?: string;
// ... pozostałe pola
}

Pobiera pojedynczą wizytę:

const visit = await safeInvoke<Visit | null>(
'get_visit',
withSessionPayload({ visit_id })
);

// Wszystkie wizyty
const visits = await safeInvoke<Visit[]>(
'get_all_visits',
withSessionPayload({})
);
// Ostatnie N wizyt
const recentVisits = await safeInvoke<Visit[]>(
'get_recent_visits',
withSessionPayload({ limit: 50 })
);

Aktualizuje wizytę - używane przez autosave i manualne zapisy:

interface UpdateVisitRequest {
visit_id: string; // Required
// SOAP sections
soap_subjective?: string;
soap_objective?: string;
soap_assessment?: string;
soap_plan?: string;
// Other fields
visit_status?: 'draft' | 'in_progress' | 'completed' | 'cancelled';
visit_type?: string;
internal_notes?: string;
ai_sugestie?: string;
// Audio/transcript
transcript?: string;
raw_transcript?: string;
audio_source_url?: string;
audio_source_type?: 'live' | 'upload';
}
const updated = await safeInvoke<Visit>(
'update_visit',
withSessionPayload({ visit_data: request })
);

await safeInvoke(
'delete_visit',
withSessionPayload({ visit_id })
);
// Emituje event: visit-deleted

Finalizuje wizytę - blokuje dalszą edycję:

sequenceDiagram
participant UI as VisitEditor
participant Tauri as Tauri Backend
participant DB as SQLite
participant AI as AI Tasks
UI->>Tauri: finalize_visit(visit_id)
Tauri->>DB: UPDATE visits SET visit_status = 'completed', finalized_at = NOW()
DB-->>Tauri: OK
alt autoGenerateTasks enabled
Tauri->>AI: unified_ai_generate_tasks(visit_id)
AI-->>Tauri: TaskSuggestion[]
alt suggestions found
Tauri--)UI: Event: vista-tasks-refresh
end
end
Tauri-->>UI: FinalizedVisit
// Frontend (src/hooks/visits/useVisitEditor.ts)
const finalized = await safeInvoke<Visit>(
'finalize_visit',
withSessionPayload({ visit_id })
);
// Po finalizacji:
// 1. visit_status = 'completed'
// 2. finalized_at = current timestamp
// 3. Opcjonalnie: AI generuje task suggestions
// 4. Event 'vista-tasks-refresh' dla UI

Duplikuje wizytę (np. follow-up):

const duplicated = await safeInvoke<Visit>(
'duplicate_visit',
withSessionPayload({
source_visit_id: originalVisitId,
new_visit_date: '2025-01-15' // Opcjonalnie
})
);
// Kopiuje: patient_id, visit_type, SOAP sections
// Resetuje: visit_status = 'draft', visit_date = new/now
// Nie kopiuje: transcript, audio, finalized_at

flowchart TB
subgraph SOAP["SOAP Note"]
S[S - Subjective<br/>Wywiad od właściciela]
O[O - Objective<br/>Badanie kliniczne]
A[A - Assessment<br/>Rozpoznanie]
P[P - Plan<br/>Plan leczenia]
end
S --> O --> A --> P
PoleOpisPrzykład
soap_subjectiveWywiad - co zgłasza właściciel”Pies nie je od 2 dni, wymiotuje”
soap_objectiveBadanie - co stwierdził lekarz”Temp. 39.5°C, tkliwość brzucha”
soap_assessmentRozpoznanie”Podejrzenie gastroenteritis”
soap_planPlan leczenia”Płyny IV, dieta lekkostrawna”
// Frontend (src/hooks/visits/useSOAPGeneration.ts)
const { generateSOAP, isGeneratingSOAP, progress } = useSOAPGeneration();
// Generowanie SOAP z transkrypcji
const result = await generateSOAP(
patientData, // Patient info for context
visitData, // Current visit data
visitId,
onProgress, // Progress callback
userId
);
// result zawiera wygenerowane sekcje S/O/A/P
// które są automatycznie zapisywane przez useVisitEditor

PoleOpis
transcriptFinalna transkrypcja
raw_transcriptSurowa transkrypcja (przed cleanup)
pending_transcriptFragment w trakcie transkrypcji
audio_source_urlŚcieżka do pliku audio
audio_source_type'live' (nagranie) lub 'upload'
recording_idFK do tabeli recordings
// Transkrypcja i zapis do wizyty
await safeInvoke(
'transcribe_and_save_to_visit',
withSessionPayload({
file_path: audioPath,
visit_id: visitId,
source_type: 'live' // lub 'upload'
})
);
// Backend:
// 1. Transkrybuje audio (STT)
// 2. Zapisuje transcript do wizyty
// 3. Emituje event: transcription_completed

Dla długich nagrań, transkrypcja może być stopniowa:

// Merge pending fragment z główną transkrypcją
await safeInvoke(
'merge_pending_transcript',
withSessionPayload({ visit_id })
);
// Odrzuć pending fragment
await safeInvoke(
'dismiss_pending_transcript',
withSessionPayload({ visit_id })
);

Frontend obsługuje filtrowanie po stronie klienta:

src/hooks/visits/useVisitsFiltering.ts
interface VisitFilters {
status?: 'draft' | 'in_progress' | 'completed' | 'cancelled';
visitType?: string;
dateRange?: { start: Date; end: Date };
patientId?: string;
userId?: string; // Lekarz
hasTranscript?: boolean;
hasAudio?: boolean;
}
const filteredVisits = useMemo(() => {
return visits.filter(visit => {
if (filters.status && visit.visit_status !== filters.status) return false;
if (filters.visitType && visit.visit_type !== filters.visitType) return false;
if (filters.patientId && visit.patient_id !== filters.patientId) return false;
// ... więcej filtrów
return true;
});
}, [visits, filters]);

EventPayloadKiedy
visit-created{ visit_id }Po utworzeniu
visit-updated{ visit_id }Po aktualizacji
visit-deleted{ visit_id }Po usunięciu
visit-finalized{ visit_id }Po finalizacji
transcription_completed{ visit_id }Po zakończeniu STT
vista-tasks-refresh-Po wygenerowaniu AI tasks

Centralny hook do edycji wizyt:

src/hooks/visits/useVisitEditor.ts
const {
// Stan
visit,
editedData,
isDirty,
isSaving,
isGeneratingSOAP,
// Akcje
setEditedData,
saveVisit,
finalize,
generateSOAP,
cancelGeneration,
// SOAP
updateSOAPSection,
// Audio
attachAudio,
retryTranscription,
} = useVisitEditor(visitId);
// Autosave
useEffect(() => {
if (isDirty) {
const timer = setTimeout(() => saveVisit(), 30000);
return () => clearTimeout(timer);
}
}, [isDirty, editedData]);

const data = await safeInvoke<string>(
'export_visits',
withSessionPayload({ format: 'json' })
);
// Zapis do pliku
await saveFile(data, 'visits-export.json');
const result = await safeInvoke<ImportResult>(
'import_visits',
withSessionPayload({
format: 'json',
data: fileContent
})
);

KodOpisRozwiązanie
visit_not_foundWizyta nie istniejeSprawdź ID
visit_already_finalizedWizyta już sfinalizowanaUtwórz duplikat
patient_not_foundPacjent nie istniejeSprawdź patient_id
invalid_visit_statusNieprawidłowy statusSprawdź dozwolone wartości
transcription_failedBłąd transkrypcjiSprawdź audio
soap_generation_failedBłąd generowania SOAPSprawdź transkrypcję