Commit Graph

43 Commits

Author SHA1 Message Date
Brummel 9072d7a833 Refactor analyze recovery and worker busy status
The analyze recovery scan has been refactored to iterate through user
directories and enqueue pending analysis jobs more efficiently. The
`WorkerBusy` status has been introduced as an `Arc<AtomicBool>` to track
whether the analyze worker is currently processing a job.

This `WorkerBusy` flag is used in the `analyze::worker::run` function
and managed by a `BusyGuard` RAII struct, ensuring the flag is correctly
set and unset even in case of panics.

The `handle_my_cases` and `handle_case_detail` handlers in `user_web.rs`
now utilize the `WorkerBusy` flag to accurately display the "analyzing"
status and to trigger a self-heal of orphaned analysis inputs when the
worker is idle.

Additionally, the `WorkerBusy` type is now exported from
`server/src/lib.rs` to be accessible by other modules. The test cases
have been updated to include the `WorkerBusy` parameter when spawning
the analyze worker.
2026-04-16 00:19:08 +02:00
Brummel 4e145dd167 Refactor back button link on document page 2026-04-15 23:32:41 +02:00
Brummel 05f11872fc Refactor case detail and document views
Introduce a "Back to Cases" button in the document view and update the
case detail page to conditionally display the "Analyze" button. Also,
add a copy-to-clipboard functionality for the document content.
2026-04-15 23:30:30 +02:00
Brummel 0b63d8ff56 Feat: Add bulk actions for case analysis and deletion
Introduces a new `/web/cases/bulk` endpoint to handle multiple case
actions simultaneously.

This change adds the following:
- A new `bulk` module for handling bulk operations.
- The `handle_bulk_action` function in `bulk.rs` to dispatch to specific
  actions.
- `bulk_analyze` and `bulk_delete` functions to process the actions.
- Updates `Cargo.toml` and `Cargo.lock` to include necessary
  dependencies: `form_urlencoded`, `serde_core`, `serde_html_form`, and
  `serde_path_to_error`.
- Modified `axum-extra` features to include `form`.
- Adds checks for deleted cases in `collect_pending` for both analysis
  and transcription recovery.
- Renames and modifies `handle_close_case` to `handle_analyze_case` in
  `case_actions.rs` to better reflect its functionality.
- Adds a new `handle_delete_case` and `handle_undo_delete` to
  `case_actions.rs`.
- Updates `my_cases.html` and `case_detail.html` to support bulk
  actions, including checkboxes, a bulk action bar, and an "Undo last
  delete" feature.
- Modifies `handle_audio` and `scan_cases` in `web.rs` to respect delete
  markers.
- Updates `analyze_test.rs` with new request builders for analyze and
  delete actions.
2026-04-15 23:06:31 +02:00
Brummel 36b3e5f6c1 Add delete marker support to paths
Introduce DeleteMarker struct and associated functions for managing
soft-delete markers within case directories. This enables tracking
deleted cases and supports undo functionality.
feat: Add delete marker support to paths

Introduces a `.deleted` file to mark cases as soft-deleted. The marker
contains a `batch` UUID for grouping deletions and a `deleted_at`
timestamp for ordering.

New functions `is_deleted`, `read_delete_marker`, and
`write_delete_marker` are added to manage this marker. The `locate_case`
function is updated to also consider soft-deleted cases as not found.
2026-04-15 22:41:47 +02:00
Brummel 11eb645f3f Refactor: Simplify case directory structure
The distinction between `open/` and `done/` directories for cases has
been removed.
All cases for a user now reside directly under the user's directory
(e.g., `<data_path>/<slug>/<case_id>/`).

This simplifies path management and eliminates redundant directory
traversals.

Key changes include:
- Removed `open/` and `done/` subdirectories in path resolution.
- Introduced a `paths::case_dir` function as a single source of truth
  for case directory layout.
- Updated various modules (`recovery`, `auth`, `routes`, `transcribe`,
  `tests`) to use the new path structure.
- Adjusted templates to reflect the simplified case status
  representation.
