Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -33,6 +33,7 @@ RATING_CHOICES = ["Bad (0)", "Borderline (1)", "Perfect (2)"]
|
|
| 33 |
RATING_MAP = {"Bad (0)": 0, "Borderline (1)": 1, "Perfect (2)": 2}
|
| 34 |
LABEL_NAMES = ["Bad", "Borderline", "Perfect"]
|
| 35 |
BAR_COLORS = ["#ef4444", "#f59e0b", "#22c55e"]
|
|
|
|
| 36 |
|
| 37 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
# Globals
|
|
@@ -289,7 +290,19 @@ def check_consensus_and_route(doc_id):
|
|
| 289 |
MONGO_COL.update_one({'_id': doc_id}, {'$set': {'status': 'conflict'}})
|
| 290 |
|
| 291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
def route_completed(doc_id):
|
|
|
|
| 293 |
if MONGO_DB is None:
|
| 294 |
return
|
| 295 |
doc = MONGO_COL.find_one({'_id': doc_id})
|
|
@@ -301,22 +314,28 @@ def route_completed(doc_id):
|
|
| 301 |
routed = {k: v for k, v in doc.items() if k != '_id'}
|
| 302 |
routed['original_id'] = doc['_id']
|
| 303 |
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
clf_match = (hd == md and hr == mr and ha == ma)
|
| 308 |
try:
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
MONGO_DB[tgt].update_one({'original_id': doc['_id']}, {'$set': routed}, upsert=True)
|
| 311 |
except Exception as e:
|
| 312 |
print(f"Route clf err: {e}")
|
| 313 |
|
|
|
|
| 314 |
if not overlap:
|
|
|
|
|
|
|
| 315 |
try:
|
| 316 |
-
if ha == 2 and
|
| 317 |
MONGO_DB['experiments_seed_success_reg_failed'].update_one(
|
| 318 |
{'original_id': doc['_id']}, {'$set': routed}, upsert=True)
|
| 319 |
-
if
|
| 320 |
MONGO_DB['experiments_reg_success_seed_failed'].update_one(
|
| 321 |
{'original_id': doc['_id']}, {'$set': routed}, upsert=True)
|
| 322 |
except Exception as e:
|
|
@@ -397,9 +416,10 @@ def fetch_next(username):
|
|
| 397 |
return _empty_ws("β MongoDB not connected.")
|
| 398 |
|
| 399 |
try:
|
|
|
|
| 400 |
doc = MONGO_COL.find_one(
|
| 401 |
{'status': 'pending', 'tagged_by': {'$nin': [username]}},
|
| 402 |
-
sort=[('num_votes', ASCENDING)])
|
| 403 |
except Exception as e:
|
| 404 |
return _empty_ws(f"β DB query failed: {e}")
|
| 405 |
|
|
@@ -477,16 +497,58 @@ def submit_feedback(username, doc_id, overlap, model_preds,
|
|
| 477 |
eval_doc['fit_quality'] = {'regular_fit': fqr, 'ai_fit': fqa}
|
| 478 |
eval_doc['fit_comment'] = {'regular_fit': c_reg or '', 'ai_fit': c_ai or ''}
|
| 479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
try:
|
| 481 |
-
|
| 482 |
'$push': {'evaluations': eval_doc},
|
| 483 |
'$inc': {'num_votes': 1},
|
| 484 |
'$addToSet': {'tagged_by': username},
|
| 485 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
updated = MONGO_COL.find_one({'_id': doc_id})
|
| 487 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
check_consensus_and_route(doc_id)
|
| 489 |
-
|
|
|
|
|
|
|
| 490 |
except Exception as e:
|
| 491 |
status = f"β Save error: {e}"
|
| 492 |
|
|
@@ -910,6 +972,234 @@ def build_ui():
|
|
| 910 |
return demo
|
| 911 |
|
| 912 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 913 |
if __name__ == '__main__':
|
| 914 |
-
|
| 915 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
RATING_MAP = {"Bad (0)": 0, "Borderline (1)": 1, "Perfect (2)": 2}
|
| 34 |
LABEL_NAMES = ["Bad", "Borderline", "Perfect"]
|
| 35 |
BAR_COLORS = ["#ef4444", "#f59e0b", "#22c55e"]
|
| 36 |
+
FAST_TRACK_THRESHOLD = 0.90 # Min confidence for 1-vote completion
|
| 37 |
|
| 38 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 39 |
# Globals
|
|
|
|
| 290 |
MONGO_COL.update_one({'_id': doc_id}, {'$set': {'status': 'conflict'}})
|
| 291 |
|
| 292 |
|
| 293 |
+
def _max_deviation(consensus, model_classes):
|
| 294 |
+
"""Calculate the maximum abs difference between human consensus and model prediction."""
|
| 295 |
+
max_diff = 0
|
| 296 |
+
for key in ('data_quality', 'regular_fit_quality', 'ai_fit_quality'):
|
| 297 |
+
h = consensus.get(key)
|
| 298 |
+
m = model_classes.get(key)
|
| 299 |
+
if h is not None and m is not None:
|
| 300 |
+
max_diff = max(max_diff, abs(h - m))
|
| 301 |
+
return max_diff
|
| 302 |
+
|
| 303 |
+
|
| 304 |
def route_completed(doc_id):
|
| 305 |
+
"""Route completed doc to granular collections based on human-model deviation."""
|
| 306 |
if MONGO_DB is None:
|
| 307 |
return
|
| 308 |
doc = MONGO_COL.find_one({'_id': doc_id})
|
|
|
|
| 314 |
routed = {k: v for k, v in doc.items() if k != '_id'}
|
| 315 |
routed['original_id'] = doc['_id']
|
| 316 |
|
| 317 |
+
# --- Classification routing (granular) ---
|
| 318 |
+
dev = _max_deviation(consensus, mc)
|
|
|
|
|
|
|
| 319 |
try:
|
| 320 |
+
if dev == 0:
|
| 321 |
+
tgt = 'experiments_clf_success'
|
| 322 |
+
elif dev >= 2:
|
| 323 |
+
tgt = 'experiments_clf_catastrophic_failure'
|
| 324 |
+
else:
|
| 325 |
+
tgt = 'experiments_clf_failed'
|
| 326 |
MONGO_DB[tgt].update_one({'original_id': doc['_id']}, {'$set': routed}, upsert=True)
|
| 327 |
except Exception as e:
|
| 328 |
print(f"Route clf err: {e}")
|
| 329 |
|
| 330 |
+
# --- Seed vs Regular edge-case routing ---
|
| 331 |
if not overlap:
|
| 332 |
+
hd = consensus.get('regular_fit_quality')
|
| 333 |
+
ha = consensus.get('ai_fit_quality')
|
| 334 |
try:
|
| 335 |
+
if ha == 2 and hd is not None and hd <= 1:
|
| 336 |
MONGO_DB['experiments_seed_success_reg_failed'].update_one(
|
| 337 |
{'original_id': doc['_id']}, {'$set': routed}, upsert=True)
|
| 338 |
+
if hd == 2 and ha is not None and ha <= 1:
|
| 339 |
MONGO_DB['experiments_reg_success_seed_failed'].update_one(
|
| 340 |
{'original_id': doc['_id']}, {'$set': routed}, upsert=True)
|
| 341 |
except Exception as e:
|
|
|
|
| 416 |
return _empty_ws("β MongoDB not connected.")
|
| 417 |
|
| 418 |
try:
|
| 419 |
+
# Smart queue: prioritize experiments flagged needs_second_opinion
|
| 420 |
doc = MONGO_COL.find_one(
|
| 421 |
{'status': 'pending', 'tagged_by': {'$nin': [username]}},
|
| 422 |
+
sort=[('needs_second_opinion', -1), ('num_votes', ASCENDING)])
|
| 423 |
except Exception as e:
|
| 424 |
return _empty_ws(f"β DB query failed: {e}")
|
| 425 |
|
|
|
|
| 497 |
eval_doc['fit_quality'] = {'regular_fit': fqr, 'ai_fit': fqa}
|
| 498 |
eval_doc['fit_comment'] = {'regular_fit': c_reg or '', 'ai_fit': c_ai or ''}
|
| 499 |
|
| 500 |
+
# Determine if this evaluation contains any manual corrections
|
| 501 |
+
all_sources = [dq['source']]
|
| 502 |
+
if overlap:
|
| 503 |
+
all_sources.append(eval_doc['fit_quality']['combined']['source'])
|
| 504 |
+
else:
|
| 505 |
+
all_sources.append(eval_doc['fit_quality']['regular_fit']['source'])
|
| 506 |
+
all_sources.append(eval_doc['fit_quality']['ai_fit']['source'])
|
| 507 |
+
has_correction = any(s == 'manual_correction' for s in all_sources)
|
| 508 |
+
all_implicit = all(s == 'implicit_agreement' for s in all_sources)
|
| 509 |
+
|
| 510 |
try:
|
| 511 |
+
update_ops = {
|
| 512 |
'$push': {'evaluations': eval_doc},
|
| 513 |
'$inc': {'num_votes': 1},
|
| 514 |
'$addToSet': {'tagged_by': username},
|
| 515 |
+
}
|
| 516 |
+
# Flag for smart queue prioritization if user corrected the model
|
| 517 |
+
if has_correction:
|
| 518 |
+
update_ops['$set'] = {'needs_second_opinion': True}
|
| 519 |
+
|
| 520 |
+
MONGO_COL.update_one({'_id': doc_id}, update_ops)
|
| 521 |
updated = MONGO_COL.find_one({'_id': doc_id})
|
| 522 |
+
votes = updated.get('num_votes', 0) if updated else 0
|
| 523 |
+
|
| 524 |
+
# --- Fast-Track: 1-vote completion ---
|
| 525 |
+
if votes == 1 and all_implicit and model_preds:
|
| 526 |
+
probs = model_preds or {}
|
| 527 |
+
dp = probs.get('data_probs') or []
|
| 528 |
+
rfp = probs.get('regular_fit_probs') or []
|
| 529 |
+
afp = probs.get('ai_fit_probs') or rfp # overlap β same as regular
|
| 530 |
+
|
| 531 |
+
high_conf = (max(dp, default=0) >= FAST_TRACK_THRESHOLD
|
| 532 |
+
and max(rfp, default=0) >= FAST_TRACK_THRESHOLD
|
| 533 |
+
and max(afp, default=0) >= FAST_TRACK_THRESHOLD)
|
| 534 |
+
|
| 535 |
+
if high_conf:
|
| 536 |
+
scores = _extract_scores(eval_doc)
|
| 537 |
+
consensus = {'data_quality': scores['data'],
|
| 538 |
+
'regular_fit_quality': scores['reg_fit'],
|
| 539 |
+
'ai_fit_quality': scores['ai_fit']}
|
| 540 |
+
MONGO_COL.update_one({'_id': doc_id},
|
| 541 |
+
{'$set': {'status': 'completed', 'final_consensus': consensus,
|
| 542 |
+
'fast_tracked': True}})
|
| 543 |
+
route_completed(doc_id)
|
| 544 |
+
status = "β‘ Fast-tracked! High-confidence implicit agreement."
|
| 545 |
+
else:
|
| 546 |
+
status = "β
Saved. Awaiting 2nd vote."
|
| 547 |
+
elif votes >= VOTES_REQUIRED:
|
| 548 |
check_consensus_and_route(doc_id)
|
| 549 |
+
status = "β
Feedback saved!"
|
| 550 |
+
else:
|
| 551 |
+
status = "β
Saved. Awaiting 2nd vote."
|
| 552 |
except Exception as e:
|
| 553 |
status = f"β Save error: {e}"
|
| 554 |
|
|
|
|
| 972 |
return demo
|
| 973 |
|
| 974 |
|
| 975 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 976 |
+
# Validation Tests (run with: python app.py --test)
|
| 977 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 978 |
+
|
| 979 |
+
def run_validation_tests():
|
| 980 |
+
"""Verify MLOps logic: fast-track, catastrophic routing, smart queue."""
|
| 981 |
+
print("=" * 60)
|
| 982 |
+
print("VALIDATION TESTS")
|
| 983 |
+
print("=" * 60)
|
| 984 |
+
passed = 0
|
| 985 |
+
failed = 0
|
| 986 |
+
|
| 987 |
+
def check(name, condition):
|
| 988 |
+
nonlocal passed, failed
|
| 989 |
+
if condition:
|
| 990 |
+
print(f" β
{name}")
|
| 991 |
+
passed += 1
|
| 992 |
+
else:
|
| 993 |
+
print(f" β {name}")
|
| 994 |
+
failed += 1
|
| 995 |
+
|
| 996 |
+
# --- 1. _max_deviation ---
|
| 997 |
+
print("\n1. Catastrophic Failure Detection (_max_deviation)")
|
| 998 |
+
check("Perfect match = 0",
|
| 999 |
+
_max_deviation({'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1000 |
+
{'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2}) == 0)
|
| 1001 |
+
check("Off-by-1 = 1",
|
| 1002 |
+
_max_deviation({'data_quality': 2, 'regular_fit_quality': 1, 'ai_fit_quality': 2},
|
| 1003 |
+
{'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2}) == 1)
|
| 1004 |
+
check("Catastrophic (2β0) = 2",
|
| 1005 |
+
_max_deviation({'data_quality': 0, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1006 |
+
{'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2}) == 2)
|
| 1007 |
+
check("Catastrophic (0β2) = 2",
|
| 1008 |
+
_max_deviation({'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1009 |
+
{'data_quality': 0, 'regular_fit_quality': 2, 'ai_fit_quality': 2}) == 2)
|
| 1010 |
+
check("Mixed: worst wins (data diff=2, fit diff=1) = 2",
|
| 1011 |
+
_max_deviation({'data_quality': 0, 'regular_fit_quality': 1, 'ai_fit_quality': 2},
|
| 1012 |
+
{'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2}) == 2)
|
| 1013 |
+
check("Missing keys handled gracefully",
|
| 1014 |
+
_max_deviation({'data_quality': 2}, {'regular_fit_quality': 0}) == 0)
|
| 1015 |
+
|
| 1016 |
+
# --- 2. Fast-Track confidence check ---
|
| 1017 |
+
print("\n2. Fast-Track Confidence Logic")
|
| 1018 |
+
high_probs = {'data_probs': [0.02, 0.03, 0.95],
|
| 1019 |
+
'regular_fit_probs': [0.01, 0.05, 0.94],
|
| 1020 |
+
'ai_fit_probs': [0.03, 0.02, 0.95]}
|
| 1021 |
+
dp = high_probs['data_probs']
|
| 1022 |
+
rfp = high_probs['regular_fit_probs']
|
| 1023 |
+
afp = high_probs['ai_fit_probs']
|
| 1024 |
+
check("All β₯ 0.90 β fast-track eligible",
|
| 1025 |
+
max(dp) >= FAST_TRACK_THRESHOLD and max(rfp) >= FAST_TRACK_THRESHOLD and max(afp) >= FAST_TRACK_THRESHOLD)
|
| 1026 |
+
|
| 1027 |
+
low_probs = {'data_probs': [0.1, 0.3, 0.6],
|
| 1028 |
+
'regular_fit_probs': [0.01, 0.05, 0.94],
|
| 1029 |
+
'ai_fit_probs': [0.03, 0.02, 0.95]}
|
| 1030 |
+
dp2 = low_probs['data_probs']
|
| 1031 |
+
check("Data conf < 0.90 β NOT eligible",
|
| 1032 |
+
not (max(dp2) >= FAST_TRACK_THRESHOLD))
|
| 1033 |
+
|
| 1034 |
+
# --- 3. _resolve_rating implicit vs manual ---
|
| 1035 |
+
print("\n3. Implicit Agreement vs Manual Correction")
|
| 1036 |
+
mp = {'model_classes': {'data_quality': 2, 'regular_fit_quality': 1, 'ai_fit_quality': 0}}
|
| 1037 |
+
r1 = _resolve_rating(None, mp, 'data_quality')
|
| 1038 |
+
check("None input β implicit_agreement, score=2",
|
| 1039 |
+
r1 == {'score': 2, 'source': 'implicit_agreement'})
|
| 1040 |
+
r2 = _resolve_rating("Bad (0)", mp, 'data_quality')
|
| 1041 |
+
check("'Bad (0)' input β manual_correction, score=0",
|
| 1042 |
+
r2 == {'score': 0, 'source': 'manual_correction'})
|
| 1043 |
+
|
| 1044 |
+
# --- 4. Smart Queue needs_second_opinion ---
|
| 1045 |
+
print("\n4. Smart Queue Prioritization")
|
| 1046 |
+
sources_all_implicit = ['implicit_agreement', 'implicit_agreement']
|
| 1047 |
+
sources_with_correction = ['implicit_agreement', 'manual_correction']
|
| 1048 |
+
check("All implicit β no needs_second_opinion flag",
|
| 1049 |
+
not any(s == 'manual_correction' for s in sources_all_implicit))
|
| 1050 |
+
check("Has correction β needs_second_opinion = True",
|
| 1051 |
+
any(s == 'manual_correction' for s in sources_with_correction))
|
| 1052 |
+
|
| 1053 |
+
# --- 5. Username normalization ---
|
| 1054 |
+
print("\n5. Username Normalization")
|
| 1055 |
+
check("'aDI mASKIL' β 'Adi Maskil'", 'aDI mASKIL'.strip().title() == 'Adi Maskil')
|
| 1056 |
+
check("'bob' β 'Bob'", 'bob'.strip().title() == 'Bob')
|
| 1057 |
+
check("'ALICE SMITH' β 'Alice Smith'", 'ALICE SMITH'.strip().title() == 'Alice Smith')
|
| 1058 |
+
|
| 1059 |
+
print(f"\n{'=' * 60}")
|
| 1060 |
+
print(f"RESULTS: {passed} passed, {failed} failed")
|
| 1061 |
+
print(f"{'=' * 60}")
|
| 1062 |
+
return failed == 0
|
| 1063 |
+
|
| 1064 |
+
|
| 1065 |
+
def run_mongodb_routing_test():
|
| 1066 |
+
"""E2E test: inject mock data β route β verify collections exist in Atlas."""
|
| 1067 |
+
print("=" * 60)
|
| 1068 |
+
print("MONGODB E2E ROUTING TEST")
|
| 1069 |
+
print("=" * 60)
|
| 1070 |
+
|
| 1071 |
+
# 1. Connect using same logic as init_globals
|
| 1072 |
+
client = None
|
| 1073 |
+
for kw in [dict(tlsCAFile=certifi.where()),
|
| 1074 |
+
dict(tls=True, tlsAllowInvalidCertificates=True)]:
|
| 1075 |
+
try:
|
| 1076 |
+
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000, **kw)
|
| 1077 |
+
client.admin.command('ping')
|
| 1078 |
+
break
|
| 1079 |
+
except Exception:
|
| 1080 |
+
client = None
|
| 1081 |
+
if not client:
|
| 1082 |
+
print("β Cannot connect to MongoDB. Aborting.")
|
| 1083 |
+
return False
|
| 1084 |
+
|
| 1085 |
+
db = client[DB_NAME]
|
| 1086 |
+
test_col = db['experiments_test_mock']
|
| 1087 |
+
print(f"β Connected to {DB_NAME}")
|
| 1088 |
+
|
| 1089 |
+
# 2. Clean up previous test data
|
| 1090 |
+
test_col.delete_many({})
|
| 1091 |
+
for col_name in ['experiments_clf_success', 'experiments_clf_failed',
|
| 1092 |
+
'experiments_clf_catastrophic_failure',
|
| 1093 |
+
'experiments_seed_success_reg_failed',
|
| 1094 |
+
'experiments_reg_success_seed_failed']:
|
| 1095 |
+
db[col_name].delete_many({'original_id': {'$regex': '^e2e_test_'}})
|
| 1096 |
+
print("β Cleaned previous test data")
|
| 1097 |
+
|
| 1098 |
+
# 3. Insert mock documents for each routing scenario
|
| 1099 |
+
test_cases = [
|
| 1100 |
+
{
|
| 1101 |
+
'name': 'Catastrophic Failure (model=2, human=0)',
|
| 1102 |
+
'_id': 'e2e_test_catastrophic',
|
| 1103 |
+
'model_predictions': {
|
| 1104 |
+
'model_classes': {'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1105 |
+
'data_probs': [0.01, 0.02, 0.97],
|
| 1106 |
+
'regular_fit_probs': [0.02, 0.03, 0.95],
|
| 1107 |
+
'ai_fit_probs': [0.01, 0.04, 0.95],
|
| 1108 |
+
},
|
| 1109 |
+
'final_consensus': {'data_quality': 0, 'regular_fit_quality': 0, 'ai_fit_quality': 0},
|
| 1110 |
+
'curves_overlap': True,
|
| 1111 |
+
'status': 'completed',
|
| 1112 |
+
'expected_collection': 'experiments_clf_catastrophic_failure',
|
| 1113 |
+
},
|
| 1114 |
+
{
|
| 1115 |
+
'name': 'Minor Failure (model=2, human=1)',
|
| 1116 |
+
'_id': 'e2e_test_minor_fail',
|
| 1117 |
+
'model_predictions': {
|
| 1118 |
+
'model_classes': {'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1119 |
+
'data_probs': [0.01, 0.02, 0.97],
|
| 1120 |
+
'regular_fit_probs': [0.02, 0.03, 0.95],
|
| 1121 |
+
'ai_fit_probs': [0.01, 0.04, 0.95],
|
| 1122 |
+
},
|
| 1123 |
+
'final_consensus': {'data_quality': 1, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1124 |
+
'curves_overlap': True,
|
| 1125 |
+
'status': 'completed',
|
| 1126 |
+
'expected_collection': 'experiments_clf_failed',
|
| 1127 |
+
},
|
| 1128 |
+
{
|
| 1129 |
+
'name': 'Perfect Match (model=human)',
|
| 1130 |
+
'_id': 'e2e_test_success',
|
| 1131 |
+
'model_predictions': {
|
| 1132 |
+
'model_classes': {'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1133 |
+
'data_probs': [0.01, 0.02, 0.97],
|
| 1134 |
+
'regular_fit_probs': [0.02, 0.03, 0.95],
|
| 1135 |
+
'ai_fit_probs': [0.01, 0.04, 0.95],
|
| 1136 |
+
},
|
| 1137 |
+
'final_consensus': {'data_quality': 2, 'regular_fit_quality': 2, 'ai_fit_quality': 2},
|
| 1138 |
+
'curves_overlap': True,
|
| 1139 |
+
'status': 'completed',
|
| 1140 |
+
'expected_collection': 'experiments_clf_success',
|
| 1141 |
+
},
|
| 1142 |
+
]
|
| 1143 |
+
|
| 1144 |
+
# Insert all test docs into the test collection
|
| 1145 |
+
for tc in test_cases:
|
| 1146 |
+
doc = {k: v for k, v in tc.items() if k not in ('name', 'expected_collection')}
|
| 1147 |
+
doc['raw_data'] = {'measured_data': {'x_values': [0, 1], 'y_values': [0, 1]}}
|
| 1148 |
+
doc['evaluations'] = []
|
| 1149 |
+
doc['tagged_by'] = []
|
| 1150 |
+
doc['num_votes'] = 2
|
| 1151 |
+
test_col.insert_one(doc)
|
| 1152 |
+
print(f"β Inserted {len(test_cases)} mock documents into experiments_test_mock")
|
| 1153 |
+
|
| 1154 |
+
# 4. Run routing on each mock doc using the REAL route_completed logic
|
| 1155 |
+
# We temporarily point MONGO_COL and MONGO_DB to our test setup
|
| 1156 |
+
global MONGO_COL, MONGO_DB
|
| 1157 |
+
original_col = MONGO_COL
|
| 1158 |
+
original_db = MONGO_DB
|
| 1159 |
+
MONGO_COL = test_col
|
| 1160 |
+
MONGO_DB = db
|
| 1161 |
+
|
| 1162 |
+
print("\nRouting documents...")
|
| 1163 |
+
for tc in test_cases:
|
| 1164 |
+
route_completed(tc['_id'])
|
| 1165 |
+
print(f" β Routed: {tc['name']}")
|
| 1166 |
+
|
| 1167 |
+
# 5. Verify each document landed in the correct collection
|
| 1168 |
+
print("\nChecking for collection creation...")
|
| 1169 |
+
all_passed = True
|
| 1170 |
+
for tc in test_cases:
|
| 1171 |
+
target = tc['expected_collection']
|
| 1172 |
+
found = db[target].find_one({'original_id': tc['_id']})
|
| 1173 |
+
if found:
|
| 1174 |
+
print(f" β
{target} contains '{tc['name']}'")
|
| 1175 |
+
else:
|
| 1176 |
+
print(f" β {target} MISSING '{tc['name']}'")
|
| 1177 |
+
all_passed = False
|
| 1178 |
+
|
| 1179 |
+
# 6. List all collections to confirm they exist in Atlas
|
| 1180 |
+
print("\nCollections in database:")
|
| 1181 |
+
for name in sorted(db.list_collection_names()):
|
| 1182 |
+
count = db[name].count_documents({})
|
| 1183 |
+
print(f" π {name} ({count} docs)")
|
| 1184 |
+
|
| 1185 |
+
# Restore globals
|
| 1186 |
+
MONGO_COL = original_col
|
| 1187 |
+
MONGO_DB = original_db
|
| 1188 |
+
|
| 1189 |
+
print(f"\n{'=' * 60}")
|
| 1190 |
+
print(f"E2E RESULT: {'ALL PASSED β
' if all_passed else 'FAILURES DETECTED β'}")
|
| 1191 |
+
print(f"{'=' * 60}")
|
| 1192 |
+
return all_passed
|
| 1193 |
+
|
| 1194 |
+
|
| 1195 |
if __name__ == '__main__':
|
| 1196 |
+
import sys
|
| 1197 |
+
if '--test' in sys.argv:
|
| 1198 |
+
success = run_validation_tests()
|
| 1199 |
+
sys.exit(0 if success else 1)
|
| 1200 |
+
elif '--e2e' in sys.argv:
|
| 1201 |
+
success = run_mongodb_routing_test()
|
| 1202 |
+
sys.exit(0 if success else 1)
|
| 1203 |
+
else:
|
| 1204 |
+
demo = build_ui()
|
| 1205 |
+
demo.launch(css=CSS, js=TOGGLE_JS)
|