audex 1.0.7a3__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.
- audex/__init__.py +9 -0
- audex/__main__.py +7 -0
- audex/cli/__init__.py +189 -0
- audex/cli/apis/__init__.py +12 -0
- audex/cli/apis/init/__init__.py +34 -0
- audex/cli/apis/init/gencfg.py +130 -0
- audex/cli/apis/init/setup.py +330 -0
- audex/cli/apis/init/vprgroup.py +125 -0
- audex/cli/apis/serve.py +141 -0
- audex/cli/args.py +356 -0
- audex/cli/exceptions.py +44 -0
- audex/cli/helper/__init__.py +0 -0
- audex/cli/helper/ansi.py +193 -0
- audex/cli/helper/display.py +288 -0
- audex/config/__init__.py +64 -0
- audex/config/core/__init__.py +30 -0
- audex/config/core/app.py +29 -0
- audex/config/core/audio.py +45 -0
- audex/config/core/logging.py +163 -0
- audex/config/core/session.py +11 -0
- audex/config/helper/__init__.py +1 -0
- audex/config/helper/client/__init__.py +1 -0
- audex/config/helper/client/http.py +28 -0
- audex/config/helper/client/websocket.py +21 -0
- audex/config/helper/provider/__init__.py +1 -0
- audex/config/helper/provider/dashscope.py +13 -0
- audex/config/helper/provider/unisound.py +18 -0
- audex/config/helper/provider/xfyun.py +23 -0
- audex/config/infrastructure/__init__.py +31 -0
- audex/config/infrastructure/cache.py +51 -0
- audex/config/infrastructure/database.py +48 -0
- audex/config/infrastructure/recorder.py +32 -0
- audex/config/infrastructure/store.py +19 -0
- audex/config/provider/__init__.py +18 -0
- audex/config/provider/transcription.py +109 -0
- audex/config/provider/vpr.py +99 -0
- audex/container.py +40 -0
- audex/entity/__init__.py +468 -0
- audex/entity/doctor.py +109 -0
- audex/entity/doctor.pyi +51 -0
- audex/entity/fields.py +401 -0
- audex/entity/segment.py +115 -0
- audex/entity/segment.pyi +38 -0
- audex/entity/session.py +133 -0
- audex/entity/session.pyi +47 -0
- audex/entity/utterance.py +142 -0
- audex/entity/utterance.pyi +48 -0
- audex/entity/vp.py +68 -0
- audex/entity/vp.pyi +35 -0
- audex/exceptions.py +157 -0
- audex/filters/__init__.py +692 -0
- audex/filters/generated/__init__.py +21 -0
- audex/filters/generated/doctor.py +987 -0
- audex/filters/generated/segment.py +723 -0
- audex/filters/generated/session.py +978 -0
- audex/filters/generated/utterance.py +939 -0
- audex/filters/generated/vp.py +815 -0
- audex/helper/__init__.py +1 -0
- audex/helper/hash.py +33 -0
- audex/helper/mixin.py +65 -0
- audex/helper/net.py +19 -0
- audex/helper/settings/__init__.py +830 -0
- audex/helper/settings/fields.py +317 -0
- audex/helper/stream.py +153 -0
- audex/injectors/__init__.py +1 -0
- audex/injectors/config.py +12 -0
- audex/injectors/lifespan.py +7 -0
- audex/lib/__init__.py +1 -0
- audex/lib/cache/__init__.py +383 -0
- audex/lib/cache/inmemory.py +513 -0
- audex/lib/database/__init__.py +83 -0
- audex/lib/database/sqlite.py +406 -0
- audex/lib/exporter.py +189 -0
- audex/lib/injectors/__init__.py +1 -0
- audex/lib/injectors/cache.py +25 -0
- audex/lib/injectors/container.py +47 -0
- audex/lib/injectors/exporter.py +26 -0
- audex/lib/injectors/recorder.py +33 -0
- audex/lib/injectors/server.py +17 -0
- audex/lib/injectors/session.py +18 -0
- audex/lib/injectors/sqlite.py +24 -0
- audex/lib/injectors/store.py +13 -0
- audex/lib/injectors/transcription.py +42 -0
- audex/lib/injectors/usb.py +12 -0
- audex/lib/injectors/vpr.py +65 -0
- audex/lib/injectors/wifi.py +7 -0
- audex/lib/recorder.py +844 -0
- audex/lib/repos/__init__.py +149 -0
- audex/lib/repos/container.py +23 -0
- audex/lib/repos/database/__init__.py +1 -0
- audex/lib/repos/database/sqlite.py +672 -0
- audex/lib/repos/decorators.py +74 -0
- audex/lib/repos/doctor.py +286 -0
- audex/lib/repos/segment.py +302 -0
- audex/lib/repos/session.py +285 -0
- audex/lib/repos/tables/__init__.py +70 -0
- audex/lib/repos/tables/doctor.py +137 -0
- audex/lib/repos/tables/segment.py +113 -0
- audex/lib/repos/tables/session.py +140 -0
- audex/lib/repos/tables/utterance.py +131 -0
- audex/lib/repos/tables/vp.py +102 -0
- audex/lib/repos/utterance.py +288 -0
- audex/lib/repos/vp.py +286 -0
- audex/lib/restful.py +251 -0
- audex/lib/server/__init__.py +97 -0
- audex/lib/server/auth.py +98 -0
- audex/lib/server/handlers.py +248 -0
- audex/lib/server/templates/index.html.j2 +226 -0
- audex/lib/server/templates/login.html.j2 +111 -0
- audex/lib/server/templates/static/script.js +68 -0
- audex/lib/server/templates/static/style.css +579 -0
- audex/lib/server/types.py +123 -0
- audex/lib/session.py +503 -0
- audex/lib/store/__init__.py +238 -0
- audex/lib/store/localfile.py +411 -0
- audex/lib/transcription/__init__.py +33 -0
- audex/lib/transcription/dashscope.py +525 -0
- audex/lib/transcription/events.py +62 -0
- audex/lib/usb.py +554 -0
- audex/lib/vpr/__init__.py +38 -0
- audex/lib/vpr/unisound/__init__.py +185 -0
- audex/lib/vpr/unisound/types.py +469 -0
- audex/lib/vpr/xfyun/__init__.py +483 -0
- audex/lib/vpr/xfyun/types.py +679 -0
- audex/lib/websocket/__init__.py +8 -0
- audex/lib/websocket/connection.py +485 -0
- audex/lib/websocket/pool.py +991 -0
- audex/lib/wifi.py +1146 -0
- audex/lifespan.py +75 -0
- audex/service/__init__.py +27 -0
- audex/service/decorators.py +73 -0
- audex/service/doctor/__init__.py +652 -0
- audex/service/doctor/const.py +36 -0
- audex/service/doctor/exceptions.py +96 -0
- audex/service/doctor/types.py +54 -0
- audex/service/export/__init__.py +236 -0
- audex/service/export/const.py +17 -0
- audex/service/export/exceptions.py +34 -0
- audex/service/export/types.py +21 -0
- audex/service/injectors/__init__.py +1 -0
- audex/service/injectors/container.py +53 -0
- audex/service/injectors/doctor.py +34 -0
- audex/service/injectors/export.py +27 -0
- audex/service/injectors/session.py +49 -0
- audex/service/session/__init__.py +754 -0
- audex/service/session/const.py +34 -0
- audex/service/session/exceptions.py +67 -0
- audex/service/session/types.py +91 -0
- audex/types.py +39 -0
- audex/utils.py +287 -0
- audex/valueobj/__init__.py +81 -0
- audex/valueobj/common/__init__.py +1 -0
- audex/valueobj/common/auth.py +84 -0
- audex/valueobj/common/email.py +16 -0
- audex/valueobj/common/ops.py +22 -0
- audex/valueobj/common/phone.py +84 -0
- audex/valueobj/common/version.py +72 -0
- audex/valueobj/session.py +19 -0
- audex/valueobj/utterance.py +15 -0
- audex/view/__init__.py +51 -0
- audex/view/container.py +17 -0
- audex/view/decorators.py +303 -0
- audex/view/pages/__init__.py +1 -0
- audex/view/pages/dashboard/__init__.py +286 -0
- audex/view/pages/dashboard/wifi.py +407 -0
- audex/view/pages/login.py +110 -0
- audex/view/pages/recording.py +348 -0
- audex/view/pages/register.py +202 -0
- audex/view/pages/sessions/__init__.py +196 -0
- audex/view/pages/sessions/details.py +224 -0
- audex/view/pages/sessions/export.py +443 -0
- audex/view/pages/settings.py +374 -0
- audex/view/pages/voiceprint/__init__.py +1 -0
- audex/view/pages/voiceprint/enroll.py +195 -0
- audex/view/pages/voiceprint/update.py +195 -0
- audex/view/static/css/dashboard.css +452 -0
- audex/view/static/css/glass.css +22 -0
- audex/view/static/css/global.css +541 -0
- audex/view/static/css/login.css +386 -0
- audex/view/static/css/recording.css +439 -0
- audex/view/static/css/register.css +293 -0
- audex/view/static/css/sessions/styles.css +501 -0
- audex/view/static/css/settings.css +186 -0
- audex/view/static/css/voiceprint/enroll.css +43 -0
- audex/view/static/css/voiceprint/styles.css +209 -0
- audex/view/static/css/voiceprint/update.css +44 -0
- audex/view/static/images/logo.svg +95 -0
- audex/view/static/js/recording.js +42 -0
- audex-1.0.7a3.dist-info/METADATA +361 -0
- audex-1.0.7a3.dist-info/RECORD +192 -0
- audex-1.0.7a3.dist-info/WHEEL +4 -0
- audex-1.0.7a3.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
import sqlmodel as sqlm
|
|
7
|
+
|
|
8
|
+
from audex.entity.session import Session
|
|
9
|
+
from audex.lib.repos.tables import BaseTable
|
|
10
|
+
from audex.valueobj.session import SessionStatus
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SessionTable(BaseTable[Session], table=True):
|
|
14
|
+
"""Session table model for SQLite storage.
|
|
15
|
+
|
|
16
|
+
Maps the Session entity to the database table with all necessary fields
|
|
17
|
+
for tracking conversation sessions, their status, and timing.
|
|
18
|
+
|
|
19
|
+
Table: sessions
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
__tablename__ = "sessions"
|
|
23
|
+
|
|
24
|
+
doctor_id: str = sqlm.Field(
|
|
25
|
+
index=True,
|
|
26
|
+
max_length=50,
|
|
27
|
+
description="Foreign key to doctor who owns this session",
|
|
28
|
+
)
|
|
29
|
+
patient_name: str | None = sqlm.Field(
|
|
30
|
+
default=None,
|
|
31
|
+
nullable=True,
|
|
32
|
+
max_length=100,
|
|
33
|
+
description="Patient name for this session",
|
|
34
|
+
)
|
|
35
|
+
clinic_number: str | None = sqlm.Field(
|
|
36
|
+
default=None,
|
|
37
|
+
nullable=True,
|
|
38
|
+
max_length=50,
|
|
39
|
+
description="Clinic number or ID for this session",
|
|
40
|
+
)
|
|
41
|
+
medical_record_number: str | None = sqlm.Field(
|
|
42
|
+
default=None,
|
|
43
|
+
nullable=True,
|
|
44
|
+
max_length=50,
|
|
45
|
+
description="Medical record number for this session",
|
|
46
|
+
)
|
|
47
|
+
diagnosis: str | None = sqlm.Field(
|
|
48
|
+
default=None,
|
|
49
|
+
nullable=True,
|
|
50
|
+
max_length=255,
|
|
51
|
+
description="Preliminary diagnosis for this session",
|
|
52
|
+
)
|
|
53
|
+
status: str = sqlm.Field(
|
|
54
|
+
default=SessionStatus.DRAFT.value,
|
|
55
|
+
max_length=20,
|
|
56
|
+
index=True,
|
|
57
|
+
description="Session status (draft/in_progress/completed/cancelled)",
|
|
58
|
+
)
|
|
59
|
+
started_at: datetime.datetime | None = sqlm.Field(
|
|
60
|
+
default=None,
|
|
61
|
+
nullable=True,
|
|
62
|
+
description="Timestamp when session first started",
|
|
63
|
+
)
|
|
64
|
+
ended_at: datetime.datetime | None = sqlm.Field(
|
|
65
|
+
default=None,
|
|
66
|
+
nullable=True,
|
|
67
|
+
description="Timestamp when session ended",
|
|
68
|
+
)
|
|
69
|
+
notes: str | None = sqlm.Field(
|
|
70
|
+
default=None,
|
|
71
|
+
nullable=True,
|
|
72
|
+
description="Additional notes about the session",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_entity(cls, entity: Session) -> t.Self:
|
|
77
|
+
"""Convert Session entity to table model.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
entity: The Session entity to convert.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
SessionTable instance.
|
|
84
|
+
"""
|
|
85
|
+
return cls(
|
|
86
|
+
id=entity.id,
|
|
87
|
+
doctor_id=entity.doctor_id,
|
|
88
|
+
patient_name=entity.patient_name,
|
|
89
|
+
clinic_number=entity.clinic_number,
|
|
90
|
+
medical_record_number=entity.medical_record_number,
|
|
91
|
+
diagnosis=entity.diagnosis,
|
|
92
|
+
status=entity.status.value,
|
|
93
|
+
started_at=entity.started_at,
|
|
94
|
+
ended_at=entity.ended_at,
|
|
95
|
+
notes=entity.notes,
|
|
96
|
+
created_at=entity.created_at,
|
|
97
|
+
updated_at=entity.updated_at,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def to_entity(self) -> Session:
|
|
101
|
+
"""Convert table model to Session entity.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Session entity instance.
|
|
105
|
+
"""
|
|
106
|
+
return Session(
|
|
107
|
+
id=self.id,
|
|
108
|
+
doctor_id=self.doctor_id,
|
|
109
|
+
patient_name=self.patient_name,
|
|
110
|
+
clinic_number=self.clinic_number,
|
|
111
|
+
medical_record_number=self.medical_record_number,
|
|
112
|
+
diagnosis=self.diagnosis,
|
|
113
|
+
status=SessionStatus.parse(self.status),
|
|
114
|
+
started_at=self.started_at,
|
|
115
|
+
ended_at=self.ended_at,
|
|
116
|
+
notes=self.notes,
|
|
117
|
+
created_at=self.created_at,
|
|
118
|
+
updated_at=self.updated_at,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def update(self, entity: Session) -> None:
|
|
122
|
+
"""Update table model fields from Session entity.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
entity: The Session entity with updated data.
|
|
126
|
+
"""
|
|
127
|
+
self.doctor_id = entity.doctor_id
|
|
128
|
+
self.patient_name = entity.patient_name
|
|
129
|
+
self.clinic_number = entity.clinic_number
|
|
130
|
+
self.medical_record_number = entity.medical_record_number
|
|
131
|
+
self.diagnosis = entity.diagnosis
|
|
132
|
+
self.status = entity.status.value
|
|
133
|
+
self.started_at = entity.started_at
|
|
134
|
+
self.ended_at = entity.ended_at
|
|
135
|
+
self.notes = entity.notes
|
|
136
|
+
self.updated_at = entity.updated_at
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
TABLES: set[type[sqlm.SQLModel]] = {SessionTable}
|
|
140
|
+
"""Set of all table models for the repository."""
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
import sqlmodel as sqlm
|
|
7
|
+
|
|
8
|
+
from audex.entity.utterance import Utterance
|
|
9
|
+
from audex.lib.repos.tables import BaseTable
|
|
10
|
+
from audex.valueobj.utterance import Speaker
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UtteranceTable(BaseTable[Utterance], table=True):
|
|
14
|
+
"""Utterance table model for SQLite storage.
|
|
15
|
+
|
|
16
|
+
Maps the Utterance entity to the database table with all necessary fields
|
|
17
|
+
for tracking speech utterances in conversations.
|
|
18
|
+
|
|
19
|
+
Table: utterances
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
__tablename__ = "utterances"
|
|
23
|
+
|
|
24
|
+
session_id: str = sqlm.Field(
|
|
25
|
+
index=True,
|
|
26
|
+
max_length=50,
|
|
27
|
+
description="Foreign key to session this utterance belongs to",
|
|
28
|
+
)
|
|
29
|
+
segment_id: str = sqlm.Field(
|
|
30
|
+
index=True,
|
|
31
|
+
max_length=50,
|
|
32
|
+
description="Foreign key to segment containing this utterance",
|
|
33
|
+
)
|
|
34
|
+
sequence: int = sqlm.Field(
|
|
35
|
+
nullable=False,
|
|
36
|
+
description="Sequence number within session",
|
|
37
|
+
)
|
|
38
|
+
speaker: str = sqlm.Field(
|
|
39
|
+
max_length=20,
|
|
40
|
+
nullable=False,
|
|
41
|
+
index=True,
|
|
42
|
+
description="Speaker identification (doctor/patient)",
|
|
43
|
+
)
|
|
44
|
+
text: str = sqlm.Field(
|
|
45
|
+
nullable=False,
|
|
46
|
+
description="Transcribed text content",
|
|
47
|
+
)
|
|
48
|
+
confidence: float | None = sqlm.Field(
|
|
49
|
+
default=None,
|
|
50
|
+
nullable=True,
|
|
51
|
+
description="ASR confidence score",
|
|
52
|
+
)
|
|
53
|
+
start_time_ms: int = sqlm.Field(
|
|
54
|
+
nullable=False,
|
|
55
|
+
description="Start time in segment (milliseconds)",
|
|
56
|
+
)
|
|
57
|
+
end_time_ms: int = sqlm.Field(
|
|
58
|
+
nullable=False,
|
|
59
|
+
description="End time in segment (milliseconds)",
|
|
60
|
+
)
|
|
61
|
+
timestamp: datetime.datetime = sqlm.Field(
|
|
62
|
+
nullable=False,
|
|
63
|
+
description="Absolute timestamp of utterance",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_entity(cls, entity: Utterance) -> t.Self:
|
|
68
|
+
"""Convert Utterance entity to table model.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
entity: The Utterance entity to convert.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
UtteranceTable instance.
|
|
75
|
+
"""
|
|
76
|
+
return cls(
|
|
77
|
+
id=entity.id,
|
|
78
|
+
session_id=entity.session_id,
|
|
79
|
+
segment_id=entity.segment_id,
|
|
80
|
+
sequence=entity.sequence,
|
|
81
|
+
speaker=entity.speaker.value,
|
|
82
|
+
text=entity.text,
|
|
83
|
+
confidence=entity.confidence,
|
|
84
|
+
start_time_ms=entity.start_time_ms,
|
|
85
|
+
end_time_ms=entity.end_time_ms,
|
|
86
|
+
timestamp=entity.timestamp,
|
|
87
|
+
created_at=entity.created_at,
|
|
88
|
+
updated_at=entity.updated_at,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def to_entity(self) -> Utterance:
|
|
92
|
+
"""Convert table model to Utterance entity.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Utterance entity instance.
|
|
96
|
+
"""
|
|
97
|
+
return Utterance(
|
|
98
|
+
id=self.id,
|
|
99
|
+
session_id=self.session_id,
|
|
100
|
+
segment_id=self.segment_id,
|
|
101
|
+
sequence=self.sequence,
|
|
102
|
+
speaker=Speaker.parse(self.speaker),
|
|
103
|
+
text=self.text,
|
|
104
|
+
confidence=self.confidence,
|
|
105
|
+
start_time_ms=self.start_time_ms,
|
|
106
|
+
end_time_ms=self.end_time_ms,
|
|
107
|
+
timestamp=self.timestamp,
|
|
108
|
+
created_at=self.created_at,
|
|
109
|
+
updated_at=self.updated_at,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def update(self, entity: Utterance) -> None:
|
|
113
|
+
"""Update table model fields from Utterance entity.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
entity: The Utterance entity with updated data.
|
|
117
|
+
"""
|
|
118
|
+
self.session_id = entity.session_id
|
|
119
|
+
self.segment_id = entity.segment_id
|
|
120
|
+
self.sequence = entity.sequence
|
|
121
|
+
self.speaker = entity.speaker.value
|
|
122
|
+
self.text = entity.text
|
|
123
|
+
self.confidence = entity.confidence
|
|
124
|
+
self.start_time_ms = entity.start_time_ms
|
|
125
|
+
self.end_time_ms = entity.end_time_ms
|
|
126
|
+
self.timestamp = entity.timestamp
|
|
127
|
+
self.updated_at = entity.updated_at
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
TABLES: set[type[sqlm.SQLModel]] = {UtteranceTable}
|
|
131
|
+
"""Set of all table models for the repository."""
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sqlmodel as sqlm
|
|
4
|
+
|
|
5
|
+
from audex.entity.vp import VP
|
|
6
|
+
from audex.lib.repos.tables import BaseTable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VPTable(BaseTable[VP], table=True):
|
|
10
|
+
"""Voiceprint (VP) table model for SQLite storage.
|
|
11
|
+
|
|
12
|
+
Maps the VP entity to the database table with fields for managing
|
|
13
|
+
voiceprint data associated with doctors.
|
|
14
|
+
|
|
15
|
+
Table: vps
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__tablename__ = "vps"
|
|
19
|
+
|
|
20
|
+
doctor_id: str = sqlm.Field(
|
|
21
|
+
...,
|
|
22
|
+
index=True,
|
|
23
|
+
max_length=50,
|
|
24
|
+
description="The ID of the doctor associated with this voiceprint.",
|
|
25
|
+
)
|
|
26
|
+
vpr_uid: str = sqlm.Field(
|
|
27
|
+
...,
|
|
28
|
+
unique=True,
|
|
29
|
+
index=True,
|
|
30
|
+
max_length=100,
|
|
31
|
+
description="Unique voiceprint recognition UID from the VP service.",
|
|
32
|
+
)
|
|
33
|
+
vpr_group_id: str = sqlm.Field(
|
|
34
|
+
...,
|
|
35
|
+
index=True,
|
|
36
|
+
max_length=100,
|
|
37
|
+
description="Voiceprint recognition group ID from the VP service.",
|
|
38
|
+
)
|
|
39
|
+
audio_key: str = sqlm.Field(
|
|
40
|
+
...,
|
|
41
|
+
unique=True,
|
|
42
|
+
index=True,
|
|
43
|
+
max_length=150,
|
|
44
|
+
description="Storage key for the voiceprint audio file.",
|
|
45
|
+
)
|
|
46
|
+
text_content: str = sqlm.Field(
|
|
47
|
+
...,
|
|
48
|
+
max_length=500,
|
|
49
|
+
description="The text content used for voiceprint enrollment.",
|
|
50
|
+
)
|
|
51
|
+
sample_rate: int = sqlm.Field(
|
|
52
|
+
...,
|
|
53
|
+
description="Sample rate of the voiceprint audio in Hz.",
|
|
54
|
+
)
|
|
55
|
+
is_active: bool = sqlm.Field(
|
|
56
|
+
default=True,
|
|
57
|
+
description="Indicates whether the voiceprint is active for recognition.",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_entity(cls, entity: VP) -> VPTable:
|
|
62
|
+
"""Create a VPTable instance from a VP entity."""
|
|
63
|
+
return cls(
|
|
64
|
+
id=entity.id,
|
|
65
|
+
doctor_id=entity.doctor_id,
|
|
66
|
+
vpr_uid=entity.vpr_uid,
|
|
67
|
+
vpr_group_id=entity.vpr_group_id,
|
|
68
|
+
audio_key=entity.audio_key,
|
|
69
|
+
text_content=entity.text_content,
|
|
70
|
+
sample_rate=entity.sample_rate,
|
|
71
|
+
is_active=entity.is_active,
|
|
72
|
+
created_at=entity.created_at,
|
|
73
|
+
updated_at=entity.updated_at,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def to_entity(self) -> VP:
|
|
77
|
+
return VP(
|
|
78
|
+
id=self.id,
|
|
79
|
+
doctor_id=self.doctor_id,
|
|
80
|
+
vpr_uid=self.vpr_uid,
|
|
81
|
+
vpr_group_id=self.vpr_group_id,
|
|
82
|
+
audio_key=self.audio_key,
|
|
83
|
+
text_content=self.text_content,
|
|
84
|
+
is_active=self.is_active,
|
|
85
|
+
sample_rate=self.sample_rate,
|
|
86
|
+
created_at=self.created_at,
|
|
87
|
+
updated_at=self.updated_at,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def update(self, entity: VP) -> None:
|
|
91
|
+
"""Update the VPTable instance with data from a VP entity."""
|
|
92
|
+
self.doctor_id = entity.doctor_id
|
|
93
|
+
self.vpr_uid = entity.vpr_uid
|
|
94
|
+
self.vpr_group_id = entity.vpr_group_id
|
|
95
|
+
self.audio_key = entity.audio_key
|
|
96
|
+
self.text_content = entity.text_content
|
|
97
|
+
self.sample_rate = entity.sample_rate
|
|
98
|
+
self.is_active = entity.is_active
|
|
99
|
+
self.updated_at = entity.updated_at
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
TABLES: set[type[sqlm.SQLModel]] = {VPTable}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
import sqlalchemy as sa
|
|
7
|
+
import sqlmodel as sqlm
|
|
8
|
+
|
|
9
|
+
from audex.entity.utterance import Utterance
|
|
10
|
+
from audex.filters import Filter
|
|
11
|
+
from audex.lib.database.sqlite import SQLite
|
|
12
|
+
from audex.lib.repos.database.sqlite import SQLiteRepository
|
|
13
|
+
from audex.lib.repos.tables.utterance import UtteranceTable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UtteranceRepository(SQLiteRepository[Utterance]):
|
|
17
|
+
"""SQLite implementation of Utterance repository.
|
|
18
|
+
|
|
19
|
+
Provides CRUD operations for Utterance entities with additional
|
|
20
|
+
specialized query methods for utterance management by session and
|
|
21
|
+
segment.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__table__ = UtteranceTable
|
|
25
|
+
__tablename__ = UtteranceTable.__tablename__
|
|
26
|
+
|
|
27
|
+
def __init__(self, sqlite: SQLite) -> None:
|
|
28
|
+
super().__init__(sqlite)
|
|
29
|
+
|
|
30
|
+
async def create(self, data: Utterance, /) -> str:
|
|
31
|
+
"""Create a new utterance in the database.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data: The utterance entity to create.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The ID of the created utterance.
|
|
38
|
+
"""
|
|
39
|
+
async with self.sqlite.session() as session:
|
|
40
|
+
utterance_table = UtteranceTable.from_entity(data)
|
|
41
|
+
session.add(utterance_table)
|
|
42
|
+
await session.commit()
|
|
43
|
+
await session.refresh(utterance_table)
|
|
44
|
+
return utterance_table.id
|
|
45
|
+
|
|
46
|
+
async def read(self, id: str, /) -> Utterance | None:
|
|
47
|
+
"""Read an utterance by ID.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
id: The ID (id) of the utterance to retrieve.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The utterance entity if found, None otherwise.
|
|
54
|
+
"""
|
|
55
|
+
async with self.sqlite.session() as session:
|
|
56
|
+
stmt = sqlm.select(UtteranceTable).where(UtteranceTable.id == id)
|
|
57
|
+
result = await session.execute(stmt)
|
|
58
|
+
utterance_obj = result.scalar_one_or_none()
|
|
59
|
+
|
|
60
|
+
if utterance_obj is None:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return utterance_obj.to_entity()
|
|
64
|
+
|
|
65
|
+
async def first(self, filter: Filter) -> Utterance | None:
|
|
66
|
+
"""Retrieve the first utterance matching the filter.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
filter: Filter to apply when searching for the utterance.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
The first utterance entity matching the filter, or None if no match.
|
|
73
|
+
"""
|
|
74
|
+
spec = self.build_query_spec(filter)
|
|
75
|
+
|
|
76
|
+
async with self.sqlite.session() as session:
|
|
77
|
+
stmt = sqlm.select(UtteranceTable)
|
|
78
|
+
|
|
79
|
+
for clause in spec.where:
|
|
80
|
+
stmt = stmt.where(clause)
|
|
81
|
+
|
|
82
|
+
for order in spec.order_by:
|
|
83
|
+
stmt = stmt.order_by(order)
|
|
84
|
+
|
|
85
|
+
stmt = stmt.limit(1)
|
|
86
|
+
|
|
87
|
+
result = await session.execute(stmt)
|
|
88
|
+
utterance_obj = result.scalar_one_or_none()
|
|
89
|
+
|
|
90
|
+
if utterance_obj is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
return utterance_obj.to_entity()
|
|
94
|
+
|
|
95
|
+
async def list(
|
|
96
|
+
self,
|
|
97
|
+
arg: builtins.list[str] | t.Optional[Filter] = None, # noqa
|
|
98
|
+
*,
|
|
99
|
+
page_index: int = 0,
|
|
100
|
+
page_size: int = 100,
|
|
101
|
+
) -> builtins.list[Utterance]:
|
|
102
|
+
"""List utterances by IDs or with optional filtering and
|
|
103
|
+
pagination.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
arg: Either a list of IDs to retrieve, or an optional filter.
|
|
107
|
+
page_index: Zero-based page index for pagination.
|
|
108
|
+
page_size: Number of items per page.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of utterance entities matching the criteria.
|
|
112
|
+
"""
|
|
113
|
+
async with self.sqlite.session() as session:
|
|
114
|
+
if isinstance(arg, list):
|
|
115
|
+
if not arg:
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
stmt = sqlm.select(UtteranceTable).where(sqlm.col(UtteranceTable.id).in_(arg))
|
|
119
|
+
result = await session.execute(stmt)
|
|
120
|
+
utterance_objs = result.scalars().all()
|
|
121
|
+
return [obj.to_entity() for obj in utterance_objs]
|
|
122
|
+
|
|
123
|
+
spec = self.build_query_spec(arg)
|
|
124
|
+
stmt = sqlm.select(UtteranceTable)
|
|
125
|
+
|
|
126
|
+
for clause in spec.where:
|
|
127
|
+
stmt = stmt.where(clause)
|
|
128
|
+
|
|
129
|
+
for order in spec.order_by:
|
|
130
|
+
stmt = stmt.order_by(order)
|
|
131
|
+
|
|
132
|
+
stmt = stmt.offset(page_index * page_size).limit(page_size)
|
|
133
|
+
|
|
134
|
+
result = await session.execute(stmt)
|
|
135
|
+
utterance_objs = result.scalars().all()
|
|
136
|
+
return [obj.to_entity() for obj in utterance_objs]
|
|
137
|
+
|
|
138
|
+
async def update(self, data: Utterance, /) -> str:
|
|
139
|
+
"""Update an existing utterance.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
data: The utterance entity with updated values.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
The ID of the updated utterance.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ValueError: If the utterance with the given ID does not exist.
|
|
149
|
+
"""
|
|
150
|
+
async with self.sqlite.session() as session:
|
|
151
|
+
stmt = sqlm.select(UtteranceTable).where(UtteranceTable.id == data.id)
|
|
152
|
+
result = await session.execute(stmt)
|
|
153
|
+
utterance_obj = result.scalar_one_or_none()
|
|
154
|
+
|
|
155
|
+
if utterance_obj is None:
|
|
156
|
+
raise ValueError(f"Utterance with id {data.id} not found")
|
|
157
|
+
|
|
158
|
+
utterance_obj.update(data)
|
|
159
|
+
session.add(utterance_obj)
|
|
160
|
+
await session.commit()
|
|
161
|
+
await session.refresh(utterance_obj)
|
|
162
|
+
return utterance_obj.id
|
|
163
|
+
|
|
164
|
+
async def update_many(self, datas: builtins.list[Utterance]) -> builtins.list[str]:
|
|
165
|
+
"""Update multiple utterances in the database.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
datas: List of utterance entities with updated values.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of IDs of the updated utterances.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
ValueError: If any utterance with the given ID does not exist.
|
|
175
|
+
"""
|
|
176
|
+
if not datas:
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
updated_ids: builtins.list[str] = []
|
|
180
|
+
async with self.sqlite.session() as session:
|
|
181
|
+
ids = [data.id for data in datas]
|
|
182
|
+
stmt = sqlm.select(UtteranceTable).where(sqlm.col(UtteranceTable.id).in_(ids))
|
|
183
|
+
result = await session.execute(stmt)
|
|
184
|
+
table_objs = {obj.id: obj for obj in result.scalars().all()}
|
|
185
|
+
|
|
186
|
+
missing_ids = set(ids) - set(table_objs.keys())
|
|
187
|
+
if missing_ids:
|
|
188
|
+
raise ValueError(f"Utterances with IDs {missing_ids} not found")
|
|
189
|
+
|
|
190
|
+
for data in datas:
|
|
191
|
+
utterance_obj = table_objs[data.id]
|
|
192
|
+
utterance_obj.update(data)
|
|
193
|
+
session.add(utterance_obj)
|
|
194
|
+
updated_ids.append(utterance_obj.id)
|
|
195
|
+
|
|
196
|
+
await session.commit()
|
|
197
|
+
return updated_ids
|
|
198
|
+
|
|
199
|
+
async def delete(self, id: str, /) -> bool:
|
|
200
|
+
"""Delete an utterance by ID.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
id: The ID (id) of the utterance to delete.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
True if the utterance was deleted, False if not found.
|
|
207
|
+
"""
|
|
208
|
+
async with self.sqlite.session() as session:
|
|
209
|
+
stmt = sqlm.select(UtteranceTable).where(UtteranceTable.id == id)
|
|
210
|
+
result = await session.execute(stmt)
|
|
211
|
+
utterance_obj = result.scalar_one_or_none()
|
|
212
|
+
|
|
213
|
+
if utterance_obj is None:
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
await session.delete(utterance_obj)
|
|
217
|
+
await session.commit()
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
async def delete_many(
|
|
221
|
+
self,
|
|
222
|
+
arg: builtins.list[str] | t.Optional[Filter] = None, # noqa
|
|
223
|
+
) -> builtins.list[str]:
|
|
224
|
+
"""Delete multiple utterances by IDs or matching a filter.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
arg: Either a list of IDs to delete, or an optional filter.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
If deleting by IDs, returns list of deleted IDs.
|
|
231
|
+
If deleting by filter, returns count of deleted records.
|
|
232
|
+
"""
|
|
233
|
+
async with self.sqlite.session() as session:
|
|
234
|
+
if isinstance(arg, list):
|
|
235
|
+
if not arg:
|
|
236
|
+
return []
|
|
237
|
+
|
|
238
|
+
stmt = sqlm.select(UtteranceTable).where(sqlm.col(UtteranceTable.id).in_(arg))
|
|
239
|
+
result = await session.execute(stmt)
|
|
240
|
+
utterance_objs = result.scalars().all()
|
|
241
|
+
|
|
242
|
+
if not utterance_objs:
|
|
243
|
+
return []
|
|
244
|
+
|
|
245
|
+
utterance_ids = [obj.id for obj in utterance_objs]
|
|
246
|
+
for obj in utterance_objs:
|
|
247
|
+
await session.delete(obj)
|
|
248
|
+
|
|
249
|
+
await session.commit()
|
|
250
|
+
return utterance_ids
|
|
251
|
+
|
|
252
|
+
spec = self.build_query_spec(arg)
|
|
253
|
+
stmt = sqlm.select(UtteranceTable.id) # type: ignore
|
|
254
|
+
for clause in spec.where:
|
|
255
|
+
stmt = stmt.where(clause)
|
|
256
|
+
|
|
257
|
+
result = await session.execute(stmt)
|
|
258
|
+
utterance_ids = [row[0] for row in result.all()]
|
|
259
|
+
|
|
260
|
+
if not utterance_ids:
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
delete_stmt = sa.delete(UtteranceTable).where(
|
|
264
|
+
sqlm.col(UtteranceTable.id).in_(utterance_ids)
|
|
265
|
+
)
|
|
266
|
+
await session.execute(delete_stmt)
|
|
267
|
+
await session.commit()
|
|
268
|
+
return utterance_ids
|
|
269
|
+
|
|
270
|
+
async def count(self, filter: t.Optional[Filter] = None) -> int: # noqa
|
|
271
|
+
"""Count utterances matching the filter.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
filter: Optional filter to apply. If None, counts all utterances.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Number of utterances matching the filter.
|
|
278
|
+
"""
|
|
279
|
+
spec = self.build_query_spec(filter)
|
|
280
|
+
|
|
281
|
+
async with self.sqlite.session() as session:
|
|
282
|
+
stmt = sqlm.select(sa.func.count()).select_from(UtteranceTable)
|
|
283
|
+
|
|
284
|
+
for clause in spec.where:
|
|
285
|
+
stmt = stmt.where(clause)
|
|
286
|
+
|
|
287
|
+
result = await session.execute(stmt)
|
|
288
|
+
return result.scalar_one()
|