mihretgold commited on
Commit
cf97cee
Β·
verified Β·
1 Parent(s): ceeec0a

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +44 -25
  2. study_utils.py +18 -0
app.py CHANGED
@@ -36,6 +36,8 @@ from study_utils import (
36
  log_survey_responses,
37
  log_final_selections,
38
  log_final_survey,
 
 
39
  )
40
 
41
  EMPTY_ATTR = {"attribute": "", "weight": 0.7}
@@ -1255,8 +1257,9 @@ with gr.Blocks(
1255
  if errors:
1256
  return _err("\n\n".join(errors[:10]))
1257
 
1258
- # ── Save annotations to Firebase ──
1259
  try:
 
1260
  for q in range(NUM_QUERIES):
1261
  qmeta = (all_meta or {}).get(q, {})
1262
  qr = radio_vals[q * 10:(q + 1) * 10]
@@ -1267,38 +1270,54 @@ with gr.Blocks(
1267
  linear_ids = qmeta.get("linear_ids", [])
1268
  all_ids = base_ids[:5] + linear_ids[:5]
1269
  all_ids += [None] * (10 - len(all_ids))
 
1270
 
1271
  for i, (img_id, rv) in enumerate(zip(all_ids, qr)):
1272
  if img_id:
1273
  method = "baseline" if i < 5 else "linear"
1274
- log_image_annotations(
1275
- pid, q, img_id, method,
1276
- 1 if str(rv).strip() == "Yes" else 0,
1277
- )
1278
- log_method_comparison(pid, q, cv)
1279
- log_survey_responses(
1280
- pid, q, ql[0], ql[1], ql[2], ql[3],
1281
- qmeta.get("round_sat", 0),
1282
- qmeta.get("time_elapsed", 0),
1283
- )
1284
- log_final_selections(
1285
- pid, q,
1286
- ",".join(base_ids[:5]),
1287
- ",".join(linear_ids[:5]),
1288
- qmeta.get("round_sat", 0),
1289
- qmeta.get("time_elapsed", 0),
1290
- )
1291
- except Exception as e:
1292
- return _err(f"Annotation save error: {str(e)}. Please try again.")
1293
-
1294
- # ── Save final survey ──
1295
- try:
 
 
 
 
 
 
1296
  open_fb = (feedback or "").strip()
1297
  if conc_val == "Yes" and (concept_text or "").strip():
1298
  open_fb = (concept_text or "").strip() + "\n\n" + open_fb
1299
- log_final_survey(pid, pref_val, conc_val, open_fb)
 
 
 
 
 
 
 
 
 
1300
  except Exception as e:
1301
- return _err(f"Survey save error: {str(e)}. Please try again.")
1302
 
1303
  return gr.update(visible=False), gr.update(value=""), gr.update(visible=True)
1304
 
 
36
  log_survey_responses,
37
  log_final_selections,
38
  log_final_survey,
39
+ firestore_batch_add,
40
+ _iso_ts,
41
  )
42
 
43
  EMPTY_ATTR = {"attribute": "", "weight": 0.7}
 
1257
  if errors:
1258
  return _err("\n\n".join(errors[:10]))
1259
 
1260
+ # ── Collect all writes, then batch-commit to Firebase ──
1261
  try:
1262
+ writes = [] # list of (collection, data_dict)
1263
  for q in range(NUM_QUERIES):
1264
  qmeta = (all_meta or {}).get(q, {})
1265
  qr = radio_vals[q * 10:(q + 1) * 10]
 
1270
  linear_ids = qmeta.get("linear_ids", [])
1271
  all_ids = base_ids[:5] + linear_ids[:5]
1272
  all_ids += [None] * (10 - len(all_ids))
1273
+ ts = _iso_ts()
1274
 
1275
  for i, (img_id, rv) in enumerate(zip(all_ids, qr)):
1276
  if img_id:
1277
  method = "baseline" if i < 5 else "linear"
1278
+ writes.append(("image_annotations", {
1279
+ "participant_id": pid, "query_id": q,
1280
+ "image_id": img_id, "method": method,
1281
+ "meets_intent": 1 if str(rv).strip() == "Yes" else 0,
1282
+ "timestamp": ts,
1283
+ }))
1284
+ writes.append(("method_comparison", {
1285
+ "participant_id": pid, "query_id": q,
1286
+ "linear_better": cv, "timestamp": ts,
1287
+ }))
1288
+ writes.append(("survey_responses", {
1289
+ "participant_id": pid, "query_id": q,
1290
+ "alignment_score": ql[0], "agency_score": ql[1],
1291
+ "satisfaction_score": ql[2], "frustration_score": ql[3],
1292
+ "round_satisfied": qmeta.get("round_sat", 0),
1293
+ "time_elapsed": qmeta.get("time_elapsed", 0),
1294
+ "timestamp": ts,
1295
+ }))
1296
+ writes.append(("final_selections", {
1297
+ "participant_id": pid, "query_id": q,
1298
+ "baseline_final_image_ids": ",".join(base_ids[:5]),
1299
+ "linear_final_image_ids": ",".join(linear_ids[:5]),
1300
+ "round_satisfied": qmeta.get("round_sat", 0),
1301
+ "time_elapsed": qmeta.get("time_elapsed", 0),
1302
+ "timestamp": ts,
1303
+ }))
1304
+
1305
+ # Final survey (1 document)
1306
  open_fb = (feedback or "").strip()
1307
  if conc_val == "Yes" and (concept_text or "").strip():
1308
  open_fb = (concept_text or "").strip() + "\n\n" + open_fb
1309
+ writes.append(("final_survey", {
1310
+ "participant_id": pid,
1311
+ "preferred_system": pref_val,
1312
+ "concept_changed": conc_val,
1313
+ "open_feedback": open_fb,
1314
+ "timestamp": _iso_ts(),
1315
+ }))
1316
+
1317
+ # Single batch commit (~287 docs in 1 round-trip)
1318
+ firestore_batch_add(writes)
1319
  except Exception as e:
1320
+ return _err(f"Save error: {str(e)}. Please try again.")
1321
 
1322
  return gr.update(visible=False), gr.update(value=""), gr.update(visible=True)
1323
 
study_utils.py CHANGED
@@ -91,6 +91,24 @@ def firestore_add(collection: str, data: dict) -> None:
91
  db.collection(collection).add(data)
92
 
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  def firestore_query_exists(collection: str, field: str, value) -> bool:
95
  """Return True if at least one document matches field == value."""
96
  db = _get_db()
 
91
  db.collection(collection).add(data)
92
 
93
 
94
+ def firestore_batch_add(items: list[tuple[str, dict]]) -> None:
95
+ """Add many documents efficiently using Firestore batch writes.
96
+
97
+ Args:
98
+ items: list of (collection_name, data_dict) tuples.
99
+ Firestore batches support up to 500 ops each;
100
+ this function auto-splits into multiple batches.
101
+ """
102
+ db = _get_db()
103
+ BATCH_LIMIT = 450 # stay under Firestore's 500-op limit
104
+ for start in range(0, len(items), BATCH_LIMIT):
105
+ batch = db.batch()
106
+ for collection, data in items[start:start + BATCH_LIMIT]:
107
+ ref = db.collection(collection).document()
108
+ batch.set(ref, data)
109
+ batch.commit()
110
+
111
+
112
  def firestore_query_exists(collection: str, field: str, value) -> bool:
113
  """Return True if at least one document matches field == value."""
114
  db = _get_db()