pearmut 0.2.7__py3-none-any.whl → 0.2.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pearmut/app.py CHANGED
@@ -1,5 +1,7 @@
1
+ import collections
1
2
  import json
2
3
  import os
4
+ import statistics
3
5
  from typing import Any
4
6
 
5
7
  from fastapi import FastAPI, Query
@@ -12,6 +14,7 @@ from .assignment import get_i_item, get_next_item, reset_task, update_progress
12
14
  from .utils import (
13
15
  ROOT,
14
16
  check_validation_threshold,
17
+ get_db_log,
15
18
  load_progress_data,
16
19
  save_db_payload,
17
20
  save_progress_data,
@@ -191,6 +194,58 @@ async def _dashboard_data(request: DashboardDataRequest):
191
194
  )
192
195
 
193
196
 
197
+ class DashboardResultsRequest(BaseModel):
198
+ campaign_id: str
199
+ token: str
200
+
201
+
202
+ @app.post("/dashboard-results")
203
+ async def _dashboard_results(request: DashboardResultsRequest):
204
+ campaign_id = request.campaign_id
205
+ token = request.token
206
+
207
+ if campaign_id not in progress_data:
208
+ return JSONResponse(content="Unknown campaign ID", status_code=400)
209
+
210
+ # Check if token is valid
211
+ if token != tasks_data[campaign_id]["token"]:
212
+ return JSONResponse(content="Invalid token", status_code=400)
213
+
214
+ # Compute model scores from annotations
215
+ model_scores = collections.defaultdict(dict)
216
+
217
+ # Iterate through all tasks to find items with 'model' field
218
+ log = get_db_log(campaign_id)
219
+ for entry in log:
220
+ if "item" not in entry or "annotations" not in entry:
221
+ continue
222
+ for item, annotation in zip(entry["item"], entry["annotations"]):
223
+ if "model" in item:
224
+ # pointwise
225
+ if "score" in annotation:
226
+ # make sure to only keep the latest score for each item
227
+ # json.dumps(item) creates a unique item key
228
+ model_scores[item["model"]][json.dumps(item)] = annotation["score"]
229
+ elif "models" in item:
230
+ # listwise
231
+ for model, annotation_cand in zip(item["models"], annotation):
232
+ if "score" in annotation_cand:
233
+ model_scores[model][json.dumps(item)] = (
234
+ annotation_cand["score"]
235
+ )
236
+
237
+ results = [
238
+ {
239
+ "model": model,
240
+ "score": statistics.mean(scores.values()),
241
+ "count": len(scores),
242
+ }
243
+ for model, scores in model_scores.items()
244
+ ]
245
+ results.sort(key=lambda x: x["score"], reverse=True)
246
+ return JSONResponse(content=results, status_code=200)
247
+
248
+
194
249
  class ResetTaskRequest(BaseModel):
195
250
  campaign_id: str
196
251
  user_id: str
pearmut/cli.py CHANGED
@@ -41,7 +41,9 @@ def _run(args_unknown):
41
41
  args.server + "/dashboard.html?" + "&".join([
42
42
  f"campaign_id={urllib.parse.quote_plus(campaign_id)}&token={campaign_data["token"]}"
43
43
  for campaign_id, campaign_data in tasks_data.items()
44
- ])
44
+ ]),
45
+ # this is important to flush
46
+ flush=True,
45
47
  )
46
48
 
47
49
  uvicorn.run(
@@ -278,26 +280,24 @@ def _add_single_campaign(data_file, overwrite, server):
278
280
  )
279
281
 
280
282
  # Symlink path is based on the destination, stripping the 'assets/' prefix
281
- symlink_path = f"{STATIC_DIR}/{assets_destination}"
283
+ symlink_path = f"{STATIC_DIR}/{assets_destination}".rstrip("/")
282
284
 
283
285
  # Remove existing symlink if present and we are overriding the same campaign
284
286
  if os.path.lexists(symlink_path):
285
287
  # Check if any other campaign is using this destination
286
288
  current_campaign_id = campaign_data['campaign_id']
287
- tasks_dir = f"{ROOT}/data/tasks"
288
- if os.path.exists(tasks_dir):
289
- for task_file in os.listdir(tasks_dir):
290
- if task_file.endswith('.json'):
291
- other_campaign_id = task_file[:-5]
292
- if other_campaign_id != current_campaign_id:
293
- with open(f"{tasks_dir}/{task_file}", "r") as f:
294
- other_campaign = json.load(f)
295
- other_assets = other_campaign.get("info", {}).get("assets")
296
- if other_assets and isinstance(other_assets, dict):
297
- if other_assets.get("destination") == assets_destination:
298
- raise ValueError(
299
- f"Assets destination '{assets_destination}' is already used by campaign '{other_campaign_id}'."
300
- )
289
+
290
+ for other_campaign_id in progress_data.keys():
291
+ if other_campaign_id == current_campaign_id:
292
+ continue
293
+ with open(f"{ROOT}/data/tasks/{other_campaign_id}.json", "r") as f:
294
+ other_campaign = json.load(f)
295
+ other_assets = other_campaign.get("info", {}).get("assets")
296
+ if other_assets:
297
+ if other_assets.get("destination") == assets_destination:
298
+ raise ValueError(
299
+ f"Assets destination '{assets_destination}' is already used by campaign '{other_campaign_id}'."
300
+ )
301
301
  # Only allow overwrite if it's the same campaign
302
302
  if overwrite:
303
303
  os.remove(symlink_path)
@@ -305,7 +305,8 @@ def _add_single_campaign(data_file, overwrite, server):
305
305
  raise ValueError(f"Assets destination '{assets_destination}' is already taken.")
306
306
 
307
307
  # Ensure the assets directory exists
308
- os.makedirs(f"{STATIC_DIR}/assets", exist_ok=True)
308
+ # get parent of symlink_path dir
309
+ os.makedirs(os.path.dirname(symlink_path), exist_ok=True)
309
310
 
310
311
  os.symlink(assets_real_path, symlink_path, target_is_directory=True)
311
312
  print(f"Assets symlinked: {symlink_path} -> {assets_real_path}")
@@ -391,7 +392,7 @@ def main():
391
392
  campaign_data = json.load(f)
392
393
  destination = campaign_data.get("info", {}).get("assets", {}).get("destination")
393
394
  if destination:
394
- symlink_path = f"{STATIC_DIR}/{destination}"
395
+ symlink_path = f"{STATIC_DIR}/{destination}".rstrip("/")
395
396
  if os.path.islink(symlink_path):
396
397
  os.remove(symlink_path)
397
398
  print(f"Assets symlink removed: {symlink_path}")
@@ -436,12 +437,9 @@ def main():
436
437
  )
437
438
  if confirm.lower() == 'y':
438
439
  # Unlink all assets first
439
- tasks_dir = f"{ROOT}/data/tasks"
440
- if os.path.exists(tasks_dir):
441
- for task_file in os.listdir(tasks_dir):
442
- if task_file.endswith('.json'):
443
- campaign_id = task_file[:-5]
444
- _unlink_assets(campaign_id)
440
+ progress_data = load_progress_data()
441
+ for campaign_id in progress_data.keys():
442
+ _unlink_assets(campaign_id)
445
443
  shutil.rmtree(f"{ROOT}/data/tasks", ignore_errors=True)
446
444
  shutil.rmtree(f"{ROOT}/data/outputs", ignore_errors=True)
447
445
  if os.path.exists(f"{ROOT}/data/progress.json"):