2026-04-15 22:15:13 +02:00
Brummel 70eed20909 Add LLM system prompt override
Allow overriding the LLM system prompt via the `LLM_SYSTEM_PROMPT`
environment variable. This provides flexibility for customizing LLM
behavior without code changes. A default prompt is used if the
environment variable is unset or empty.
2026-04-15 21:31:21 +02:00
Brummel 5608c8ba43 feat: Add reanalyze case functionality
Introduce a new endpoint for reanalyzing cases. This feature allows
administrators
to rebuild analysis input files and enqueue new analysis jobs. The
system will
then generate a new document version, which will become the displayed
document.
2026-04-15 21:19:56 +02:00
Brummel 7bcd94f0d0 Refactor analysis input building
Extract the logic for building `AnalysisInput` into a new function
`build_analysis_input`. This function encapsulates the process of
collecting recordings, reading transcripts, and determining the last
recording modification time.

This change improves code organization and reusability by centralizing
the input building logic.
2026-04-15 21:16:32 +02:00
Brummel 1fa737a2e6 Add Forbidden error variant 2026-04-15 21:14:01 +02:00
Brummel 1486c4db9e Update LLM URL and warn if LLM not configured
Updates the `LLM_URL` in the project plan and `.env.example` to the
correct value.
Also, adds a warning in `main.rs` to inform the user if the LLM is not
configured, which effectively disables the 'Fall abschließen' feature.
2026-04-15 21:12:22 +02:00
Brummel 9c50c003a3 feat: Add tests for closing a case
This commit introduces a comprehensive suite of integration tests for
the case closing functionality.
These tests cover various scenarios including:
- Successful case closure with proper file writing and redirection.
- Handling of invalid inputs like non-UUIDs or cases without recordings.
- Authentication and authorization checks, ensuring only the case owner
  can close it.
- Error handling for attempting to close a case twice or when the LLM
  service is unavailable.
- Verification of the analysis worker's interaction with the mocked LLM.
- Testing the recovery mechanism for re-enqueuing pending analyses.
- Validating the document view displays the latest version.
2026-04-15 19:29:35 +02:00
Brummel 4b554f04ff Refactor case detail and document view
Introduce a `DocumentTemplate` struct for rendering the document view,
replacing inline HTML.
Introduce a `CaseFlags` struct to encapsulate the logic for determining
various UI states of a case (e.g., `has_document`, `analyzing`,
`can_close`, `llm_missing`).
Update `handle_case_detail` to use `compute_flags` for populating the
`CaseDetailTemplate`.
Update `scan_user_cases` to compute `analyzing` and `has_document` flags
for the `UserCaseView`.
Add new template logic in `case_detail.html` to display actions based on
`CaseFlags`.
Add new template logic in `my_cases.html` to display `analyzing` and
`done-doc` labels for cases.
Create a new `document.html` template.
Add a helper function `any_document_exists` to check for the presence of
a document file.
2026-04-15 19:25:59 +02:00
Brummel aed4cd59d3 feat: Add case closing and document viewing routes
Adds API endpoints for closing a case, which triggers an analysis job,
and for viewing the generated document.

This also includes:
- New `AppError` variants: `Conflict` and `ServiceUnavailable`.
- Helper functions for file handling, date parsing, and document
  searching.
- Unit tests for helper functions.
2026-04-15 19:22:25 +02:00
Brummel aa6b8fd798 Add analyze channel to AppState
Introduces a new channel for the analyze pipeline, including its sender
in the `AppState`.
Also updates the test router to create both transcribe and analyze
channels.
2026-04-15 19:16:27 +02:00
Brummel e2a05a108a feat: Add analyze module for LLM integration
This commit introduces the `analyze` module, which orchestrates
communication with Large Language Models (LLMs) for text summarization
and analysis.

Key components include:
- `llm.rs`: Contains the `LlmError` enum and the `chat_once` function
  for interacting with OpenAI-compatible LLM APIs.
- `prompt.rs`: Defines the system prompt and logic for rendering user
  content from analysis inputs.
- `recovery.rs`: Implements logic to scan for and re-enqueue pending
  analysis jobs upon server startup.
- `worker.rs`: The core worker that consumes analysis jobs, processes
  them with the LLM, and writes the output.
- `mod.rs`: Defines `AnalyzeJob` and associated channel types for
  inter-component communication.

This module enables the server to process dictated recordings, summarize
them using an LLM, and store the results.
2026-04-15 19:14:28 +02:00
Brummel 5435b60b80 Refactor config to use test_default
Introduced a `test_default` method to `Config` to provide sane defaults
for integration tests. This refactors several test files to use this new
method, reducing boilerplate and improving maintainability.

Added `LLM_TIMEOUT_SECONDS` to the environment variables and `Config`
struct.
2026-04-15 19:07:58 +02:00
Brummel 872e943da9 feat: Set default run target for server 2026-04-14 15:20:37 +02:00
Brummel c55f9a392c feat: Enhance password hashing utility
This commit introduces several improvements to the `hash-password`
binary:

