CaffeinatedCoding commited on
Commit
f28eada
·
verified ·
1 Parent(s): a4f0314

Upload folder using huggingface_hub

Browse files
PIPELINE_FIXES.md ADDED
File without changes
api/main.py CHANGED
@@ -330,7 +330,7 @@ def court_new_session(request: NewSessionRequest):
330
  )
331
 
332
  # Registrar opens the session
333
- from src.court.session import get_session, add_transcript_entry
334
  session = get_session(session_id)
335
  opening = build_round_announcement(session, 0, "briefing")
336
  add_transcript_entry(
@@ -341,6 +341,9 @@ def court_new_session(request: NewSessionRequest):
341
  entry_type="announcement",
342
  )
343
 
 
 
 
344
  return {
345
  "session_id": session_id,
346
  "case_brief": case_brief,
@@ -386,7 +389,7 @@ def court_import_session(request: ImportSessionRequest):
386
  case_brief=case_brief,
387
  )
388
 
389
- from src.court.session import get_session
390
  session = get_session(session_id)
391
  opening = build_round_announcement(session, 0, "briefing")
392
  add_transcript_entry(
@@ -397,6 +400,9 @@ def court_import_session(request: ImportSessionRequest):
397
  entry_type="announcement",
398
  )
399
 
 
 
 
400
  return {
401
  "session_id": session_id,
402
  "case_brief": case_brief,
 
330
  )
331
 
332
  # Registrar opens the session
333
+ from src.court.session import get_session, add_transcript_entry, update_session
334
  session = get_session(session_id)
335
  opening = build_round_announcement(session, 0, "briefing")
336
  add_transcript_entry(
 
341
  entry_type="announcement",
342
  )
343
 
344
+ # Initialize: User is ready to submit briefing (opening argument)
345
+ update_session(session_id, {"awaiting_action": "user"})
346
+
347
  return {
348
  "session_id": session_id,
349
  "case_brief": case_brief,
 
389
  case_brief=case_brief,
390
  )
391
 
392
+ from src.court.session import get_session, add_transcript_entry, update_session
393
  session = get_session(session_id)
394
  opening = build_round_announcement(session, 0, "briefing")
395
  add_transcript_entry(
 
400
  entry_type="announcement",
401
  )
402
 
