pearmut 0.0.6__py3-none-any.whl → 0.1.1__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
@@ -8,7 +8,7 @@ from fastapi.responses import JSONResponse
8
8
  from fastapi.staticfiles import StaticFiles
9
9
  from pydantic import BaseModel
10
10
 
11
- from .protocols import get_next_item, log_response, reset_task
11
+ from .protocols import get_next_item, reset_task, update_progress
12
12
  from .utils import ROOT, load_progress_data, save_progress_data
13
13
 
14
14
  os.makedirs(f"{ROOT}/data/outputs", exist_ok=True)
@@ -51,6 +51,7 @@ async def _log_response(request: LogResponseRequest):
51
51
  if user_id not in progress_data[campaign_id]:
52
52
  return JSONResponse(content={"error": "Unknown user ID"}, status_code=400)
53
53
 
54
+ # append response to the output log
54
55
  with open(f"{ROOT}/data/outputs/{campaign_id}.jsonl", "a") as log_file:
55
56
  log_file.write(json.dumps(request.payload, ensure_ascii=False) + "\n")
56
57
 
@@ -67,7 +68,7 @@ async def _log_response(request: LogResponseRequest):
67
68
  for a, b in zip(times, times[1:])
68
69
  ])
69
70
 
70
- log_response(campaign_id, user_id, tasks_data, progress_data, request.item_i, request.payload)
71
+ update_progress(campaign_id, user_id, tasks_data, progress_data, request.item_i, request.payload)
71
72
  save_progress_data(progress_data)
72
73
 
73
74
  return JSONResponse(content={"status": "ok"}, status_code=200)
