2. Schema
All four entities are plain dataclasses (submission/core/schema.py). They
round-trip through to_dict() / from_dict() and are stored as-is, with no
ORM or mapping layer, as MongoDB documents. to_dict() writes datetimes as
ISO-8601 strings. from_dict() accepts ISO-8601 strings (a trailing Z is
allowed here), epoch seconds, or datetime objects.
A task is stored as three documents, one per entity, that share the same
social_task_id. Votes are stored separately, one document per vote.
The create request uses different key names from the stored documents. The parser maps them, as shown in the "Request key" column below and in §3.2.
2.1 SocialTaskCoreData
The task itself: who created it, who may vote, when it starts, and its
current state. Collection social_task_core_data.
| Field | Request key | Type | Required | Description |
|---|---|---|---|---|
social_task_id |
— | string |
auto | Primary key, task_ plus 8 hex characters. Generated by the parser. A value in the request is ignored. |
created_by_subject_id |
created_by_subject_id |
string |
yes | Who created the task. Used as sender_subject_id on invitations. |
created_by_subject_data |
created_by_subject_data |
object |
yes (may be {}) |
Free-form data about the creator. |
org_ids |
org_ids |
array<string> |
yes (may be []) |
Private: the fixed list of eligible voters. Evaluation waits for a qualified vote from every entry. Public: ignored. |
social_task_access_type |
access_type |
string |
yes | "private" or "public". See §1.3. Not validated. |
goal_data |
goal |
object |
yes (may be {}) |
What the vote is meant to decide. Free-form. Passed to every hook. |
social_tasks_topics |
topics |
array<string> |
yes (may be []) |
Free-form topic labels. Stored only. No service filters on them. |
social_task_properties |
properties |
object |
yes (may be {}) |
Free-form properties such as urgency or category. |
creation_time |
creation_time |
datetime |
yes | Supplied by the caller, not by the server. |
scheduled_time |
scheduled_time |
datetime | absent |
needed to start | When the task should start. Naive ISO-8601, interpreted as UTC. See §1.5 gap 6. |
duration |
duration |
integer | absent |
no | Public: the number of qualified votes that triggers evaluation (default 5). Not a time span. |
status |
status |
string | absent |
needed to start | "scheduled", "started", or "complete". Must be "scheduled" at creation for the task to start. See §1.4. |
report_json |
report |
object |
auto ({}) |
Filled in by the evaluator job. See §2.6. |
job_id |
— | string | absent |
unused | Reserved. Never written. |
enable_live_streaming |
enable_live_streaming |
boolean |
no (default false) |
Whether the WebSocket live stream will serve this task. See §3.6. |
to_dict() drops fields whose value is None, so unset optional fields are
absent from the document, not null.
Example: SocialTaskCoreData document (private task, just created):
{
"social_task_id": "task_3f9c6d2e",
"created_by_subject_id": "planner-service",
"created_by_subject_data": {"team": "localisation"},
"org_ids": ["agent-reviewer-01", "agent-reviewer-02", "agent-reviewer-03"],
"social_task_access_type": "private",
"goal_data": {"objective": "Choose the lead translator for the Q4 handbook"},
"social_tasks_topics": ["translation", "fr"],
"social_task_properties": {"urgency": "high"},
"creation_time": "2026-10-22T09:00:00",
"scheduled_time": "2026-10-22T10:00:00",
"status": "scheduled",
"report_json": {},
"enable_live_streaming": true
}
2.2 SocialChoiceSubjectSpecInput
What voters see: the topic and the options. Collection
social_choice_subject_spec.
| Field | Request key | Type | Required | Description |
|---|---|---|---|---|
social_task_id |
— | string |
auto | FK to SocialTaskCoreData.social_task_id. |
topic_title |
title |
string |
yes | Short title shown to voters. |
topic_description |
description |
string |
yes | What is being voted on. |
voting_options_map |
voting_options |
object |
yes | Option ID → label. The keys double as addresses: one invitation channel per key (§5.2). |
voting_option_metadata_map |
voting_metadata |
object |
yes (may be {}) |
Option ID → extra data, for example a candidate's profile. |
supported_protocols |
supported_protocols |
array<string> |
yes (may be []) |
Stored only. Not read by any service. |
voting_message_request_creator |
voting_request |
function spec | yes (may be {}) |
The invitation creator. {} disables it. See §2.5 and §4.2. |
Example:
{
"social_task_id": "task_3f9c6d2e",
"topic_title": "Lead translator, Q4 handbook",
"topic_description": "Pick the agent that will lead the EN→FR translation of the Q4 handbook.",
"voting_options_map": {
"agent-translator-01": "Translator 01",
"agent-translator-02": "Translator 02",
"agent-translator-03": "Translator 03"
},
"voting_option_metadata_map": {
"agent-translator-01": {"languages": ["en", "fr"], "past_jobs": 42},
"agent-translator-02": {"languages": ["en", "fr", "de"], "past_jobs": 17}
},
"supported_protocols": ["http"],
"voting_message_request_creator": {
"mode": "function",
"function_id": "voting-invite-builder:1.0-stable",
"parameters": {}
}
}
2.3 SocialChoiceEvaluationInput
How votes are qualified and counted. Collection
social_choice_evaluation_input.
| Field | Request key | Type | Required | Description |
|---|---|---|---|---|
social_task_id |
— | string |
auto | FK to SocialTaskCoreData.social_task_id. |
constraints_entry_id |
constraints_primary |
string |
yes (may be "") |
Reserved. Stored, never read. |
constraints_entry_id_1 |
constraints_secondary |
string |
yes (may be "") |
Reserved. Stored, never read. |
voting_pqt |
voting_pqt |
function spec | yes | Pre-qualification test, run on every vote. See §4.3. |
choice_evaluation |
evaluation |
function spec | yes | Computes the winner or winners. See §4.4. |
tie_breaker |
tie_breaker |
function spec | yes (may be {}) |
Runs only when choice evaluation returns more than one winner. See §4.5. |
post_awarding |
post_award |
function spec | yes | Runs after the winners are final. See §4.6. |
Example:
{
"social_task_id": "task_3f9c6d2e",
"constraints_entry_id": "",
"constraints_entry_id_1": "",
"voting_pqt": {"mode": "dsl", "workflow_id": "vote-option-is-valid:1.0"},
"choice_evaluation": {"mode": "dsl", "workflow_id": "plurality-count:1.0"},
"tie_breaker": {"mode": "dsl", "workflow_id": "earliest-vote-wins:1.0"},
"post_awarding": {"mode": "dsl", "workflow_id": "assign-lead-translator:1.0"}
}
The vote-intake service and the evaluator job currently read these hooks from string fields named
voting_pqt_dsl,choice_evaluation_dsl,tie_breaker_dsl, andpost_awarding_dsl. Those fields hold DSL workflow IDs. See §1.5 gap 1 and §4.1.
2.4 Votes
One subject's vote on one task. Collection votes.
| Field | Type | Required | Description |
|---|---|---|---|
vote_id |
string |
yes | Primary key. Chosen by the client. The server does not generate it or check it for uniqueness. Use a UUID4. |
social_task_id |
string |
yes | Which task this vote is for. |
submitter_subject_id |
string |
yes | Who is voting. Must be in org_ids for private tasks. At most one vote per subject per task, counting disqualified votes. |
vote_data |
object |
yes | The ballot itself. Free-form. Its shape is a contract between the creator and the PQT and evaluation hooks, for example {"selected_option": "agent-translator-02"} or a ranking. |
submission_time |
datetime |
must be present | Overwritten with server time on receipt. The request must still include it (§3.4). |
qualified |
boolean |
must be present | Overwritten with the PQT result. The request must still include it. |
Example:
{
"vote_id": "7d0f2a9c-2b4e-4a51-9a3e-0c6f1b2d4e8a",
"social_task_id": "task_3f9c6d2e",
"submitter_subject_id": "agent-reviewer-01",
"vote_data": {"selected_option": "agent-translator-02"},
"submission_time": "2026-10-22T10:03:12.481203",
"qualified": true
}
2.5 Function spec
Each hook is configured with a small object that says where its logic lives.
The same shape is used for voting_message_request_creator, voting_pqt,
choice_evaluation, tie_breaker, and post_awarding.
| Field | Type | Required | Description |
|---|---|---|---|
mode |
string |
yes | "function" (function registry) or "dsl" (DSL workflow registry). |
function_id |
string |
mode: "function" |
function_id in the function registry, for example voting-invite-builder:1.0-stable. |
parameters |
object |
no (default {}) |
mode: "function" only. Passed as per-call parameters when the function runs as a stateless job. |
workflow_id |
string |
mode: "dsl" |
Workflow ID in the DSL workflow registry. |
{"mode": "function", "function_id": "voting-invite-builder:1.0-stable", "parameters": {"tone": "formal"}}
{"mode": "dsl", "workflow_id": "plurality-count:1.0"}
An empty object {} disables the invitation creator and the tie-breaker. For
the other hooks, {} is accepted at creation but fails when the hook runs.
2.6 report_json
Written to SocialTaskCoreData.report_json by the evaluator job when it
finishes (§1.2 step 9).
| Field | Type | Description |
|---|---|---|
task |
object |
SocialTaskCoreData.to_dict() at evaluation time |
subject_spec |
object |
SocialChoiceSubjectSpecInput.to_dict() |
evaluation_spec |
object |
SocialChoiceEvaluationInput.to_dict() |
votes |
array |
The qualified votes that were evaluated |
winners |
array<string> |
Final winners, after the tie-breaker if it ran |
post_award_payload |
object |
Output of the post-award hook |
dsl_outputs |
object |
Raw output of each hook that ran, keyed choice_evaluation_dsl, tie_breaker_dsl (only if it ran), and post_awarding_dsl |
evaluated_at |
string |
ISO-8601 UTC timestamp |
Example:
{
"task": {"social_task_id": "task_3f9c6d2e", "status": "started", "...": "..."},
"subject_spec": {"topic_title": "Lead translator, Q4 handbook", "...": "..."},
"evaluation_spec": {"...": "..."},
"votes": [
{"vote_id": "7d0f…", "submitter_subject_id": "agent-reviewer-01", "vote_data": {"selected_option": "agent-translator-02"}, "qualified": true, "...": "..."},
{"vote_id": "c41a…", "submitter_subject_id": "agent-reviewer-02", "vote_data": {"selected_option": "agent-translator-02"}, "qualified": true, "...": "..."},
{"vote_id": "e9b3…", "submitter_subject_id": "agent-reviewer-03", "vote_data": {"selected_option": "agent-translator-01"}, "qualified": true, "...": "..."}
],
"winners": ["agent-translator-02"],
"post_award_payload": {"assigned_to": "agent-translator-02", "start_by": "2026-10-23T09:00:00Z"},
"dsl_outputs": {
"choice_evaluation_dsl": {"winner": "agent-translator-02", "tally": {"agent-translator-02": 2, "agent-translator-01": 1}},
"post_awarding_dsl": {"assigned_to": "agent-translator-02", "start_by": "2026-10-23T09:00:00Z"}
},
"evaluated_at": "2026-10-22T10:07:45.221000"
}
task.status in the report is "started", because the report is built
before the status changes to "complete".
2.7 Storage
All entities live in MongoDB, database voting_db, one collection each.
| Collection | Keyed by | Indexes |
|---|---|---|
social_task_core_data |
social_task_id |
none created by the services |
social_choice_subject_spec |
social_task_id |
none |
social_choice_evaluation_input |
social_task_id |
none |
votes |
vote_id |
none |
get(), update(), and delete() work on the entity's own ID field, never
on MongoDB's _id. update() is a $set on top-level fields. The scheduler
reads the whole social_task_core_data collection on every poll, and the
duplicate-vote check reads every vote for the task. Add indexes on
social_task_id, status, and votes.social_task_id yourself if volumes
grow.