Part of #66. Depends on storage (#69); pairs with export (#70).
Scope
grans label import <file.json> ingests the JSON downloaded from the labeling page into the speaker-label tables as label_source = 'human'.
Expected file shape (produced by the labeling template):
{
"document_id": "...",
"segmentation_version": 1,
"labels": [
{
"utterance_id": "...",
"speakers": ["person@example.com"],
"segments": [{ "sentence": 0, "speakers": ["..."] }]
}
]
}
segments is optional and present only for sentence-split labels.
Validation (strict; reject the whole file on failure, no silent skips)
document_id and every utterance_id exist and belong together.
- Every speaker is a known candidate for that meeting, or
unknown / media.
segmentation_version matches the current one; on mismatch, refuse with a message explaining sentence indices may not line up (re-export and re-label rather than guess).
- Sentence indices within bounds for the utterance's current segmentation.
Behavior
- Upsert per utterance: importing again overwrites that utterance's previous human label (labeling sessions are iterative).
- Report a summary: N utterances labeled, M with segments, K speakers seen.
- Update README (new command).
Part of #66. Depends on storage (#69); pairs with export (#70).
Scope
grans label import <file.json>ingests the JSON downloaded from the labeling page into the speaker-label tables aslabel_source = 'human'.Expected file shape (produced by the labeling template):
{ "document_id": "...", "segmentation_version": 1, "labels": [ { "utterance_id": "...", "speakers": ["person@example.com"], "segments": [{ "sentence": 0, "speakers": ["..."] }] } ] }segmentsis optional and present only for sentence-split labels.Validation (strict; reject the whole file on failure, no silent skips)
document_idand everyutterance_idexist and belong together.unknown/media.segmentation_versionmatches the current one; on mismatch, refuse with a message explaining sentence indices may not line up (re-export and re-label rather than guess).Behavior