403
+ # Initialize: User is ready to submit briefing (opening argument)
404
+ update_session(session_id, {"awaiting_action": "user"})
405
+
406
  return {
407
  "session_id": session_id,
408
  "case_brief": case_brief,
frontend/court/court.html CHANGED
@@ -93,9 +93,9 @@
93
  </nav>
94
  </header>
95
 
96
- <main class="relative z-10 pt-28 pb-12 px-8 grid grid-cols-12 gap-8 max-w-6xl mx-auto items-center min-h-screen">
97
  <!-- Centre card -->
98
- <div class="col-span-12 lg:col-span-7 flex justify-center items-center">
99
  <div class="clay-card p-8 lg:p-10 rounded-xl w-full max-w-2xl text-center">
100
  <!-- Gavel icon -->
101
  <div class="mb-6 inline-flex items-center justify-center w-16 h-16 rounded-full bg-surface-container clay-puffy">
@@ -107,35 +107,14 @@
107
  <div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
108
  <button onclick="showScreen('setup')" class="clay-btn-primary flex items-center justify-center gap-2 px-6 py-3 rounded-lg font-sans font-bold text-sm text-white">
109
  <span class="material-symbols-outlined text-lg">scale</span>
110
- New Case
111
- </button>
112
- <button onclick="showImportFlow()" class="clay-btn-secondary flex items-center justify-center gap-2 border-2 border-secondary/30 px-6 py-3 rounded-lg font-sans font-bold text-sm text-secondary">
113
- <span class="material-symbols-outlined text-lg">folder_open</span>
114
- Continue
115
  </button>
116
  </div>
117
- <a onclick="showScreen('sessions')" class="text-secondary font-sans font-bold text-sm hover:underline flex items-center justify-center gap-2 mb-6 cursor-pointer">
118
- <span class="material-symbols-outlined text-sm">list_alt</span>
119
- Past Sessions
120
- </a>
121
  <div class="pt-4 border-t border-primary/5">
122
  <p class="text-primary/40 text-xs font-sans italic">For educational simulation only. Not legal advice.</p>
123
  </div>
124
  </div>
125
  </div>
126
-
127
- <!-- Recent sessions sidebar -->
128
- <div class="col-span-12 lg:col-span-5 flex flex-col gap-4">
129
- <div class="flex items-center justify-between px-3">
130
- <h3 class="font-serif text-lg font-bold text-primary">Recent Sessions</h3>
131
- <span class="text-[10px] font-mono text-secondary bg-secondary-fixed/30 px-1.5 py-0.5 rounded">LIVE</span>
132
- </div>
133
- <div id="recent-sessions-list" class="space-y-3">
134
- <div class="clay-card p-4 rounded-lg text-center text-primary/40 text-xs font-sans">
135
- Loading...
136
- </div>
137
- </div>
138
- </div>
139
  </main>
140
 
141
  <!-- Registrar clock -->
 
93
  </nav>
94
  </header>
95
 
96
+ <main class="relative z-10 pt-28 pb-12 px-8 flex justify-center items-center min-h-screen">
97
  <!-- Centre card -->
98
+ <div class="flex justify-center items-center">
99
  <div class="clay-card p-8 lg:p-10 rounded-xl w-full max-w-2xl text-center">
100
  <!-- Gavel icon -->
101
  <div class="mb-6 inline-flex items-center justify-center w-16 h-16 rounded-full bg-surface-container clay-puffy">
 
107
  <div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
108
  <button onclick="showScreen('setup')" class="clay-btn-primary flex items-center justify-center gap-2 px-6 py-3 rounded-lg font-sans font-bold text-sm text-white">
109
  <span class="material-symbols-outlined text-lg">scale</span>
110
+ Fight A Case
 
 
 
 
111
  </button>
112
  </div>
 
 
 
 
113
  <div class="pt-4 border-t border-primary/5">
114
  <p class="text-primary/40 text-xs font-sans italic">For educational simulation only. Not legal advice.</p>
115
  </div>
116
  </div>
117
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  </main>
119
 
120
  <!-- Registrar clock -->
frontend/index_old_judicial.html ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html class="light" lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NYAYA SETU — Legal Research Registry</title>
7
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,600;0,700;1,600;1,700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
10
+ <script id="tailwind-config">
11
+ tailwind.config = {
12
+ darkMode: "class",
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ "on-secondary": "#ffffff",
17
+ "surface-container-high": "#ece8e0",
18
+ "surface-container": "#f2ede5",
19
+ "on-tertiary-fixed-variant": "#4e3e6f",
20
+ "tertiary": "#342554",
21
+ "secondary-container": "#fed174",
22
+ "primary-container": "#2c4a3e",
23
+ "outline": "#727974",
24
+ "on-tertiary-fixed": "#221141",
25
+ "surface-container-highest": "#e7e2da",
26
+ "secondary": "#795900",
27
+ "on-surface": "#1d1c17",
28
+ "secondary-fixed-dim": "#ecc165",
29
+ "on-tertiary": "#ffffff",
30
+ "error-container": "#ffdad6",
31
+ "on-error-container": "#93000a",
32
+ "surface-tint": "#466558",
33
+ "error": "#ba1a1a",
34
+ "surface-bright": "#fef9f1",
35
+ "on-tertiary-container": "#bba8e1",
36
+ "secondary-fixed": "#ffdfa0",
37
+ "inverse-on-surface": "#f5f0e8",
38
+ "primary": "#153328",
39
+ "outline-variant": "#c1c8c3",
40
+ "on-secondary-container": "#785800",
41
+ "on-secondary-fixed": "#261a00",
42
+ "on-primary-fixed": "#012016",
43
+ "surface-variant": "#e7e2da",
44
+ "on-surface-variant": "#414845",
45
+ "on-primary-container": "#98b9a9",
46
+ "tertiary-container": "#4b3b6c",
47
+ "on-background": "#1d1c17",
48
+ "inverse-surface": "#32302b",
49
+ "surface-container-lowest": "#ffffff",
50
+ "background": "#fef9f1",
51
+ "tertiary-fixed-dim": "#d1bdf7",
52
+ "on-primary": "#ffffff",
53
+ "on-error": "#ffffff",
54
+ "surface-dim": "#ded9d2",
55
+ "on-secondary-fixed-variant": "#5c4300",
56
+ "inverse-primary": "#adcebe",
57
+ "tertiary-fixed": "#eaddff",
58
+ "surface": "#fef9f1",
59
+ "on-primary-fixed-variant": "#2f4d41",
60
+ "surface-container-low": "#f8f3eb",
61
+ "primary-fixed": "#c8eada",
62
+ "primary-fixed-dim": "#adcebe"
63
+ },
64
+ borderRadius: {
65
+ DEFAULT: "1rem",
66
+ lg: "2rem",
67
+ xl: "3rem",
68
+ full: "9999px"
69
+ },
70
+ fontFamily: {
71
+ headline: ["Cormorant Garamond", "serif"],
72
+ body: ["Plus Jakarta Sans", "sans-serif"],
73
+ label: ["Cormorant Garamond", "serif"]
74
+ }
75
+ }
76
+ }
77
+ }
78
+ </script>
79
+ <style>
80
+ .clay-card {
81
+ background: #ffffff;
82
+ box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.08), inset 2px 2px 4px rgba(255, 255, 255, 0.8);
83
+ border-radius: 24px;
84
+ }
85
+ .clay-inset {
86
+ background: #f0ebe2;
87
+ box-shadow: inset 4px 4px 8px rgba(0, 0, 0, 0.06), inset -4px -4px 8px rgba(255, 255, 255, 0.9);
88
+ border-radius: 20px;
89
+ }
90
+ .clay-button-primary {
91
+ background: #2c4a3e;
92
+ box-shadow: 0 10px 20px -5px rgba(44, 74, 62, 0.4), inset 2px 2px 4px rgba(255, 255, 255, 0.3);
93
+ transition: all 0.2s ease;
94
+ cursor: pointer;
95
+ }
96
+ .clay-button-primary:hover {
97
+ transform: translateY(-2px);
98
+ box-shadow: 0 12px 24px -5px rgba(44, 74, 62, 0.5), inset 2px 2px 4px rgba(255, 255, 255, 0.3);
99
+ }
100
+ .clay-button-primary:active {
101
+ transform: scale(0.98);
102
+ box-shadow: inset 4px 4px 8px rgba(0, 0, 0, 0.3);
103
+ }
104
+ .material-symbols-outlined {
105
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
106
+ }
107
+ .custom-scrollbar::-webkit-scrollbar {
108
+ width: 4px;
109
+ }
110
+ .custom-scrollbar::-webkit-scrollbar-track {
111
+ background: transparent;
112
+ }
113
+ .custom-scrollbar::-webkit-scrollbar-thumb {
114
+ background: #dcd7ce;
115
+ border-radius: 10px;
116
+ }
117
+ </style>
118
+ </head>
119
+ <body class="bg-[#F5F0E8] text-on-surface font-body h-screen flex overflow-hidden">
120
+ <div class="app-container">
121
+
122
+ <!-- SIDEBAR - JUDICIAL PANEL -->
123
+ <aside class="sidebar" id="sidebar">
124
+ <!-- Header with Judicial Seal -->
125
+ <div class="sidebar-header">
126
+ <div class="judicial-seal">
127
+ <svg viewBox="0 0 100 100" class="seal-svg">
128
+ <circle cx="50" cy="50" r="48" fill="none" stroke="currentColor" stroke-width="2"/>
129
+ <circle cx="50" cy="50" r="42" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
130
+ <!-- Scale/Balance symbol -->
131
+ <path d="M50 20 L35 45 L65 45 Z" fill="none" stroke="currentColor" stroke-width="1.5"/>
132
+ <line x1="35" y1="45" x2="65" y2="45" stroke="currentColor" stroke-width="2"/>
133
+ <circle cx="35" cy="55" r="3" fill="currentColor"/>
134
+ <circle cx="65" cy="55" r="3" fill="currentColor"/>
135
+ <text x="50" y="75" font-size="8" text-anchor="middle" font-weight="bold" letter-spacing="1">NYAYASETU</text>
136
+ </svg>
137
+ </div>
138
+ <div class="brand-text">
139
+ <div class="brand-name">NYAYA SETU</div>
140
+ <div class="brand-sub">Legal Research Registry</div>
141
+ </div>
142
+ <button class="sidebar-toggle" onclick="toggleSidebar()">≡</button>
143
+ </div>
144
+
145
+ <!-- Primary Actions -->
146
+ <div class="action-group">
147
+ <button class="btn-primary" onclick="newChat()">⊕ New Petition</button>
148
+ <button class="btn-secondary" onclick="showAnalytics()">📋 Case Analytics</button>
149
+ <button class="btn-secondary" onclick="window.location.href='/court/ui'">⚔ Moot Court</button>
150
+ </div>
151
+
152
+ <!-- Case Registry -->
153
+ <div class="registry-section">
154
+ <div class="registry-header">
155
+ <span class="registry-title">CASE REGISTRY</span>
156
+ <div class="registry-divider"></div>
157
+ </div>
158
+ <div class="sessions-list" id="sessions-list">
159
+ <div class="sessions-empty">No active cases</div>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Sidebar Footer - Court Info -->
164
+ <div class="sidebar-footer">
165
+ <div class="court-info">
166
+ <p class="disclaimer">⚠ DISCLAIMER</p>
167
+ <p class="disclaimer-text">Not legal advice. Consult qualified advocate.</p>
168
+ </div>
169
+ <div class="court-meta">
170
+ <p class="meta-text">Supreme Court of India</p>
171
+ <p class="meta-text">Established 1950</p>
172
+ </div>
173
+ </div>
174
+ </aside>
175
+
176
+ <!-- MAIN CONTENT AREA -->
177
+ <main class="main-content">
178
+ <!-- Header/Bench -->
179
+ <header class="topbar">
180
+ <div class="topbar-left">
181
+ <h1 class="topbar-title" id="topbar-title">Research Session</h1>
182
+ <p class="topbar-subtitle" id="topbar-subtitle">Indian Legal Research System</p>
183
+ </div>
184
+ <div class="status-indicator" id="status-pill">
185
+ <span class="status-dot"></span>
186
+ <span id="status-text">Ready</span>
187
+ </div>
188
+ </header>
189
+
190
+ <!-- Welcome/Research Section -->
191
+ <section id="screen-welcome" class="screen active">
192
+ <div class="welcome-container">
193
+ <div class="welcome-graphic">
194
+ <svg viewBox="0 0 200 200" class="welcome-icon">
195
+ <!-- Judge's Bench -->
196
+ <rect x="20" y="120" width="160" height="40" fill="none" stroke="currentColor" stroke-width="2" rx="2"/>
197
+ <!-- Gavel -->
198
+ <rect x="85" y="30" width="30" height="50" fill="none" stroke="currentColor" stroke-width="2"/>
199
+ <circle cx="100" cy="20" r="12" fill="currentColor" opacity="0.3"/>
200
+ <!-- Scales -->
201
+ <line x1="50" y1="80" x2="50" y2="120" stroke="currentColor" stroke-width="2"/>
202
+ <line x1="35" y1="100" x2="65" y2="100" stroke="currentColor" stroke-width="2"/>
203
+ <circle cx="35" cy="105" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
204
+ <circle cx="65" cy="105" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
205
+ <!-- Books -->
206
+ <rect x="130" y="70" width="15" height="35" fill="currentColor" opacity="0.2" stroke="currentColor" stroke-width="1.5"/>
207
+ <rect x="148" y="65" width="15" height="40" fill="currentColor" opacity="0.2" stroke="currentColor" stroke-width="1.5"/>
208
+ </svg>
209
+ </div>
210
+ <h2>Indian Legal Research</h2>
211
+ <p class="welcome-subtitle">Query 26,688 Supreme Court Judgments<br><span class="text-muted">Cited sources | Verified citations | Complete legal brief</span></p>
212
+
213
+ <div class="suggestions">
214
+ <div class="suggestion-label">Sample Petitions:</div>
215
+ <button class="suggestion" onclick="usesuggestion(this)">Fundamental Rights — Definition & Scope</button>
216
+ <button class="suggestion" onclick="usesuggestion(this)">Right to Privacy under Article 21</button>
217
+ <button class="suggestion" onclick="usesuggestion(this)">Bail — Legal Framework & Supreme Court Tests</button>
218
+ <button class="suggestion" onclick="usesuggestion(this)">Basic Structure Doctrine — Implications</button>
219
+ <button class="suggestion" onclick="usesuggestion(this)">Article 15 — Non-discrimination Doctrine</button>
220
+ <button class="suggestion" onclick="usesuggestion(this)">Freedom of Speech — Reasonable Restrictions</button>
221
+ </div>
222
+ </div>
223
+ </section>
224
+
225
+ <!-- Chat/Case Proceedings Section -->
226
+ <section id="screen-chat" class="screen">
227
+ <div class="chat-area">
228
+ <div class="proceedings" id="messages-list"></div>
229
+ </div>
230
+ <div class="input-bench">
231
+ <textarea id="query-input" placeholder="Address the court... (Enter to submit, Shift+Enter for new line)" class="query-input"></textarea>
232
+ <button id="send-btn" class="btn-submit" onclick="submitQuery()">📤 Submit Petition</button>
233
+ </div>
234
+ </section>
235
+
236
+ <!-- Analytics Dashboard -->
237
+ <section id="screen-analytics" class="screen">
238
+ <div class="analytics-container">
239
+ <div class="analytics-header">
240
+ <h2>Case Analytics & System Metrics</h2>
241
+ <p>Live inference data from NyayaSetu research engine</p>
242
+ </div>
243
+ <div class="stats-grid">
244
+ <div class="stat-card">
245
+ <div class="stat-icon">📊</div>
246
+ <div class="stat-value" id="stat-total">—</div>
247
+ <div class="stat-label">Total Queries</div>
248
+ </div>
249
+ <div class="stat-card">
250
+ <div class="stat-icon">✓</div>
251
+ <div class="stat-value" id="stat-verified">—</div>
252
+ <div class="stat-label">Verified Answers</div>
253
+ </div>
254
+ <div class="stat-card">
255
+ <div class="stat-icon">⚡</div>
256
+ <div class="stat-value" id="stat-latency">—</div>
257
+ <div class="stat-label">Avg Latency</div>
258
+ </div>
259
+ <div class="stat-card">
260
+ <div class="stat-icon">🔍</div>
261
+ <div class="stat-value" id="stat-ood">—</div>
262
+ <div class="stat-label">Out-of-Domain</div>
263
+ </div>
264
+ <div class="stat-card">
265
+ <div class="stat-icon">📚</div>
266
+ <div class="stat-value" id="stat-sources">—</div>
267
+ <div class="stat-label">Avg Sources Used</div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </section>
272
+ </main>
273
+ </div>
274
+
275
+ <script src="/static/app.js"></script>
276
+ </body>
277
+ </html>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ </section>
282
+
283
+ <!-- INPUT AREA -->
284
+ <div class="input-area">
285
+ <div class="input-wrapper">
286
+ <textarea id="query-input" class="query-input" placeholder="Ask about Indian law..." maxlength="1000"></textarea>
287
+ <button id="send-btn" class="send-btn" onclick="submitQuery()">→</button>
288
+ </div>
289
+ <p class="disclaimer-line">Not a substitute for professional legal advice</p>
290
+ </div>
291
+ </main>
292
+ </div>
293
+
294
+ <script src="/static/app.js"></script>
295
+ </body>
296
+ </html>
src/court/orchestrator.py CHANGED
@@ -197,6 +197,10 @@ def process_user_argument(
197
 
198
  phase = session["phase"]
199
 
 
 
 
 
200
  # Route to appropriate handler
201
  try:
202
  if phase == "briefing":
@@ -319,6 +323,9 @@ def _handle_briefing(session_id: str, user_argument: str, session: Dict) -> Dict
319
  user_fell_in=False,
320
  )
