File size: 6,360 Bytes
3998131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
"""
Human-in-the-Loop Session Manager
Manages review sessions for bias detection with user approval workflow
"""

import uuid
from datetime import datetime
from typing import Dict, Optional
from api.schemas import BiasReviewSession, BiasReviewItem

class HITLSessionManager:
    """
    Manages in-memory sessions for human-in-the-loop bias detection workflow.
    Stores session state between PDF upload, review, and final PDF generation.
    """

    def __init__(self):
        """Initialize session manager with empty sessions dictionary."""
        self._sessions: Dict[str, BiasReviewSession] = {}

    def create_session(
        self,
        filename: str,
        sentences: list,
        raw_text: str,
        pdf_bytes: bytes
    ) -> BiasReviewSession:
        """
        Create a new review session.

        Args:
            filename: Original PDF filename
            sentences: List of BiasReviewItem objects
            raw_text: Raw extracted text from PDF
            pdf_bytes: Original PDF content as bytes

        Returns:
            BiasReviewSession object with generated session_id
        """
        session_id = str(uuid.uuid4())

        session = BiasReviewSession(
            session_id=session_id,
            original_filename=filename,
            sentences=sentences,
            raw_text=raw_text,
            pdf_bytes=pdf_bytes,
            created_at=datetime.utcnow().isoformat(),
            status="pending_review"
        )

        self._sessions[session_id] = session
        return session

    def get_session(self, session_id: str) -> Optional[BiasReviewSession]:
        """
        Retrieve a session by ID.

        Args:
            session_id: Session identifier

        Returns:
            BiasReviewSession if found, None otherwise
        """
        return self._sessions.get(session_id)

    def update_sentence_status(
        self,
        session_id: str,
        sentence_id: str,
        status: str,
        approved_suggestion: Optional[str] = None
    ) -> bool:
        """
        Update the status of a specific sentence in a session.

        Args:
            session_id: Session identifier
            sentence_id: Sentence identifier
            status: New status ("pending", "approved", "needs_regeneration")
            approved_suggestion: Approved suggestion text (if status is "approved")

        Returns:
            True if update successful, False otherwise
        """
        session = self.get_session(session_id)
        if not session:
            return False

        for sentence in session.sentences:
            if sentence.sentence_id == sentence_id:
                sentence.status = status
                if approved_suggestion:
                    sentence.approved_suggestion = approved_suggestion

                # Update session status to in_progress once first action taken
                if session.status == "pending_review":
                    session.status = "in_progress"

                return True

        return False

    def update_sentence_suggestion(
        self,
        session_id: str,
        sentence_id: str,
        new_suggestion: str
    ) -> bool:
        """
        Update the suggestion for a specific sentence.

        Args:
            session_id: Session identifier
            sentence_id: Sentence identifier
            new_suggestion: New suggestion text from LLM

        Returns:
            True if update successful, False otherwise
        """
        session = self.get_session(session_id)
        if not session:
            return False

        for sentence in session.sentences:
            if sentence.sentence_id == sentence_id:
                sentence.suggestion = new_suggestion
                sentence.status = "pending"  # Reset to pending after regeneration
                return True

        return False

    def get_session_stats(self, session_id: str) -> Optional[Dict]:
        """
        Get statistics for a session.

        Args:
            session_id: Session identifier

        Returns:
            Dictionary with counts or None if session not found
        """
        session = self.get_session(session_id)
        if not session:
            return None

        total = len(session.sentences)
        pending = sum(1 for s in session.sentences if s.status == "pending")
        approved = sum(1 for s in session.sentences if s.status == "approved")
        needs_regen = sum(1 for s in session.sentences if s.status == "needs_regeneration")

        return {
            "total_sentences": total,
            "pending_count": pending,
            "approved_count": approved,
            "needs_regeneration_count": needs_regen
        }

    def is_session_ready_for_pdf(self, session_id: str) -> bool:
        """
        Check if all sentences in a session have been reviewed.

        Args:
            session_id: Session identifier

        Returns:
            True if all sentences are approved, False otherwise
        """
        session = self.get_session(session_id)
        if not session:
            return False

        # Check if all sentences are either approved or were neutral
        for sentence in session.sentences:
            if sentence.is_biased and sentence.status != "approved":
                return False

        return True

    def mark_session_completed(self, session_id: str) -> bool:
        """
        Mark a session as completed.

        Args:
            session_id: Session identifier

        Returns:
            True if successful, False otherwise
        """
        session = self.get_session(session_id)
        if not session:
            return False

        session.status = "completed"
        return True

    def delete_session(self, session_id: str) -> bool:
        """
        Delete a session from memory.

        Args:
            session_id: Session identifier

        Returns:
            True if deleted, False if not found
        """
        if session_id in self._sessions:
            del self._sessions[session_id]
            return True
        return False

    def get_all_sessions(self) -> Dict[str, BiasReviewSession]:
        """
        Get all active sessions (for debugging/monitoring).

        Returns:
            Dictionary of all sessions
        """
        return self._sessions