File size: 4,431 Bytes
68b18cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ab27fab
68b18cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
Stream State — SESSION_STATE_V1

Rolling session state that tracks camera, audio, observer, and builder
across the five loops.
"""

import time
import uuid
import hashlib
import json
from collections import deque
from dataclasses import dataclass, field
from typing import Any, Optional


@dataclass
class FrameEvidence:
    ts: float
    sha256: str
    phash: str
    entropy: float
    motion_score: float
    width: int
    height: int
    jpeg_b64: str


@dataclass
class AudioChunk:
    ts: float
    chunk_id: str
    duration_ms: int
    speaker: str
    transcript: str
    confidence: float
    sha256: str
    is_speech: bool


@dataclass
class SessionState:
    session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: float = field(default_factory=time.time)

    # Camera
    camera_source: str = "webcam"
    frame_rate_sampled: float = 1.0
    frames: deque = field(default_factory=lambda: deque(maxlen=16))
    last_frame_hash: str = ""
    motion_score: float = 0.0

    # Audio
    audio_chunks: deque = field(default_factory=lambda: deque(maxlen=30))
    speakers: dict = field(default_factory=dict)

    # Observer
    scene_summary: str = ""
    intent_summary: str = ""
    novelty_score: float = 0.0
    code_relevance_score: float = 0.0
    observer_state: dict = field(default_factory=dict)
    last_observer_ts: float = 0.0

    # Builder
    current_artifact: str = ""
    last_patch_hash: str = ""
    builder_state: dict = field(default_factory=dict)
    last_builder_ts: float = 0.0

    # QVD
    qvd: float = 0.0
    qvd_threshold: float = 0.08

    # Receipts
    receipt_count: int = 0

    # User
    user_instruction: str = ""
    mode: str = "continuous_code"

    def add_frame(self, frame: FrameEvidence):
        self.frames.append(frame)
        self.last_frame_hash = frame.sha256
        self.motion_score = frame.motion_score

    def add_audio_chunk(self, chunk: AudioChunk):
        self.audio_chunks.append(chunk)
        if chunk.speaker not in self.speakers:
            self.speakers[chunk.speaker] = {
                "transcript": "",
                "confidence": 0.0,
                "chunk_count": 0,
            }
        s = self.speakers[chunk.speaker]
        s["transcript"] = (s["transcript"] + " " + chunk.transcript).strip()[-2000:]
        s["confidence"] = chunk.confidence
        s["chunk_count"] += 1

    def frame_hashes(self) -> list:
        return [f.sha256 for f in self.frames]

    def audio_chunk_hashes(self) -> list:
        return [c.sha256 for c in self.audio_chunks]

    def speaker_segments(self) -> list:
        return [
            {"speaker": c.speaker, "text": c.transcript, "confidence": c.confidence}
            for c in self.audio_chunks if c.is_speech
        ]

    def to_dict(self) -> dict:
        return {
            "session_id": self.session_id,
            "created_at": self.created_at,
            "camera": {
                "source": self.camera_source,
                "frame_rate_sampled": self.frame_rate_sampled,
                "last_frame_hash": self.last_frame_hash,
                "motion_score": round(self.motion_score, 4),
                "frames_buffered": len(self.frames),
            },
            "audio": {
                "chunks_buffered": len(self.audio_chunks),
                "speakers": self.speakers,
            },
            "observer": {
                "scene_summary": self.scene_summary,
                "intent_summary": self.intent_summary,
                "novelty_score": round(self.novelty_score, 4),
                "code_relevance_score": round(self.code_relevance_score, 4),
                "state": self.observer_state,
                "last_ts": self.last_observer_ts,
            },
            "builder": {
                "current_artifact": self.current_artifact[:500],
                "last_patch_hash": self.last_patch_hash,
                "state": self.builder_state,
                "last_ts": self.last_builder_ts,
            },
            "qvd": round(self.qvd, 4),
            "qvd_threshold": self.qvd_threshold,
            "receipt_count": self.receipt_count,
            "user_instruction": self.user_instruction,
            "mode": self.mode,
        }


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def sha256_text(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()