- **New command-line interface:** Supports printing hashes directly,
  hashing via prompts, or patching `web_password` in `users.toml` in
  place.
- **Atomic file patching:** Utilizes `toml_edit` to preserve comments,
  ordering, and formatting, and writes changes atomically via a
  temporary file and rename operation.
- **Robust argument parsing:** Handles various command-line scenarios
  and provides informative usage messages.
- **Error handling:** Improves error reporting for failed operations.
- **Added unit tests:** Includes tests for patching behavior and error
  conditions.
2026-04-14 15:12:00 +02:00
Brummel d489716c9b Feat: Implement basic web authentication and UI
This commit introduces the foundation for web-based authentication and
user interface. It includes:

- **Session Management**: Securely handling user sessions using
  cryptographically generated tokens, cookies with appropriate security
  flags, and server-side storage.
- **Login/Logout Functionality**: Endpoints for users to log in with
  their credentials and log out, clearing their session.
- **User Interface**: Basic templates for the login page, a list of
  cases, and a detailed view of a single case, allowing users to
  navigate and view their data.
- **Authorization**: An `AuthenticatedWebUser` extractor to ensure only
  logged-in users can access protected web routes.
- **Configuration**: Added a `cookie_secure` option to the configuration
  to control the `Secure` flag on session cookies, useful for local
  development.
- **Dependencies**: Added necessary dependencies for password hashing,
  time handling, and TOML file manipulation.
- **Helper Binary**: Included a `hash-password` binary to simplify
  generating bcrypt hashes for user passwords.
2026-04-14 15:09:23 +02:00
Brummel 61b5c8e125 feat: Mark failed recordings with .m4a.failed
Add a new `failed` field to `RecordingView` and corresponding logic in
the worker.
When FFmpeg remuxing or Whisper transcription fails, the worker now
renames the
original `.m4a` file to `.m4a.failed`. This prevents the recovery scan
from
reprocessing these files and signals to the UI that transcription
failed.

The UI displays a message indicating failure and suggests renaming the
file
back to `.m4a` to retry. A new integration test
`worker_renames_audio_to_failed_on_whisper_error`
is included to verify this behavior.
2026-04-14 14:32:22 +02:00
Brummel f5b99106b1 Add per-user whisper settings for hotwords and initial prompt
This commit introduces the capability to configure user-specific Whisper
settings, including language, hotwords, and initial prompts. These
settings are stored in `users.toml` and are passed to the Whisper
service for more tailored transcription results.

New unit tests have been added to verify that the `transcribe` function
correctly forwards these optional settings to the Whisper client and
omits them when they are not provided.

Additionally, a new directory `tests/fixtures/dictations` has been
created to store M4A audio files, their expected transcripts, and
associated hotword files. This serves as a regression corpus for the
Whisper pipeline. A README file explains the structure and conventions
for adding new fixtures. Several new fixtures have been added, covering
various medical domains and transcription scenarios.
2026-04-14 11:56:48 +02:00
Brummel b63a269776 feat: Forward user settings to Whisper API
Include per-user hotwords and initial prompts in the multipart form data
sent to the Whisper API. The language parameter is now dynamically set
based on user configuration, defaulting to "de" if not specified.
2026-04-14 11:04:28 +02:00
Brummel fc8efc62e0 Add user slug to transcription jobs
The `TranscribeJob` struct now includes a `user_slug` field. This is
necessary for the transcription worker to look up per-user settings such
as hotwords and initial prompts. The `collect_pending` function has been
updated to return user slugs along with audio paths, and the recovery
scan now enqueues jobs with the correct user slug.
2026-04-14 11:02:37 +02:00
Brummel 0ef9109876 Add Whisper user settings to config
Introduce a new struct `WhisperUserSettings` to hold per-user
transcription settings. This allows users to customize Whisper
transcription for their specific needs directly in the `users.toml`
configuration file.

The changes include:
- Defining `WhisperUserSettings` with optional fields for `language`,
  `hotwords`, and `initial_prompt`.
- Integrating `WhisperUserSettings` into the `User` struct, with
  `#[serde(default)]` to ensure it defaults to
  `WhisperUserSettings::default()` if not present in the TOML.
- Adding new unit tests to verify that users can be parsed correctly
  with and without a `[user.whisper]` block.
