← Files Meetings (Beta)ARCHIVED FILE
scripts/meetings_note_status.py
8.07 KB · Oct 8, 2026 · 12:02 UTC
"""Project Record lifecycle states for Meetings note lists and details."""
from __future__ import annotations
import re
from collections.abc import Mapping
from typing import Literal
from meetings_api_client_common import nonempty_string
from record_meeting_interaction_types import RecordMeetingNoteStatus, RecordMeetingProcessingStage
from helpers import is_json
RecordMeetingFetchStatus = Literal["fetchable", "processing", "unavailable"]
_NOTE_WAITING_STATUSES = frozenset({"waiting_room"})
_NOTE_JOINING_STATUSES = frozenset(
{
"dispatched",
"joining",
"joining_meeting",
"not_attempted",
"pending",
"pending_join",
"pending_start",
"queue",
"queued",
"queued_for_start",
"replacement_pending_start",
"waiting",
}
)
_NOTE_RECORDING_STATUSES = frozenset(
{
"active",
"capturing",
"in_progress",
"joined",
"joined_meeting",
"live",
"recording",
"recording_in_progress",
"running",
"started",
}
)
_NOTE_UPLOADING_STATUSES = frozenset({"uploading"})
_NOTE_PROCESSING_STATUSES = frozenset(
{
"diarized",
"diarizing",
"gathering_meeting_data",
"preparing_transcript",
"processing",
"reprocessing",
"summarizing",
"summarizing_from_original_transcript",
"summarizing_without_audio",
"transcribing",
}
)
_NOTE_ACTIVE_STATUSES = (
_NOTE_WAITING_STATUSES
| _NOTE_JOINING_STATUSES
| _NOTE_RECORDING_STATUSES
| _NOTE_UPLOADING_STATUSES
| _NOTE_PROCESSING_STATUSES
)
_NOTE_GENERIC_ACTIVE_STATUSES = frozenset({"active", "in_progress", "running"})
_NOTE_FAILED_STATUSES = frozenset(
{
"blocked",
"caas_launch_failed",
"canceled",
"cancelled",
"container_start_failed",
"denied",
"dispatch_failed",
"failed",
"failure",
"host_denied_admission",
"in_waiting_room_timeout",
"join_failed",
"join_failed_internal",
"join_failed_waiting_room_timeout",
"join_timeout",
"left_meeting",
"pending_start_failed",
"start_failed",
"stopped",
"waiting_room_timeout",
}
)
_NOTE_PRE_CAPTURE_TERMINAL_STATUSES = frozenset(
{
"blocked",
"caas_launch_failed",
"canceled",
"cancelled",
"container_start_failed",
"denied",
"dispatch_failed",
"host_denied_admission",
"in_waiting_room_timeout",
"join_failed",
"join_failed_internal",
"join_failed_waiting_room_timeout",
"join_timeout",
"pending_start_failed",
"start_failed",
"stopped",
"waiting_room_timeout",
}
)
_NOTE_PRE_CAPTURE_STATUSES = (
_NOTE_WAITING_STATUSES | _NOTE_JOINING_STATUSES | _NOTE_PRE_CAPTURE_TERMINAL_STATUSES
)
_NOTE_READY_STATUSES = frozenset(
{
"complete",
"completed",
"done",
"finished",
"notes_published",
"processed",
"ready",
"succeeded",
"success",
}
)
def project_status(row: Mapping[str, object]) -> RecordMeetingNoteStatus:
labels = _project_status_labels(row)
if any(
label in _NOTE_FAILED_STATUSES or label.endswith(("_failed", "_failure"))
for _, label in labels
):
return "failed"
active_hosted_status = any(
index < 4 and label in _NOTE_ACTIVE_STATUSES for index, label in labels
)
for _, label in labels:
if label == "cca_complete" and not active_hosted_status:
return "cca_complete"
if label in _NOTE_WAITING_STATUSES:
return "waiting"
if label in _NOTE_JOINING_STATUSES:
return "joining"
if label in _NOTE_RECORDING_STATUSES:
return "recording"
if label in _NOTE_UPLOADING_STATUSES:
return "uploading"
if label in _NOTE_PROCESSING_STATUSES:
return "processing"
if label in _NOTE_READY_STATUSES and not active_hosted_status:
return "ready"
return "ready"
def _project_status_labels(
row: Mapping[str, object],
) -> tuple[tuple[int, str], ...]:
status_value = row.get("status")
status: Mapping[str, object] = status_value if is_json(status_value) else {}
candidates = (
status.get("processingStatus"),
row.get("processingStatus"),
status.get("processingState"),
row.get("processingState"),
status.get("status"),
status_value if isinstance(status_value, str) else None,
)
labels = tuple(
(index, re.sub(r"[-\s]+", "_", value.lower()))
for index, candidate in enumerate(candidates)
if (value := nonempty_string(candidate, maximum_bytes=128)) is not None
)
return labels
def project_list_status(
row: Mapping[str, object],
) -> RecordMeetingNoteStatus | None:
"""Project only lifecycle states that belong in the Past notes surface."""
status_value = row.get("status")
if is_json(status_value):
labels = _project_status_labels(row)
if any(label in _NOTE_PRE_CAPTURE_STATUSES for _, label in labels):
return None
legacy_status = project_status(row)
return legacy_status
top_level_status = _normalized_status_label(status_value)
if top_level_status == "cca_complete":
return "cca_complete"
if top_level_status is None:
# Historical completed rows may not carry a status. Do not promote a
# processing or failure side-channel into a lifecycle outcome.
return "ready" if project_status(row) == "ready" else None
if top_level_status in _NOTE_READY_STATUSES:
return "ready"
if top_level_status == "empty":
return "ready"
if top_level_status in {"failed", "failure"}:
return "failed"
if top_level_status in _NOTE_WAITING_STATUSES | _NOTE_JOINING_STATUSES:
return None
if top_level_status in _NOTE_ACTIVE_STATUSES:
return _project_active_list_status(row, top_level_status=top_level_status)
return None
def _project_active_list_status(
row: Mapping[str, object],
*,
top_level_status: str,
) -> RecordMeetingNoteStatus | None:
"""Refine an active lifecycle without accepting stale terminal signals."""
if top_level_status not in _NOTE_GENERIC_ACTIVE_STATUSES:
if top_level_status in _NOTE_RECORDING_STATUSES:
return "recording"
if top_level_status in _NOTE_UPLOADING_STATUSES:
return "uploading"
if top_level_status in _NOTE_PROCESSING_STATUSES:
return "processing"
for _, label in _project_status_labels(row):
if label in _NOTE_PRE_CAPTURE_STATUSES:
return None
if label in _NOTE_RECORDING_STATUSES:
return "recording"
if label in _NOTE_UPLOADING_STATUSES:
return "uploading"
if label in _NOTE_PROCESSING_STATUSES:
return "processing"
return None
def project_processing_stage(
row: Mapping[str, object], status: RecordMeetingNoteStatus
) -> RecordMeetingProcessingStage | None:
if status != "processing":
return None
for index, label in _project_status_labels(row):
if index > 1:
continue
if label in {"transcribing", "diarizing", "preparing_transcript"}:
return "transcribing"
if label in {
"summarizing",
"summarizing_from_original_transcript",
"summarizing_without_audio",
}:
return "preparing-notes"
return None
def _normalized_status_label(value: object) -> str | None:
label = nonempty_string(value, maximum_bytes=128)
if label is None:
return None
return re.sub(r"[-\s]+", "_", label.lower())
def project_fetch_status(
*,
status: RecordMeetingNoteStatus,
) -> RecordMeetingFetchStatus:
if status in {"joining", "waiting", "recording", "uploading", "processing"}:
return "processing"
if status in {"ready", "cca_complete"}:
return "fetchable"
return "unavailable"
SHA-256: 7155bd15d300d915fc04ffa0528a65c05d808a65e7adc5e4322458e90ead9ff5