Catalog PyPI
Back to packageRELEASE DETAILS

0.2.0

PyPI

aied-unplugged

Loaders, graders and baselines for the AIED-Unplugged preview competition

automated-essay-scoringcompetitioneducationhandwritingocr
Published25 Sept 2026
Direct dependencies10
Transitive dependenciesNot calculated

README

Version 0.2.0 documentation

AIED-Unplugged Dataset SDK

Python SDK for the AIED Preview Competition: the official graders, dataset loaders, exploration helpers, and scaffolding to run a Hugging Face model on a track.

pip install aied-unplugged            # graders, loaders, submission tooling
pip install 'aied-unplugged[all]'     # + kagglehub, datasets, matplotlib, transformers

The SDK is optional. It reads the published dataset and writes the published submission format.


The tracks

TracktrackPredictRanked by
Automated Essay Scoringaes, essayscompetence_1 … competence_5mean QWK
Mathematical Exam Diagnosticmath-diagnostic, mathdiagnosticmacro F1
Answer Sheet Detectionanswer-sheet, sheetsanswerscell accuracy

1. Grading

from aied_unplugged import evaluate

result = evaluate("math", "my_predictions.csv", "validation_reference.csv")
result.primary  # 0.41…  the ranking metric
result.metrics  # every metric the track reports
result.per_example  # one row per item: true, pred, correct

References can be a CSV or a validation split loaded from the dataset, with the same target columns either way.

from aied_unplugged import load_track, evaluate

validation = load_track("math", "validation")
evaluate("math", predictions, validation)

Metrics

TrackPrimaryDefinitionAlso reported
Essaysmean QWKQuadratic weighted kappa per competence, averaged over the five. Uses the full six-point scale rather than the values in the split, so scores don't shift with the split.exact_match (per competence cell), rmse pooled over the five, qwk_competence_1…_5, rmse_competence_1…_5
Mathmacro F1Unweighted mean of per-class F1 over the classes present in the reference. Predicting an absent class still costs you a false negative on the correct class.accuracy
Answer sheetscell accuracyCorrect cells over total reference cells, so a 26-question sheet counts more than a 16-question one.sheet_exact_match (1 only when every question on a sheet matches), cell-level macro_f1

A constant prediction zeroes the QWK expected-agreement denominator. That case scores 1.0 when the prediction is right everywhere and 0.0 otherwise, instead of NaN. Macro F1 skips classes absent from the reference: averaging over the full taxonomy hands you a guaranteed zero for classes the split never uses, and the preview sample omits two of the thirteen.

Validation checks

A grader rejects the whole file instead of scoring the rows it can read. It raises SubmissionError naming the ids and columns at fault, without quoting a target value. Rejected files include:

  • an empty file, a missing column, or an extra column
  • a blank or duplicated identifier
  • an empty prediction
  • an ungraded row, or a missing row
  • a competence score off the 0/40/80/120/160/200 grid
  • a diagnostic outside the thirteen labels
  • an answer-sheet value outside the seven
  • a question_number that is not an integer (1.0 and "1" both fail)
  • a question repeated, missing, or not on the sheet

2. Loading

Getting the dataset

download() pulls the dataset from Kaggle. Needs aied-unplugged[kaggle].

from aied_unplugged import download, load_track

download()  # into the kagglehub cache, returns the path
load_track("math")  # the loaders below now find it
load_track("math", source="kaggle")  # same, in one call
load_track("math", source="hf")  # in-memory DatasetDict from the HF Hub

Later calls reuse the cache; pass force=True to re-fetch. download() also sets the default root for the process, so explore and verify run without root=.

Both mirrors carry the same release:

To unpack one by hand, put metadata/ and schema/ under a single directory, then name that directory with root=, with AIED_UNPLUGGED_DATA, or as competition-dataset/ in your working directory. root= wins over AIED_UNPLUGGED_DATA, which wins over download(), which wins over competition-dataset/.

Loading a track

from aied_unplugged import load_track, load_metadata, open_image

splits = load_track("essays")  # local release tree if present, else the Hub
splits.train, splits.validation, splits.test

train = load_track("math", "train")  # one split
image = open_image(train.iloc[0], "equation_image")

The local loaders return pandas DataFrames with the Parquet metadata and absolute image paths in <column>_path.

load_schema("math")  # the published taxonomy, fields and metrics
graded_ids("aes")  # the ids a submission must carry
question_counts()  # questions per graded answer sheet
sample_submission("aes")  # the published placeholder file
verify()  # re-check every SHA-256 in the release