- Updating existing tests in `auth_test.rs`, `upload_test.rs`, and
  `web_test.rs` to include the new `whisper` field, ensuring backward
  compatibility.
2026-04-14 11:00:52 +02:00
Brummel 6dac672089 Tighten oneliner prompt: keywords, no adjectives, no therapy
Previous prompt allowed 80 chars and evaluative adjectives, producing
oneliners like 'HOCM, massive Septumhypertrophie, LVT, Indikation
CASH/Myektomie'. Reframed from 'Hauptdiagnose' to keyword extraction
since gemma cannot reliably identify primary diagnoses.
2026-04-14 00:22:36 +02:00
Brummel d3ef78796d Add oneliner to case view
Introduce an `oneliner` field to the `CaseView` struct and update the
`scan_cases` function to read it from `oneliner.txt`. The `cases.html`
template is updated to display the oneliner if present. A new
integration test is added to verify the functionality.
2026-04-13 19:23:35 +02:00
Brummel 33da841087 Add oneliner generation from transcriptions
This commit introduces functionality to automatically generate a
one-liner summary from a completed transcription.

The `ensure_oneliner` function checks if `oneliner.txt` already exists
in the case directory. If not, it uses the Ollama API to generate a
summary from the full transcript. Any errors during generation or
writing are logged but do not halt the transcription process.
2026-04-13 17:29:58 +02:00
Brummel 890f55ab8e Add tests for Ollama client
This commit adds several unit tests for the `generate_oneliner`
function, which interacts with the Ollama API. These tests cover:
- Successful parsing of a chat response.
- Normalization of the response content to remove extra whitespace and
  quotes.
- Handling of HTTP 500 errors from the server.
- Verification of sent request parameters like `keep_alive` and `model`.
- Parsing errors when the `content` field is missing in the response.
2026-04-13 17:28:33 +02:00
Brummel 47829b953c Add ollama module for LLM summarization
Introduces a new module `ollama` to handle communication with an Ollama
LLM for generating concise, one-line summaries of medical transcripts.
This module includes:

- `generate_oneliner`: The main function to interact with the Ollama
  API.
- `normalize`: A pure function for cleaning and formatting the LLM's
  output according to specific rules.
- Error handling for API communication and response parsing.
- Unit tests for the normalization logic.
2026-04-13 17:27:23 +02:00
Brummel 1e3cc9574c Refactor user config validation and add transcript display
Introduces a dedicated function `validate_and_index_users` to
encapsulate the logic for validating user configurations, checking for
duplicate slugs and API keys. This function returns a `Result` to handle
errors gracefully, and its usage in `load_users` is updated to panic
with a more informative error message.

Additionally, this commit modifies the web routing to fetch and display
audio transcripts. When scanning recordings, it now attempts to read a
corresponding `.transcript.txt` file. If found, the transcript content
is included in the `RecordingView` and rendered in the `cases.html`
template. If the transcript file is not found, a "Transcription pending"
message is displayed.

The `transcribe` function in `whisper.rs` is updated to explicitly set
the language to German (`language=de`), which can improve transcription
accuracy by skipping language detection. The `encode=false` query
parameter has been removed, as the service is designed to handle encoded
audio.

Finally, tests have been added for the new `validate_and_index_users`
function to ensure duplicate slugs and API keys are correctly rejected.
Integration tests in `web_test.rs` have been updated to verify the
rendering of transcripts and the pending status.
2026-04-13 17:08:21 +02:00
Brummel 13375b8297 feat: Add recovery scan for pending recordings
This commit introduces a new module `transcribe::recovery` and a
function `scan_and_enqueue`.
This function is called at startup to find audio files that have not yet
been transcribed
and re-enqueues them for processing. This ensures that recordings are
not lost if the
server restarts before transcription is complete.

A new integration test `recovery_enqueues_only_pending_recordings` has
been added to
verify the functionality.
2026-04-13 16:47:38 +02:00
Brummel bcae4e5466 feat: Add transcription worker and state management
Introduce an `AppState` struct to hold shared application state,
including configuration and the transcription sender.
Modify `create_router` and `create_router_with_state` to use `AppState`.
Update `main.rs` to initialize the transcription worker and pass the
`AppState` to the router.
Implement the transcription worker logic in
`server/src/transcribe/worker.rs`, handling job queues, FFmpeg remuxing,
and Whisper transcription.
Update `server/src/auth.rs` to use `Arc<Config>` from the shared state.
Modify `server/src/routes/upload.rs` to enqueue transcription jobs and
handle potential errors.
Add `TranscribeJob` and channel types in `server/src/transcribe/mod.rs`.
2026-04-13 16:45:24 +02:00
Brummel 84f8df2bf4 feat: Add Whisper transcription client
Introduces a new module for interacting with the Whisper ASR service.
This includes
an async function to POST audio files and retrieve transcripts. Error
handling
for network and HTTP status codes is also implemented.

