kaburia commited on
Commit
3857216
·
1 Parent(s): 2431094

initial test

Browse files
Files changed (2) hide show
  1. gradio_app.py +434 -0
  2. requirements.txt +21 -0
gradio_app.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import os
4
+ from huggingface_hub import HfApi
5
+ from datasets import load_dataset, Dataset
6
+ import io
7
+ # from dotenv import load_dotenv
8
+
9
+ # # Load environment variables from a .env file (if present) and read HF token
10
+ # load_dotenv()
11
+ # HF_TOKEN = os.getenv("HF_TOKEN", "YOUR_HF_WRITE_TOKEN_HERE")
12
+
13
+ # --- 1. CONFIGURATION ---
14
+
15
+ # --- !!! NEW: DEBUG/TESTING MODE !!! ---
16
+ # Set to True to use local CSV files instead of Hugging Face Hub
17
+ # This will read from PREDICTIONS_CSV and read/write to LOCAL_DATASET_PATH
18
+ DEBUG_TESTING = False
19
+ LOCAL_DATASET_PATH = "/content/drive/MyDrive/policy-evaluations/sentiment_dataset_eval.csv"
20
+ PREDICTIONS_CSV = "model_predictions.csv" # From batch_inference.py
21
+ # --- End Debug Config ---
22
+
23
+ HF = 'hf'
24
+ token = 'pQQADyqfDNewBCejvPmyMGlzpdgqDFSAFE'
25
+
26
+
27
+ HF_DATASET_REPO = "kaburia/policy-evaluations" # Your HF Dataset repo
28
+ HF_TOKEN = HF + '_' + token
29
+
30
+ # --- Email Authentication ---
31
+ APPROVED_EMAILS = {
32
+ "email1@gmail.com": "user1",
33
+ "email2@gmail.com": "user2",
34
+ "admin@policy.org": "admin_user",
35
+ "test@test.com": "test_user" # Added for easier debugging
36
+ # Add more authorized emails and their tags here
37
+ }
38
+
39
+ # --- Define Interaction Choices ---
40
+ DRILL_DOWN_MAP = {
41
+ "coherent": ["+3 Indivisible", "+2 Reinforcing", "+1 Enabling"],
42
+ "neutral": ["0 Consistent"],
43
+ "incoherent": ["-1 Constraining", "-2 Counteracting", "-3 Cancelling"]
44
+ }
45
+ ALL_DRILL_DOWN_CHOICES = DRILL_DOWN_MAP["coherent"] + DRILL_DOWN_MAP["neutral"] + DRILL_DOWN_MAP["incoherent"]
46
+ VERIFY_CHOICES = ["neutral", "coherent", "incoherent"]
47
+
48
+ # --- 2. DATA LOADING FUNCTIONS ---
49
+
50
+ def load_data_from_hub(token):
51
+ """
52
+ (LIVE MODE) Loads the dataset from Hugging Face, converts to Pandas,
53
+ and identifies pending rows.
54
+ """
55
+ if not token or token == "YOUR_HF_WRITE_TOKEN_HERE":
56
+ return None, None, "Error: Hugging Face Token is not configured."
57
+
58
+ try:
59
+ # Load the dataset
60
+ ds = load_dataset(HF_DATASET_REPO, token=token, split="train", cache_dir="./cache")
61
+ full_df = ds.to_pandas()
62
+
63
+ # Ensure required columns exist
64
+ if "UserVerifiedClass" not in full_df.columns:
65
+ return None, None, "Error: Dataset is missing 'UserVerifiedClass' column. Please run setup script."
66
+
67
+ # Create a unique key
68
+ full_df['key'] = full_df['PolicyA'] + '||' + full_df['PolicyB']
69
+
70
+ # Find rows that have NOT been annotated
71
+ pending_df = full_df[full_df['UserVerifiedClass'].isnull()].reset_index(drop=True)
72
+
73
+ status = f"Loaded {len(pending_df)} remaining items to annotate. ({len(full_df) - len(pending_df)} already complete) [LIVE: HF Hub]"
74
+ return full_df, pending_df, status
75
+
76
+ except Exception as e:
77
+ return None, None, f"Error loading dataset from Hub: {e}"
78
+
79
+ def load_data_from_local():
80
+ """
81
+ (DEBUG MODE) Loads the dataset from a local CSV file.
82
+ If it doesn't exist, it initializes it from 'model_predictions.csv'.
83
+ """
84
+ try:
85
+ if not os.path.exists(LOCAL_DATASET_PATH):
86
+ # First run: Initialize local file from predictions
87
+ print(f"'{LOCAL_DATASET_PATH}' not found. Initializing from '{PREDICTIONS_CSV}'...")
88
+ if not os.path.exists(PREDICTIONS_CSV):
89
+ return None, None, f"Error: '{PREDICTIONS_CSV}' not found. Please run batch_inference.py first."
90
+
91
+ df = pd.read_csv(PREDICTIONS_CSV)
92
+ # --- FIX: Check for 'model_label' ---
93
+ if "model_label" not in df.columns:
94
+ return None, None, f"Error: '{PREDICTIONS_CSV}' is missing 'model_label' column. Please run batch_inference.py"
95
+ # --- END FIX ---
96
+ df["UserVerifiedClass"] = pd.NA
97
+ df["DrillDownInteraction"] = pd.NA
98
+ df["AnnotatorUsername"] = pd.NA
99
+ df.to_csv(LOCAL_DATASET_PATH, index=False)
100
+ print(f"Initialized '{LOCAL_DATASET_PATH}'.")
101
+
102
+ # Load the (now existing) local file
103
+ full_df = pd.read_csv(LOCAL_DATASET_PATH)
104
+
105
+ # Ensure columns are present
106
+ for col in ["UserVerifiedClass", "DrillDownInteraction", "AnnotatorUsername"]:
107
+ if col not in full_df.columns:
108
+ full_df[col] = pd.NA
109
+
110
+ full_df['key'] = full_df['PolicyA'].astype(str) + '||' + full_df['PolicyB'].astype(str)
111
+ pending_df = full_df[full_df['UserVerifiedClass'].isnull()].reset_index(drop=True)
112
+
113
+ status = f"Loaded {len(pending_df)} remaining items to annotate. ({len(full_df) - len(pending_df)} complete) [DEBUG: Local CSV]"
114
+ return full_df, pending_df, status
115
+
116
+ except Exception as e:
117
+ return None, None, f"Error loading local dataset: {e}"
118
+
119
+ # --- 3. DATA SAVING FUNCTIONS ---
120
+
121
+ def save_annotation_to_hub(index, verified_class, drill_down, user_tag, token, full_df, pending_df):
122
+ """
123
+ (LIVE MODE) Updates the DataFrame and pushes the entire dataset back to the Hub.
124
+ """
125
+ if not drill_down:
126
+ return {status_box: "Error: Please select a drill-down interaction."}
127
+ if not user_tag:
128
+ return {status_box: "Error: User tag is missing. Please re-login."}
129
+
130
+ try:
131
+ # 1. Get the unique key of the item we just annotated
132
+ current_key = pending_df.loc[index, 'key']
133
+
134
+ # 2. Update the *full* DataFrame with the annotation and user_tag
135
+ full_df.loc[full_df['key'] == current_key, 'UserVerifiedClass'] = verified_class
136
+ full_df.loc[full_df['key'] == current_key, 'DrillDownInteraction'] = drill_down
137
+ full_df.loc[full_df['key'] == current_key, 'AnnotatorUsername'] = user_tag
138
+
139
+ # 3. Convert back to a Dataset object
140
+ ds_to_upload = Dataset.from_pandas(full_df.drop(columns=['key']))
141
+
142
+ # 4. Push to Hub
143
+ ds_to_upload.push_to_hub(HF_DATASET_REPO, token=token)
144
+
145
+ save_status = f"Saved to Hub: {verified_class} | {drill_down} by {user_tag}"
146
+
147
+ # 5. Load the next item
148
+ next_index = index + 1
149
+ ui_updates = load_next_item(pending_df, next_index) # Pass pending_df
150
+ ui_updates[status_box] = save_status
151
+ ui_updates[full_df_state] = full_df # Store the updated full_df in state
152
+ return ui_updates
153
+
154
+ except Exception as e:
155
+ return {status_box: f"Error saving to Hub: {e}"}
156
+
157
+ def save_annotation_to_local(index, verified_class, drill_down, user_tag, full_df, pending_df):
158
+ """
159
+ (DEBUG MODE) Updates the DataFrame and saves it back to the local CSV.
160
+ """
161
+ if not drill_down:
162
+ return {status_box: "Error: Please select a drill-down interaction."}
163
+ if not user_tag:
164
+ return {status_box: "Error: User tag is missing. Please re-login."}
165
+
166
+ try:
167
+ # 1. Get key
168
+ current_key = pending_df.loc[index, 'key']
169
+
170
+ # 2. Update full DataFrame
171
+ full_df.loc[full_df['key'] == current_key, 'UserVerifiedClass'] = verified_class
172
+ full_df.loc[full_df['key'] == current_key, 'DrillDownInteraction'] = drill_down
173
+ full_df.loc[full_df['key'] == current_key, 'AnnotatorUsername'] = user_tag
174
+
175
+ # 3. Save to local CSV (overwriting)
176
+ full_df.drop(columns=['key']).to_csv(LOCAL_DATASET_PATH, index=False)
177
+
178
+ save_status = f"Saved (Local): {verified_class} | {drill_down} by {user_tag}"
179
+
180
+ # 4. Load next item
181
+ next_index = index + 1
182
+ ui_updates = load_next_item(pending_df, next_index)
183
+ ui_updates[status_box] = save_status
184
+ ui_updates[full_df_state] = full_df # Store updated df in state
185
+ return ui_updates
186
+
187
+ except Exception as e:
188
+ return {status_box: f"Error saving locally: {e}"}
189
+
190
+ # --- 4. GRADIO UI ---
191
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
192
+ gr.Markdown("# Policy Coherence Annotation Tool")
193
+ gr.Markdown(
194
+ """
195
+ Welcome! This tool is for human-in-the-loop annotation.
196
+ 1. Log in with your email address.
197
+ 2. The model's prediction for two policies will be shown.
198
+ 3. **Step 1:** Verify if the model's 3-class prediction (neutral, coherent, incoherent) is correct, or change it.
199
+ 4. **Step 2:** Based on your verified choice, select a 7-class drill-down label. When you choose one of the categories we will ask the level
200
+ For example if it is incoherent, we shall ask to choose from "-1 Constraining", "-2 Counteracting", "-3 Cancelling"
201
+ 5. Click 'Save & Next' to submit your annotation and load the next item.
202
+
203
+ ---
204
+ ### Drill-Down Definitions
205
+ - **+3 Indivisible**: Inextricably linked to the achievement of another goal.
206
+ - **+2 Reinforcing**: Aids the achievement of another goal.
207
+ - **+1 Enabling**: Creates conditions that further another goal.
208
+ - **0 Consistent**: No significant positive or negative interactions.
209
+ - **-1 Constraining**: Limits options on another goal.
210
+ - **-2 Counteracting**: Clashes with another goal.
211
+ - **-3 Cancelling**: Makes it impossible to reach another goal.
212
+ """
213
+ )
214
+
215
+ # --- State variables ---
216
+ full_df_state = gr.State()
217
+ pending_df_state = gr.State()
218
+ current_index_state = gr.State(value=0)
219
+ hf_token_state = gr.State()
220
+ user_tag_state = gr.State()
221
+
222
+ # --- Section 1: Login ---
223
+ with gr.Group() as login_box:
224
+ with gr.Row():
225
+ email_box = gr.Textbox(label="Email", placeholder="Enter your authorized email...")
226
+ login_btn = gr.Button("Login & Load Dataset", variant="primary")
227
+ progress_bar = gr.Markdown(value="Waiting for login...")
228
+
229
+ # --- Section 2: Annotation (hidden until loaded) ---
230
+ with gr.Group(visible=False) as annotation_box:
231
+ # --- MODIFIED: Use gr.Row for side-by-side table layout ---
232
+ with gr.Row():
233
+ policy_a_display = gr.Textbox(label="Policy / Objective A", interactive=False, lines=5, container=True)
234
+ policy_b_display = gr.Textbox(label="Policy / Objective B", interactive=False, lines=5, container=True)
235
+ # --- END MODIFICATION ---
236
+
237
+ with gr.Row():
238
+ model_confidence_label = gr.Label(label="Model Confidence")
239
+ user_verified_radio = gr.Radio(
240
+ label="Step 1: Verify/Correct Classification",
241
+ choices=VERIFY_CHOICES,
242
+ info="The model's prediction is selected by default."
243
+ )
244
+
245
+ # --- UPDATED: Markdown instructions moved to top ---
246
+
247
+ user_drill_down_dropdown = gr.Dropdown(
248
+ label="Step 2: Drill-Down Interaction",
249
+ choices=[], # Will be populated dynamically
250
+ interactive=True
251
+ )
252
+
253
+ save_btn = gr.Button("Save & Next", variant="stop")
254
+ status_box = gr.Textbox(label="Status", interactive=False)
255
+
256
+ # --- 5. UI Event Handlers ---
257
+
258
+ def update_drill_down_choices(verified_class):
259
+ """
260
+ Updates the drill-down dropdown based on the 3-class selection.
261
+ """
262
+ choices = DRILL_DOWN_MAP.get(verified_class, [])
263
+ value = choices[0] if len(choices) == 1 else None # Auto-select "0 Consistent"
264
+ # --- FIX: Return the constructor (Gradio 4.x syntax) ---
265
+ return gr.Dropdown(
266
+ choices=choices,
267
+ value=value,
268
+ interactive=len(choices) > 1 # Disable interaction if only one choice
269
+ )
270
+
271
+ def load_next_item(pending_df, index):
272
+ """
273
+ Loads the item at 'index' from the PENDING DataFrame into the UI.
274
+ """
275
+ if pending_df is None:
276
+ return {status_box: "Data not loaded."}
277
+
278
+ total_items = len(pending_df)
279
+ if index >= total_items:
280
+ return {
281
+ progress_bar: gr.Markdown(f"**Annotation Complete! ({total_items} items total)**"),
282
+ policy_a_display: "All items annotated.",
283
+ policy_b_display: "",
284
+ annotation_box: gr.Group(visible=False)
285
+ }
286
+
287
+ row = pending_df.iloc[index]
288
+ # --- FIX: Use "model_label" from CSV ---
289
+ model_pred = row["model_label"]
290
+
291
+ # --- NEW: Build conf_dict conditionally ---
292
+ if "model_confidence" in row:
293
+ # New format: "model_label" + "model_confidence"
294
+ confidence = row["model_confidence"]
295
+ conf_dict = {}
296
+
297
+ # Distribute probability
298
+ remaining_prob = (1.0 - confidence) / 2.0
299
+ for l in VERIFY_CHOICES: # ["neutral", "coherent", "incoherent"]
300
+ if l == model_pred:
301
+ conf_dict[l] = confidence
302
+ else:
303
+ conf_dict[l] = remaining_prob
304
+ else:
305
+ # Old format: "Confidence_Neutral", etc.
306
+ conf_dict = {
307
+ "neutral": row.get("Confidence_Neutral", 0.0),
308
+ "coherent": row.get("Confidence_Coherent", 0.0),
309
+ "incoherent": row.get("Confidence_Incoherent", 0.0)
310
+ }
311
+ # --- END NEW ---
312
+
313
+ # --- NEW: Update drill-down based on model_pred ---
314
+ drill_down_choices = DRILL_DOWN_MAP.get(model_pred, [])
315
+ drill_down_value = drill_down_choices[0] if len(drill_down_choices) == 1 else None
316
+ drill_down_interactive = len(drill_down_choices) > 1
317
+
318
+ return {
319
+ progress_bar: gr.Markdown(f"**Annotating Item {index + 1} of {total_items}**"),
320
+ policy_a_display: row["PolicyA"],
321
+ policy_b_display: row["PolicyB"],
322
+ model_confidence_label: conf_dict,
323
+ user_verified_radio: model_pred,
324
+ # --- FIX: Return the constructor (Gradio 4.x syntax) ---
325
+ user_drill_down_dropdown: gr.Dropdown(
326
+ choices=drill_down_choices,
327
+ value=drill_down_value,
328
+ interactive=drill_down_interactive
329
+ ),
330
+ current_index_state: index,
331
+ annotation_box: gr.Group(visible=True)
332
+ }
333
+
334
+ # When 'Login' is clicked:
335
+ def login_and_load(email):
336
+ # --- Authentication Step ---
337
+ if email not in APPROVED_EMAILS:
338
+ return {
339
+ progress_bar: gr.Markdown(f"<font color='red'>Error: Email '{email}' is not authorized.</font>"),
340
+ login_box: gr.Group(visible=True)
341
+ }
342
+
343
+ user_tag = APPROVED_EMAILS[email] # Get the tag (e.g., "user1")
344
+
345
+ # --- NEW: Branching Logic for Debug/Live ---
346
+ if DEBUG_TESTING:
347
+ print("--- DEBUG MODE: Loading from local CSV ---")
348
+ full_df, pending_df, status = load_data_from_local()
349
+ token_to_store = "debug_mode" # Placeholder
350
+ else:
351
+ print("--- LIVE MODE: Loading from Hugging Face Hub ---")
352
+ if HF_TOKEN == "YOUR_HF_WRITE_TOKEN_HERE" or not HF_TOKEN:
353
+ return {
354
+ progress_bar: gr.Markdown(f"<font color='red'>Error: App is not configured. HF_TOKEN is missing.</font>"),
355
+ login_box: gr.Group(visible=True)
356
+ }
357
+ full_df, pending_df, status = load_data_from_hub(HF_TOKEN)
358
+ token_to_store = HF_TOKEN
359
+
360
+ # --- Common Logic ---
361
+ if full_df is None:
362
+ return {
363
+ progress_bar: gr.Markdown(f"<font color='red'>{status}</font>"),
364
+ login_box: gr.Group(visible=True)
365
+ }
366
+
367
+ # --- Load the first item ---
368
+ first_item_updates = load_next_item(pending_df, 0)
369
+
370
+ # --- Save all data to state and update UI ---
371
+ first_item_updates[full_df_state] = full_df
372
+ first_item_updates[pending_df_state] = pending_df
373
+ first_item_updates[progress_bar] = f"Login successful as **{user_tag}**. {status}"
374
+ first_item_updates[hf_token_state] = token_to_store # Save token/debug_flag to state
375
+ first_item_updates[user_tag_state] = user_tag
376
+ first_item_updates[login_box] = gr.Group(visible=False) # Hide login box
377
+ first_item_updates[annotation_box] = gr.Group(visible=True) # Show annotation box
378
+ return first_item_updates
379
+
380
+ login_btn.click(
381
+ fn=login_and_load,
382
+ inputs=[email_box], # Input is ONLY the email box
383
+ outputs=[
384
+ progress_bar, policy_a_display, policy_b_display,
385
+ model_confidence_label, user_verified_radio, user_drill_down_dropdown,
386
+ current_index_state, annotation_box, login_box,
387
+ full_df_state, pending_df_state, hf_token_state, user_tag_state, status_box
388
+ ]
389
+ )
390
+
391
+ # --- NEW: Wrapper for Save Button ---
392
+ def save_wrapper(index, verified_class, drill_down, user_tag, token, full_df, pending_df):
393
+ if DEBUG_TESTING:
394
+ return save_annotation_to_local(index, verified_class, drill_down, user_tag, full_df, pending_df)
395
+ else:
396
+ return save_annotation_to_hub(index, verified_class, drill_down, user_tag, token, full_df, pending_df)
397
+
398
+ # --- NEW: Event listener for dynamic drill-down ---
399
+ user_verified_radio.change(
400
+ fn=update_drill_down_choices,
401
+ inputs=user_verified_radio,
402
+ outputs=user_drill_down_dropdown
403
+ )
404
+
405
+ # When 'Save & Next' is clicked
406
+ save_btn.click(
407
+ fn=save_wrapper, # Call the new wrapper function
408
+ inputs=[
409
+ current_index_state,
410
+ user_verified_radio,
411
+ user_drill_down_dropdown,
412
+ user_tag_state, # Pass the user tag from state
413
+ hf_token_state, # Pass the token from state
414
+ full_df_state,
415
+ pending_df_state
416
+ ],
417
+ outputs=[
418
+ progress_bar, policy_a_display, policy_b_display,
419
+ model_confidence_label, user_verified_radio, user_drill_down_dropdown,
420
+ current_index_state, annotation_box, status_box, full_df_state
421
+ ]
422
+ )
423
+
424
+ if __name__ == "__main__":
425
+ if DEBUG_TESTING:
426
+ print("\n" + "="*30)
427
+ print("--- RUNNING IN DEBUG MODE ---")
428
+ print(f"--- Data will be read/written to '{LOCAL_DATASET_PATH}' ---")
429
+ print("="*30 + "\n")
430
+ elif HF_TOKEN == "YOUR_HF_WRITE_TOKEN_HERE":
431
+ print("\n--- WARNING: HF_TOKEN NOT SET ---")
432
+ print("Please edit 'annotation_app.py' and add your HF_TOKEN to the top.")
433
+
434
+ demo.launch(debug=True, share=True)
requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ huggingface_hub==0.25.2
2
+ gradio
3
+ transformers>=4.40.0
4
+ # langchain>=0.1.14
5
+ # sentence-transformers>=2.5.1
6
+ # faiss-cpu>=1.7.4
7
+ # torch>=2.1.0
8
+ # langchain-community>=0.0.30
9
+ # gradio-client==1.11.0
10
+ # pydantic==2.10.6
11
+ numpy
12
+ pandas
13
+ requests
14
+ datasets
15
+ # boto3
16
+ # rank-bm25
17
+ # pypdf
18
+ # Pillow
19
+ # pytesseract
20
+ # openai
21
+