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 +3 -2
- pearmut/cli.py +28 -1
- pearmut/protocols.py +15 -2
- pearmut/static/assets/style.css +12 -8
- pearmut/static/dashboard.bundle.js +1 -1
- pearmut/static/dashboard.html +4 -3
- pearmut/static/pointwise.bundle.js +1 -1
- pearmut/static/pointwise.html +85 -23
- {pearmut-0.0.6.dist-info → pearmut-0.1.1.dist-info}/METADATA +41 -23
- pearmut-0.1.1.dist-info/RECORD +17 -0
- pearmut/model.py +0 -61
- pearmut-0.0.6.dist-info/RECORD +0 -18
- {pearmut-0.0.6.dist-info → pearmut-0.1.1.dist-info}/WHEEL +0 -0
- {pearmut-0.0.6.dist-info → pearmut-0.1.1.dist-info}/entry_points.txt +0 -0
- {pearmut-0.0.6.dist-info → pearmut-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {pearmut-0.0.6.dist-info → pearmut-0.1.1.dist-info}/top_level.txt +0 -0
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,
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
|
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)
|
pearmut/static/assets/style.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
body {
|
|
2
2
|
margin: 0;
|
|
3
3
|
padding: 0;
|
|
4
|
-
background: linear-gradient(135deg, #b9e2a1 0%, #
|
|
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:
|
|
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:
|
|
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: #
|
|
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:
|
|
56
|
+
border-radius: 8px;
|
|
54
57
|
background: #fff;
|
|
55
58
|
padding: 15pt;
|
|
59
|
+
box-shadow: 0 4px 6px #0000001a
|
|
56
60
|
}
|