karaoke-gen 0.75.54__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of karaoke-gen might be problematic. Click here for more details.
- karaoke_gen/__init__.py +38 -0
- karaoke_gen/audio_fetcher.py +1614 -0
- karaoke_gen/audio_processor.py +790 -0
- karaoke_gen/config.py +83 -0
- karaoke_gen/file_handler.py +387 -0
- karaoke_gen/instrumental_review/__init__.py +45 -0
- karaoke_gen/instrumental_review/analyzer.py +408 -0
- karaoke_gen/instrumental_review/editor.py +322 -0
- karaoke_gen/instrumental_review/models.py +171 -0
- karaoke_gen/instrumental_review/server.py +475 -0
- karaoke_gen/instrumental_review/static/index.html +1529 -0
- karaoke_gen/instrumental_review/waveform.py +409 -0
- karaoke_gen/karaoke_finalise/__init__.py +1 -0
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +1833 -0
- karaoke_gen/karaoke_gen.py +1026 -0
- karaoke_gen/lyrics_processor.py +474 -0
- karaoke_gen/metadata.py +160 -0
- karaoke_gen/pipeline/__init__.py +87 -0
- karaoke_gen/pipeline/base.py +215 -0
- karaoke_gen/pipeline/context.py +230 -0
- karaoke_gen/pipeline/executors/__init__.py +21 -0
- karaoke_gen/pipeline/executors/local.py +159 -0
- karaoke_gen/pipeline/executors/remote.py +257 -0
- karaoke_gen/pipeline/stages/__init__.py +27 -0
- karaoke_gen/pipeline/stages/finalize.py +202 -0
- karaoke_gen/pipeline/stages/render.py +165 -0
- karaoke_gen/pipeline/stages/screens.py +139 -0
- karaoke_gen/pipeline/stages/separation.py +191 -0
- karaoke_gen/pipeline/stages/transcription.py +191 -0
- karaoke_gen/resources/AvenirNext-Bold.ttf +0 -0
- karaoke_gen/resources/Montserrat-Bold.ttf +0 -0
- karaoke_gen/resources/Oswald-Bold.ttf +0 -0
- karaoke_gen/resources/Oswald-SemiBold.ttf +0 -0
- karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf +0 -0
- karaoke_gen/style_loader.py +531 -0
- karaoke_gen/utils/__init__.py +18 -0
- karaoke_gen/utils/bulk_cli.py +492 -0
- karaoke_gen/utils/cli_args.py +432 -0
- karaoke_gen/utils/gen_cli.py +978 -0
- karaoke_gen/utils/remote_cli.py +3268 -0
- karaoke_gen/video_background_processor.py +351 -0
- karaoke_gen/video_generator.py +424 -0
- karaoke_gen-0.75.54.dist-info/METADATA +718 -0
- karaoke_gen-0.75.54.dist-info/RECORD +287 -0
- karaoke_gen-0.75.54.dist-info/WHEEL +4 -0
- karaoke_gen-0.75.54.dist-info/entry_points.txt +5 -0
- karaoke_gen-0.75.54.dist-info/licenses/LICENSE +21 -0
- lyrics_transcriber/__init__.py +10 -0
- lyrics_transcriber/cli/__init__.py +0 -0
- lyrics_transcriber/cli/cli_main.py +285 -0
- lyrics_transcriber/core/__init__.py +0 -0
- lyrics_transcriber/core/config.py +50 -0
- lyrics_transcriber/core/controller.py +594 -0
- lyrics_transcriber/correction/__init__.py +0 -0
- lyrics_transcriber/correction/agentic/__init__.py +9 -0
- lyrics_transcriber/correction/agentic/adapter.py +71 -0
- lyrics_transcriber/correction/agentic/agent.py +313 -0
- lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
- lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
- lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
- lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
- lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
- lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
- lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
- lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
- lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
- lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
- lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
- lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
- lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
- lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
- lyrics_transcriber/correction/agentic/models/enums.py +38 -0
- lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
- lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
- lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
- lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
- lyrics_transcriber/correction/agentic/models/utils.py +19 -0
- lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
- lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
- lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
- lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
- lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
- lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
- lyrics_transcriber/correction/agentic/providers/base.py +36 -0
- lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
- lyrics_transcriber/correction/agentic/providers/config.py +73 -0
- lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
- lyrics_transcriber/correction/agentic/providers/health.py +28 -0
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
- lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
- lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
- lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
- lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
- lyrics_transcriber/correction/agentic/router.py +35 -0
- lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
- lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
- lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
- lyrics_transcriber/correction/anchor_sequence.py +919 -0
- lyrics_transcriber/correction/corrector.py +760 -0
- lyrics_transcriber/correction/feedback/__init__.py +2 -0
- lyrics_transcriber/correction/feedback/schemas.py +107 -0
- lyrics_transcriber/correction/feedback/store.py +236 -0
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +52 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
- lyrics_transcriber/correction/handlers/llm.py +293 -0
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
- lyrics_transcriber/correction/handlers/repeat.py +88 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
- lyrics_transcriber/correction/handlers/word_operations.py +187 -0
- lyrics_transcriber/correction/operations.py +352 -0
- lyrics_transcriber/correction/phrase_analyzer.py +435 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/frontend/.gitignore +23 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/README.md +50 -0
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
- lyrics_transcriber/frontend/__init__.py +25 -0
- lyrics_transcriber/frontend/eslint.config.js +28 -0
- lyrics_transcriber/frontend/index.html +18 -0
- lyrics_transcriber/frontend/package.json +42 -0
- lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/public/favicon.ico +0 -0
- lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/src/App.tsx +214 -0
- lyrics_transcriber/frontend/src/api.ts +254 -0
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
- lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
- lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +413 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1387 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +185 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +704 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +80 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +905 -0
- lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
- lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +127 -0
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
- lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +336 -0
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
- lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
- lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
- lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
- lyrics_transcriber/frontend/src/main.tsx +17 -0
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +199 -0
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
- lyrics_transcriber/frontend/tsconfig.app.json +26 -0
- lyrics_transcriber/frontend/tsconfig.json +25 -0
- lyrics_transcriber/frontend/tsconfig.node.json +23 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
- lyrics_transcriber/frontend/update_version.js +11 -0
- lyrics_transcriber/frontend/vite.config.d.ts +2 -0
- lyrics_transcriber/frontend/vite.config.js +10 -0
- lyrics_transcriber/frontend/vite.config.ts +11 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js +43288 -0
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
- lyrics_transcriber/frontend/web_assets/index.html +18 -0
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/__init__.py +0 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
- lyrics_transcriber/lyrics/file_provider.py +95 -0
- lyrics_transcriber/lyrics/genius.py +384 -0
- lyrics_transcriber/lyrics/lrclib.py +231 -0
- lyrics_transcriber/lyrics/musixmatch.py +156 -0
- lyrics_transcriber/lyrics/spotify.py +290 -0
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/__init__.py +0 -0
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/ass/ass.py +2088 -0
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +180 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +265 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +619 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/countdown_processor.py +306 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +257 -0
- lyrics_transcriber/output/lrc_to_cdg.py +61 -0
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +96 -0
- lyrics_transcriber/output/segment_resizer.py +431 -0
- lyrics_transcriber/output/subtitles.py +397 -0
- lyrics_transcriber/output/video.py +544 -0
- lyrics_transcriber/review/__init__.py +0 -0
- lyrics_transcriber/review/server.py +676 -0
- lyrics_transcriber/storage/__init__.py +0 -0
- lyrics_transcriber/storage/dropbox.py +225 -0
- lyrics_transcriber/transcribers/__init__.py +0 -0
- lyrics_transcriber/transcribers/audioshake.py +379 -0
- lyrics_transcriber/transcribers/base_transcriber.py +157 -0
- lyrics_transcriber/transcribers/whisper.py +330 -0
- lyrics_transcriber/types.py +650 -0
- lyrics_transcriber/utils/__init__.py +0 -0
- lyrics_transcriber/utils/word_utils.py +27 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Base handler interface for gap correction."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseHandler(ABC):
|
|
9
|
+
"""Base class for category-specific correction handlers."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, artist: str = None, title: str = None):
|
|
12
|
+
"""Initialize handler with song metadata.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
artist: Song artist name
|
|
16
|
+
title: Song title
|
|
17
|
+
"""
|
|
18
|
+
self.artist = artist
|
|
19
|
+
self.title = title
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def handle(
|
|
23
|
+
self,
|
|
24
|
+
gap_id: str,
|
|
25
|
+
gap_words: List[Dict[str, Any]],
|
|
26
|
+
preceding_words: str,
|
|
27
|
+
following_words: str,
|
|
28
|
+
reference_contexts: Dict[str, str],
|
|
29
|
+
classification_reasoning: str = ""
|
|
30
|
+
) -> List[CorrectionProposal]:
|
|
31
|
+
"""Process a gap and return correction proposals.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
gap_id: Unique identifier for the gap
|
|
35
|
+
gap_words: List of word dictionaries with id, text, start_time, end_time
|
|
36
|
+
preceding_words: Context before the gap
|
|
37
|
+
following_words: Context after the gap
|
|
38
|
+
reference_contexts: Dictionary of reference lyrics by source
|
|
39
|
+
classification_reasoning: Reasoning from the classifier
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List of CorrectionProposal objects
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def category(self) -> GapCategory:
|
|
49
|
+
"""Return the gap category this handler processes."""
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Handler for complex gaps with multiple error types."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ComplexMultiErrorHandler(BaseHandler):
|
|
9
|
+
"""Handles large, complex gaps with multiple types of errors."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def category(self) -> GapCategory:
|
|
13
|
+
return GapCategory.COMPLEX_MULTI_ERROR
|
|
14
|
+
|
|
15
|
+
def handle(
|
|
16
|
+
self,
|
|
17
|
+
gap_id: str,
|
|
18
|
+
gap_words: List[Dict[str, Any]],
|
|
19
|
+
preceding_words: str,
|
|
20
|
+
following_words: str,
|
|
21
|
+
reference_contexts: Dict[str, str],
|
|
22
|
+
classification_reasoning: str = ""
|
|
23
|
+
) -> List[CorrectionProposal]:
|
|
24
|
+
"""Flag complex gaps for human review."""
|
|
25
|
+
|
|
26
|
+
if not gap_words:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
# Complex multi-error gaps are too difficult for automatic correction
|
|
30
|
+
# Always flag for human review
|
|
31
|
+
gap_text = ' '.join(w.get('text', '') for w in gap_words)
|
|
32
|
+
word_count = len(gap_words)
|
|
33
|
+
|
|
34
|
+
proposal = CorrectionProposal(
|
|
35
|
+
word_ids=[w['id'] for w in gap_words],
|
|
36
|
+
action="Flag",
|
|
37
|
+
confidence=0.3,
|
|
38
|
+
reason=f"Complex gap with {word_count} words and multiple error types: '{gap_text[:100]}...'. Too complex for automatic correction. {classification_reasoning}",
|
|
39
|
+
gap_category=self.category,
|
|
40
|
+
requires_human_review=True,
|
|
41
|
+
artist=self.artist,
|
|
42
|
+
title=self.title
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return [proposal]
|
|
46
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Handler for extra filler words at sentence starts."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ExtraWordsHandler(BaseHandler):
|
|
9
|
+
"""Handles gaps with extra filler words like 'And', 'But', 'Well'."""
|
|
10
|
+
|
|
11
|
+
# Common filler words that are often incorrectly added by transcription
|
|
12
|
+
FILLER_WORDS = {'and', 'but', 'well', 'so', 'or', 'then', 'now'}
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def category(self) -> GapCategory:
|
|
16
|
+
return GapCategory.EXTRA_WORDS
|
|
17
|
+
|
|
18
|
+
def handle(
|
|
19
|
+
self,
|
|
20
|
+
gap_id: str,
|
|
21
|
+
gap_words: List[Dict[str, Any]],
|
|
22
|
+
preceding_words: str,
|
|
23
|
+
following_words: str,
|
|
24
|
+
reference_contexts: Dict[str, str],
|
|
25
|
+
classification_reasoning: str = ""
|
|
26
|
+
) -> List[CorrectionProposal]:
|
|
27
|
+
"""Propose deletion of filler words."""
|
|
28
|
+
|
|
29
|
+
if not gap_words:
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
proposals = []
|
|
33
|
+
|
|
34
|
+
# Look for filler words at the start of the gap
|
|
35
|
+
for i, word in enumerate(gap_words):
|
|
36
|
+
text = word.get('text', '').strip().lower().rstrip(',.!?;:')
|
|
37
|
+
|
|
38
|
+
if text in self.FILLER_WORDS:
|
|
39
|
+
# Check if this is likely at a sentence/line start
|
|
40
|
+
# (either it's the first word or preceded by punctuation)
|
|
41
|
+
is_sentence_start = (
|
|
42
|
+
i == 0 or
|
|
43
|
+
gap_words[i-1].get('text', '').strip()[-1:] in '.!?'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if is_sentence_start:
|
|
47
|
+
proposal = CorrectionProposal(
|
|
48
|
+
word_id=word['id'],
|
|
49
|
+
action="DeleteWord",
|
|
50
|
+
confidence=0.80,
|
|
51
|
+
reason=f"Extra filler word '{word.get('text')}' at sentence start not in reference. {classification_reasoning}",
|
|
52
|
+
gap_category=self.category,
|
|
53
|
+
requires_human_review=False,
|
|
54
|
+
artist=self.artist,
|
|
55
|
+
title=self.title
|
|
56
|
+
)
|
|
57
|
+
proposals.append(proposal)
|
|
58
|
+
|
|
59
|
+
# If no filler words found, flag for review
|
|
60
|
+
if not proposals:
|
|
61
|
+
proposal = CorrectionProposal(
|
|
62
|
+
word_ids=[w['id'] for w in gap_words],
|
|
63
|
+
action="Flag",
|
|
64
|
+
confidence=0.5,
|
|
65
|
+
reason=f"Classified as extra words but no obvious fillers found. {classification_reasoning}",
|
|
66
|
+
gap_category=self.category,
|
|
67
|
+
requires_human_review=True,
|
|
68
|
+
artist=self.artist,
|
|
69
|
+
title=self.title
|
|
70
|
+
)
|
|
71
|
+
proposals.append(proposal)
|
|
72
|
+
|
|
73
|
+
return proposals
|
|
74
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Handler for gaps where transcription matches at least one reference source."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NoErrorHandler(BaseHandler):
|
|
9
|
+
"""Handles gaps where the transcription is correct (matches a reference source)."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def category(self) -> GapCategory:
|
|
13
|
+
return GapCategory.NO_ERROR
|
|
14
|
+
|
|
15
|
+
def handle(
|
|
16
|
+
self,
|
|
17
|
+
gap_id: str,
|
|
18
|
+
gap_words: List[Dict[str, Any]],
|
|
19
|
+
preceding_words: str,
|
|
20
|
+
following_words: str,
|
|
21
|
+
reference_contexts: Dict[str, str],
|
|
22
|
+
classification_reasoning: str = ""
|
|
23
|
+
) -> List[CorrectionProposal]:
|
|
24
|
+
"""Return NO_ACTION since transcription is correct."""
|
|
25
|
+
|
|
26
|
+
if not gap_words:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
# Create a single NO_ACTION proposal
|
|
30
|
+
proposal = CorrectionProposal(
|
|
31
|
+
word_ids=[w['id'] for w in gap_words],
|
|
32
|
+
action="NoAction",
|
|
33
|
+
confidence=0.99,
|
|
34
|
+
reason=f"Transcription matches at least one reference source. {classification_reasoning}",
|
|
35
|
+
gap_category=self.category,
|
|
36
|
+
requires_human_review=False,
|
|
37
|
+
artist=self.artist,
|
|
38
|
+
title=self.title
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return [proposal]
|
|
42
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Handler for punctuation-only differences."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PunctuationHandler(BaseHandler):
|
|
9
|
+
"""Handles gaps where only punctuation/capitalization differs."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def category(self) -> GapCategory:
|
|
13
|
+
return GapCategory.PUNCTUATION_ONLY
|
|
14
|
+
|
|
15
|
+
def handle(
|
|
16
|
+
self,
|
|
17
|
+
gap_id: str,
|
|
18
|
+
gap_words: List[Dict[str, Any]],
|
|
19
|
+
preceding_words: str,
|
|
20
|
+
following_words: str,
|
|
21
|
+
reference_contexts: Dict[str, str],
|
|
22
|
+
classification_reasoning: str = ""
|
|
23
|
+
) -> List[CorrectionProposal]:
|
|
24
|
+
"""Return NO_ACTION for punctuation-only differences."""
|
|
25
|
+
# For punctuation differences, we don't need to make any changes
|
|
26
|
+
# The transcription is correct, just styled differently
|
|
27
|
+
|
|
28
|
+
if not gap_words:
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
# Create a single NO_ACTION proposal for the entire gap
|
|
32
|
+
proposal = CorrectionProposal(
|
|
33
|
+
word_ids=[w['id'] for w in gap_words],
|
|
34
|
+
action="NoAction",
|
|
35
|
+
confidence=0.95,
|
|
36
|
+
reason=f"Punctuation/style difference only. {classification_reasoning}",
|
|
37
|
+
gap_category=self.category,
|
|
38
|
+
requires_human_review=False,
|
|
39
|
+
artist=self.artist,
|
|
40
|
+
title=self.title
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return [proposal]
|
|
44
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Registry for mapping gap categories to handlers."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Type
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from .punctuation import PunctuationHandler
|
|
6
|
+
from .sound_alike import SoundAlikeHandler
|
|
7
|
+
from .background_vocals import BackgroundVocalsHandler
|
|
8
|
+
from .extra_words import ExtraWordsHandler
|
|
9
|
+
from .repeated_section import RepeatedSectionHandler
|
|
10
|
+
from .complex_multi_error import ComplexMultiErrorHandler
|
|
11
|
+
from .ambiguous import AmbiguousHandler
|
|
12
|
+
from .no_error import NoErrorHandler
|
|
13
|
+
from ..models.schemas import GapCategory
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HandlerRegistry:
|
|
17
|
+
"""Registry for mapping gap categories to their handler classes."""
|
|
18
|
+
|
|
19
|
+
_handlers: Dict[GapCategory, Type[BaseHandler]] = {
|
|
20
|
+
GapCategory.PUNCTUATION_ONLY: PunctuationHandler,
|
|
21
|
+
GapCategory.SOUND_ALIKE: SoundAlikeHandler,
|
|
22
|
+
GapCategory.BACKGROUND_VOCALS: BackgroundVocalsHandler,
|
|
23
|
+
GapCategory.EXTRA_WORDS: ExtraWordsHandler,
|
|
24
|
+
GapCategory.REPEATED_SECTION: RepeatedSectionHandler,
|
|
25
|
+
GapCategory.COMPLEX_MULTI_ERROR: ComplexMultiErrorHandler,
|
|
26
|
+
GapCategory.AMBIGUOUS: AmbiguousHandler,
|
|
27
|
+
GapCategory.NO_ERROR: NoErrorHandler,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_handler(cls, category: GapCategory, artist: str = None, title: str = None) -> BaseHandler:
|
|
32
|
+
"""Get a handler instance for the given category.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
category: Gap category
|
|
36
|
+
artist: Song artist name
|
|
37
|
+
title: Song title
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Handler instance for the category
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If category is not registered
|
|
44
|
+
"""
|
|
45
|
+
handler_class = cls._handlers.get(category)
|
|
46
|
+
if not handler_class:
|
|
47
|
+
raise ValueError(f"No handler registered for category: {category}")
|
|
48
|
+
|
|
49
|
+
return handler_class(artist=artist, title=title)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def register_handler(cls, category: GapCategory, handler_class: Type[BaseHandler]):
|
|
53
|
+
"""Register a custom handler for a category.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
category: Gap category
|
|
57
|
+
handler_class: Handler class to register
|
|
58
|
+
"""
|
|
59
|
+
cls._handlers[category] = handler_class
|
|
60
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Handler for repeated sections (chorus, verse repetitions)."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RepeatedSectionHandler(BaseHandler):
|
|
9
|
+
"""Handles gaps where transcription includes repeated sections not in condensed references."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def category(self) -> GapCategory:
|
|
13
|
+
return GapCategory.REPEATED_SECTION
|
|
14
|
+
|
|
15
|
+
def handle(
|
|
16
|
+
self,
|
|
17
|
+
gap_id: str,
|
|
18
|
+
gap_words: List[Dict[str, Any]],
|
|
19
|
+
preceding_words: str,
|
|
20
|
+
following_words: str,
|
|
21
|
+
reference_contexts: Dict[str, str],
|
|
22
|
+
classification_reasoning: str = ""
|
|
23
|
+
) -> List[CorrectionProposal]:
|
|
24
|
+
"""Flag repeated sections for human review."""
|
|
25
|
+
|
|
26
|
+
if not gap_words:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
# Repeated sections need audio verification - always flag for review
|
|
30
|
+
gap_text = ' '.join(w.get('text', '') for w in gap_words)
|
|
31
|
+
|
|
32
|
+
proposal = CorrectionProposal(
|
|
33
|
+
word_ids=[w['id'] for w in gap_words],
|
|
34
|
+
action="Flag",
|
|
35
|
+
confidence=0.5,
|
|
36
|
+
reason=f"Repeated section detected: '{gap_text[:100]}...'. Reference lyrics may be condensed. Requires audio verification. {classification_reasoning}",
|
|
37
|
+
gap_category=self.category,
|
|
38
|
+
requires_human_review=True,
|
|
39
|
+
artist=self.artist,
|
|
40
|
+
title=self.title
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return [proposal]
|
|
44
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Handler for sound-alike transcription errors."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any, Optional
|
|
4
|
+
from .base import BaseHandler
|
|
5
|
+
from ..models.schemas import CorrectionProposal, GapCategory
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SoundAlikeHandler(BaseHandler):
|
|
10
|
+
"""Handles gaps with sound-alike errors (homophones, similar-sounding phrases)."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def category(self) -> GapCategory:
|
|
14
|
+
return GapCategory.SOUND_ALIKE
|
|
15
|
+
|
|
16
|
+
def _extract_replacement_from_references(
|
|
17
|
+
self,
|
|
18
|
+
gap_words: List[Dict[str, Any]],
|
|
19
|
+
reference_contexts: Dict[str, str],
|
|
20
|
+
preceding_words: str,
|
|
21
|
+
following_words: str
|
|
22
|
+
) -> Optional[str]:
|
|
23
|
+
"""Try to extract the correct text from reference lyrics.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
gap_words: Words in the gap
|
|
27
|
+
reference_contexts: Reference lyrics from each source
|
|
28
|
+
preceding_words: Words before gap
|
|
29
|
+
following_words: Words after gap
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Replacement text if found, None otherwise
|
|
33
|
+
"""
|
|
34
|
+
if not reference_contexts:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
# Normalize preceding and following for matching
|
|
38
|
+
preceding_norm = self._normalize_text(preceding_words)
|
|
39
|
+
following_norm = self._normalize_text(following_words)
|
|
40
|
+
|
|
41
|
+
# Take last few words of preceding and first few words of following
|
|
42
|
+
preceding_tokens = preceding_norm.split()[-5:] if preceding_norm else []
|
|
43
|
+
following_tokens = following_norm.split()[:5] if following_norm else []
|
|
44
|
+
|
|
45
|
+
# Try to find the context in each reference
|
|
46
|
+
for source, ref_text in reference_contexts.items():
|
|
47
|
+
ref_norm = self._normalize_text(ref_text)
|
|
48
|
+
|
|
49
|
+
# Try to find the preceding context
|
|
50
|
+
if preceding_tokens:
|
|
51
|
+
preceding_pattern = ' '.join(preceding_tokens)
|
|
52
|
+
if preceding_pattern in ref_norm:
|
|
53
|
+
# Found the context, now extract what comes after
|
|
54
|
+
start_idx = ref_norm.index(preceding_pattern) + len(preceding_pattern)
|
|
55
|
+
remaining = ref_norm[start_idx:].strip()
|
|
56
|
+
|
|
57
|
+
# Find where following context starts
|
|
58
|
+
if following_tokens:
|
|
59
|
+
following_pattern = ' '.join(following_tokens)
|
|
60
|
+
if following_pattern in remaining:
|
|
61
|
+
end_idx = remaining.index(following_pattern)
|
|
62
|
+
replacement = remaining[:end_idx].strip()
|
|
63
|
+
if replacement:
|
|
64
|
+
return replacement
|
|
65
|
+
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def _normalize_text(self, text: str) -> str:
|
|
69
|
+
"""Normalize text for comparison (lowercase, remove punctuation)."""
|
|
70
|
+
# Remove punctuation except apostrophes in contractions
|
|
71
|
+
text = re.sub(r'[^\w\s\']', ' ', text.lower())
|
|
72
|
+
# Normalize whitespace
|
|
73
|
+
text = ' '.join(text.split())
|
|
74
|
+
return text
|
|
75
|
+
|
|
76
|
+
def handle(
|
|
77
|
+
self,
|
|
78
|
+
gap_id: str,
|
|
79
|
+
gap_words: List[Dict[str, Any]],
|
|
80
|
+
preceding_words: str,
|
|
81
|
+
following_words: str,
|
|
82
|
+
reference_contexts: Dict[str, str],
|
|
83
|
+
classification_reasoning: str = ""
|
|
84
|
+
) -> List[CorrectionProposal]:
|
|
85
|
+
"""Propose replacement based on reference lyrics."""
|
|
86
|
+
|
|
87
|
+
if not gap_words:
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
# Try to extract the correct replacement from references
|
|
91
|
+
replacement_text = self._extract_replacement_from_references(
|
|
92
|
+
gap_words,
|
|
93
|
+
reference_contexts,
|
|
94
|
+
preceding_words,
|
|
95
|
+
following_words
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if replacement_text:
|
|
99
|
+
# Found a replacement in reference lyrics
|
|
100
|
+
proposal = CorrectionProposal(
|
|
101
|
+
word_ids=[w['id'] for w in gap_words],
|
|
102
|
+
action="ReplaceWord",
|
|
103
|
+
replacement_text=replacement_text,
|
|
104
|
+
confidence=0.75,
|
|
105
|
+
reason=f"Sound-alike error. Reference suggests: '{replacement_text}'. {classification_reasoning}",
|
|
106
|
+
gap_category=self.category,
|
|
107
|
+
requires_human_review=False,
|
|
108
|
+
artist=self.artist,
|
|
109
|
+
title=self.title
|
|
110
|
+
)
|
|
111
|
+
return [proposal]
|
|
112
|
+
else:
|
|
113
|
+
# Could not extract replacement, flag for human review
|
|
114
|
+
gap_text = ' '.join(w.get('text', '') for w in gap_words)
|
|
115
|
+
proposal = CorrectionProposal(
|
|
116
|
+
word_ids=[w['id'] for w in gap_words],
|
|
117
|
+
action="Flag",
|
|
118
|
+
confidence=0.6,
|
|
119
|
+
reason=f"Sound-alike error detected for '{gap_text}' but could not extract replacement from references. {classification_reasoning}",
|
|
120
|
+
gap_category=self.category,
|
|
121
|
+
requires_human_review=True,
|
|
122
|
+
artist=self.artist,
|
|
123
|
+
title=self.title
|
|
124
|
+
)
|
|
125
|
+
return [proposal]
|
|
126
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .enums import CorrectionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AICorrection:
|
|
10
|
+
id: str
|
|
11
|
+
original_text: str
|
|
12
|
+
corrected_text: str
|
|
13
|
+
confidence_score: float
|
|
14
|
+
reasoning: str
|
|
15
|
+
model_used: str
|
|
16
|
+
correction_type: CorrectionType
|
|
17
|
+
processing_time_ms: int
|
|
18
|
+
tokens_used: int
|
|
19
|
+
created_at: datetime
|
|
20
|
+
word_position: int
|
|
21
|
+
session_id: str
|
|
22
|
+
|
|
23
|
+
def validate(self) -> None:
|
|
24
|
+
if not (0.0 <= self.confidence_score <= 1.0):
|
|
25
|
+
raise ValueError("confidence_score must be between 0.0 and 1.0")
|
|
26
|
+
if self.original_text == self.corrected_text:
|
|
27
|
+
raise ValueError("original_text and corrected_text must differ")
|
|
28
|
+
if self.processing_time_ms <= 0:
|
|
29
|
+
raise ValueError("processing_time_ms must be positive")
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional, Dict
|
|
4
|
+
|
|
5
|
+
from .enums import SessionType, SessionStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class CorrectionSession:
|
|
10
|
+
id: str
|
|
11
|
+
audio_file_hash: str
|
|
12
|
+
session_type: SessionType
|
|
13
|
+
ai_model_config: Dict[str, object]
|
|
14
|
+
total_corrections: int
|
|
15
|
+
accepted_corrections: int
|
|
16
|
+
human_modifications: int
|
|
17
|
+
session_duration_ms: int
|
|
18
|
+
accuracy_improvement: float
|
|
19
|
+
started_at: datetime
|
|
20
|
+
completed_at: Optional[datetime]
|
|
21
|
+
status: SessionStatus
|
|
22
|
+
|
|
23
|
+
def validate(self) -> None:
|
|
24
|
+
# Basic validations per data-model
|
|
25
|
+
if any(v < 0 for v in (self.total_corrections, self.accepted_corrections, self.human_modifications)):
|
|
26
|
+
raise ValueError("correction counts must be non-negative")
|
|
27
|
+
if self.completed_at is not None and self.completed_at < self.started_at:
|
|
28
|
+
raise ValueError("completed_at must be after started_at")
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CorrectionType(str, Enum):
|
|
5
|
+
WORD_SUBSTITUTION = "WORD_SUBSTITUTION"
|
|
6
|
+
WORD_INSERTION = "WORD_INSERTION"
|
|
7
|
+
WORD_DELETION = "WORD_DELETION"
|
|
8
|
+
PUNCTUATION = "PUNCTUATION"
|
|
9
|
+
TIMING_ADJUSTMENT = "TIMING_ADJUSTMENT"
|
|
10
|
+
LINGUISTIC_IMPROVEMENT = "LINGUISTIC_IMPROVEMENT"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ReviewerAction(str, Enum):
|
|
14
|
+
ACCEPT = "ACCEPT"
|
|
15
|
+
REJECT = "REJECT"
|
|
16
|
+
MODIFY = "MODIFY"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FeedbackCategory(str, Enum):
|
|
20
|
+
AI_CORRECT = "AI_CORRECT"
|
|
21
|
+
AI_INCORRECT = "AI_INCORRECT"
|
|
22
|
+
AI_SUBOPTIMAL = "AI_SUBOPTIMAL"
|
|
23
|
+
CONTEXT_NEEDED = "CONTEXT_NEEDED"
|
|
24
|
+
SUBJECTIVE_PREFERENCE = "SUBJECTIVE_PREFERENCE"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SessionType(str, Enum):
|
|
28
|
+
FULL_CORRECTION = "FULL_CORRECTION"
|
|
29
|
+
PARTIAL_REVIEW = "PARTIAL_REVIEW"
|
|
30
|
+
REPROCESSING = "REPROCESSING"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SessionStatus(str, Enum):
|
|
34
|
+
IN_PROGRESS = "IN_PROGRESS"
|
|
35
|
+
COMPLETED = "COMPLETED"
|
|
36
|
+
FAILED = "FAILED"
|
|
37
|
+
|
|
38
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .enums import ReviewerAction, FeedbackCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class HumanFeedback:
|
|
10
|
+
id: str
|
|
11
|
+
ai_correction_id: str
|
|
12
|
+
reviewer_action: ReviewerAction
|
|
13
|
+
final_text: Optional[str]
|
|
14
|
+
reason_category: FeedbackCategory
|
|
15
|
+
reason_detail: Optional[str]
|
|
16
|
+
reviewer_confidence: float
|
|
17
|
+
review_time_ms: int
|
|
18
|
+
reviewer_id: Optional[str]
|
|
19
|
+
created_at: datetime
|
|
20
|
+
session_id: str
|
|
21
|
+
|
|
22
|
+
def validate(self) -> None:
|
|
23
|
+
if self.reviewer_action == ReviewerAction.MODIFY and not self.final_text:
|
|
24
|
+
raise ValueError("final_text required when action is MODIFY")
|
|
25
|
+
if self.reviewer_confidence is not None and not (0.0 <= self.reviewer_confidence <= 1.0):
|
|
26
|
+
raise ValueError("reviewer_confidence must be between 0.0 and 1.0")
|
|
27
|
+
if self.review_time_ms <= 0:
|
|
28
|
+
raise ValueError("review_time_ms must be positive")
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class LearningData:
|
|
8
|
+
id: str
|
|
9
|
+
session_id: str
|
|
10
|
+
error_patterns: Dict[str, int]
|
|
11
|
+
correction_strategies: Dict[str, int]
|
|
12
|
+
model_performance: Dict[str, float]
|
|
13
|
+
feedback_trends: Dict[str, int]
|
|
14
|
+
improvement_metrics: Dict[str, float]
|
|
15
|
+
data_quality_score: float
|
|
16
|
+
created_at: datetime
|
|
17
|
+
expires_at: datetime
|
|
18
|
+
|
|
19
|
+
def validate(self) -> None:
|
|
20
|
+
if not (0.0 <= self.data_quality_score <= 1.0):
|
|
21
|
+
raise ValueError("data_quality_score must be between 0.0 and 1.0")
|
|
22
|
+
# Note: exact 3-year check depends on business rule; enforce >= 3 years
|
|
23
|
+
if (self.expires_at - self.created_at).days < 365 * 3:
|
|
24
|
+
raise ValueError("expires_at must be at least 3 years from created_at")
|
|
25
|
+
|
|
26
|
+
|