feat: improvement
This commit is contained in:
+127
-10
@@ -37,6 +37,10 @@ pub enum Kind {
|
||||
Lecture,
|
||||
/// `objectives/*.yaml`.
|
||||
Objective,
|
||||
/// `analysis/calibration.yaml`.
|
||||
Calibration,
|
||||
/// `analysis/administrations/*.yaml`.
|
||||
Measurements,
|
||||
/// `banks/*.yaml`.
|
||||
Bank,
|
||||
/// `assessments/*.yaml`.
|
||||
@@ -45,13 +49,15 @@ pub enum Kind {
|
||||
|
||||
impl Kind {
|
||||
/// Every kind.
|
||||
pub const ALL: [Kind; 6] = [
|
||||
pub const ALL: [Kind; 8] = [
|
||||
Kind::Course,
|
||||
Kind::References,
|
||||
Kind::Lecture,
|
||||
Kind::Objective,
|
||||
Kind::Bank,
|
||||
Kind::Assessment,
|
||||
Kind::Calibration,
|
||||
Kind::Measurements,
|
||||
];
|
||||
|
||||
/// The file name a schema is written to.
|
||||
@@ -61,6 +67,8 @@ impl Kind {
|
||||
Kind::References => "references.schema.json",
|
||||
Kind::Lecture => "lecture.schema.json",
|
||||
Kind::Objective => "objective.schema.json",
|
||||
Kind::Calibration => "calibration.schema.json",
|
||||
Kind::Measurements => "measurements.schema.json",
|
||||
Kind::Bank => "bank.schema.json",
|
||||
Kind::Assessment => "assessment.schema.json",
|
||||
}
|
||||
@@ -99,6 +107,8 @@ pub fn schema(kind: Kind) -> Value {
|
||||
Kind::References => references_schema(),
|
||||
Kind::Lecture => lecture_fragment_schema(),
|
||||
Kind::Objective => objective_fragment_schema(),
|
||||
Kind::Calibration => calibration_file_schema(),
|
||||
Kind::Measurements => measurements_file_schema(),
|
||||
Kind::Bank => bank_schema(),
|
||||
Kind::Assessment => assessment_schema(),
|
||||
}
|
||||
@@ -381,9 +391,12 @@ fn target_schema() -> Value {
|
||||
"order": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Position among the other targets of the same objective, low \
|
||||
first. Ordered within its objective rather than across the \
|
||||
course, so inserting one renumbers nothing outside its group."
|
||||
"description": "Ignored since 2.0. An objective's targets are a set of question \
|
||||
templates, not steps in a sequence — they are not taught in \
|
||||
order and an exam samples from them — so a position asserts an \
|
||||
order that does not exist. Where one target depends on another, \
|
||||
say so with `prerequisites`. `coursebank migrate order` removes \
|
||||
this."
|
||||
},
|
||||
"level_ceiling": level(),
|
||||
"prerequisites": string_array(
|
||||
@@ -412,8 +425,10 @@ fn objective_schema() -> Value {
|
||||
"order": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Position in teaching order, low first. Without it objectives \
|
||||
sort by id, which puts one before its own prerequisite."
|
||||
"description": "Position in teaching order, low first. Derived since 2.0 from \
|
||||
the position of this objective in a lecture's `teaches` list; \
|
||||
an authored value still wins, and `coursebank migrate order` \
|
||||
removes them."
|
||||
},
|
||||
"level_ceiling": level(),
|
||||
"prerequisites": string_array(
|
||||
@@ -1058,6 +1073,106 @@ fn option_history_schema() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
/// The schema for `analysis/calibration.yaml`.
|
||||
fn calibration_file_schema() -> Value {
|
||||
json!({
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": format!("{BASE}/calibration.schema.json"),
|
||||
"title": "coursebank calibration store",
|
||||
"description": "The pooled statistics, by item id. Kept out of the banks: a bank's diff \
|
||||
should be a change of intent, not the output of a grading run. Committed \
|
||||
— every number here is a cohort aggregate, and there is no field for a \
|
||||
student.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"schema_version": { "type": ["string", "number"] },
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "Keyed by item id, which since 2.0 names the item course-wide and \
|
||||
carries no file name, so moving a question between banks does \
|
||||
not orphan its statistics.",
|
||||
"additionalProperties": calibration_schema()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The schema for one file under `analysis/administrations/`.
|
||||
fn measurements_file_schema() -> Value {
|
||||
json!({
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": format!("{BASE}/measurements.schema.json"),
|
||||
"title": "coursebank administration record",
|
||||
"description": "What one administration measured. Written once and not rewritten, like \
|
||||
a seal: it records a thing that happened on a day. Cohort aggregates \
|
||||
only — no per-section or per-student breakdown, because a small cell \
|
||||
crossed with anything else stops being an aggregate.",
|
||||
"type": "object",
|
||||
"required": ["administration"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"schema_version": { "type": ["string", "number"] },
|
||||
"administration": {
|
||||
"type": "object",
|
||||
"required": ["id", "assessment", "n_examinees"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"assessment": { "type": "string" },
|
||||
"term": { "type": "string" },
|
||||
"date": date("When it was given."),
|
||||
"forms": string_array("The forms in play."),
|
||||
"n_examinees": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The number to read before any of the others: a \
|
||||
point-biserial on twenty-seven students is a different \
|
||||
kind of claim than one on three hundred."
|
||||
},
|
||||
"model": { "type": "string" },
|
||||
"generated": date("When the analysis was run."),
|
||||
"coursebank": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["item", "number", "n"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"item": { "type": "string" },
|
||||
"number": { "type": "integer", "minimum": 1 },
|
||||
"variant": { "type": "string" },
|
||||
"stem_digest": { "type": "string" },
|
||||
"n": { "type": "integer", "minimum": 0 },
|
||||
"p_value": proportion("Proportion correct on this administration."),
|
||||
"point_biserial": { "type": "number", "minimum": -1.0, "maximum": 1.0 },
|
||||
"discrimination_index": {
|
||||
"type": "number", "minimum": -1.0, "maximum": 1.0
|
||||
},
|
||||
"option_stats": {
|
||||
"type": "object",
|
||||
"additionalProperties": option_stat_schema()
|
||||
},
|
||||
"irt": irt_schema(),
|
||||
"flags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": strings(
|
||||
&Flag::ALL.iter().map(|f| f.as_str()).collect::<Vec<&str>>()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The schema for a retirement record.
|
||||
fn retirement_schema() -> Value {
|
||||
json!({
|
||||
@@ -1080,9 +1195,12 @@ fn item_identity_properties() -> Value {
|
||||
json!({
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^q-[a-z0-9]+(-[a-z0-9]+)*-[0-9]{3}$",
|
||||
"description": "Item id, e.g. q-glycolysis-014. Stable forever: assessment records \
|
||||
and stored responses refer to it."
|
||||
"pattern": "^q-[a-z0-9]+(-[a-z0-9]+)*$",
|
||||
"description": "Item id, e.g. q-glycolysis-rate-limiting-step. Stable forever: \
|
||||
assessment records and stored responses refer to it, so renaming one \
|
||||
is a migration rather than an edit. A trailing counter is no longer \
|
||||
expected — it recorded when the item was written, which git knows — \
|
||||
but an id that still has one stays valid."
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
@@ -1149,7 +1267,6 @@ fn item_content_properties() -> Value {
|
||||
"prerequisites": string_array("Objective ids a student needs before this item."),
|
||||
"assets": { "type": "array", "items": asset_schema() },
|
||||
"design": design_schema(),
|
||||
"calibration": calibration_schema(),
|
||||
"review": review_schema(),
|
||||
"history": {
|
||||
"type": "array",
|
||||
|
||||
Reference in New Issue
Block a user