Files
Copies/docs/final_output.md

259 lines
13 KiB
Markdown

# Final output: `A Rendre`
This documents the current implementation, as of 2026-09-12. The return folder
referred to as « À rendre » is named **`A Rendre`** on disk. JPEG files use the
extension **`.jpg`**, not `.jpeg`.
## Files and their sources
After grouped correction and review:
```sh
python -m copienator read-grouped Interro
python -m copienator giving-names Interro BGnot
```
The expected layout for a copy is:
```text
Interro/A Rendre/
└── Student Name (01)/
├── Student Name.jpg
├── Student Name.pdf
├── score.json
├── info.json
└── answers/ # when individual answer export is enabled
├── 001 - Ex 1.jpg
└── 002 - Ex 2.jpg
```
The name comes from `Copies/Copie01.json` (`name`), with filename sanitization.
The copy ID distinguishes folders even when several copies have the same name.
`giving-names` links the full JPEG, PDF and score file (or copies them when links
are unavailable). It writes `info.json` and optionally composes the individual
answer JPEGs.
| Return file | Source under `BGnot/Copie01/` | Contents |
| --- | --- | --- |
| `Student Name.jpg` | `Concat.jpg` | Full continuous image of the compiled answers and corrections. |
| `Student Name.pdf` | `Concat_F.pdf` | Filtered, paginated correction with context, questions and solutions. |
| `score.json` | `score.json` | Per-question scores, including questions omitted from the filtered PDF. |
| `info.json` | `info.json` | Answer presence, empty-answer classification, PDF membership and score. |
| `answers/*.jpg` | Final per-label JPEGs selected by `info.json` | One annotated non-empty answer, with optional supplementary material. |
## Enabling or disabling outputs
Set these independent options in `config.py` (the defaults also apply when
absent from an older personal configuration):
```python
RETURN_JPEG_ENABLED = True
RETURN_PDF_ENABLED = True
RETURN_ANSWERS_ENABLED = False
RETURN_ANSWERS_CONTEXT = False
RETURN_ANSWERS_QUESTION = True
RETURN_ANSWERS_SOLUTION = False
```
The personal `config.py` enables `RETURN_ANSWERS_ENABLED`; the distributed
default is `False`. Set a full-output option to `False`, then rerun `giving-names` to omit that file from
`A Rendre`. For each prepared copy, any existing named return file of a disabled
type is removed, including a symlink or fallback copy. Its annotation source
remains intact. These options control return publication, not intermediate
rendering or scoring. `score.json` and `info.json` are always included and have
no disabling options.
Cleanup allows the JPEG to be absent when disabled, still requires `score.json`,
and preserves return PDFs when present.
## Individual answer JPEGs
With `RETURN_ANSWERS_ENABLED = True`, `giving-names` generates an `answers/`
subdirectory inside each student's return folder. It includes **every non-empty
compiled answer**, even a perfect answer omitted from the filtered PDF. Labels
marked `empty-answer` and labels without an answer are excluded. Every JPEG
contains the final annotated student answer, including retained feedback and
extracted handwriting.
The three supplementary options independently prepend, in this order:
1. Applicable context PDFs, if `RETURN_ANSWERS_CONTEXT` is enabled.
2. The question, if `RETURN_ANSWERS_QUESTION` is enabled (the default).
3. The model solution, if `RETURN_ANSWERS_SOLUTION` is enabled.
4. The annotated student answer, always.
These use the same `Text2`/`Sol2` sources as the filtered PDF. Missing supplements
are skipped; an unreadable existing file fails the export. They are concatenated
vertically on white, without PDF pagination or its black/blue borders. The
options affect only these individual images, not the full JPEG or filtered PDF.
Disabling all supplements produces just the annotated answer.
Filenames use natural label order, a three-digit minimum sequence number, and a
sanitized label (`001 - Ex 1.jpg`). Numbering prevents filename collisions when
labels differ only by characters forbidden in filenames. JSON keys retain exact
labels. The managed `answers/` directory is replaced on successful generation,
so removed/empty answers do not leave stale images; failures preserve the previous
directory. Disabling the option clears this directory on the next `giving-names`
run. Separate storage keeps these images out of the personal final-mark stamping
step, which reads only JPEGs directly inside the student's folder.
Recompile annotations once before exporting old evaluations with this option:
the compiler now saves every final answer block and writes `info.json`
next to them. For the grouped workflow, run `read-grouped`, then `giving-names`.
This avoids reconstructing a reviewed answer from outdated correction data.
## `info.json`: per-question information
Every label in `score.json` has an object containing exactly four fields:
```json
{
"Ex 1": {"present": true, "not_empty": true, "touched": false, "score": "4"},
"Ex 2": {"present": true, "not_empty": true, "touched": true, "score": "2"},
"Empty": {"present": true, "not_empty": false, "touched": false, "score": "0"},
"Absent": {"present": false, "not_empty": false, "touched": false, "score": ""}
}
```
- `present`: an answer entry exists for this student and label in the compilation
data. A supplied answer judged empty still has `present: true`.
- `not_empty`: the answer was not marked `empty-answer` and was successfully
compiled. Absent answers have `not_empty: false`. When individual export is
enabled, a JPEG is generated if and only if both `present` and `not_empty` are
true. These fields describe the answer regardless of export settings.
- `touched`: the answer appears in the compiled filtered `Concat_F.pdf`, using
the actual selection including handwriting and selective redo preservation.
It is not a flag for human edits. Empty and absent answers have `false`.
- `score`: the same value as `score.json` (normally a numeric string, or `""`
for an unpopulated score). Editing scores requires recompilation to update the
images; return publication uses current `score.json` values for this field.
`info.json` is always exported, even when individual JPEGs or the named PDF are
disabled. `touched` describes the source filtered PDF. In normal `Anot` and
`Bnot` flows, no filtered PDF is produced and all `touched` values are false.
This file replaces `touched.json` and the internal `answer_labels.json` manifest.
Recompile old annotations, then run `giving-names`; successful regeneration and
publication remove the obsolete files from their respective folders. Missing
or malformed metadata requires recompilation rather than guessing answer presence
from scores. Cleanup preserves `info.json` alongside `score.json`.
## JPEG: the full compiled correction
The JPEG stacks the rendered answer blocks vertically in natural label order
(for example, Ex 2 precedes Ex 10). Each block contains the scanned answer, its
label and score, retained global and local feedback, and detected handwritten
review annotations. Local feedback can include red rectangles and comments in
the left margin. Review checkboxes are applied as actions rather than reproduced
as controls; internal error labels are hidden during recompilation.
The result is one RGB image of variable height, with no page breaks. It contains
all successfully compiled answer blocks, including answers scored 4 with no
remaining feedback. “Full” refers to those answer blocks, not the original scan
pages or every question in the statement. Missing/unrenderable answers cannot
be included, and the renderer normally suppresses `empty-answer` results.
The grouped compiler refuses to publish a new set when compilation is incomplete.
The JPEG does not prepend the question, context or model solution PDFs.
## PDF: a different selection and layout
**The PDF is not a PDF conversion of the JPEG.** During an ordinary full grouped
recompilation, an answer is omitted only when all three conditions hold:
- Its score is at least 4.
- Every feedback item is marked `to_delete` (also true for an empty feedback list).
- There are no significant detected handwritten annotations for that answer.
Thus, a 4/4 answer with retained feedback or handwriting still appears. Scores
for omitted answers remain in `score.json`, and their answer blocks remain in
the JPEG. Handwriting significance currently means more than 20 pixels with
alpha greater than 50 in the extracted annotation layer.
For each retained answer, the PDF stacks the following available material:
1. Applicable context PDFs from `Text2/CTXT first_label -> last_label.pdf`.
2. The question from `Text2/<label>.pdf`.
3. The model solution from `Sol2/<label>.pdf`.
4. The same compiled answer block used for the JPEG.
Missing supplementary PDFs are skipped. Contexts can repeat for successive
questions. Each question's complete group stays together on one page; groups
are packed until the next would exceed the target height. An oversized group
gets its own taller page, rather than being split. Pages are raster images saved
as PDF at 100 dpi, with variable heights, not fixed A4 sheets or searchable text.
The target height is `int(max_image_width * 1.414 * 1.25)` pixels. Small white
margins are added on the left and above/below the page. The first image of each
group receives a black border and the second a blue border. These borders are
assigned by position, so they do not consistently identify question and solution
when contexts are present or supplementary files are missing.
If nothing survives filtering, `read-grouped` removes old `Concat_F` outputs and
does not create a new PDF. During a selective `--refaire` merge, saved answer
images outside the selection are kept in the filtered output without reapplying
the perfect-answer filter; the resulting PDF can therefore retain more answers
than a full recompilation.
## JSON: per-question scores
`score.json` is a flat JSON object keyed by the exact question labels. For example
(illustrative data):
```json
{
"Ex 1 : 1)": "4",
"Ex 1 : 2)": "2.5",
"Ex 2": ""
}
```
Values are **strings**, including numeric scores. The normal question scale is
0 to 4. `""` means no score was populated for that label; it is distinct from
`"0"`. The compiler initializes all labels from the evaluation's `labels` file
to `""`, then fills processed scores. This file contains no student identity,
feedback, annotation coordinates, grading weights, or overall final mark.
Scores incorporate review checkbox changes and, when requested, existing score
overrides via `read-grouped --update-score` (or `read-annotations --update-score`
for `Bnot`). Editing the JSON alone does not update the rendered scores. Overrides
are read from the annotation source folder: a return symlink points there, but
an independent fallback copy does not. After regeneration, rerun `giving-names`
to refresh copied return files.
`update-ods` reads these return JSON files; empty values become `NT` in the normal
per-question export. Its `--sum` option sums numeric values and skips nonnumeric
ones. Weighting and the final overall mark belong to the separate grading flow.
## Availability and later steps
- `giving-names` accepts `BGnot`, `Bnot`, or `Anot`. It selects the requested
source if `score.json` and either `Concat.jpg` or `info.json` exist,
otherwise it tries `Anot/CopieXX`. This also permits returns for an entirely
empty copy. It links the PDF only if `Concat_F.pdf` exists. The normal
`Bnot` reader produces a filtered `Concat_F.jpg`, and simple annotation produces
`Concat.jpg`; these paths do not guarantee a filtered PDF.
- Preparing returns removes disabled named outputs, and removes older named
outputs whose source is now absent, including broken symlinks.
- `add-final-score` writes to `FINAL_SCORE_OUTPUT_DIR/<evaluation>/`. It stamps
the overall mark from the configured ODS in red at the JPEG's upper right,
rounded down to one decimal place. It copies PDFs unchanged and does not export
either JSON file or the `answers/` directory. It does not add that mark to the
files inside `A Rendre`.
- The `clean` command retains return images (including individual answers), PDFs,
`score.json` and `info.json`, materializing
retained symlinks before deleting their sources.
## Implementation references
- [Naming and return links](../copienator/commands/giving_names.py)
- [Individual answer publication](../copienator/return_answers.py)
- [Grouped compilation, filtering and PDF pagination](../copienator/commands/reading_grouped_annotations.py)
- [Answer rendering](../copienator/commands/annotating.py)
- [Handwriting detection and per-copy compilation](../copienator/commands/reading_annotations.py)
- [Score and feedback actions](../copienator/annotation_actions.py)
- [Context, question and solution lookup](../copienator/utils.py)
- [ODS export](../copienator/commands/update_ods.py)
- [Final-mark stamping](../copienator/commands/add_final_score.py)
- [Cleanup retention](../copienator/commands/clean.py)