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.
Files changed (192) hide show
  1. audex/__init__.py +9 -0
  2. audex/__main__.py +7 -0
  3. audex/cli/__init__.py +189 -0
  4. audex/cli/apis/__init__.py +12 -0
  5. audex/cli/apis/init/__init__.py +34 -0
  6. audex/cli/apis/init/gencfg.py +130 -0
  7. audex/cli/apis/init/setup.py +330 -0
  8. audex/cli/apis/init/vprgroup.py +125 -0
  9. audex/cli/apis/serve.py +141 -0
  10. audex/cli/args.py +356 -0
  11. audex/cli/exceptions.py +44 -0
  12. audex/cli/helper/__init__.py +0 -0
  13. audex/cli/helper/ansi.py +193 -0
  14. audex/cli/helper/display.py +288 -0
  15. audex/config/__init__.py +64 -0
  16. audex/config/core/__init__.py +30 -0
  17. audex/config/core/app.py +29 -0
  18. audex/config/core/audio.py +45 -0
  19. audex/config/core/logging.py +163 -0
  20. audex/config/core/session.py +11 -0
  21. audex/config/helper/__init__.py +1 -0
  22. audex/config/helper/client/__init__.py +1 -0
  23. audex/config/helper/client/http.py +28 -0
  24. audex/config/helper/client/websocket.py +21 -0
  25. audex/config/helper/provider/__init__.py +1 -0
  26. audex/config/helper/provider/dashscope.py +13 -0
  27. audex/config/helper/provider/unisound.py +18 -0
  28. audex/config/helper/provider/xfyun.py +23 -0
  29. audex/config/infrastructure/__init__.py +31 -0
  30. audex/config/infrastructure/cache.py +51 -0
  31. audex/config/infrastructure/database.py +48 -0
  32. audex/config/infrastructure/recorder.py +32 -0
  33. audex/config/infrastructure/store.py +19 -0
  34. audex/config/provider/__init__.py +18 -0
  35. audex/config/provider/transcription.py +109 -0
  36. audex/config/provider/vpr.py +99 -0
  37. audex/container.py +40 -0
  38. audex/entity/__init__.py +468 -0
  39. audex/entity/doctor.py +109 -0
  40. audex/entity/doctor.pyi +51 -0
  41. audex/entity/fields.py +401 -0
  42. audex/entity/segment.py +115 -0
  43. audex/entity/segment.pyi +38 -0
  44. audex/entity/session.py +133 -0
  45. audex/entity/session.pyi +47 -0
  46. audex/entity/utterance.py +142 -0
  47. audex/entity/utterance.pyi +48 -0
  48. audex/entity/vp.py +68 -0
  49. audex/entity/vp.pyi +35 -0
  50. audex/exceptions.py +157 -0
  51. audex/filters/__init__.py +692 -0
  52. audex/filters/generated/__init__.py +21 -0
  53. audex/filters/generated/doctor.py +987 -0
  54. audex/filters/generated/segment.py +723 -0
  55. audex/filters/generated/session.py +978 -0
  56. audex/filters/generated/utterance.py +939 -0
  57. audex/filters/generated/vp.py +815 -0
  58. audex/helper/__init__.py +1 -0
  59. audex/helper/hash.py +33 -0
  60. audex/helper/mixin.py +65 -0
  61. audex/helper/net.py +19 -0
  62. audex/helper/settings/__init__.py +830 -0
  63. audex/helper/settings/fields.py +317 -0
  64. audex/helper/stream.py +153 -0
  65. audex/injectors/__init__.py +1 -0
  66. audex/injectors/config.py +12 -0
  67. audex/injectors/lifespan.py +7 -0
  68. audex/lib/__init__.py +1 -0
  69. audex/lib/cache/__init__.py +383 -0
  70. audex/lib/cache/inmemory.py +513 -0
  71. audex/lib/database/__init__.py +83 -0
  72. audex/lib/database/sqlite.py +406 -0
  73. audex/lib/exporter.py +189 -0
  74. audex/lib/injectors/__init__.py +1 -0
  75. audex/lib/injectors/cache.py +25 -0
  76. audex/lib/injectors/container.py +47 -0
  77. audex/lib/injectors/exporter.py +26 -0
  78. audex/lib/injectors/recorder.py +33 -0
  79. audex/lib/injectors/server.py +17 -0
  80. audex/lib/injectors/session.py +18 -0
  81. audex/lib/injectors/sqlite.py +24 -0
  82. audex/lib/injectors/store.py +13 -0
  83. audex/lib/injectors/transcription.py +42 -0
  84. audex/lib/injectors/usb.py +12 -0
  85. audex/lib/injectors/vpr.py +65 -0
  86. audex/lib/injectors/wifi.py +7 -0
  87. audex/lib/recorder.py +844 -0
  88. audex/lib/repos/__init__.py +149 -0
  89. audex/lib/repos/container.py +23 -0
  90. audex/lib/repos/database/__init__.py +1 -0
  91. audex/lib/repos/database/sqlite.py +672 -0
  92. audex/lib/repos/decorators.py +74 -0
  93. audex/lib/repos/doctor.py +286 -0
  94. audex/lib/repos/segment.py +302 -0
  95. audex/lib/repos/session.py +285 -0
  96. audex/lib/repos/tables/__init__.py +70 -0
  97. audex/lib/repos/tables/doctor.py +137 -0
  98. audex/lib/repos/tables/segment.py +113 -0
  99. audex/lib/repos/tables/session.py +140 -0
  100. audex/lib/repos/tables/utterance.py +131 -0
  101. audex/lib/repos/tables/vp.py +102 -0
  102. audex/lib/repos/utterance.py +288 -0
  103. audex/lib/repos/vp.py +286 -0
  104. audex/lib/restful.py +251 -0
  105. audex/lib/server/__init__.py +97 -0
  106. audex/lib/server/auth.py +98 -0
  107. audex/lib/server/handlers.py +248 -0
  108. audex/lib/server/templates/index.html.j2 +226 -0
  109. audex/lib/server/templates/login.html.j2 +111 -0
  110. audex/lib/server/templates/static/script.js +68 -0
  111. audex/lib/server/templates/static/style.css +579 -0
  112. audex/lib/server/types.py +123 -0
  113. audex/lib/session.py +503 -0
  114. audex/lib/store/__init__.py +238 -0
  115. audex/lib/store/localfile.py +411 -0
  116. audex/lib/transcription/__init__.py +33 -0
  117. audex/lib/transcription/dashscope.py +525 -0
  118. audex/lib/transcription/events.py +62 -0
  119. audex/lib/usb.py +554 -0
  120. audex/lib/vpr/__init__.py +38 -0
  121. audex/lib/vpr/unisound/__init__.py +185 -0
  122. audex/lib/vpr/unisound/types.py +469 -0
  123. audex/lib/vpr/xfyun/__init__.py +483 -0
  124. audex/lib/vpr/xfyun/types.py +679 -0
  125. audex/lib/websocket/__init__.py +8 -0
  126. audex/lib/websocket/connection.py +485 -0
  127. audex/lib/websocket/pool.py +991 -0
  128. audex/lib/wifi.py +1146 -0
  129. audex/lifespan.py +75 -0
  130. audex/service/__init__.py +27 -0
  131. audex/service/decorators.py +73 -0
  132. audex/service/doctor/__init__.py +652 -0
  133. audex/service/doctor/const.py +36 -0
  134. audex/service/doctor/exceptions.py +96 -0
  135. audex/service/doctor/types.py +54 -0
  136. audex/service/export/__init__.py +236 -0
  137. audex/service/export/const.py +17 -0
  138. audex/service/export/exceptions.py +34 -0
  139. audex/service/export/types.py +21 -0
  140. audex/service/injectors/__init__.py +1 -0
  141. audex/service/injectors/container.py +53 -0
  142. audex/service/injectors/doctor.py +34 -0
  143. audex/service/injectors/export.py +27 -0
  144. audex/service/injectors/session.py +49 -0
  145. audex/service/session/__init__.py +754 -0
  146. audex/service/session/const.py +34 -0
  147. audex/service/session/exceptions.py +67 -0
  148. audex/service/session/types.py +91 -0
  149. audex/types.py +39 -0
  150. audex/utils.py +287 -0
  151. audex/valueobj/__init__.py +81 -0
  152. audex/valueobj/common/__init__.py +1 -0
  153. audex/valueobj/common/auth.py +84 -0
  154. audex/valueobj/common/email.py +16 -0
  155. audex/valueobj/common/ops.py +22 -0
  156. audex/valueobj/common/phone.py +84 -0
  157. audex/valueobj/common/version.py +72 -0
  158. audex/valueobj/session.py +19 -0
  159. audex/valueobj/utterance.py +15 -0
  160. audex/view/__init__.py +51 -0
  161. audex/view/container.py +17 -0
  162. audex/view/decorators.py +303 -0
  163. audex/view/pages/__init__.py +1 -0
  164. audex/view/pages/dashboard/__init__.py +286 -0
  165. audex/view/pages/dashboard/wifi.py +407 -0
  166. audex/view/pages/login.py +110 -0
  167. audex/view/pages/recording.py +348 -0
  168. audex/view/pages/register.py +202 -0
  169. audex/view/pages/sessions/__init__.py +196 -0
  170. audex/view/pages/sessions/details.py +224 -0
  171. audex/view/pages/sessions/export.py +443 -0
  172. audex/view/pages/settings.py +374 -0
  173. audex/view/pages/voiceprint/__init__.py +1 -0
  174. audex/view/pages/voiceprint/enroll.py +195 -0
  175. audex/view/pages/voiceprint/update.py +195 -0
  176. audex/view/static/css/dashboard.css +452 -0
  177. audex/view/static/css/glass.css +22 -0
  178. audex/view/static/css/global.css +541 -0
  179. audex/view/static/css/login.css +386 -0
  180. audex/view/static/css/recording.css +439 -0
  181. audex/view/static/css/register.css +293 -0
  182. audex/view/static/css/sessions/styles.css +501 -0
  183. audex/view/static/css/settings.css +186 -0
  184. audex/view/static/css/voiceprint/enroll.css +43 -0
  185. audex/view/static/css/voiceprint/styles.css +209 -0
  186. audex/view/static/css/voiceprint/update.css +44 -0
  187. audex/view/static/images/logo.svg +95 -0
  188. audex/view/static/js/recording.js +42 -0
  189. audex-1.0.7a3.dist-info/METADATA +361 -0
  190. audex-1.0.7a3.dist-info/RECORD +192 -0
  191. audex-1.0.7a3.dist-info/WHEEL +4 -0
  192. audex-1.0.7a3.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from audex.exceptions import AudexError