321
 
 
 
 
322
  return {
323
  "opposing_response": opposing_response,
324
  "judge_question": judge_question,
@@ -340,6 +347,11 @@ def _handle_round(session_id: str, user_argument: str, session: Dict) -> Dict:
340
  user_side = session["user_side"]
341
  user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
342
 
 
 
 
 
 
343
  # Add user argument to transcript
344
  add_transcript_entry(
345
  session_id=session_id,
@@ -439,10 +451,17 @@ def _handle_round(session_id: str, user_argument: str, session: Dict) -> Dict:
439
  # Registrar announcement
440
  if new_phase == "cross_examination":
441
  registrar_note = build_round_announcement(session, new_round, "cross_examination")
 
 
442
  elif new_round <= max_rounds:
443
  registrar_note = build_round_announcement(session, new_round, "rounds")
 
444
  else:
445
  registrar_note = build_round_announcement(session, new_round, "closing")
 
 
 
 
446
 
447
  add_transcript_entry(
448
  session_id=session_id,
@@ -452,6 +471,36 @@ def _handle_round(session_id: str, user_argument: str, session: Dict) -> Dict:
452
  entry_type="announcement",
453
  )
454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  # Detect concessions
456
  new_concessions = _detect_concessions(user_argument, session_id, current_round)
457
 
@@ -478,12 +527,18 @@ def _handle_cross_exam_answer(
478
  user_side = session["user_side"]
479
  user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
480
 
481
- # Count how many cross-exam questions have been asked
482
- cross_entries = [
 
 
 
 
 
483
  e for e in session.get("transcript", [])
484
- if e.get("phase") == "cross_examination"
485
  ]
486
- question_number = len([e for e in cross_entries if e.get("speaker") == "OPPOSING_COUNSEL"]) + 1
 
487
 
488
  # Add user's answer
489
  add_transcript_entry(
@@ -497,8 +552,8 @@ def _handle_cross_exam_answer(
497
  # Detect concessions in answer
498
  new_concessions = _detect_concessions(user_answer, session_id, session["current_round"])
499
 
500
- # If more questions remaining (max 3), get next question
501
- if question_number < 3:
502
  query = " ".join(session.get("legal_issues", []))
503
  retrieved_precedents = _retrieve_for_court(query, session)
504
 
@@ -508,27 +563,30 @@ def _handle_cross_exam_answer(
508
 
509
  cross_messages = build_cross_examination_prompt(
510
  session=fresh_session,
511
- question_number=question_number + 1,
512
  retrieved_context=combined_context,
513
  )
514
 
515
  try:
516
  next_question = _call_llm(cross_messages)
517
  except Exception as e:
518
- next_question = f"Question {question_number + 1}: Counsel, would you agree that [question]?"
519
 
520
  add_transcript_entry(
521
  session_id=session_id,
522
  speaker="OPPOSING_COUNSEL",
523
  role_label="RESPONDENT'S COUNSEL" if user_side == "petitioner" else "PETITIONER'S COUNSEL",
524
  content=next_question,
525
- entry_type="question",
526
  )
527
 
 
 
 
528
  return {
529
  "opposing_response": next_question,
530
  "judge_question": "",
531
- "registrar_note": f"Question {question_number + 1} of 3.",
532
  "trap_detected": False,
533
  "trap_warning": "",
534
  "new_concessions": new_concessions,
@@ -541,7 +599,8 @@ def _handle_cross_exam_answer(
541
  else:
542
  # Cross-examination complete — advance to closing
543
  advance_phase(session_id)
544
- registrar_note = build_round_announcement(session, session["current_round"], "closing")
 
545
 
546
  add_transcript_entry(
547
  session_id=session_id,
@@ -551,6 +610,30 @@ def _handle_cross_exam_answer(
551
  entry_type="announcement",
552
  )
553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
  return {
555
  "opposing_response": "",
556
  "judge_question": "",
@@ -558,7 +641,7 @@ def _handle_cross_exam_answer(
558
  "trap_detected": False,
559
  "trap_warning": "",
560
  "new_concessions": new_concessions,
561
- "round_number": session["current_round"],
562
  "phase": "closing",
563
  "cross_exam_complete": True,
564
  "session_ended": False,
@@ -566,11 +649,27 @@ def _handle_cross_exam_answer(
566
 
567
 
568
  def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
569
- """Handle closing arguments from both sides, then generate analysis."""
 
 
 
 
 
 
 
 
570
 
571
  user_side = session["user_side"]
572
  user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
573
 
 
 
 
 
 
 
 
 
574
  # Add user's closing
575
  add_transcript_entry(
576
  session_id=session_id,
@@ -582,33 +681,68 @@ def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
582
 
583
  fresh_session = get_session(session_id)
584
 
585
- # Opposing counsel's closing
586
- closing_messages = build_opposing_closing_prompt(fresh_session)
587
- try:
588
- opposing_closing = _call_llm(closing_messages)
589
- except Exception as e:
590
- opposing_closing = (
591
- "My Lords, for the reasons submitted throughout these proceedings, "
592
- "we respectfully submit that the petition deserves to be dismissed."
 
 
 
 
 
 
 
 
 
 
 
593
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
 
595
- add_transcript_entry(
596
- session_id=session_id,
597
- speaker="OPPOSING_COUNSEL",
598
- role_label="RESPONDENT'S COUNSEL" if user_side == "petitioner" else "PETITIONER'S COUNSEL",
599
- content=opposing_closing,
600
- entry_type="closing_argument",
601
- )
602
-
603
- # Judge's final observations
604
  fresh_session = get_session(session_id)
605
  judge_closing_messages = build_judge_closing_prompt(fresh_session)
606
  try:
607
  judge_final = _call_llm(judge_closing_messages)
608
  except Exception as e:
609
  judge_final = (
610
- "The court has heard submissions from both sides. "
611
- "The matter is reserved for orders."
612
  )
613
 
614
  add_transcript_entry(
@@ -632,7 +766,7 @@ def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
632
  advance_phase(session_id)
633
 
634
  return {
635
- "opposing_response": opposing_closing,
636
  "judge_question": judge_final,
637
  "registrar_note": registrar_final,
638
  "trap_detected": False,
@@ -687,11 +821,16 @@ def process_objection(
687
  objection_type: str,
688
  objection_text: str,
689
  ) -> Dict:
690
- """Handle a user-raised objection."""
691
  session = get_session(session_id)
692
  if not session:
693
  return {"error": "Session not found"}
694
 
 
 
 
 
 
695
  # Get what the objection is about from last transcript entry
696
  transcript = session.get("transcript", [])
697
  last_entry = transcript[-1] if transcript else {}
@@ -722,9 +861,14 @@ def process_objection(
722
 
723
  sustained = "sustained" in ruling.lower()
724
 
 
 
 
725
  return {
726
  "ruling": ruling,
727
  "sustained": sustained,
 
 
728
  }
729
 
730
 
 
197
 
198
  phase = session["phase"]
199
 
200
+ # Validate user is allowed to submit
201
+ if phase not in ["briefing", "rounds", "cross_examination", "closing"]:
202
+ return {"error": f"Session is in {phase} phase. Cannot accept submissions."}
203
+
204
  # Route to appropriate handler
205
  try:
206
  if phase == "briefing":
 
323
  user_fell_in=False,
324
  )
325
 
326
+ # UPDATE: After briefing, user is now in Round 1 and should submit their argument
327
+ update_session(session_id, {"awaiting_action": "user"})
328
+
329
  return {
330
  "opposing_response": opposing_response,
331
  "judge_question": judge_question,
 
347
  user_side = session["user_side"]
348
  user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
349
 
350
+ # TURN VALIDATION: Only user should be able to submit arguments in "rounds" phase
351
+ awaiting = session.get("awaiting_action", "user")
352
+ if awaiting != "user":
353
+ return {"error": f"It is currently waiting for {awaiting}'s submission. You cannot speak now."}
354
+
355
  # Add user argument to transcript
356
  add_transcript_entry(
357
  session_id=session_id,
 
451
  # Registrar announcement
452
  if new_phase == "cross_examination":
453
  registrar_note = build_round_announcement(session, new_round, "cross_examination")
454
+ # First cross-exam question will be generated, user will answer it
455
+ next_awaiting = "user"
456
  elif new_round <= max_rounds:
457
  registrar_note = build_round_announcement(session, new_round, "rounds")
458
+ next_awaiting = "user" # User gets to submit next round argument
459
  else:
460
  registrar_note = build_round_announcement(session, new_round, "closing")
461
+ next_awaiting = "user" # User gives closing arguments
462
+
463
+ # UPDATE: Mark that awaiting_action should be set AFTER round advances
464
+ update_session(session_id, {"awaiting_action": next_awaiting})
465
 
466
  add_transcript_entry(
467
  session_id=session_id,
 
471
  entry_type="announcement",
472
  )
473
 
474
+ # If transitioning to cross-examination, generate the first cross-exam question now
475
+ if new_phase == "cross_examination":
476
+ fresh_session = get_session(session_id)
477
+
478
+ # Retrieve precedents for cross-exam context
479
+ combined_context = _build_full_context(fresh_session)
480
+
481
+ cross_exam_messages = build_cross_examination_prompt(
482
+ session=fresh_session,
483
+ question_number=1,
484
+ retrieved_context=combined_context,
485
+ )
486
+ try:
487
+ first_cross_question = _call_llm(cross_exam_messages)
488
+ except Exception as e:
489
+ logger.error(f"Cross-exam question generation failed: {e}")
490
+ first_cross_question = (
491
+ "Counsel, in your submitted brief, you stated that [key assertion]. "
492
+ "Can you elaborate on the legal precedent supporting this position?"
493
+ )
494
+
495
+ add_transcript_entry(
496
+ session_id=session_id,
497
+ speaker="OPPOSING_COUNSEL",
498
+ role_label="RESPONDENT'S COUNSEL" if session["user_side"] == "petitioner" else "PETITIONER'S COUNSEL",
499
+ content=first_cross_question,
500
+ entry_type="cross_exam_question",
501
+ metadata={"question_number": 1},
502
+ )
503
+
504
  # Detect concessions
505
  new_concessions = _detect_concessions(user_argument, session_id, current_round)
506
 
 
527
  user_side = session["user_side"]
528
  user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
529
 
530
+ # TURN VALIDATION: Only user should answer during cross-exam
531
+ awaiting = session.get("awaiting_action", "user")
532
+ if awaiting != "user":
533
+ return {"error": f"Cannot submit answer now. Awaiting {awaiting}'s action."}
534
+
535
+ # Count how many QUESTIONS have been asked by opposing counsel
536
+ all_questions = [
537
  e for e in session.get("transcript", [])
538
+ if e.get("speaker") == "OPPOSING_COUNSEL" and e.get("entry_type") in ["cross_exam_question", "question", "answer_request"]
539
  ]
540
+ current_question_number = len(all_questions) # This is the question the user is answering
541
+ next_question_number = current_question_number + 1 # This will be the next question if there is one
542
 
543
  # Add user's answer
544
  add_transcript_entry(
 
552
  # Detect concessions in answer
553
  new_concessions = _detect_concessions(user_answer, session_id, session["current_round"])
554
 
555
+ # If more questions remaining (max 3 total), get next question
556
+ if next_question_number <= 3:
557
  query = " ".join(session.get("legal_issues", []))
558
  retrieved_precedents = _retrieve_for_court(query, session)
559
 
 
563
 
564
  cross_messages = build_cross_examination_prompt(
565
  session=fresh_session,
566
+ question_number=next_question_number,
567
  retrieved_context=combined_context,
568
  )
569
 
570
  try:
571
  next_question = _call_llm(cross_messages)
572
  except Exception as e:
573
+ next_question = f"Question {next_question_number}: Counsel, would you agree that [question]?"
574
 
575
  add_transcript_entry(
576
  session_id=session_id,
577
  speaker="OPPOSING_COUNSEL",
578
  role_label="RESPONDENT'S COUNSEL" if user_side == "petitioner" else "PETITIONER'S COUNSEL",
579
  content=next_question,
580
+ entry_type="cross_exam_question",
581
  )
582
 
583
+ # UPDATE awaiting_action: Waiting for answer to next question
584
+ update_session(session_id, {"awaiting_action": "user"})
585
+
586
  return {
587
  "opposing_response": next_question,
588
  "judge_question": "",
589
+ "registrar_note": f"Question {next_question_number} of 3.",
590
  "trap_detected": False,
591
  "trap_warning": "",
592
  "new_concessions": new_concessions,
 
599
  else:
600
  # Cross-examination complete — advance to closing
601
  advance_phase(session_id)
602
+ fresh_session = get_session(session_id)
603
+ registrar_note = build_round_announcement(fresh_session, fresh_session["current_round"], "closing")
604
 
605
  add_transcript_entry(
606
  session_id=session_id,
 
610
  entry_type="announcement",
611
  )
612
 
613
+ # If user is RESPONDENT, opposing (PETITIONER) closes first → user rebuts
614
+ # If user is PETITIONER, user closes first → opposing rebuts
615
+ if session["user_side"] == "respondent":
616
+ # Generate petitioner's closing first
617
+ closing_messages = build_opposing_closing_prompt(fresh_session)
618
+ try:
619
+ opposing_closing = _call_llm(closing_messages)
620
+ except Exception as e:
621
+ opposing_closing = (
622
+ "My Lords, the arguments advanced by Respondent's Counsel are fundamentally flawed. "
623
+ "For the reasons submitted, we pray for relief in the Petition."
624
+ )
625
+
626
+ add_transcript_entry(
627
+ session_id=session_id,
628
+ speaker="OPPOSING_COUNSEL",
629
+ role_label="PETITIONER'S COUNSEL",
630
+ content=opposing_closing,
631
+ entry_type="closing_argument",
632
+ )
633
+
634
+ # UPDATE awaiting_action: User gets to submit their closing
635
+ update_session(session_id, {"awaiting_action": "user"})
636
+
637
  return {
638
  "opposing_response": "",
639
  "judge_question": "",
 
641
  "trap_detected": False,
642
  "trap_warning": "",
643
  "new_concessions": new_concessions,
644
+ "round_number": fresh_session["current_round"],
645
  "phase": "closing",
646
  "cross_exam_complete": True,
647
  "session_ended": False,
 
649
 
650
 
651
  def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
652
+ """Handle closing arguments from both sides, then generate analysis.
653
+
654
+ In proper moot court procedure:
655
+ - Petitioner's counsel gives closing first
656
+ - Respondent's counsel gives rebuttal closing
657
+ - Petitioner does NOT get final rebuttal
658
+
659
+ This handler manages both sides appropriately based on user_side.
660
+ """
661
 
662
  user_side = session["user_side"]
663
  user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
664
 
665
+ # Check if this is first or second closing
666
+ closing_count = len([e for e in session.get("transcript", []) if e.get("entry_type") == "closing_argument"])
667
+
668
+ # TURN VALIDATION
669
+ awaiting = session.get("awaiting_action", "user")
670
+ if awaiting != "user":
671
+ return {"error": f"Cannot submit closing now. It is {awaiting}'s turn."}
672
+
673
  # Add user's closing
674
  add_transcript_entry(
675
  session_id=session_id,
 
681
 
682
  fresh_session = get_session(session_id)
683
 
684
+ # If user is petitioner (first closing), get opposing rebuttal
685
+ # If user is respondent, we're done and can finalize
686
+ if user_side == "petitioner":
687
+ # Get respondent's rebuttal closing
688
+ closing_messages = build_opposing_closing_prompt(fresh_session)
689
+ try:
690
+ opposing_closing = _call_llm(closing_messages)
691
+ except Exception as e:
692
+ opposing_closing = (
693
+ "My Lords, with respect, the submissions of the Petitioner's Counsel "
694
+ "are fundamentally flawed. For the reasons we have advanced, the petition should be dismissed."
695
+ )
696
+
697
+ add_transcript_entry(
698
+ session_id=session_id,
699
+ speaker="OPPOSING_COUNSEL",
700
+ role_label="RESPONDENT'S COUNSEL",
701
+ content=opposing_closing,
702
+ entry_type="closing_argument",
703
  )
704
+
705
+ # Mark that session is ending
706
+ update_session(session_id, {"awaiting_action": "judge"})
707
+
708
+ intermediate_return = {
709
+ "opposing_response": opposing_closing,
710
+ "judge_question": "",
711
+ "registrar_note": "Respondent's counsel has concluded. The matter is now with the Court.",
712
+ "trap_detected": False,
713
+ "trap_warning": "",
714
+ "new_concessions": [],
715
+ "round_number": session["current_round"],
716
+ "phase": "closing",
717
+ "closing_step": "second_closing_done",
718
+ "ready_for_analysis": False,
719
+ "session_ended": False,
720
+ }
721
+ else:
722
+ # User is respondent and just gave rebuttal — session is complete
723
+ update_session(session_id, {"awaiting_action": "judge"})
724
+ intermediate_return = {
725
+ "opposing_response": "",
726
+ "judge_question": "",
727
+ "registrar_note": "Respondent's counsel has concluded. The matter is now with the Court.",
728
+ "trap_detected": False,
729
+ "trap_warning": "",
730
+ "new_concessions": [],
731
+ "round_number": session["current_round"],
732
+ "phase": "closing",
733
+ "closing_step": "respondent_closing_done",
734
+ "ready_for_analysis": True,
735
+ "session_ended": False,
736
+ }
737
 
738
+ # Get judge's final observations
 
 
 
 
 
 
 
 
739
  fresh_session = get_session(session_id)
740
  judge_closing_messages = build_judge_closing_prompt(fresh_session)
741
  try:
742
  judge_final = _call_llm(judge_closing_messages)
743
  except Exception as e:
744
  judge_final = (
745
+ "The court has heard submissions from both sides. The judgment is reserved."
 
746
  )
747
 
748
  add_transcript_entry(
 
766
  advance_phase(session_id)
767
 
768
  return {
769
+ "opposing_response": intermediate_return["opposing_response"],
770
  "judge_question": judge_final,
771
  "registrar_note": registrar_final,
772
  "trap_detected": False,
 
821
  objection_type: str,
822
  objection_text: str,
823
  ) -> Dict:
824
+ """Handle a user-raised objection. Pauses round and requests judge ruling."""
825
  session = get_session(session_id)
826
  if not session:
827
  return {"error": "Session not found"}
828
 
829
+ # Objection pauses the round — mark round as "objection_pending"
830
+ phase = session.get("phase", "rounds")
831
+ if phase not in ["rounds", "cross_examination"]:
832
+ return {"error": "Cannot raise objection in current phase"}
833
+
834
  # Get what the objection is about from last transcript entry
835
  transcript = session.get("transcript", [])
836
  last_entry = transcript[-1] if transcript else {}
 
861
 
862
  sustained = "sustained" in ruling.lower()
863
 
864
+ # After ruling, resume normal flow — mark awaiting_action as "user" to continue
865
+ update_session(session_id, {"awaiting_action": "user"})
866
+
867
  return {
868
  "ruling": ruling,
869
  "sustained": sustained,
870
+ "objection_resolved": True,
871
+ "can_continue": True,
872
  }
873
 
874