Skip to content

Commit 77da763

Browse files
committed
feat(timeline): add semantic episode analysis
1 parent 86478dc commit 77da763

34 files changed

Lines changed: 2599 additions & 291 deletions

‎backends/advanced/src/advanced_omi_backend/app_factory.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
)
3838
from advanced_omi_backend.models.memory_audit import MemoryAuditEntry
3939
from advanced_omi_backend.models.system_event import SystemEvent
40+
from advanced_omi_backend.models.timeline import (
41+
AudioEvidenceSpan,
42+
TimelineAnalysisRun,
43+
TimelineDay,
44+
TimelineEpisode,
45+
)
4046
from advanced_omi_backend.models.waveform import WaveformData
4147
from advanced_omi_backend.redis_factory import create_async_redis
4248
from advanced_omi_backend.routers.api_router import router as api_router
@@ -90,6 +96,10 @@ async def lifespan(app: FastAPI):
9096
PairingCode,
9197
DeviceInputItem,
9298
DeviceInputJob,
99+
AudioEvidenceSpan,
100+
TimelineAnalysisRun,
101+
TimelineEpisode,
102+
TimelineDay,
93103
],
94104
)
95105
application_logger.info("Beanie initialized for all document models")
@@ -282,6 +292,9 @@ async def _init_cron_scheduler():
282292
process_observation_curation,
283293
)
284294
from advanced_omi_backend.services.person_photos import sync_person_photos
295+
from advanced_omi_backend.services.timeline.discovery import (
296+
process_current_timeline_days,
297+
)
285298
from advanced_omi_backend.workers.annotation_jobs import (
286299
surface_error_suggestions,
287300
)
@@ -305,6 +318,7 @@ async def _init_cron_scheduler():
305318
register_cron_job("device_audio_ingest", process_device_audio)
306319
register_cron_job("observation_curation", process_observation_curation)
307320
register_cron_job("screen_context_retention", purge_screen_context)
321+
register_cron_job("timeline_analysis", process_current_timeline_days)
308322

309323
scheduler = get_scheduler()
310324
await scheduler.start()

‎backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py‎

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import logging
1111
import os
12-
import time
1312
import uuid
1413

1514
from fastapi import UploadFile
@@ -51,6 +50,12 @@ async def upload_and_process_audio_files(
5150
device_name: str = "upload",
5251
source: str = "upload",
5352
annotation_only: bool = False,
53+
external_source_id: str | None = None,
54+
external_source_type: str | None = None,
55+
data_purpose: str | None = None,
56+
memory_excluded: bool | None = None,
57+
memory_exclusion_reason: str | None = None,
58+
skip_post_processing: bool = False,
5459
) -> dict:
5560
"""
5661
Upload audio files and process them directly.
@@ -114,19 +119,17 @@ async def upload_and_process_audio_files(
114119
continue
115120

116121
# Track external source for deduplication (Google Drive, etc.)
117-
external_source_id = None
118-
external_source_type = None
119122
if source == "gdrive":
120-
external_source_id = getattr(file, "file_id", None) or getattr(
121-
file, "audio_uuid", None
123+
external_source_id = (
124+
external_source_id
125+
or getattr(file, "file_id", None)
126+
or getattr(file, "audio_uuid", None)
122127
)
123-
external_source_type = "gdrive"
128+
external_source_type = external_source_type or "gdrive"
124129
if not external_source_id:
125130
audio_logger.warning(
126131
f"Missing file_id for gdrive file: {filename}"
127132
)
128-
timestamp = int(time.time() * 1000)
129-
130133
# Validate and prepare audio (read format from WAV file)
131134
try:
132135
audio_data, sample_rate, sample_width, channels, duration = (
@@ -169,11 +172,13 @@ async def upload_and_process_audio_files(
169172
),
170173
external_source_id=external_source_id,
171174
external_source_type=external_source_type,
172-
data_purpose="annotation" if annotation_only else None,
173-
memory_excluded=annotation_only,
174-
memory_exclusion_reason=(
175-
"annotation_only_upload" if annotation_only else None
175+
data_purpose=data_purpose
176+
or ("annotation" if annotation_only else None),
177+
memory_excluded=(
178+
annotation_only if memory_excluded is None else memory_excluded
176179
),
180+
memory_exclusion_reason=memory_exclusion_reason
181+
or ("annotation_only_upload" if annotation_only else None),
177182
)
178183
await conversation.insert()
179184
conversation_id = (
@@ -260,14 +265,22 @@ async def upload_and_process_audio_files(
260265
)
261266

262267
# Enqueue post-conversation processing job chain (depends on transcription)
263-
job_ids = start_post_conversation_jobs(
264-
conversation_id=conversation_id,
265-
user_id=user.user_id,
266-
transcript_version_id=version_id, # Pass the version_id from transcription job
267-
depends_on_job=transcription_job, # Wait for transcription to complete (or None)
268-
client_id=client_id, # Pass client_id for UI tracking
269-
skip_memory_extraction=annotation_only,
270-
)
268+
if skip_post_processing:
269+
job_ids = {
270+
"speaker_recognition": None,
271+
"memory": None,
272+
"title_summary": None,
273+
"event_dispatch": None,
274+
}
275+
else:
276+
job_ids = start_post_conversation_jobs(
277+
conversation_id=conversation_id,
278+
user_id=user.user_id,
279+
transcript_version_id=version_id,
280+
depends_on_job=transcription_job,
281+
client_id=client_id,
282+
skip_memory_extraction=annotation_only,
283+
)
271284

272285
file_result = {
273286
"filename": filename,

‎backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ async def get_conversations(
378378
"""
379379
try:
380380
user_filter = {} if user.is_superuser else {"user_id": str(user.user_id)}
381+
# Continuous ScreenPipe artifacts are evidence for semantic episodes, not
382+
# user-facing conversations. The raw device-input API remains available.
383+
user_filter["external_source_type"] = {"$ne": "screenpipe"}
381384

382385
if starred_only:
383386
user_filter["starred"] = True
@@ -544,7 +547,10 @@ async def _regex_search_conversations(
544547
"""Filter conversations by text across the selected conversation fields."""
545548
collection = Conversation.get_pymongo_collection()
546549

547-
match_filter: dict = {"deleted": False}
550+
match_filter: dict = {
551+
"deleted": False,
552+
"external_source_type": {"$ne": "screenpipe"},
553+
}
548554
if not user.is_superuser:
549555
match_filter["user_id"] = str(user.user_id)
550556

‎backends/advanced/src/advanced_omi_backend/models/job.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ async def _ensure_beanie_initialized():
5454
PairingCode,
5555
)
5656
from advanced_omi_backend.models.memory_audit import MemoryAuditEntry
57+
from advanced_omi_backend.models.timeline import (
58+
AudioEvidenceSpan,
59+
TimelineAnalysisRun,
60+
TimelineDay,
61+
TimelineEpisode,
62+
)
5763
from advanced_omi_backend.models.user import User
5864
from advanced_omi_backend.models.waveform import WaveformData
5965

@@ -86,6 +92,10 @@ async def _ensure_beanie_initialized():
8692
PairingCode,
8793
DeviceInputItem,
8894
DeviceInputJob,
95+
AudioEvidenceSpan,
96+
TimelineAnalysisRun,
97+
TimelineEpisode,
98+
TimelineDay,
8999
],
90100
)
91101

0 commit comments

Comments
 (0)