3. Exploring

Needs aied-unplugged[explore] and a local copy of the dataset, from download() or from a mirror you unpacked yourself.

from aied_unplugged import explore

explore.summary()  # rows per track and split
explore.label_distribution("math")  # counts and shares, taxonomy order
explore.plot_label_distribution("math")
explore.describe_taxonomy("math")  # labels with their Portuguese source terms

explore.show_essay("essay-1-3")  # the page, with its five competence scores
explore.show_math("math-604-01")  # question above, student working below
explore.show_sheet(0)  # a sheet with what was marked
explore.render_math("math-604-01")  # the same pair as one PIL image

Name an item by id, by position, or by passing its metadata row.


4. Running a model

Needs aied-unplugged[models]. Two strategies, either one enough for a valid submission and a baseline score.

from aied_unplugged import models, submission

# Predict the training majority for everything.
predictions = models.majority_baseline("math")

# Zero-shot with a local vision-language model.
predictions = models.predict("math", "Qwen/Qwen2.5-VL-3B-Instruct", limit=20)

submission.write("math", predictions, "submission.csv")

predict prompts the model once per item with the track's images and parses the reply into the submission schema. An unparseable reply falls back to a safe default, so a run always ends with a gradeable file.

Fine-tuning covers the math track only, as image classification over the student's working:

trainer = models.finetune(model="google/vit-base-patch16-224-in21k", epochs=3)
predictions = models.predict_with_classifier(trainer, split="test")

Not included: essay or answer-sheet fine-tuning, multi-GPU, LoRA, hyperparameter search.


5. Submissions

from aied_unplugged import build, graded_ids, question_counts, validate, write

frame = build("answer-sheet", [{"sheet_id": "s1", "answers": {1: "A", 2: "Blank"}}])
report = validate(
    frame,
    "answer-sheet",
    expected_ids=graded_ids("answer-sheet"),
    expected_questions=question_counts(),
)
if not report.valid:
    print(report)
write("answer-sheet", frame, "submission.csv")

validate checks the exact column set, the identifiers, the competence scale, the diagnostic labels, and the JSON encoding of the answer-sheet column, including integer question numbers. With expected_questions, it also checks the question count on each sheet. build takes answers as a list of records or as a {question_number: label} mapping and encodes both to the same JSON.


Development

uv run --with-editable . --with pytest pytest -q
uv run ruff check .

Tests needing the release tree skip when competition-dataset/ is absent.

MIT licensed. The dataset itself is CC BY 4.0 and is published separately.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 16

Native requirements
10 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
numpy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
numpy>=1.24runtime
pandas
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pandas>=2.0runtime
pillow
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pillow>=10runtime
pyarrow
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pyarrow>=14runtime
accelerate
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
accelerate>=0.30; extra == "all"runtime
datasets
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
datasets>=2.20; extra == "all"runtime
kagglehub
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
kagglehub>=0.3; extra == "all"runtime
matplotlib
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
matplotlib>=3.7; extra == "all"runtime
torch
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
torch>=2.2; extra == "all"runtime
transformers
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
transformers>=4.44; extra == "all"runtime
matplotlib
Environment
{
  "extras": "",
  "marker": "extra == \"explore\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
matplotlib>=3.7; extra == "explore"runtime
datasets
Environment
{
  "extras": "",
  "marker": "extra == \"hf\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
datasets>=2.20; extra == "hf"runtime
kagglehub
Environment
{
  "extras": "",
  "marker": "extra == \"kaggle\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
kagglehub>=0.3; extra == "kaggle"runtime
accelerate
Environment
{
  "extras": "",
  "marker": "extra == \"models\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
accelerate>=0.30; extra == "models"runtime
torch
Environment
{
  "extras": "",
  "marker": "extra == \"models\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
torch>=2.2; extra == "models"runtime
transformers
Environment
{
  "extras": "",
  "marker": "extra == \"models\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
transformers>=4.44; extra == "models"runtime

Distribution files

2 collected
aied_unplugged-0.2.0-py3-none-any.whl

bdist_wheel · 24.85 KB · Python py3

Source file
aied_unplugged-0.2.0.tar.gz

sdist · 277.67 KB · Python source

Source file

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 10:37 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:ea6448e4cb5fed79d9e63e80aec245a38c85f04ed0495bd1a87faf40b67ae26b

Other collected versions

View all collected versions →