6
+ from audex.exceptions import InternalError
7
+
8
+
9
+ class DoctorServiceError(AudexError):
10
+ """Base exception for doctor service errors."""
11
+
12
+ default_message = "Doctor service error"
13
+ code: t.ClassVar[int] = 0x30
14
+
15
+
16
+ class InternalDoctorServiceError(InternalError):
17
+ """Internal error in doctor service."""
18
+
19
+ default_message = "Internal doctor service error"
20
+ code: t.ClassVar[int] = 0x31
21
+
22
+
23
+ class DoctorNotFoundError(DoctorServiceError):
24
+ """Raised when doctor is not found."""
25
+
26
+ __slots__ = ("doctor_id", "message")
27
+
28
+ default_message = "Doctor not found"
29
+ code: t.ClassVar[int] = 0x32
30
+
31
+ def __init__(self, message: str, *, doctor_id: str) -> None:
32
+ """Initialize the exception.
33
+
34
+ Args:
35
+ message: Error message (typically in Chinese for users).
36
+ doctor_id: The ID of the doctor that was not found.
37
+ """
38
+ self.doctor_id = doctor_id
39
+ super().__init__(message)
40
+
41
+
42
+ class InvalidCredentialsError(DoctorServiceError):
43
+ """Raised for invalid login credentials."""
44
+
45
+ __slots__ = ("message", "reason")
46
+
47
+ default_message = "Invalid credentials"
48
+ code: t.ClassVar[int] = 0x33
49
+
50
+ def __init__(self, message: str, *, reason: str | None = None) -> None:
51
+ """Initialize the exception.
52
+
53
+ Args:
54
+ message: Error message (typically in Chinese for users).
55
+ reason: Machine-readable reason code (from InvalidCredentialReasons).
56
+ """
57
+ self.reason = reason
58
+ super().__init__(message)
59
+
60
+
61
+ class VoiceprintNotFoundError(DoctorServiceError):
62
+ """Raised when voiceprint is not found."""
63
+
64
+ __slots__ = ("doctor_id", "message")
65
+
66
+ default_message = "Voiceprint not found"
67
+ code: t.ClassVar[int] = 0x34
68
+
69
+ def __init__(self, message: str, *, doctor_id: str) -> None:
70
+ """Initialize the exception.
71
+
72
+ Args:
73
+ message: Error message (typically in Chinese for users).
74
+ doctor_id: The ID of the doctor whose voiceprint was not found.
75
+ """
76
+ self.doctor_id = doctor_id
77
+ super().__init__(message)
78
+
79
+
80
+ class DuplicateEIDError(DoctorServiceError):
81
+ """Raised when trying to register with an existing EID."""
82
+
83
+ __slots__ = ("eid", "message")
84
+
85
+ default_message = "Duplicate EID"
86
+ code: t.ClassVar[int] = 0x35
87
+
88
+ def __init__(self, message: str, *, eid: str) -> None:
89
+ """Initialize the exception.
90
+
91
+ Args:
92
+ message: Error message (typically in Chinese for users).
93
+ eid: The duplicate employee ID.
94
+ """
95
+ self.eid = eid
96
+ super().__init__(message)
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from audex.valueobj.common.auth import Password
6
+ from audex.valueobj.common.email import Email
7
+ from audex.valueobj.common.phone import CNPhone
8
+
9
+
10
+ class LoginCommand(t.NamedTuple):
11
+ """Command for doctor login."""
12
+
13
+ eid: str
14
+ password: Password
15
+
16
+
17
+ class RegisterCommand(t.NamedTuple):
18
+ """Command for doctor registration."""
19
+
20
+ eid: str
21
+ password: Password
22
+ name: str
23
+ department: str | None = None
24
+ title: str | None = None
25
+ hospital: str | None = None
26
+ phone: CNPhone | None = None
27
+ email: Email | None = None
28
+
29
+
30
+ class UpdateCommand(t.NamedTuple):
31
+ """Command for updating doctor profile."""
32
+
33
+ name: str | None = None
34
+ department: str | None = None
35
+ title: str | None = None
36
+ hospital: str | None = None
37
+ phone: CNPhone | None = None
38
+ email: Email | None = None
39
+
40
+
41
+ class VPEnrollResult(t.NamedTuple):
42
+ """Result of voiceprint enrollment/update.
43
+
44
+ Attributes:
45
+ vp_id: ID of the VP entity.
46
+ vpr_uid: UID in the VPR system.
47
+ audio_key: Storage key of the audio file.
48
+ duration_ms: Duration of the recording in milliseconds.
49
+ """
50
+
51
+ vp_id: str
52
+ vpr_uid: str
53
+ audio_key: str
54
+ duration_ms: int
@@ -0,0 +1,236 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextlib
5
+
6
+ from audex.helper import net
7
+ from audex.lib.cache import KVCache
8
+ from audex.lib.exporter import Exporter
9
+ from audex.lib.repos.doctor import DoctorRepository
10
+ from audex.lib.server import Server
11
+ from audex.lib.session import SessionManager
12
+ from audex.lib.usb import USBDevice
13
+ from audex.lib.usb import USBManager
14
+ from audex.service import BaseService
15
+ from audex.service.decorators import require_auth
16
+ from audex.service.export.const import ErrorMessages
17
+ from audex.service.export.exceptions import ExportServiceError
18
+ from audex.service.export.exceptions import InternalExportServiceError
19
+ from audex.service.export.exceptions import NoUSBDeviceError
20
+ from audex.service.export.exceptions import ServerAlreadyRunningError
21
+ from audex.service.export.types import ExportResult
22
+ from audex.service.export.types import ServerInfo
23
+
24
+
25
+ class ExportService(BaseService):
26
+ """Service for exporting session data via HTTP or USB."""
27
+
28
+ __logtag__ = "audex.service.export"
29
+
30
+ def __init__(
31
+ self,
32
+ session_manager: SessionManager,
33
+ cache: KVCache,
34
+ doctor_repo: DoctorRepository,
35
+ usb: USBManager,
36
+ exporter: Exporter,
37
+ server: Server,
38
+ ):
39
+ super().__init__(session_manager=session_manager, cache=cache, doctor_repo=doctor_repo)
40
+ self.usb = usb
41
+ self.exporter = exporter
42
+ self.server = server
43
+ self._server_task: asyncio.Task[None] | None = None
44
+ self._server_running = False
45
+
46
+ @require_auth
47
+ async def start_server(self) -> ServerInfo:
48
+ """Start HTTP export server.
49
+
50
+ Returns:
51
+ ServerInfo with host and port.
52
+
53
+ Raises:
54
+ ServerAlreadyRunningError: If server is already running.
55
+ InternalExportServiceError: For internal errors.
56
+ """
57
+ try:
58
+ if self._server_running:
59
+ raise ServerAlreadyRunningError(ErrorMessages.SERVER_ALREADY_RUNNING)
60
+
61
+ # Start server in background task
62
+ addr = net.getaddr()
63
+ port = net.getfreeport()
64
+ self._server_task = asyncio.create_task(self.server.start(host="0.0.0.0", port=port))
65
+ self._server_running = True
66
+
67
+ # Get server info
68
+ server_info = ServerInfo(
69
+ host=addr,
70
+ port=port,
71
+ url=f"http://{addr}:{port}",
72
+ )
73
+
74
+ self.logger.info(f"Started export server at {server_info.url}")
75
+ return server_info
76
+
77
+ except ServerAlreadyRunningError:
78
+ raise
79
+
80
+ except Exception as e:
81
+ self.logger.error(f"Failed to start server: {e}")
82
+ raise InternalExportServiceError(ErrorMessages.SERVER_START_FAILED) from e
83
+
84
+ @require_auth
85
+ async def stop_server(self) -> None:
86
+ """Stop HTTP export server.
87
+
88
+ Raises:
89
+ InternalExportServiceError: For internal errors.
90
+ """
91
+ try:
92
+ if not self._server_running:
93
+ self.logger.warning("Server is not running")
94
+ return
95
+
96
+ # Stop server
97
+ await self.server.close()
98
+
99
+ # Cancel task if exists
100
+ if self._server_task:
101
+ self._server_task.cancel()
102
+ with contextlib.suppress(asyncio.CancelledError):
103
+ await self._server_task
104
+ self._server_task = None
105
+
106
+ self._server_running = False
107
+ self.logger.info("Stopped export server")
108
+
109
+ except Exception as e:
110
+ self.logger.error(f"Failed to stop server: {e}")
111
+ raise InternalExportServiceError(ErrorMessages.SERVER_STOP_FAILED) from e
112
+
113
+ @require_auth
114
+ async def is_server_running(self) -> bool:
115
+ """Check if HTTP export server is running."""
116
+ return self._server_running
117
+
118
+ @require_auth
119
+ async def list_usb_devices(self) -> list[USBDevice]:
120
+ """List all connected USB storage devices.
121
+
122
+ Returns:
123
+ List of connected USB devices.
124
+ """
125
+ try:
126
+ devices = self.usb.list_devices()
127
+ self.logger.debug(f"Found {len(devices)} USB device(s)")
128
+ return devices
129
+ except Exception as e:
130
+ self.logger.error(f"Failed to list USB devices: {e}")
131
+ return []
132
+
133
+ @require_auth
134
+ async def export_to_usb(
135
+ self,
136
+ session_ids: list[str],
137
+ device: USBDevice | None = None,
138
+ ) -> ExportResult:
139
+ """Export sessions to USB device.
140
+
141
+ Args:
142
+ session_ids: List of session IDs to export.
143
+ device: Target USB device. If None, uses first available device.
144
+
145
+ Returns:
146
+ ExportResult with success status and details.
147
+
148
+ Raises:
149
+ NoUSBDeviceError: If no USB device is available.
150
+ InternalExportServiceError: For internal errors.
151
+ """
152
+ try:
153
+ # Get USB device
154
+ if device is None:
155
+ devices = await self.list_usb_devices()
156
+ if not devices:
157
+ raise NoUSBDeviceError(ErrorMessages.NO_USB_DEVICE)
158
+ device = devices[0]
159
+ self.logger.info(f"Using first USB device: {device.mount_point}")
160
+
161
+ # Verify doctor owns all sessions
162
+ session = await self.session_manager.get_session()
163
+ if not session:
164
+ raise ExportServiceError(ErrorMessages.NO_ACTIVE_SESSION)
165
+
166
+ for session_id in session_ids:
167
+ sess = await self.exporter.session_repo.read(session_id)
168
+ if not sess or sess.doctor_id != session.doctor_id:
169
+ raise ExportServiceError(
170
+ f"无权访问会话 {session_id}",
171
+ )
172
+
173
+ # Export each session
174
+ export_tasks = []
175
+
176
+ for session_id in session_ids:
177
+ # Generate ZIP
178
+ zip_data = await self.exporter.export_session_zip(session_id)
179
+
180
+ # Get session info for filename
181
+ sess = await self.exporter.session_repo.read(session_id)
182
+ filename = f"{session_id}"
183
+ if sess and sess.patient_name:
184
+ filename = f"{sess.patient_name}_{session_id}"
185
+ filename += ".zip"
186
+
187
+ # Write to temp file
188
+ import pathlib
189
+ import tempfile
190
+
191
+ temp_path = pathlib.Path(tempfile.mkdtemp()) / filename
192
+ temp_path.write_bytes(zip_data)
193
+
194
+ # Add export task
195
+ self.usb.add_export_task(
196
+ source=temp_path,
197
+ dest_name=f"audex_export/{filename}",
198
+ is_directory=False,
199
+ )
200
+ export_tasks.append((session_id, temp_path))
201
+
202
+ # Export to USB
203
+ results = await self.usb.export(device)
204
+
205
+ # Clean up temp files
206
+ for _, temp_path in export_tasks:
207
+ try:
208
+ temp_path.unlink()
209
+ temp_path.parent.rmdir()
210
+ except Exception:
211
+ pass
212
+
213
+ # Clear export tasks
214
+ self.usb.clear_export_tasks()
215
+
216
+ # Check results
217
+ success_count = sum(1 for success in results.values() if success)
218
+ total_count = len(results)
219
+
220
+ export_result = ExportResult(
221
+ success=success_count == total_count,
222
+ total=total_count,
223
+ success_count=success_count,
224
+ failed_count=total_count - success_count,
225
+ device_label=device.label or device.mount_point,
226
+ )
227
+
228
+ self.logger.info(f"USB export completed: {success_count}/{total_count} successful")
229
+
230
+ return export_result
231
+
232
+ except (NoUSBDeviceError, ExportServiceError):
233
+ raise
234
+ except Exception as e:
235
+ self.logger.error(f"USB export failed: {e}")
236
+ raise InternalExportServiceError(ErrorMessages.USB_EXPORT_FAILED) from e
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class ErrorMessages:
5
+ """Error messages in Chinese for export service."""
6
+
7
+ # Server errors
8
+ SERVER_ALREADY_RUNNING = "服务器已在运行"
9
+ SERVER_START_FAILED = "启动服务器失败"
10
+ SERVER_STOP_FAILED = "停止服务器失败"
11
+
12
+ # USB errors
13
+ NO_USB_DEVICE = "未检测到U盘,请插入U盘后重试"
14
+ USB_EXPORT_FAILED = "导出到U盘失败"
15
+
16
+ # Session errors
17
+ NO_ACTIVE_SESSION = "登录状态已过期,请重新登录"
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from audex.exceptions import AudexError
6
+ from audex.exceptions import InternalError
7
+
8
+
9
+ class ExportServiceError(AudexError):
10
+ """Base exception for export service errors."""
11
+
12
+ default_message = "Export service error"
13
+ code: t.ClassVar[int] = 0x50
14
+
15
+
16
+ class InternalExportServiceError(InternalError):
17
+ """Internal error in export service."""
18
+
19
+ default_message = "Internal export service error"
20
+ code: t.ClassVar[int] = 0x51
21
+
22
+
23
+ class NoUSBDeviceError(ExportServiceError):
24
+ """Raised when no USB device is available."""
25
+
26
+ default_message = "No USB device available"
27
+ code: t.ClassVar[int] = 0x52
28
+
29
+
30
+ class ServerAlreadyRunningError(ExportServiceError):
31
+ """Raised when server is already running."""
32
+
33
+ default_message = "Server already running"
34
+ code: t.ClassVar[int] = 0x53
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+
6
+ class ServerInfo(t.NamedTuple):
7
+ """HTTP server information."""
8
+
9
+ host: str
10
+ port: int
11
+ url: str
12
+
13
+
14
+ class ExportResult(t.NamedTuple):
15
+ """Result of USB export operation."""
16
+
17
+ success: bool
18
+ total: int
19
+ success_count: int
20
+ failed_count: int
21
+ device_label: str
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from dependency_injector import containers
4
+ from dependency_injector import providers
5
+
6
+ from audex.config import Config
7
+ from audex.service.injectors.doctor import make_doctor_service
8
+ from audex.service.injectors.export import make_export_service
9
+ from audex.service.injectors.session import make_session_service
10
+
11
+
12
+ class ServiceContainer(containers.DeclarativeContainer):
13
+ # Dependencies
14
+ config = providers.Dependency(instance_of=Config)
15
+ infrastructure = providers.DependenciesContainer()
16
+ repository = providers.DependenciesContainer()
17
+
18
+ # Components
19
+ doctor = providers.Factory(
20
+ make_doctor_service,
21
+ session_manager=infrastructure.session_manager,
22
+ cache=infrastructure.cache,
23
+ config=config,
24
+ doctor_repo=repository.doctor,
25
+ vp_repo=repository.vp,
26
+ vpr=infrastructure.vpr,
27
+ recorder=infrastructure.recorder,
28
+ )
29
+
30
+ session = providers.Factory(
31
+ make_session_service,
32
+ session_manager=infrastructure.session_manager,
33
+ cache=infrastructure.cache,
34
+ config=config,
35
+ doctor_repo=repository.doctor,
36
+ session_repo=repository.session,
37
+ segment_repo=repository.segment,
38
+ utterance_repo=repository.utterance,
39
+ vp_repo=repository.vp,
40
+ vpr=infrastructure.vpr,
41
+ transcription=infrastructure.transcription,
42
+ recorder=infrastructure.recorder,
43
+ )
44
+
45
+ export = providers.Factory(
46
+ make_export_service,
47
+ session_manager=infrastructure.session_manager,
48
+ cache=infrastructure.cache,
49
+ doctor_repo=repository.doctor,
50
+ usb=infrastructure.usb,
51
+ exporter=infrastructure.exporter,
52
+ server=infrastructure.server,
53
+ )
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from audex.config import Config
4
+ from audex.lib.cache import KVCache
5
+ from audex.lib.recorder import AudioRecorder
6
+ from audex.lib.repos.doctor import DoctorRepository
7
+ from audex.lib.repos.vp import VPRepository
8
+ from audex.lib.session import SessionManager
9
+ from audex.lib.vpr import VPR
10
+ from audex.service.doctor import DoctorService
11
+ from audex.service.doctor import DoctorServiceConfig
12
+
13
+
14
+ def make_doctor_service(
15
+ session_manager: SessionManager,
16
+ cache: KVCache,
17
+ config: Config,
18
+ doctor_repo: DoctorRepository,
19
+ vp_repo: VPRepository,
20
+ vpr: VPR,
21
+ recorder: AudioRecorder,
22
+ ) -> DoctorService:
23
+ return DoctorService(
24
+ session_manager=session_manager,
25
+ cache=cache,
26
+ config=DoctorServiceConfig(
27
+ vpr_sr=config.core.audio.vpr_sample_rate,
28
+ vpr_text_content=config.core.audio.vpr_text_content,
29
+ ),
30
+ doctor_repo=doctor_repo,
31
+ vp_repo=vp_repo,
32
+ vpr=vpr,
33
+ recorder=recorder,
34
+ )
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from audex.lib.cache import KVCache
4
+ from audex.lib.exporter import Exporter
5
+ from audex.lib.repos.doctor import DoctorRepository
6
+ from audex.lib.server import Server
7
+ from audex.lib.session import SessionManager
8
+ from audex.lib.usb import USBManager
9
+ from audex.service.export import ExportService
10
+
11
+
12
+ def make_export_service(
13
+ session_manager: SessionManager,
14
+ cache: KVCache,
15
+ doctor_repo: DoctorRepository,
16
+ usb: USBManager,
17
+ exporter: Exporter,
18
+ server: Server,
19
+ ) -> ExportService:
20
+ return ExportService(
21
+ session_manager=session_manager,
22
+ cache=cache,
23
+ doctor_repo=doctor_repo,
24
+ usb=usb,
25
+ exporter=exporter,
26
+ server=server,
27
+ )
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from audex.config import Config
4
+ from audex.lib.cache import KVCache
5
+ from audex.lib.recorder import AudioRecorder
6
+ from audex.lib.repos.doctor import DoctorRepository
7
+ from audex.lib.repos.segment import SegmentRepository
8
+ from audex.lib.repos.session import SessionRepository
9
+ from audex.lib.repos.utterance import UtteranceRepository
10
+ from audex.lib.repos.vp import VPRepository
11
+ from audex.lib.session import SessionManager
12
+ from audex.lib.transcription import Transcription
13
+ from audex.lib.vpr import VPR
14
+ from audex.service.session import SessionService
15
+ from audex.service.session import SessionServiceConfig
16
+
17
+
18
+ def make_session_service(
19
+ session_manager: SessionManager,
20
+ cache: KVCache,
21
+ config: Config,
22
+ doctor_repo: DoctorRepository,
23
+ session_repo: SessionRepository,
24
+ segment_repo: SegmentRepository,
25
+ utterance_repo: UtteranceRepository,
26
+ vp_repo: VPRepository,
27
+ vpr: VPR,
28
+ transcription: Transcription,
29
+ recorder: AudioRecorder,
30
+ ) -> SessionService:
31
+ return SessionService(
32
+ session_manager=session_manager,
33
+ cache=cache,
34
+ config=SessionServiceConfig(
35
+ audio_key_prefix=config.core.audio.key_prefix,
36
+ segment_buffer_ms=config.core.audio.segment_buffer,
37
+ sr=config.core.audio.sample_rate,
38
+ vpr_sr=config.core.audio.vpr_sample_rate,
39
+ vpr_threshold=config.core.audio.vpr_threshold,
40
+ ),
41
+ doctor_repo=doctor_repo,
42
+ session_repo=session_repo,
43
+ segment_repo=segment_repo,
44
+ utterance_repo=utterance_repo,
45
+ vp_repo=vp_repo,
46
+ vpr=vpr,
47
+ transcription=transcription,
48
+ recorder=recorder,
49
+ )