New tests are added to cover successful transcription, HTTP error
responses,
and request timeouts.
2026-04-13 16:40:20 +02:00
Brummel 73692b0a02 feat: Add ffmpeg remuxing for faststart 2026-04-13 16:37:52 +02:00
Brummel fbf8681df0 Feat: Add web interface for case listing and audio playback
Introduces a new web interface to list cases and play back audio
recordings. This includes:

- A new `web` module in `routes` to handle web requests.
- `handle_case_list` to scan and display available cases and their
  recordings.
- `handle_audio` to serve audio files, with validation to prevent path
  traversal and ensure correct file types.
- `AppError::NotFound` variant to handle missing resources gracefully.
- Updates to `projektplan.md` to document the change in STT container
  and m4a preprocessing.
- New tests in `server/tests/web_test.rs` to ensure the web interface
  functions correctly.
2026-04-13 16:24:26 +02:00
Brummel ef6efec9f0 Add logging and request limits 2026-04-13 13:04:56 +02:00
Brummel 1051e83da3 feat: Add file upload endpoint
This commit introduces a new API endpoint `/api/upload` for handling
audio file uploads. It supports multipart form data, extracts `case_id`,
`recorded_at`, and `audio` fields. The audio data is saved to a
filesystem path determined by the user and case ID, within either the
`open/` or `done/` directories.

New features include:
- Integration with `axum`'s `Multipart` extractor for parsing form data.
- Validation of `case_id` as a UUID.
- Creation of case directories if they do not exist.
- Handling of late uploads to already closed cases by placing files in
  the `done/` directory and removing any `.remove` marker.
- Logging of received recordings.
- Definition of `AckResponse` and `AckStatus` models for API responses.
- Addition of comprehensive unit tests for various upload scenarios,
  including new case creation, invalid inputs, authentication, and late
  uploads.
2026-04-13 13:01:29 +02:00
Brummel 08c6e12a74 Update config and dependencies for auth
Refactor configuration loading to separate user credentials from server
settings.
Update dependencies to their latest compatible versions to leverage new
features
and security patches.

Key changes include:
- Moving user credentials (`api_key`, `web_password`, `role`) from
  `.env`
  to a dedicated `users.toml` file for better manageability and
  security.
- Introducing an `AuthenticatedUser` struct and extractor for API
  key-based
  authentication.
- Updating `axum` and related crates to leverage Rust edition 2024
  improvements.
- Adjusting configuration values and tests to reflect these changes.
2026-04-13 12:51:27 +02:00
Brummel 80a6537e16 feat: Add health check endpoint
Introduce a new health check endpoint at `/api/health`. This endpoint
returns a JSON response indicating the server's status and configuration
details, such as the port, data path, and configured URLs for external
services.

This change also refactors the application setup by:
- Creating a `lib.rs` file to house the `create_router` function, making
  it
  reusable for both the main application and integration tests.
- Moving configuration loading logic into `main.rs` and passing it as a
  shared state (`Arc<Config>`) to the router.
- Adding a unit test (`health_test.rs`) to verify the functionality of
  the
  health check endpoint.
- Updating `Cargo.toml` and `Cargo.lock` to include the `tower`
  dependency,
  which is used for testing.
2026-04-13 12:41:40 +02:00
Brummel 44dac06961 Add configuration loading and error handling
Introduces a new `config` module for loading application settings from
environment variables and a `users.toml` file. An `error` module is
added to handle application-specific errors gracefully. An
`.env.example` file is created to show available configuration options,
and `users.toml.example` provides a template for user data. The main
function now initializes the configuration and prints the server port.
2026-04-13 12:36:01 +02:00
Brummel 0a00e773dc Add .gitignore file
This commit introduces a .gitignore file to exclude build artifacts and
temporary files from version control.
Specifically, it ignores the 'target' directory (where Rust builds
output), new snapshot files from tests (`*.snap.new`), and the
`repomix-output.xml` file.
2026-04-13 12:20:11 +02:00
Brummel 8dd5967675 Add Rust project scaffold with dependencies 2026-04-13 12:14:36 +02:00