pearmut/cli.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Command-line interface for managing and running the Pearmut server.
3
+ """
4
+
1
5
  import argparse
2
6
  import hashlib
3
7
  import json
@@ -46,8 +50,10 @@ def _run(args_unknown):
46
50
  )
47
51
 
48
52
 
49
-
50
53
  def _add_campaign(args_unknown):
54
+ """
55
+ Add a new campaign from a JSON data file.
56
+ """
51
57
  import random
52
58
 
53
59
  import wonderwords
@@ -80,13 +86,30 @@ def _add_campaign(args_unknown):
80
86
  )
81
87
  exit(1)
82
88
 
89
+ if "info" not in campaign_data:
90
+ raise ValueError("Campaign data must contain 'info' field.")
91
+ if "data" not in campaign_data:
92
+ raise ValueError("Campaign data must contain 'data' field.")
93
+ if "type" not in campaign_data["info"]:
94
+ raise ValueError("Campaign 'info' must contain 'type' field.")
95
+ if "template" not in campaign_data["info"]:
96
+ raise ValueError("Campaign 'info' must contain 'template' field.")
97
+
83
98
  # use random words for identifying users
84
99
  rng = random.Random(campaign_data["campaign_id"])
85
100
  rword = wonderwords.RandomWord(rng=rng)
86
101
  if campaign_data["info"]["type"] == "task-based":
87
102
  tasks = campaign_data["data"]
103
+ if not isinstance(tasks, list):
104
+ raise ValueError("Task-based campaign 'data' must be a list of tasks.")
105
+ if not all(isinstance(task, list) for task in tasks):
106
+ raise ValueError("Each task in task-based campaign 'data' must be a list of items.")
88
107
  amount = len(tasks)
89
108
  elif campaign_data["info"]["type"] == "dynamic":
109
+ if "num_users" not in campaign_data:
110
+ raise ValueError("Dynamic campaigns must specify 'num_users'.")
111
+ if not isinstance(campaign_data["data"], list):
112
+ raise ValueError("Dynamic campaign 'data' must be a list of items.")
90
113
  amount = campaign_data["num_users"]
91
114
  else:
92
115
  raise ValueError(
@@ -94,6 +117,7 @@ def _add_campaign(args_unknown):
94
117
 
95
118
  user_ids = []
96
119
  while len(user_ids) < amount:
120
+ # generate random user IDs
97
121
  new_id = f"{rword.random_words(amount=1, include_parts_of_speech=['adjective'])[0]}-{rword.random_words(amount=1, include_parts_of_speech=['noun'])[0]}"
98
122
  if new_id not in user_ids:
99
123
  user_ids.append(new_id)
@@ -150,6 +174,9 @@ def _add_campaign(args_unknown):
150
174
 
151
175
 
152
176
  def main():
177
+ """
178
+ Main entry point for the CLI.
179
+ """
153
180
  args = argparse.ArgumentParser()
154
181
  args.add_argument('command', type=str, choices=['run', 'add', 'purge'])
155
182
  args, args_unknown = args.parse_known_args()
pearmut/protocols.py CHANGED
@@ -9,6 +9,9 @@ def get_next_item(
9
9
  tasks_data: dict,
10
10
  progress_data: dict,
11
11
  ) -> JSONResponse:
12
+ """
13
+ Get the next item for the user in the specified campaign.
14
+ """
12
15
  if tasks_data[campaign_id]["info"]["type"] == "task-based":
13
16
  return get_next_item_taskbased(campaign_id, user_id, tasks_data, progress_data)
14
17
  elif tasks_data[campaign_id]["info"]["type"] == "dynamic":
@@ -23,6 +26,9 @@ def get_next_item_taskbased(
23
26
  data_all: dict,
24
27
  progress_data: dict,
25
28
  ) -> JSONResponse:
29
+ """
30
+ Get the next item for task-based protocol.
31
+ """
26
32
  if all(progress_data[campaign_id][user_id]["progress"]):
27
33
  # all items completed
28
34
  # TODO: add check for data quality
@@ -51,7 +57,7 @@ def get_next_item_taskbased(
51
57
  "total": len(data_all[campaign_id]["data"][user_id]),
52
58
  },
53
59
  "info": {
54
- "status_message": data_all[campaign_id]["info"].get("status_message", ""),
60
+ "instructions": data_all[campaign_id]["info"].get("instructions", ""),
55
61
  "item_i": item_i,
56
62
  } | {
57
63
  k: v
@@ -74,6 +80,9 @@ def reset_task(
74
80
  tasks_data: dict,
75
81
  progress_data: dict,
76
82
  ) -> JSONResponse:
83
+ """
84
+ Reset the task progress for the user in the specified campaign.
85
+ """
77
86
  if tasks_data[campaign_id]["info"]["type"] == "task-based":
78
87
  progress_data[campaign_id][user_id]["progress"] = [False]*len(tasks_data[campaign_id]["data"][user_id])
79
88
  progress_data[campaign_id][user_id]["time"] = 0.0
@@ -89,7 +98,7 @@ def reset_task(
89
98
 
90
99
 
91
100
 
92
- def log_response(
101
+ def update_progress(
93
102
  campaign_id: str,
94
103
  user_id: str,
95
104
  tasks_data: dict,
@@ -97,9 +106,13 @@ def log_response(
97
106
  item_i: int,
98
107
  payload: Any,
99
108
  ) -> JSONResponse:
109
+ """
110
+ Log the user's response for the specified item in the campaign.
111
+ """
100
112
  if tasks_data[campaign_id]["info"]["type"] == "task-based":
101
113
  # even if it's already set it should be fine
102
114
  progress_data[campaign_id][user_id]["progress"][item_i] = True
115
+ # TODO: log attention checks/quality?
103
116
  return JSONResponse(content={"status": "ok"}, status_code=200)
104
117
  elif tasks_data[campaign_id]["info"]["type"] == "dynamic":
105
118
  return JSONResponse(content={"status": "error", "message": "Dynamic protocol logging not implemented yet."}, status_code=400)
@@ -1,7 +1,7 @@
1
1
  body {
2
2
  margin: 0;
3
3
  padding: 0;
4
- background: linear-gradient(135deg, #b9e2a1 0%, #dbcfe7 100%);
4
+ background: linear-gradient(135deg, #b9e2a1 0%, #e7e2cf 100%);
5
5
  background-attachment: fixed;
6
6
  }
7
7
 
@@ -14,7 +14,7 @@ body {
14
14
  width: 30%;
15
15
  background-color: #fffc;
16
16
  padding: 10px;
17
- border-radius: 4px;
17
+ border-radius: 8px;
18
18
  vertical-align: top;
19
19
  margin-left: 5px;
20
20
  }
@@ -32,25 +32,29 @@ body {
32
32
  input[type="button"] {
33
33
  background: #fff;
34
34
  border: none;
35
- border-radius: 5px;
35
+ border-radius: 8px;
36
36
  font-size: large;
37
+ box-shadow: 0 2px 4px #0001;
37
38
  }
38
39
 
39
- .button_navigation.button_selected {
40
- background: #8db3ec !important;
41
- }
42
40
 
43
41
  input[type="button"]:hover:not(:disabled) {
44
- background: #ffe;
42
+ background: #ffd;
45
43
  cursor: pointer;
46
44
  }
47
45
 
46
+ input[type="button"]:disabled {
47
+ background: #bbb;
48
+ cursor: not-allowed;
49
+ }
50
+
48
51
  label {
49
52
  user-select: none;
50
53
  }
51
54
 
52
55
  .white-box {
53
- border-radius: 5pt;
56
+ border-radius: 8px;
54
57
  background: #fff;
55
58
  padding: 15pt;
59
+ box-shadow: 0 4px 6px #0000001a
56
60
  }