mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 20:08:24 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5b79afde1 | |||
| 052d3c1b22 | |||
| dff5aa9575 | |||
| ded93bf24f | |||
| 5ba1885411 | |||
| 273af221e6 | |||
| e8d6aba306 | |||
| 449503208a | |||
| 7eb3553bea | |||
| f0356af365 | |||
| 9921ef9d09 | |||
| 68e999a037 | |||
| 34103bf326 | |||
| e273ac9e43 |
@@ -10,6 +10,26 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(summary) report exception type in failure analytics
|
||||
|
||||
## Fixed
|
||||
|
||||
- 🐛(transcription) fix silent bug in speaker assignment
|
||||
- 🐛(summary) extend tasks auto retry logic
|
||||
- 🐛(summary) properly detect when failure webhook should be sent
|
||||
|
||||
### Changed
|
||||
|
||||
- ⬆️(frontend) upgrade @mediapipe/tasks-vision from 0.10.14 to 0.10.35
|
||||
- ⬆️(frontend) upgrade i18next from 26.3.1 to 26.3.2
|
||||
- ⬆️(frontend) upgrade posthog-js from 1.391.2 to 1.395.0
|
||||
- ⬆️(frontend) upgrade @tanstack/react-query from 5.101.0 to 5.101.1
|
||||
- ⬆️(frontend) upgrade livekit-client from 2.19.2 to 2.20.0
|
||||
|
||||
## [1.24.0] - 2026-07-21
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(backend) allow searching the recording admin table by owner email
|
||||
- ✨(frontend) add participant color gradient when camera is off #1490
|
||||
- ✨(all) allow forcing SSO display name for authenticated users
|
||||
|
||||
@@ -164,6 +164,7 @@ echo " - src/backend/pyproject.toml"
|
||||
echo " - src/backend/uv.lock"
|
||||
echo " - src/summary/pyproject.toml"
|
||||
echo " - src/agents/pyproject.toml"
|
||||
echo " - src/agents/uv.lock"
|
||||
echo " - CHANGELOG.md"
|
||||
echo ""
|
||||
print_warning "Next steps:"
|
||||
|
||||
Generated
+4
-4
@@ -10,7 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js": "3.49.0",
|
||||
"i18next": "26.3.1",
|
||||
"i18next": "^26.3.2",
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"regenerator-runtime": "0.14.1"
|
||||
},
|
||||
@@ -9364,9 +9364,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "26.3.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz",
|
||||
"integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==",
|
||||
"version": "26.3.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.2.tgz",
|
||||
"integrity": "sha512-QQkXAM1sPDHqhxMQuBeHVMUn6mJchF+wdpOoQerciLAFqO3ZYdxO0EUbeEhruyutnNwpUQIITDVzLjwnNL0T1w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "3.49.0",
|
||||
"i18next": "26.3.1",
|
||||
"i18next": "26.3.2",
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"regenerator-runtime": "0.14.1"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "agents"
|
||||
version = "1.23.0"
|
||||
version = "1.24.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"livekit-agents==1.6.4",
|
||||
|
||||
Generated
+1
-1
@@ -9,7 +9,7 @@ resolution-markers = [
|
||||
|
||||
[[package]]
|
||||
name = "agents"
|
||||
version = "1.23.0"
|
||||
version = "1.24.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "livekit-agents" },
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "uv_build"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "1.23.0"
|
||||
version = "1.24.0"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
Generated
+1
-1
@@ -1187,7 +1187,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "meet"
|
||||
version = "1.23.0"
|
||||
version = "1.24.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
Generated
+32
-26
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meet",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
|
||||
"@fontsource-variable/lexend": "5.2.11",
|
||||
@@ -15,11 +15,11 @@
|
||||
"@livekit/components-react": "2.9.21",
|
||||
"@livekit/components-styles": "1.2.0",
|
||||
"@livekit/track-processors": "0.7.2",
|
||||
"@mediapipe/tasks-vision": "0.10.14",
|
||||
"@mediapipe/tasks-vision": "0.10.35",
|
||||
"@pandacss/preset-panda": "1.11.3",
|
||||
"@react-types/overlays": "3.10.0",
|
||||
"@remixicon/react": "4.9.0",
|
||||
"@tanstack/react-query": "5.101.0",
|
||||
"@tanstack/react-query": "5.101.1",
|
||||
"@timephy/rnnoise-wasm": "1.0.0",
|
||||
"crisp-sdk-web": "1.1.2",
|
||||
"derive-valtio": "0.2.0",
|
||||
@@ -29,8 +29,8 @@
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"i18next-parser": "9.4.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"livekit-client": "2.19.2",
|
||||
"posthog-js": "1.391.2",
|
||||
"livekit-client": "2.20.0",
|
||||
"posthog-js": "1.395.0",
|
||||
"react": "18.3.1",
|
||||
"react-aria": "3.50.0",
|
||||
"react-aria-components": "1.19.0",
|
||||
@@ -1028,9 +1028,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@livekit/protocol": {
|
||||
"version": "1.45.8",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.45.8.tgz",
|
||||
"integrity": "sha512-Q+l57E7w/xxOBFVWzdX5rkAZO7ffyF+rlDzNUYq2SU114+5aTyCq+PK4unaEVDNd4952Af7wteKr3sOgasGuaA==",
|
||||
"version": "1.46.6",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.46.6.tgz",
|
||||
"integrity": "sha512-upzlHP1vi/kZ/QqALZTFskQ0ifqc2f15RKucHYOsIHJsaXvEYanG75mAb7o+Yomfs4XhQ4BaRsdY+TFHXpaqrg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.10.0"
|
||||
@@ -1049,12 +1049,18 @@
|
||||
"livekit-client": "^1.12.0 || ^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mediapipe/tasks-vision": {
|
||||
"node_modules/@livekit/track-processors/node_modules/@mediapipe/tasks-vision": {
|
||||
"version": "0.10.14",
|
||||
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.14.tgz",
|
||||
"integrity": "sha512-vOifgZhkndgybdvoRITzRkIueWWSiCKuEUXXK6Q4FaJsFvRJuwgg++vqFUMlL0Uox62U5aEXFhHxlhV7Ja5e3Q==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@mediapipe/tasks-vision": {
|
||||
"version": "0.10.35",
|
||||
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz",
|
||||
"integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
|
||||
@@ -2415,9 +2421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.101.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz",
|
||||
"integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==",
|
||||
"version": "5.101.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.1.tgz",
|
||||
"integrity": "sha512-Y6Y92dkXtNqx67m2pMSxUsA3zOCwv862JexZRP8/EPwvKXMPu9m8rv43spiXWzOUIggQ3SQApttALStzhA8B4g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2436,12 +2442,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.101.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz",
|
||||
"integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==",
|
||||
"version": "5.101.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.1.tgz",
|
||||
"integrity": "sha512-ZnONUuQKJe1bJMStXUL1s5uKN9FcfC28j5cK+iDZcdSHtUv1wtin1cGc/Oewhf2Oc4eKY7lggtpvT/AbMmhHew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.101.0"
|
||||
"@tanstack/query-core": "5.101.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -8119,13 +8125,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.19.2",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.2.tgz",
|
||||
"integrity": "sha512-Kvk07QYDWRAbmYNLRll04ZIuxMQobW/oLPYnmR1kCy8GGHpU0gqyHf704Rz+29zfy8IJZRjKqeVbzGSKn9sumw==",
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.20.0.tgz",
|
||||
"integrity": "sha512-RIJcpvBmOmwz3jTj3rmdY6Dzr55HrhcaJjMgY+HSmoEM+yIRyA40m7r8UKv0hnZWM3z/AYhP1q8C8ciz5UWFKQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.1.1",
|
||||
"@livekit/protocol": "1.45.8",
|
||||
"@livekit/protocol": "1.46.6",
|
||||
"events": "^3.3.0",
|
||||
"jose": "^6.1.0",
|
||||
"loglevel": "^1.9.2",
|
||||
@@ -9130,13 +9136,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/posthog-js": {
|
||||
"version": "1.391.2",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.391.2.tgz",
|
||||
"integrity": "sha512-q0DZN6ljchSnAFJIXf+sQFTPlsLjTlRa+TvrL+QRb6413BGtib/MNiQy1bnwLKt8KR+f6xJYvkqdLyty9s4Aww==",
|
||||
"version": "1.395.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.395.0.tgz",
|
||||
"integrity": "sha512-5iTb00CGt2eQUUiBQysQiX89RAbCN6wK2sDNzvs9zv0alaY8mJ0ZySrUD3LQ+XyLhgM5pCpacBuUwChqiYDLDw==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@posthog/core": "^1.35.3",
|
||||
"@posthog/types": "^1.390.2",
|
||||
"@posthog/core": "^1.38.0",
|
||||
"@posthog/types": "^1.391.1",
|
||||
"core-js": "^3.38.1",
|
||||
"dompurify": "^3.3.2",
|
||||
"fflate": "^0.4.8",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
@@ -22,11 +22,11 @@
|
||||
"@livekit/components-react": "2.9.21",
|
||||
"@livekit/components-styles": "1.2.0",
|
||||
"@livekit/track-processors": "0.7.2",
|
||||
"@mediapipe/tasks-vision": "0.10.14",
|
||||
"@mediapipe/tasks-vision": "0.10.35",
|
||||
"@pandacss/preset-panda": "1.11.3",
|
||||
"@react-types/overlays": "3.10.0",
|
||||
"@remixicon/react": "4.9.0",
|
||||
"@tanstack/react-query": "5.101.0",
|
||||
"@tanstack/react-query": "5.101.1",
|
||||
"@timephy/rnnoise-wasm": "1.0.0",
|
||||
"crisp-sdk-web": "1.1.2",
|
||||
"derive-valtio": "0.2.0",
|
||||
@@ -36,8 +36,8 @@
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"i18next-parser": "9.4.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"livekit-client": "2.19.2",
|
||||
"posthog-js": "1.391.2",
|
||||
"livekit-client": "2.20.0",
|
||||
"posthog-js": "1.395.0",
|
||||
"react": "18.3.1",
|
||||
"react-aria": "3.50.0",
|
||||
"react-aria-components": "1.19.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: meet
|
||||
version: 0.0.26
|
||||
version: 0.0.27
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mail_mjml",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.6.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sdk",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"./library",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "summary"
|
||||
version = "1.23.0"
|
||||
version = "1.24.0"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.105.0",
|
||||
"uvicorn>=0.24.0",
|
||||
|
||||
@@ -202,13 +202,16 @@ class MetadataManager:
|
||||
},
|
||||
)
|
||||
|
||||
def capture(self, task_id, event_name):
|
||||
def capture(self, task_id, event_name, extra_properties=None):
|
||||
"""Capture analytics event with task metadata and clean up."""
|
||||
if self._is_disabled or not self.has_task_id(task_id):
|
||||
return
|
||||
|
||||
metadata = self._get_metadata(task_id)
|
||||
|
||||
if extra_properties:
|
||||
metadata = {**metadata, **extra_properties}
|
||||
|
||||
if "start_time" in metadata:
|
||||
metadata["execution_time"] = round(time.time() - metadata["start_time"], 2)
|
||||
del metadata["start_time"]
|
||||
|
||||
@@ -431,7 +431,7 @@ def _should_auto_create_summary(payload: TranscribeTaskJob) -> bool:
|
||||
@celery.task(
|
||||
max_retries=3,
|
||||
queue=settings.call_webhook_queue_v2,
|
||||
autoretry_for=[exceptions.HTTPError],
|
||||
autoretry_for=[exceptions.RequestException],
|
||||
)
|
||||
def call_webhook_v2_task(
|
||||
payload: dict,
|
||||
@@ -445,7 +445,9 @@ def call_webhook_v2_task(
|
||||
|
||||
@celery.task(
|
||||
bind=True,
|
||||
autoretry_for=[exceptions.HTTPError],
|
||||
autoretry_for=[
|
||||
exceptions.RequestException,
|
||||
],
|
||||
max_retries=settings.celery_max_retries,
|
||||
queue=settings.transcribe_queue_v2,
|
||||
)
|
||||
@@ -575,12 +577,6 @@ def task_retry_handler_transcript(request=None, reason=None, einfo=None, **kwarg
|
||||
metadata_manager.retry(request.id)
|
||||
|
||||
|
||||
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
|
||||
def task_failure_handler_transcript(task_id, exception=None, **kwargs):
|
||||
"""Signal handler called when task execution fails permanently."""
|
||||
metadata_manager.capture(task_id, settings.posthog_transcript_failure)
|
||||
|
||||
|
||||
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
|
||||
def handle_transcribe_v2_failed(
|
||||
sender,
|
||||
@@ -594,30 +590,26 @@ def handle_transcribe_v2_failed(
|
||||
):
|
||||
"""Handle the failure of transcribe_v2_task.
|
||||
|
||||
This function is triggered when the transcribe_v2_task fails.
|
||||
It sends a webhook failure payload to notify the client of the failure.
|
||||
Tracks the failure event in analytics and sends a failure webhook to the client.
|
||||
"""
|
||||
n_retries_left = sender.max_retries - sender.request.retries - 1
|
||||
if n_retries_left > 0:
|
||||
logger.info(
|
||||
"Transcribe task %s failed, %s retries left.",
|
||||
task_id,
|
||||
n_retries_left,
|
||||
)
|
||||
else:
|
||||
logger.warn(
|
||||
"Transcribe task %s failed, no more retries left, sending failure webhook.",
|
||||
task_id,
|
||||
)
|
||||
call_webhook_v2_task.apply_async(
|
||||
args=[
|
||||
TranscribeWebhookFailurePayload(
|
||||
job_id=task_id,
|
||||
error_code="unknown_error",
|
||||
).model_dump(),
|
||||
args[0]["tenant_id"],
|
||||
]
|
||||
)
|
||||
logger.error(
|
||||
"Transcribe task %s failed, no more retries left, sending failure webhook.",
|
||||
task_id,
|
||||
)
|
||||
metadata_manager.capture(
|
||||
task_id,
|
||||
settings.posthog_transcript_failure,
|
||||
{"exception_type": type(exception).__name__},
|
||||
)
|
||||
call_webhook_v2_task.apply_async(
|
||||
args=[
|
||||
TranscribeWebhookFailurePayload(
|
||||
job_id=task_id,
|
||||
error_code="unknown_error",
|
||||
).model_dump(),
|
||||
args[0]["tenant_id"],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@celery.task(
|
||||
@@ -681,12 +673,6 @@ def task_retry_handler_summary(request=None, reason=None, einfo=None, **kwargs):
|
||||
metadata_manager.retry(request.id)
|
||||
|
||||
|
||||
@signals.task_failure.connect(sender=summarize_v2_task)
|
||||
def task_failure_handler_summary(task_id, exception=None, **kwargs):
|
||||
"""Signal handler called when task execution fails permanently."""
|
||||
metadata_manager.capture(task_id, settings.posthog_summary_failure)
|
||||
|
||||
|
||||
@signals.task_failure.connect(sender=summarize_v2_task)
|
||||
def handle_summarize_v2_failed(
|
||||
sender,
|
||||
@@ -700,27 +686,23 @@ def handle_summarize_v2_failed(
|
||||
):
|
||||
"""Handle the failure of summarize_v2_task.
|
||||
|
||||
This function is triggered when the summarize_v2_task fails.
|
||||
It sends a webhook failure payload to notify the client of the failure.
|
||||
Tracks the failure event in analytics and sends a failure webhook to the client.
|
||||
"""
|
||||
n_retries_left = sender.max_retries - sender.request.retries - 1
|
||||
if n_retries_left > 0:
|
||||
logger.info(
|
||||
"Summary task %s failed, %s retries left.",
|
||||
task_id,
|
||||
n_retries_left,
|
||||
)
|
||||
else:
|
||||
logger.warn(
|
||||
"Summary task %s failed, no more retries left, sending failure webhook.",
|
||||
task_id,
|
||||
)
|
||||
call_webhook_v2_task.apply_async(
|
||||
args=[
|
||||
SummarizeWebhookFailurePayload(
|
||||
job_id=task_id,
|
||||
error_code="unknown_error",
|
||||
).model_dump(),
|
||||
args[0]["tenant_id"],
|
||||
]
|
||||
)
|
||||
logger.warn(
|
||||
"Summary task %s failed, no more retries left, sending failure webhook.",
|
||||
task_id,
|
||||
)
|
||||
metadata_manager.capture(
|
||||
task_id,
|
||||
settings.posthog_summary_failure,
|
||||
{"exception_type": type(exception).__name__},
|
||||
)
|
||||
call_webhook_v2_task.apply_async(
|
||||
args=[
|
||||
SummarizeWebhookFailurePayload(
|
||||
job_id=task_id,
|
||||
error_code="unknown_error",
|
||||
).model_dump(),
|
||||
args[0]["tenant_id"],
|
||||
]
|
||||
)
|
||||
|
||||
@@ -275,7 +275,7 @@ def _build_participant_timelines(
|
||||
def _build_speaker_timelines(transcription: Any) -> dict[str, list[Interval]]:
|
||||
"""Build interval timelines from WhisperX transcription segments."""
|
||||
intervals: dict[str, list[Interval]] = {}
|
||||
segments = transcription.segments if hasattr(transcription, "segments") else []
|
||||
segments = transcription.get("segments") or []
|
||||
max_word_duration = settings.resolve_speaker_identities_max_word_duration
|
||||
|
||||
for segment in segments:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Tests for the speaker-to-user assignment service."""
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
from summary.core import user_assign
|
||||
@@ -17,11 +16,9 @@ from summary.core.user_assign import (
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeTranscription:
|
||||
"""Mimics the OpenAI Transcription pydantic model for testing."""
|
||||
|
||||
segments: list = field(default_factory=list)
|
||||
def make_transcription(segments: list | None = None) -> dict:
|
||||
"""Build a WhisperX transcription dict, matching WhisperXResponse.model_dump()."""
|
||||
return {"segments": segments or []}
|
||||
|
||||
|
||||
RECORDING_START = datetime.fromisoformat("2026-03-17T15:30:33.000001")
|
||||
@@ -68,7 +65,7 @@ METADATA_SINGLE_USER = {
|
||||
],
|
||||
}
|
||||
|
||||
DIARIZATION_SINGLE_SPEAKER = FakeTranscription(
|
||||
DIARIZATION_SINGLE_SPEAKER = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 1.363,
|
||||
@@ -182,7 +179,7 @@ class TestBuildSpeakerTimelines:
|
||||
|
||||
def test_segment_without_words_falls_back_to_segment_bounds(self):
|
||||
"""Segments missing a `words` key use the segment start/end as one interval."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[{"start": 1.5, "end": 3.5, "speaker": "SPEAKER_00"}],
|
||||
)
|
||||
result = _build_speaker_timelines(transcription)
|
||||
@@ -190,7 +187,7 @@ class TestBuildSpeakerTimelines:
|
||||
|
||||
def test_segment_with_only_none_word_timestamps_falls_back(self):
|
||||
"""If every word has None start/end, fall back to segment bounds."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 1.0,
|
||||
@@ -208,7 +205,7 @@ class TestBuildSpeakerTimelines:
|
||||
|
||||
def test_short_words_only_uses_segment_start_and_last_word_end(self):
|
||||
"""With no overly long words, the interval runs segment start to end."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 1.0,
|
||||
@@ -231,7 +228,7 @@ class TestBuildSpeakerTimelines:
|
||||
max_word_duration = (
|
||||
user_assign.settings.resolve_speaker_identities_max_word_duration
|
||||
)
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 0.0,
|
||||
@@ -252,7 +249,7 @@ class TestBuildSpeakerTimelines:
|
||||
|
||||
def test_long_word_in_middle_splits_segment(self):
|
||||
"""Short words around a long word produce two intervals (before-cap + after)."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 0.0,
|
||||
@@ -275,7 +272,7 @@ class TestBuildSpeakerTimelines:
|
||||
|
||||
def test_tail_word_is_capped_at_max_duration(self):
|
||||
"""The trailing word's end is capped at word.start + max_word_duration."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 0.0,
|
||||
@@ -302,7 +299,7 @@ class TestBuildSpeakerTimelines:
|
||||
update={"resolve_speaker_identities_enable_split_on_words": False},
|
||||
),
|
||||
)
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 0.0,
|
||||
@@ -322,7 +319,7 @@ class TestBuildSpeakerTimelines:
|
||||
|
||||
def test_multiple_speakers_keep_separate_timelines(self):
|
||||
"""Segments from different speakers populate independent timeline entries."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{
|
||||
"start": 0.0,
|
||||
@@ -402,7 +399,7 @@ class TestResolveSpeakerIdentities:
|
||||
],
|
||||
"participants": [{"participantId": "user-a", "name": "Shared Mic"}],
|
||||
}
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 1.0, "end": 3.0, "speaker": "SPEAKER_00"},
|
||||
{"start": 5.0, "end": 7.0, "speaker": "SPEAKER_01"},
|
||||
@@ -445,7 +442,7 @@ class TestResolveSpeakerIdentities:
|
||||
{"participantId": "user-b", "name": "Bob"},
|
||||
],
|
||||
}
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 1.5, "end": 3.5, "speaker": "SPEAKER_00"},
|
||||
{"start": 5.5, "end": 7.5, "speaker": "SPEAKER_01"},
|
||||
@@ -493,7 +490,7 @@ class TestResolveSpeakerIdentities:
|
||||
{"participantId": "user-b", "name": "Bob"},
|
||||
],
|
||||
}
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 1.5, "end": 5.5, "speaker": "SPEAKER_00"},
|
||||
{"start": 4.0, "end": 7.5, "speaker": "SPEAKER_01"},
|
||||
@@ -529,7 +526,7 @@ class TestResolveSpeakerIdentities:
|
||||
],
|
||||
"participants": [{"participantId": "user-a", "name": "Brief User"}],
|
||||
}
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 1.0, "end": 10.0, "speaker": "SPEAKER_00"},
|
||||
],
|
||||
@@ -561,7 +558,7 @@ class TestResolveSpeakerIdentities:
|
||||
],
|
||||
"participants": [{"participantId": "user-a", "name": "Early User"}],
|
||||
}
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 0.0, "end": 3.0, "speaker": "SPEAKER_00"},
|
||||
],
|
||||
@@ -576,7 +573,7 @@ class TestResolveSpeakerIdentities:
|
||||
"""No segments produces empty result."""
|
||||
result = resolve_speaker_identities(
|
||||
METADATA_SINGLE_USER,
|
||||
FakeTranscription(segments=[]),
|
||||
make_transcription(segments=[]),
|
||||
RECORDING_START,
|
||||
RECORDING_END,
|
||||
)
|
||||
@@ -584,7 +581,7 @@ class TestResolveSpeakerIdentities:
|
||||
|
||||
def test_segment_without_speaker_ignored(self):
|
||||
"""Segments missing speaker key are skipped."""
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 1.0, "end": 3.0, "text": "no speaker"},
|
||||
],
|
||||
@@ -608,7 +605,7 @@ class TestResolveSpeakerIdentities:
|
||||
],
|
||||
"participants": [{"participantId": "user-a", "name": "Still Talking"}],
|
||||
}
|
||||
transcription = FakeTranscription(
|
||||
transcription = make_transcription(
|
||||
segments=[
|
||||
{"start": 2.0, "end": 9.0, "speaker": "SPEAKER_00"},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user