pearmut 0.2.9__tar.gz → 0.2.11__tar.gz
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-0.2.9 → pearmut-0.2.11}/PKG-INFO +53 -3
- {pearmut-0.2.9 → pearmut-0.2.11}/README.md +52 -2
- {pearmut-0.2.9 → pearmut-0.2.11}/pearmut.egg-info/PKG-INFO +53 -3
- {pearmut-0.2.9 → pearmut-0.2.11}/pearmut.egg-info/SOURCES.txt +2 -2
- {pearmut-0.2.9 → pearmut-0.2.11}/pyproject.toml +1 -1
- {pearmut-0.2.9 → pearmut-0.2.11}/server/app.py +20 -2
- {pearmut-0.2.9 → pearmut-0.2.11}/server/assignment.py +24 -8
- {pearmut-0.2.9 → pearmut-0.2.11}/server/cli.py +3 -8
- pearmut-0.2.11/server/static/dashboard.bundle.js +1 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/server/static/dashboard.html +1 -1
- pearmut-0.2.11/server/static/index.html +1 -0
- pearmut-0.2.11/server/static/listwise.bundle.js +1 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/server/static/listwise.html +2 -2
- pearmut-0.2.11/server/static/pointwise.bundle.js +1 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/server/static/pointwise.html +2 -2
- pearmut-0.2.9/server/static/dashboard.bundle.js +0 -1
- pearmut-0.2.9/server/static/index.html +0 -1
- pearmut-0.2.9/server/static/listwise.bundle.js +0 -1
- pearmut-0.2.9/server/static/pointwise.bundle.js +0 -1
- {pearmut-0.2.9 → pearmut-0.2.11}/LICENSE +0 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/pearmut.egg-info/dependency_links.txt +0 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/pearmut.egg-info/entry_points.txt +0 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/pearmut.egg-info/requires.txt +0 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/pearmut.egg-info/top_level.txt +0 -0
- {pearmut-0.2.9/server/static/assets → pearmut-0.2.11/server/static}/favicon.svg +0 -0
- {pearmut-0.2.9/server/static/assets → pearmut-0.2.11/server/static}/style.css +0 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/server/utils.py +0 -0
- {pearmut-0.2.9 → pearmut-0.2.11}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pearmut
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
4
4
|
Summary: A tool for evaluation of model outputs, primarily MT.
|
|
5
5
|
Author-email: Vilém Zouhar <vilem.zouhar@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -47,9 +47,13 @@ Dynamic: license-file
|
|
|
47
47
|
- [Hosting Assets](#hosting-assets)
|
|
48
48
|
- [Campaign Management](#campaign-management)
|
|
49
49
|
- [CLI Commands](#cli-commands)
|
|
50
|
+
- [Terminology](#terminology)
|
|
50
51
|
- [Development](#development)
|
|
51
52
|
- [Citation](#citation)
|
|
52
53
|
|
|
54
|
+
|
|
55
|
+
**Error Span** — A highlighted segment of text marked as containing an error, with optional severity (`minor`, `major`, `neutral`) and MQM category labels.
|
|
56
|
+
|
|
53
57
|
## Quick Start
|
|
54
58
|
|
|
55
59
|
Install and run locally without cloning:
|
|
@@ -193,7 +197,21 @@ Add `validation` rules for tutorials or attention checks:
|
|
|
193
197
|
- **Silent attention checks**: Omit `warning` to log failures without notification (quality control)
|
|
194
198
|
|
|
195
199
|
For listwise, `validation` is an array (one per candidate). Dashboard shows ✅/❌ based on `validation_threshold` in `info` (integer for max failed count, float \[0,1\) for max proportion, default 0).
|
|
196
|
-
|
|
200
|
+
|
|
201
|
+
**Listwise score comparison:** Use `score_greaterthan` to ensure one candidate scores higher than another:
|
|
202
|
+
```python
|
|
203
|
+
{
|
|
204
|
+
"src": "AI transforms industries.",
|
|
205
|
+
"tgt": ["UI transformuje průmysly.", "Umělá inteligence mění obory."],
|
|
206
|
+
"validation": [
|
|
207
|
+
{"warning": "A has error, score 20-40.", "score": [20, 40]},
|
|
208
|
+
{"warning": "B is correct and must score higher than A.", "score": [70, 90], "score_greaterthan": 0}
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
The `score_greaterthan` field specifies the index of the candidate that must have a lower score than the current candidate.
|
|
213
|
+
|
|
214
|
+
See [examples/tutorial_pointwise.json](examples/tutorial_pointwise.json), [examples/tutorial_listwise.json](examples/tutorial_listwise.json), and [examples/tutorial_listwise_score_greaterthan.json](examples/tutorial_listwise_score_greaterthan.json).
|
|
197
215
|
|
|
198
216
|
### Single-stream Assignment
|
|
199
217
|
|
|
@@ -294,6 +312,38 @@ Items need `model` field (pointwise) or `models` field (listwise) and the `proto
|
|
|
294
312
|
```
|
|
295
313
|
See an example in [Campaign Management](#campaign-management)
|
|
296
314
|
|
|
315
|
+
|
|
316
|
+
## Terminology
|
|
317
|
+
|
|
318
|
+
- **Campaign**: An annotation project that contains configuration, data, and user assignments. Each campaign has a unique identifier and is defined in a JSON file.
|
|
319
|
+
- **Campaign File**: A JSON file that defines the campaign configuration, including the campaign ID, assignment type, protocol settings, and annotation data.
|
|
320
|
+
- **Campaign ID**: A unique identifier for a campaign (e.g., `"wmt25_#_en-cs_CZ"`). Used to reference and manage specific campaigns.
|
|
321
|
+
- **Task**: A unit of work assigned to a user. In task-based assignment, each task consists of a predefined set of items for a specific user.
|
|
322
|
+
- **Item** — A single annotation unit within a task. For translation evaluation, an item typically represents a document (source text and target translation). Items can contain text, images, audio, or video.
|
|
323
|
+
- **Document** — A collection of one or more segments (sentence pairs or text units) that are evaluated together as a single item.
|
|
324
|
+
- **User** / **Annotator**: A person who performs annotations in a campaign. Each user is identified by a unique user ID and accesses the campaign through a unique URL.
|
|
325
|
+
- **Attention Check** — A validation item with known correct answers used to ensure annotator quality. Can be:
|
|
326
|
+
- **Loud**: Shows warning message and forces retry on failure
|
|
327
|
+
- **Silent**: Logs failures without notifying the user (for quality control analysis)
|
|
328
|
+
- **Token** — A completion code shown to users when they finish their annotations. Tokens verify the completion and whether the user passed quality control checks:
|
|
329
|
+
- **Pass Token** (`token_pass`): Shown when user meets validation thresholds
|
|
330
|
+
- **Fail Token** (`token_fail`): Shown when user fails to meet validation requirements
|
|
331
|
+
- **Tutorial**: An instructional validation item that teaches users how to annotate. Includes `allow_skip: true` to let users skip if they have seen it before.
|
|
332
|
+
- **Validation**: Quality control rules attached to items that check if annotations match expected criteria (score ranges, error span locations, etc.). Used for tutorials and attention checks.
|
|
333
|
+
- **Model**: The system or model that generated the output being evaluated (e.g., `"GPT-4"`, `"Claude"`). Used for tracking and ranking model performance.
|
|
334
|
+
- **Dashboard**: The management interface that shows campaign progress, annotator statistics, access links, and allows downloading annotations. Accessed via a special management URL with token authentication.
|
|
335
|
+
- **Protocol**: The annotation scheme defining what data is collected:
|
|
336
|
+
- **Score**: Numeric quality rating (0-100)
|
|
337
|
+
- **Error Spans**: Text highlights marking errors
|
|
338
|
+
- **Error Categories**: MQM taxonomy labels for errors
|
|
339
|
+
- **Template**: The annotation interface type:
|
|
340
|
+
- **Pointwise**: Evaluate one output at a time
|
|
341
|
+
- **Listwise**: Compare multiple outputs simultaneously
|
|
342
|
+
- **Assignment**: The method for distributing items to users:
|
|
343
|
+
- **Task-based**: Each user has predefined items
|
|
344
|
+
- **Single-stream**: Users draw from a shared pool with random assignment
|
|
345
|
+
- **Dynamic**: Work in progress
|
|
346
|
+
|
|
297
347
|
## Development
|
|
298
348
|
|
|
299
349
|
Server responds to data-only requests from frontend (no template coupling). Frontend served from pre-built `static/` on install.
|
|
@@ -333,7 +383,7 @@ If you use this work in your paper, please cite as following.
|
|
|
333
383
|
author={Vilém Zouhar},
|
|
334
384
|
title={Pearmut: Platform for Evaluating and Reviewing of Multilingual Tasks},
|
|
335
385
|
url={https://github.com/zouharvi/pearmut/},
|
|
336
|
-
year={
|
|
386
|
+
year={2026},
|
|
337
387
|
}
|
|
338
388
|
```
|
|
339
389
|
|
|
@@ -27,9 +27,13 @@
|
|
|
27
27
|
- [Hosting Assets](#hosting-assets)
|
|
28
28
|
- [Campaign Management](#campaign-management)
|
|
29
29
|
- [CLI Commands](#cli-commands)
|
|
30
|
+
- [Terminology](#terminology)
|
|
30
31
|
- [Development](#development)
|
|
31
32
|
- [Citation](#citation)
|
|
32
33
|
|
|
34
|
+
|
|
35
|
+
**Error Span** — A highlighted segment of text marked as containing an error, with optional severity (`minor`, `major`, `neutral`) and MQM category labels.
|
|
36
|
+
|
|
33
37
|
## Quick Start
|
|
34
38
|
|
|
35
39
|
Install and run locally without cloning:
|
|
@@ -173,7 +177,21 @@ Add `validation` rules for tutorials or attention checks:
|
|
|
173
177
|
- **Silent attention checks**: Omit `warning` to log failures without notification (quality control)
|
|
174
178
|
|
|
175
179
|
For listwise, `validation` is an array (one per candidate). Dashboard shows ✅/❌ based on `validation_threshold` in `info` (integer for max failed count, float \[0,1\) for max proportion, default 0).
|
|
176
|
-
|
|
180
|
+
|
|
181
|
+
**Listwise score comparison:** Use `score_greaterthan` to ensure one candidate scores higher than another:
|
|
182
|
+
```python
|
|
183
|
+
{
|
|
184
|
+
"src": "AI transforms industries.",
|
|
185
|
+
"tgt": ["UI transformuje průmysly.", "Umělá inteligence mění obory."],
|
|
186
|
+
"validation": [
|
|
187
|
+
{"warning": "A has error, score 20-40.", "score": [20, 40]},
|
|
188
|
+
{"warning": "B is correct and must score higher than A.", "score": [70, 90], "score_greaterthan": 0}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
The `score_greaterthan` field specifies the index of the candidate that must have a lower score than the current candidate.
|
|
193
|
+
|
|
194
|
+
See [examples/tutorial_pointwise.json](examples/tutorial_pointwise.json), [examples/tutorial_listwise.json](examples/tutorial_listwise.json), and [examples/tutorial_listwise_score_greaterthan.json](examples/tutorial_listwise_score_greaterthan.json).
|
|
177
195
|
|
|
178
196
|
### Single-stream Assignment
|
|
179
197
|
|
|
@@ -274,6 +292,38 @@ Items need `model` field (pointwise) or `models` field (listwise) and the `proto
|
|
|
274
292
|
```
|
|
275
293
|
See an example in [Campaign Management](#campaign-management)
|
|
276
294
|
|
|
295
|
+
|
|
296
|
+
## Terminology
|
|
297
|
+
|
|
298
|
+
- **Campaign**: An annotation project that contains configuration, data, and user assignments. Each campaign has a unique identifier and is defined in a JSON file.
|
|
299
|
+
- **Campaign File**: A JSON file that defines the campaign configuration, including the campaign ID, assignment type, protocol settings, and annotation data.
|
|
300
|
+
- **Campaign ID**: A unique identifier for a campaign (e.g., `"wmt25_#_en-cs_CZ"`). Used to reference and manage specific campaigns.
|
|
301
|
+
- **Task**: A unit of work assigned to a user. In task-based assignment, each task consists of a predefined set of items for a specific user.
|
|
302
|
+
- **Item** — A single annotation unit within a task. For translation evaluation, an item typically represents a document (source text and target translation). Items can contain text, images, audio, or video.
|
|
303
|
+
- **Document** — A collection of one or more segments (sentence pairs or text units) that are evaluated together as a single item.
|
|
304
|
+
- **User** / **Annotator**: A person who performs annotations in a campaign. Each user is identified by a unique user ID and accesses the campaign through a unique URL.
|
|
305
|
+
- **Attention Check** — A validation item with known correct answers used to ensure annotator quality. Can be:
|
|
306
|
+
- **Loud**: Shows warning message and forces retry on failure
|
|
307
|
+
- **Silent**: Logs failures without notifying the user (for quality control analysis)
|
|
308
|
+
- **Token** — A completion code shown to users when they finish their annotations. Tokens verify the completion and whether the user passed quality control checks:
|
|
309
|
+
- **Pass Token** (`token_pass`): Shown when user meets validation thresholds
|
|
310
|
+
- **Fail Token** (`token_fail`): Shown when user fails to meet validation requirements
|
|
311
|
+
- **Tutorial**: An instructional validation item that teaches users how to annotate. Includes `allow_skip: true` to let users skip if they have seen it before.
|
|
312
|
+
- **Validation**: Quality control rules attached to items that check if annotations match expected criteria (score ranges, error span locations, etc.). Used for tutorials and attention checks.
|
|
313
|
+
- **Model**: The system or model that generated the output being evaluated (e.g., `"GPT-4"`, `"Claude"`). Used for tracking and ranking model performance.
|
|
314
|
+
- **Dashboard**: The management interface that shows campaign progress, annotator statistics, access links, and allows downloading annotations. Accessed via a special management URL with token authentication.
|
|
315
|
+
- **Protocol**: The annotation scheme defining what data is collected:
|
|
316
|
+
- **Score**: Numeric quality rating (0-100)
|
|
317
|
+
- **Error Spans**: Text highlights marking errors
|
|
318
|
+
- **Error Categories**: MQM taxonomy labels for errors
|
|
319
|
+
- **Template**: The annotation interface type:
|
|
320
|
+
- **Pointwise**: Evaluate one output at a time
|
|
321
|
+
- **Listwise**: Compare multiple outputs simultaneously
|
|
322
|
+
- **Assignment**: The method for distributing items to users:
|
|
323
|
+
- **Task-based**: Each user has predefined items
|
|
324
|
+
- **Single-stream**: Users draw from a shared pool with random assignment
|
|
325
|
+
- **Dynamic**: Work in progress
|
|
326
|
+
|
|
277
327
|
## Development
|
|
278
328
|
|
|
279
329
|
Server responds to data-only requests from frontend (no template coupling). Frontend served from pre-built `static/` on install.
|
|
@@ -313,7 +363,7 @@ If you use this work in your paper, please cite as following.
|
|
|
313
363
|
author={Vilém Zouhar},
|
|
314
364
|
title={Pearmut: Platform for Evaluating and Reviewing of Multilingual Tasks},
|
|
315
365
|
url={https://github.com/zouharvi/pearmut/},
|
|
316
|
-
year={
|
|
366
|
+
year={2026},
|
|
317
367
|
}
|
|
318
368
|
```
|
|
319
369
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pearmut
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
4
4
|
Summary: A tool for evaluation of model outputs, primarily MT.
|
|
5
5
|
Author-email: Vilém Zouhar <vilem.zouhar@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -47,9 +47,13 @@ Dynamic: license-file
|
|
|
47
47
|
- [Hosting Assets](#hosting-assets)
|
|
48
48
|
- [Campaign Management](#campaign-management)
|
|
49
49
|
- [CLI Commands](#cli-commands)
|
|
50
|
+
- [Terminology](#terminology)
|
|
50
51
|
- [Development](#development)
|
|
51
52
|
- [Citation](#citation)
|
|
52
53
|
|
|
54
|
+
|
|
55
|
+
**Error Span** — A highlighted segment of text marked as containing an error, with optional severity (`minor`, `major`, `neutral`) and MQM category labels.
|
|
56
|
+
|
|
53
57
|
## Quick Start
|
|
54
58
|
|
|
55
59
|
Install and run locally without cloning:
|
|
@@ -193,7 +197,21 @@ Add `validation` rules for tutorials or attention checks:
|
|
|
193
197
|
- **Silent attention checks**: Omit `warning` to log failures without notification (quality control)
|
|
194
198
|
|
|
195
199
|
For listwise, `validation` is an array (one per candidate). Dashboard shows ✅/❌ based on `validation_threshold` in `info` (integer for max failed count, float \[0,1\) for max proportion, default 0).
|
|
196
|
-
|
|
200
|
+
|
|
201
|
+
**Listwise score comparison:** Use `score_greaterthan` to ensure one candidate scores higher than another:
|
|
202
|
+
```python
|
|
203
|
+
{
|
|
204
|
+
"src": "AI transforms industries.",
|
|
205
|
+
"tgt": ["UI transformuje průmysly.", "Umělá inteligence mění obory."],
|
|
206
|
+
"validation": [
|
|
207
|
+
{"warning": "A has error, score 20-40.", "score": [20, 40]},
|
|
208
|
+
{"warning": "B is correct and must score higher than A.", "score": [70, 90], "score_greaterthan": 0}
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
The `score_greaterthan` field specifies the index of the candidate that must have a lower score than the current candidate.
|
|
213
|
+
|
|
214
|
+
See [examples/tutorial_pointwise.json](examples/tutorial_pointwise.json), [examples/tutorial_listwise.json](examples/tutorial_listwise.json), and [examples/tutorial_listwise_score_greaterthan.json](examples/tutorial_listwise_score_greaterthan.json).
|
|
197
215
|
|
|
198
216
|
### Single-stream Assignment
|
|
199
217
|
|
|
@@ -294,6 +312,38 @@ Items need `model` field (pointwise) or `models` field (listwise) and the `proto
|
|
|
294
312
|
```
|
|
295
313
|
See an example in [Campaign Management](#campaign-management)
|
|
296
314
|
|
|
315
|
+
|
|
316
|
+
## Terminology
|
|
317
|
+
|
|
318
|
+
- **Campaign**: An annotation project that contains configuration, data, and user assignments. Each campaign has a unique identifier and is defined in a JSON file.
|
|
319
|
+
- **Campaign File**: A JSON file that defines the campaign configuration, including the campaign ID, assignment type, protocol settings, and annotation data.
|
|
320
|
+
- **Campaign ID**: A unique identifier for a campaign (e.g., `"wmt25_#_en-cs_CZ"`). Used to reference and manage specific campaigns.
|
|
321
|
+
- **Task**: A unit of work assigned to a user. In task-based assignment, each task consists of a predefined set of items for a specific user.
|
|
322
|
+
- **Item** — A single annotation unit within a task. For translation evaluation, an item typically represents a document (source text and target translation). Items can contain text, images, audio, or video.
|
|
323
|
+
- **Document** — A collection of one or more segments (sentence pairs or text units) that are evaluated together as a single item.
|
|
324
|
+
- **User** / **Annotator**: A person who performs annotations in a campaign. Each user is identified by a unique user ID and accesses the campaign through a unique URL.
|
|
325
|
+
- **Attention Check** — A validation item with known correct answers used to ensure annotator quality. Can be:
|
|
326
|
+
- **Loud**: Shows warning message and forces retry on failure
|
|
327
|
+
- **Silent**: Logs failures without notifying the user (for quality control analysis)
|
|
328
|
+
- **Token** — A completion code shown to users when they finish their annotations. Tokens verify the completion and whether the user passed quality control checks:
|
|
329
|
+
- **Pass Token** (`token_pass`): Shown when user meets validation thresholds
|
|
330
|
+
- **Fail Token** (`token_fail`): Shown when user fails to meet validation requirements
|
|
331
|
+
- **Tutorial**: An instructional validation item that teaches users how to annotate. Includes `allow_skip: true` to let users skip if they have seen it before.
|
|
332
|
+
- **Validation**: Quality control rules attached to items that check if annotations match expected criteria (score ranges, error span locations, etc.). Used for tutorials and attention checks.
|
|
333
|
+
- **Model**: The system or model that generated the output being evaluated (e.g., `"GPT-4"`, `"Claude"`). Used for tracking and ranking model performance.
|
|
334
|
+
- **Dashboard**: The management interface that shows campaign progress, annotator statistics, access links, and allows downloading annotations. Accessed via a special management URL with token authentication.
|
|
335
|
+
- **Protocol**: The annotation scheme defining what data is collected:
|
|
336
|
+
- **Score**: Numeric quality rating (0-100)
|
|
337
|
+
- **Error Spans**: Text highlights marking errors
|
|
338
|
+
- **Error Categories**: MQM taxonomy labels for errors
|
|
339
|
+
- **Template**: The annotation interface type:
|
|
340
|
+
- **Pointwise**: Evaluate one output at a time
|
|
341
|
+
- **Listwise**: Compare multiple outputs simultaneously
|
|
342
|
+
- **Assignment**: The method for distributing items to users:
|
|
343
|
+
- **Task-based**: Each user has predefined items
|
|
344
|
+
- **Single-stream**: Users draw from a shared pool with random assignment
|
|
345
|
+
- **Dynamic**: Work in progress
|
|
346
|
+
|
|
297
347
|
## Development
|
|
298
348
|
|
|
299
349
|
Server responds to data-only requests from frontend (no template coupling). Frontend served from pre-built `static/` on install.
|
|
@@ -333,7 +383,7 @@ If you use this work in your paper, please cite as following.
|
|
|
333
383
|
author={Vilém Zouhar},
|
|
334
384
|
title={Pearmut: Platform for Evaluating and Reviewing of Multilingual Tasks},
|
|
335
385
|
url={https://github.com/zouharvi/pearmut/},
|
|
336
|
-
year={
|
|
386
|
+
year={2026},
|
|
337
387
|
}
|
|
338
388
|
```
|
|
339
389
|
|
|
@@ -13,10 +13,10 @@ server/cli.py
|
|
|
13
13
|
server/utils.py
|
|
14
14
|
server/static/dashboard.bundle.js
|
|
15
15
|
server/static/dashboard.html
|
|
16
|
+
server/static/favicon.svg
|
|
16
17
|
server/static/index.html
|
|
17
18
|
server/static/listwise.bundle.js
|
|
18
19
|
server/static/listwise.html
|
|
19
20
|
server/static/pointwise.bundle.js
|
|
20
21
|
server/static/pointwise.html
|
|
21
|
-
server/static/
|
|
22
|
-
server/static/assets/style.css
|
|
22
|
+
server/static/style.css
|
|
@@ -291,7 +291,11 @@ async def _download_annotations(
|
|
|
291
291
|
with open(output_path, "r") as f:
|
|
292
292
|
output[campaign_id] = [json.loads(x) for x in f.readlines()]
|
|
293
293
|
|
|
294
|
-
return JSONResponse(
|
|
294
|
+
return JSONResponse(
|
|
295
|
+
content=output,
|
|
296
|
+
status_code=200,
|
|
297
|
+
headers={"Content-Disposition": 'inline; filename="annotations.json"'}
|
|
298
|
+
)
|
|
295
299
|
|
|
296
300
|
|
|
297
301
|
@app.get("/download-progress")
|
|
@@ -315,7 +319,11 @@ async def _download_progress(
|
|
|
315
319
|
|
|
316
320
|
output[cid] = progress_data[cid]
|
|
317
321
|
|
|
318
|
-
return JSONResponse(
|
|
322
|
+
return JSONResponse(
|
|
323
|
+
content=output,
|
|
324
|
+
status_code=200,
|
|
325
|
+
headers={"Content-Disposition": 'inline; filename="progress.json"'}
|
|
326
|
+
)
|
|
319
327
|
|
|
320
328
|
|
|
321
329
|
static_dir = f"{os.path.dirname(os.path.abspath(__file__))}/static/"
|
|
@@ -324,6 +332,16 @@ if not os.path.exists(static_dir + "index.html"):
|
|
|
324
332
|
"Static directory not found. Please build the frontend first."
|
|
325
333
|
)
|
|
326
334
|
|
|
335
|
+
# Mount user assets from data/assets/
|
|
336
|
+
assets_dir = f"{ROOT}/data/assets"
|
|
337
|
+
os.makedirs(assets_dir, exist_ok=True)
|
|
338
|
+
|
|
339
|
+
app.mount(
|
|
340
|
+
"/assets",
|
|
341
|
+
StaticFiles(directory=assets_dir, follow_symlink=True),
|
|
342
|
+
name="assets",
|
|
343
|
+
)
|
|
344
|
+
|
|
327
345
|
app.mount(
|
|
328
346
|
"/",
|
|
329
347
|
StaticFiles(directory=static_dir, html=True, follow_symlink=True),
|
|
@@ -84,9 +84,13 @@ def get_i_item_taskbased(
|
|
|
84
84
|
|
|
85
85
|
# try to get existing annotations if any
|
|
86
86
|
items_existing = get_db_log_item(campaign_id, user_id, item_i)
|
|
87
|
+
payload_existing = None
|
|
87
88
|
if items_existing:
|
|
88
89
|
# get the latest ones
|
|
89
|
-
|
|
90
|
+
latest_item = items_existing[-1]
|
|
91
|
+
payload_existing = {"annotations": latest_item["annotations"]}
|
|
92
|
+
if "comment" in latest_item:
|
|
93
|
+
payload_existing["comment"] = latest_item["comment"]
|
|
90
94
|
|
|
91
95
|
if item_i < 0 or item_i >= len(data_all[campaign_id]["data"][user_id]):
|
|
92
96
|
return JSONResponse(
|
|
@@ -107,7 +111,7 @@ def get_i_item_taskbased(
|
|
|
107
111
|
if k.startswith("protocol")
|
|
108
112
|
},
|
|
109
113
|
"payload": data_all[campaign_id]["data"][user_id][item_i]
|
|
110
|
-
} | ({"payload_existing": payload_existing} if
|
|
114
|
+
} | ({"payload_existing": payload_existing} if payload_existing else {}),
|
|
111
115
|
status_code=200
|
|
112
116
|
)
|
|
113
117
|
|
|
@@ -127,9 +131,13 @@ def get_i_item_singlestream(
|
|
|
127
131
|
# try to get existing annotations if any
|
|
128
132
|
# note the None user_id since it is shared
|
|
129
133
|
items_existing = get_db_log_item(campaign_id, None, item_i)
|
|
134
|
+
payload_existing = None
|
|
130
135
|
if items_existing:
|
|
131
136
|
# get the latest ones
|
|
132
|
-
|
|
137
|
+
latest_item = items_existing[-1]
|
|
138
|
+
payload_existing = {"annotations": latest_item["annotations"]}
|
|
139
|
+
if "comment" in latest_item:
|
|
140
|
+
payload_existing["comment"] = latest_item["comment"]
|
|
133
141
|
|
|
134
142
|
if item_i < 0 or item_i >= len(data_all[campaign_id]["data"]):
|
|
135
143
|
return JSONResponse(
|
|
@@ -150,7 +158,7 @@ def get_i_item_singlestream(
|
|
|
150
158
|
if k.startswith("protocol")
|
|
151
159
|
},
|
|
152
160
|
"payload": data_all[campaign_id]["data"][item_i]
|
|
153
|
-
} | ({"payload_existing": payload_existing} if
|
|
161
|
+
} | ({"payload_existing": payload_existing} if payload_existing else {}),
|
|
154
162
|
status_code=200
|
|
155
163
|
)
|
|
156
164
|
|
|
@@ -173,9 +181,13 @@ def get_next_item_taskbased(
|
|
|
173
181
|
|
|
174
182
|
# try to get existing annotations if any
|
|
175
183
|
items_existing = get_db_log_item(campaign_id, user_id, item_i)
|
|
184
|
+
payload_existing = None
|
|
176
185
|
if items_existing:
|
|
177
186
|
# get the latest ones
|
|
178
|
-
|
|
187
|
+
latest_item = items_existing[-1]
|
|
188
|
+
payload_existing = {"annotations": latest_item["annotations"]}
|
|
189
|
+
if "comment" in latest_item:
|
|
190
|
+
payload_existing["comment"] = latest_item["comment"]
|
|
179
191
|
|
|
180
192
|
return JSONResponse(
|
|
181
193
|
content={
|
|
@@ -190,7 +202,7 @@ def get_next_item_taskbased(
|
|
|
190
202
|
if k.startswith("protocol")
|
|
191
203
|
},
|
|
192
204
|
"payload": data_all[campaign_id]["data"][user_id][item_i]
|
|
193
|
-
} | ({"payload_existing": payload_existing} if
|
|
205
|
+
} | ({"payload_existing": payload_existing} if payload_existing else {}),
|
|
194
206
|
status_code=200
|
|
195
207
|
)
|
|
196
208
|
|
|
@@ -222,9 +234,13 @@ def get_next_item_singlestream(
|
|
|
222
234
|
# try to get existing annotations if any
|
|
223
235
|
# note the None user_id since it is shared
|
|
224
236
|
items_existing = get_db_log_item(campaign_id, None, item_i)
|
|
237
|
+
payload_existing = None
|
|
225
238
|
if items_existing:
|
|
226
239
|
# get the latest ones
|
|
227
|
-
|
|
240
|
+
latest_item = items_existing[-1]
|
|
241
|
+
payload_existing = {"annotations": latest_item["annotations"]}
|
|
242
|
+
if "comment" in latest_item:
|
|
243
|
+
payload_existing["comment"] = latest_item["comment"]
|
|
228
244
|
|
|
229
245
|
return JSONResponse(
|
|
230
246
|
content={
|
|
@@ -239,7 +255,7 @@ def get_next_item_singlestream(
|
|
|
239
255
|
if k.startswith("protocol")
|
|
240
256
|
},
|
|
241
257
|
"payload": data_all[campaign_id]["data"][item_i]
|
|
242
|
-
} | ({"payload_existing": payload_existing} if
|
|
258
|
+
} | ({"payload_existing": payload_existing} if payload_existing else {}),
|
|
243
259
|
status_code=200
|
|
244
260
|
)
|
|
245
261
|
|
|
@@ -272,15 +272,10 @@ def _add_single_campaign(data_file, overwrite, server):
|
|
|
272
272
|
|
|
273
273
|
if not os.path.isdir(assets_real_path):
|
|
274
274
|
raise ValueError(f"Assets source path '{assets_real_path}' must be an existing directory.")
|
|
275
|
-
|
|
276
|
-
if not os.path.isdir(STATIC_DIR):
|
|
277
|
-
raise ValueError(
|
|
278
|
-
f"Static directory '{STATIC_DIR}' does not exist. "
|
|
279
|
-
"Please build the frontend first."
|
|
280
|
-
)
|
|
281
275
|
|
|
282
276
|
# Symlink path is based on the destination, stripping the 'assets/' prefix
|
|
283
|
-
|
|
277
|
+
# User assets are now stored under data/assets/ instead of static/assets/
|
|
278
|
+
symlink_path = f"{ROOT}/data/{assets_destination}".rstrip("/")
|
|
284
279
|
|
|
285
280
|
# Remove existing symlink if present and we are overriding the same campaign
|
|
286
281
|
if os.path.lexists(symlink_path):
|
|
@@ -392,7 +387,7 @@ def main():
|
|
|
392
387
|
campaign_data = json.load(f)
|
|
393
388
|
destination = campaign_data.get("info", {}).get("assets", {}).get("destination")
|
|
394
389
|
if destination:
|
|
395
|
-
symlink_path = f"{
|
|
390
|
+
symlink_path = f"{ROOT}/data/{destination}".rstrip("/")
|
|
396
391
|
if os.path.islink(symlink_path):
|
|
397
392
|
os.remove(symlink_path)
|
|
398
393
|
print(f"Assets symlink removed: {symlink_path}")
|