19 KiB
OpenAI Transcription Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add independent OpenAI transcription of prepared chunks with Brazilian-Portuguese prompting, durable incremental manifests, resumability, finite retries, cancellation, and responsive GUI progress.
Architecture: Extend the existing preprocessing manifest into a versioned durable job stored beneath the configured output directory. A pure manifest repository owns atomic state changes; an OpenAI adapter owns one SDK request; a transcription orchestrator owns resume, retries, progress, cancellation, and ordered transcript assembly; Qt only adapts worker callbacks into UI signals.
Tech Stack: Python 3.12+, OpenAI Python SDK 3.6.0-compatible interface, PySide6, keyring/Windows Credential Manager, pytest, pytest-qt, FFmpeg/ffprobe.
Spec: docs/superpowers/specs/2026-08-30-openai-transcription-design.md
Global Constraints
- Use
client.audio.transcriptions.create, not the translation endpoint. - Default model is exactly
gpt-transcribe; never silently substitute another model. - Seed model choices with
gpt-transcribe,gpt-4o-transcribe, andgpt-4o-mini-transcribe, while accepting future strings. - Present Brazilian Portuguese as
pt-BRand send documented ISO-639-1ptto the API. - Read the API key from the exact Windows Credential Manager target
OPENAI_API_KEY; never persist or log it elsewhere. - Every successful chunk must be atomically recorded before the next request.
- Never retranscribe a completed chunk except through an explicit reset/retranscribe operation.
- No overlap deduplication or translation in this milestone.
- All network and job work remains outside the GUI thread.
- Use test-driven development: add one failing behavior test, verify RED, implement minimally, verify GREEN, then refactor.
File Map
src/voice_transcriptor/models.py: add context/vocabulary toAppSettings.src/voice_transcriptor/services/settings.py: persist new defaults and migrate older files.src/voice_transcriptor/services/credentials.py: target-based Windows credential lookup/update.src/voice_transcriptor/services/job_manifest.py: atomic schema-2 job persistence, recovery, resume, and transcript assembly.src/voice_transcriptor/services/preprocessing.py: durable output-root jobs and per-chunk preprocessing status updates.src/voice_transcriptor/services/transcription.py: prompt normalization, SDK adapter, error classification, retries, cancellation, and orchestration.src/voice_transcriptor/ui/settings_dialog.py: editable model combo and context editor.src/voice_transcriptor/ui/main_window.py: combined job worker, progress labels, elapsed time, API errors, resume and cancellation.src/voice_transcriptor/app.py: concrete OpenAI/job service wiring.tests/test_settings.py,tests/test_credentials.py: settings and exact credential-target coverage.tests/test_job_manifest.py: durable state and resume invariants.tests/test_preprocessing.py: durable job integration.tests/test_transcription.py: API arguments, retry/error behavior, persistence, resume, and cancellation.tests/ui/test_settings_dialog.py,tests/ui/test_main_window.py,tests/ui/test_app.py: GUI settings, worker progress, errors, cancellation, and startup.README.md: API credential, model/context, job/resume, and validation documentation.
Task 1: Settings and Existing Windows Credential Target
Files:
- Modify:
src/voice_transcriptor/models.py - Modify:
src/voice_transcriptor/services/settings.py - Modify:
src/voice_transcriptor/services/credentials.py - Modify:
tests/test_settings.py - Modify:
tests/test_credentials.py
Interfaces:
-
Produces:
DEFAULT_CONTEXT,SUPPORTED_MODEL_SUGGESTIONS, andAppSettings(..., context_vocabulary: str = DEFAULT_CONTEXT). -
Produces:
CredentialService.get_api_key() -> str | None,has_api_key() -> bool, andset_api_key(value: str) -> Noneusing targetOPENAI_API_KEYand backendget_credential(service_name, username=None). -
Preserves all existing public settings and credential methods.
-
Step 1: Add failing settings tests asserting the default model is
gpt-transcribe, language remainspt-BR, the default context contains Brazilian guidance and vocabulary examples, older JSON files receive that context, and save/load round-trips it without an API key. -
Step 2: Run
python -m pytest tests/test_settings.py -vand verify failures report the old model or missingcontext_vocabularyfield. -
Step 3: Implement the minimal settings/model changes by appending the dataclass field (to preserve positional callers), updating defaults, parsing optional string context, and serializing it.
-
Step 4: Re-run
python -m pytest tests/test_settings.py -vand confirm GREEN. -
Step 5: Add failing credential tests with a fake backend whose
get_credential("OPENAI_API_KEY", None)returns an object containingusernameandpassword; assert retrieval, absence, sanitized backend failures, update preserving username, and creation using usernameOPENAI_API_KEY. -
Step 6: Run
python -m pytest tests/test_credentials.py -vand verify RED because the current service queriesvoice-transcriptor/openai-api-keywithget_password. -
Step 7: Implement exact-target lookup/update using
TARGET_NAME = "OPENAI_API_KEY",get_credential, andset_password(TARGET_NAME, resolved_username, value)without exposing backend details. -
Step 8: Run
python -m pytest tests/test_settings.py tests/test_credentials.py -vand confirm GREEN. -
Step 9: Commit with
git add src/voice_transcriptor/models.py src/voice_transcriptor/services/settings.py src/voice_transcriptor/services/credentials.py tests/test_settings.py tests/test_credentials.py && git commit -m "feat: configure transcription models and credential target".
Task 2: Atomic Resumable Job Manifest
Files:
- Create:
src/voice_transcriptor/services/job_manifest.py - Create:
tests/test_job_manifest.py
Interfaces:
-
Produces:
ChunkStatus(StrEnum)withPENDING,PROCESSING,COMPLETED,FAILED. -
Produces:
JobManifestRepository.create(job_directory: Path, source: dict, settings: dict, chunks: Sequence[dict]) -> dict. -
Produces:
load(path: Path) -> dict,save(path: Path, manifest: dict) -> None,recover_for_resume(path: Path) -> dict,mark_processing(path, index),mark_completed(path, index, text),mark_failed(path, index, message),mark_job_state(path, state, error=None), andreset_completed(path, indexes=None). -
Produces:
assemble_transcript(manifest_path: Path) -> Pathwriting siblingtranscript.txtatomically in chunk-index order. -
Step 1: Write failing manifest tests for schema 2 creation, exact decimal timing preservation, four chunk statuses, safe relative paths, atomic
.tmpreplacement, completed counters, and rejection of malformed/out-of-directory chunk paths. -
Step 2: Run
python -m pytest tests/test_job_manifest.py -vand verify import failure. -
Step 3: Implement creation, validation, load, and atomic save with UTF-8 JSON and ISO-8601 UTC timestamps.
-
Step 4: Re-run the focused tests and confirm the first state tests pass.
-
Step 5: Add failing transition tests asserting each mutator immediately changes disk state,
processingrecovers topending,completedsurvives recovery, explicit resume resetsfailed, and ordinary transitions refuse to overwrite completed chunks. -
Step 6: Run the focused tests and verify RED on missing transition methods.
-
Step 7: Implement transition and recovery methods with index validation, attempt counts, sanitized error strings, and completed-count recalculation.
-
Step 8: Add failing assembly tests asserting completed texts are newline-concatenated in index order, partial transcripts contain only completed chunks, and source timing remains unchanged.
-
Step 9: Implement atomic transcript assembly and explicit completed-reset behavior.
-
Step 10: Run
python -m pytest tests/test_job_manifest.py -vand confirm GREEN. -
Step 11: Commit with
git add src/voice_transcriptor/services/job_manifest.py tests/test_job_manifest.py && git commit -m "feat: add durable resumable job manifests".
Task 3: Durable Preprocessing Jobs
Files:
- Modify:
src/voice_transcriptor/services/preprocessing.py - Modify:
tests/test_preprocessing.py
Interfaces:
-
Consumes:
JobManifestRepositoryand its schema-2 chunk records. -
Changes:
PreprocessingOptionsgainsjob_root: Path | None = NoneorPreprocessingService.preprocess(..., job_root: Path | None = None); the implementation must receive the configured persistent root rather than use a system temporary directory for transcribable jobs. -
Produces:
PreprocessingResultwhose directory remains durable and whose manifest contains independently reusable encoded chunks. -
Step 1: Add failing preprocessing tests asserting jobs are created below
<output>/voice-transcriptor-jobs, a pending transcript status is written after every successful encoded chunk, cancellation/failure retains the job, and existing valid chunks can be reused on resume. -
Step 2: Run
python -m pytest tests/test_preprocessing.py -vand verify RED against temporary-directory deletion/schema 1 behavior. -
Step 3: Inject
JobManifestRepositoryand durable root selection while preserving the existing FFmpeg command, exact boundaries, and atomic per-chunk writes. -
Step 4: Implement preprocessing resume so valid existing chunk files are skipped, missing files are regenerated, completed transcript text is never cleared, and partial current outputs are removed after FFmpeg failure.
-
Step 5: Run
python -m pytest tests/test_preprocessing.py tests/test_chunking.py -vand confirm GREEN. -
Step 6: Commit with
git add src/voice_transcriptor/services/preprocessing.py tests/test_preprocessing.py && git commit -m "feat: persist and resume preprocessing jobs".
Task 4: OpenAI Adapter, Prompting, Retry Policy, and Orchestrator
Files:
- Replace:
src/voice_transcriptor/services/transcription.py - Replace:
tests/test_transcription.py
Interfaces:
-
Produces:
normalize_language(value: str) -> strmapping case-insensitivept-BR/pt_BRtoptand otherwise using the primary ISO language subtag. -
Produces:
build_prompt(context: str) -> strcombining the fixed no-translation Brazilian instruction with trimmed optional context. -
Produces:
OpenAITranscriptionClient(client)withtranscribe(path: Path, model: str, language: str, prompt: str) -> strcallingclient.audio.transcriptions.createexactly once. -
Produces:
RetryPolicy(max_attempts=5, initial_delay_seconds=1.0, max_delay_seconds=30.0, jitter_ratio=0.2). -
Produces:
TranscriptionProgress(completed: int, total: int, current_chunk: int | None, elapsed_seconds: float, phase: str, message: str, api_error: str | None = None). -
Produces:
TranscriptionService.run(manifest_path, settings, token, progress) -> Pathreturningtranscript.txt. -
Produces typed
TranscriptionError,PermanentTranscriptionError, andRetryExhaustedErrorwith sanitized messages. -
Step 1: Write failing pure tests for language normalization, fixed no-translation prompt content, optional vocabulary trimming, and arbitrary models.
-
Step 2: Run
python -m pytest tests/test_transcription.py -vand verify RED because the service is still a milestone stub. -
Step 3: Implement only the pure helpers and re-run those tests to GREEN.
-
Step 4: Add a failing SDK adapter test using a fake endpoint; assert the opened binary file, exact model,
language="pt", combinedprompt,response_format="json", and returned.text. Assert no translation method is touched. -
Step 5: Implement
OpenAITranscriptionClientwith the verified installed-SDK call signature and a context manager for the chunk file. -
Step 6: Add failing retry-classification tests for
RateLimitError,APIConnectionError,APITimeoutError, 408/409/429/5xx, authentication/permission/bad request, maximum attempts, exponential delays, jitter bounds, and cappedRetry-After. -
Step 7: Implement deterministic injectable retry dependencies (
sleep, monotonic clock, random source) and classification by SDK class/status code. Disable SDK retries during app wiring so only this policy applies. -
Step 8: Add failing orchestration tests asserting one independent call per unfinished chunk, immediate disk completion after each success, skip of completed chunks, recovery of
processing, explicit retry of failed chunks, timing retention, ordered assembly, cancellation before a request and during backoff, progress fields, and sanitized errors without key/header-like content. -
Step 9: Implement orchestration minimally around
JobManifestRepository, checking the token before each call and via cancellation-aware backoff waits. -
Step 10: Run
python -m pytest tests/test_transcription.py tests/test_job_manifest.py -vand confirm GREEN. -
Step 11: Commit with
git add src/voice_transcriptor/services/transcription.py tests/test_transcription.py && git commit -m "feat: transcribe chunks with retries and resume".
Task 5: Settings GUI for Model and Context
Files:
- Modify:
src/voice_transcriptor/ui/settings_dialog.py - Modify:
tests/ui/test_settings_dialog.py
Interfaces:
-
Changes:
model_inputbecomes an editableQComboBoxseeded fromSUPPORTED_MODEL_SUGGESTIONSwhile preserving the object namemodelInput. -
Produces: multiline
context_input: QPlainTextEditwith object namecontextVocabularyInput. -
Emits the extended
AppSettingsvalue without exposing the API key. -
Step 1: Add failing Qt tests for the three seeded model suggestions, editable future model value, populated default context, round-trip user context, and validation of a blank model.
-
Step 2: Run
python -m pytest tests/ui/test_settings_dialog.py -vand verify RED on the missing combo/context controls. -
Step 3: Implement the editable combo and context field and adapt save logic to
currentText()andtoPlainText(). -
Step 4: Run
python -m pytest tests/ui/test_settings_dialog.py tests/test_settings.py -vand confirm GREEN. -
Step 5: Commit with
git add src/voice_transcriptor/ui/settings_dialog.py tests/ui/test_settings_dialog.py && git commit -m "feat: add transcription model and vocabulary settings".
Task 6: GUI Job Worker, Progress, Resume, Errors, and Cancellation
Files:
- Modify:
src/voice_transcriptor/ui/main_window.py - Modify:
tests/ui/test_main_window.py
Interfaces:
-
Consumes: preprocessing service, transcription service, settings repository, credentials.
-
Produces: a
JobWorker(QRunnable)that preprocesses/resumes then transcribes and emits typed progress, completion, cancellation, and sanitized failure signals. -
Produces visible widgets
chunk_progress_label,elapsed_label,current_chunk_label, and existing read-only log entries for API errors. -
Changes primary action text to
Transcribe; retainsCanceland thread-pool execution. -
Step 1: Add failing widget tests for Transcribe action text,
0 / 0chunk progress, elapsed display, current chunk display, and API error log surface. -
Step 2: Run
python -m pytest tests/ui/test_main_window.py -vand verify RED. -
Step 3: Add the progress widgets and slots without changing worker behavior; re-run widget tests to GREEN.
-
Step 4: Add failing worker-flow tests with fake services asserting the click returns without executing network work on the GUI thread, progress updates all required fields, completed results report the transcript path, and controls restore on success/failure/cancellation.
-
Step 5: Implement
JobWorkerand connect signals; retrieve the credential before constructing work, pass no key through signals/logs, and reuse the existing cancellation token. -
Step 6: Add failing resume tests asserting a retained matching manifest is offered/reused and completed chunks are not reset; test that no implicit retranscribe path exists.
-
Step 7: Implement resume selection using a deterministic matching-manifest service method; retain all job data on terminal cancellation/failure.
-
Step 8: Run
python -m pytest tests/ui/test_main_window.py -vand confirm GREEN. -
Step 9: Commit with
git add src/voice_transcriptor/ui/main_window.py tests/ui/test_main_window.py && git commit -m "feat: add responsive transcription job interface".
Task 7: Application Wiring and Integrated Validation
Files:
- Modify:
src/voice_transcriptor/app.py - Modify:
tests/ui/test_app.py - Modify:
README.md
Interfaces:
-
create_main_window()wiresOpenAI(api_key=credential, max_retries=0),JobManifestRepository, durable preprocessing, andTranscriptionServicewithout making an API request at startup. -
Startup remains usable for selecting media and editing settings when the credential is absent; Transcribe reports a concise configuration error.
-
Step 1: Add a failing app-wiring test with injected factories asserting
max_retries=0, no startup API call, exact credential retrieval, and all concrete services reachMainWindow. -
Step 2: Run
python -m pytest tests/ui/test_app.py -vand verify RED. -
Step 3: Refactor app wiring for injectable factories and construct the concrete SDK client only when a transcription job starts.
-
Step 4: Run
python -m pytest tests/ui/test_app.py tests/ui/test_main_window.py -vand confirm GREEN. -
Step 5: Update README with Windows Credential Manager target
OPENAI_API_KEY, model behavior (including the documented-model caveat forgpt-transcribe), Brazilian context, durable job directory, resume semantics, retries/rate limits, cancellation, and test commands. -
Step 6: Run the complete automated suite:
python -m pytest -v. -
Step 7: Run static/startup validation:
python -m compileall -q src tests, then$env:QT_QPA_PLATFORM='offscreen'; python -c "from PySide6.QtCore import QTimer; from PySide6.QtWidgets import QApplication; from voice_transcriptor.app import create_main_window; app=QApplication([]); window=create_main_window(); window.show(); QTimer.singleShot(100, app.quit); app.exec(); window.close()". -
Step 8: Run local media-tool checks:
ffmpeg -versionandffprobe -version; if available, execute the existing generated-fixture preprocessing smoke path without a live OpenAI call. -
Step 9: Audit the result:
git diff --check,git status --short, andrg -n "Authorization|Bearer |sk-[A-Za-z0-9]" src tests README.md docsto confirm no credential/header leakage. -
Step 10: Use
superpowers:verification-before-completionand report the exact test, compile, startup, FFmpeg, and audit outputs before claiming completion. -
Step 11: Commit with
git add src/voice_transcriptor/app.py tests/ui/test_app.py README.md && git commit -m "docs: wire and validate OpenAI transcription".