Source code for plom.db.buildPlomDB

# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2019-2021 Andrew Rechnitzer
# Copyright (C) 2019-2023 Colin B. Macdonald
# Copyright (C) 2020 Dryden Wiebe
# Copyright (C) 2021 Nicholas J H Lai

import logging
import random

from plom import check_version_map, make_random_version_map

# from plom.db import PlomDB


log = logging.getLogger("DB")


def buildSpecialRubrics(spec, db):
    """Add special rubrics such as deltas and per-question specific.

    Returns:
        None

    Raises:
        ValueError: if a rubric already exists (likely b/c you've called
            this twice)
    """
    log.info("Building special manager-generated rubrics")
    # create standard manager delta-rubrics - but no 0, nor +/- max-mark
    for q in range(1, 1 + spec["numberOfQuestions"]):
        mx = spec["question"]["{}".format(q)]["mark"]
        # make zero mark and full mark rubrics
        # Note: the precise "no answer given" string is repated in db_create.py
        rubric = {
            "kind": "absolute",
            "display_delta": f"0 of {mx}",
            "value": "0",
            "out_of": mx,
            "text": "no answer given",
            "question": q,
            "meta": "Is this answer blank or nearly blank?  Please do not use "
            + "if there is any possibility of relevant writing on the page.",
        }
        ok, key_or_err = db.McreateRubric("manager", rubric)
        if not ok:
            raise ValueError(f"Failed to build no-answer-rubric Q{q}: {key_or_err}")
        log.info("Built no-answer-rubric Q%s: key %s", q, key_or_err)

        rubric = {
            "kind": "absolute",
            "display_delta": f"0 of {mx}",
            "value": "0",
            "out_of": mx,
            "text": "no marks",
            "question": q,
            "meta": "There is writing here but its not sufficient for any points.",
        }
        ok, key_or_err = db.McreateRubric("manager", rubric)
        if not ok:
            raise ValueError(f"Failed to build no-marks-rubric Q{q}: {key_or_err}")
        log.info("Built no-marks-rubric Q%s: key %s", q, key_or_err)

        rubric = {
            "kind": "absolute",
            "display_delta": f"{mx} of {mx}",
            "value": f"{mx}",
            "out_of": mx,
            "text": "full marks",
            "question": q,
        }
        ok, key_or_err = db.McreateRubric("manager", rubric)
        if not ok:
            raise ValueError(f"Failed to build full-marks-rubric Q{q}: {key_or_err}")
        log.info("Built full-marks-rubric Q%s: key %s", q, key_or_err)

        # now make delta-rubrics
        for m in range(1, mx + 1):
            # make positive delta
            rubric = {
                "display_delta": "+{}".format(m),
                "value": m,
                "out_of": 0,
                "text": ".",
                "kind": "relative",
                "question": q,
            }
            ok, key_or_err = db.McreateRubric("manager", rubric)
            if not ok:
                raise ValueError(
                    f"Failed to build delta-rubric +{m} for Q{q}: {key_or_err}"
                )
            log.info("Built delta-rubric +%d for Q%s: %s", m, q, key_or_err)
            # make negative delta
            rubric = {
                "display_delta": "-{}".format(m),
                "value": -m,
                "out_of": 0,
                "text": ".",
                "kind": "relative",
                "question": q,
            }
            ok, key_or_err = db.McreateRubric("manager", rubric)
            if not ok:
                raise ValueError(
                    f"Failed to build delta-rubric -{m} for Q{q}: {key_or_err}"
                )
            log.info("Built delta-rubric -%d for Q%s: %s", m, q, key_or_err)


[docs]def initialiseExamDatabaseFromSpec(spec, db, version_map=None): """Build metadata for exams from spec but do not build tests in DB. Arguments: spec (dict): exam specification, see :func:`plom.SpecVerifier`. db (database): the database to populate. version_map (dict/None): optional predetermined version map keyed by test number and question number. If None, we will build our own random version mapping. For the map format see :func:`plom.finish.make_random_version_map`. Returns: dict: the question-version map. Raises: ValueError: if database already populated, or attempt to build paper n without paper n-1. KeyError: invalid question selection scheme in spec. """ if db.is_paper_database_initialised(): raise ValueError("Database already initialised") buildSpecialRubrics(spec, db) if not db.createReplacementBundle(): raise ValueError("Could not create bundle for replacement pages") if not version_map: # TODO: move reproducible random seed support to the make fcn? random.seed(spec["privateSeed"]) version_map = make_random_version_map(spec) check_version_map(version_map) return version_map