Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
1082321
Ignore extracted compareExpressions-packages nested repo
m-messer Sep 11, 2026
710a92a
Depend on the extracted compareexpressions packages
m-messer Sep 12, 2026
c88110e
Use compareexpressions.criteria and slr_parsing; add an app-local result
m-messer Sep 12, 2026
60e58b5
Use compareexpressions.expression_parsing for symbolic expressions
m-messer Sep 12, 2026
93909cd
Use compareexpressions.units for physical quantities
m-messer Sep 12, 2026
377cfbb
Drop the lambda-feedback latex2sympy fork
m-messer Sep 12, 2026
0c70e71
Document the compareexpressions packages
m-messer Sep 12, 2026
6accd12
Install the packages from m-messer/compare-expressions-packages
m-messer Sep 12, 2026
6e31934
"Added physical quantity preview implementation and associated tests"
m-messer Sep 15, 2026
572c547
Add symbolic_preview.py and feedback/tags.py, moved from expression_p…
m-messer Sep 16, 2026
f0551d1
Wire up the moved symbolic preview in preview.py and context/symbolic.py
m-messer Sep 16, 2026
416aed0
Rebuild physical_quantity_preview.py against the new expression_parsi…
m-messer Sep 16, 2026
a8f1837
Catch typed notation exceptions in evaluation.py instead of checking …
m-messer Sep 16, 2026
c48fe3a
Update physical_quantity to handle reverted_unit feedback
m-messer Sep 16, 2026
53ac036
Replace `add_feedback_from_tags` with `CriteriaGraph.export_feedback`…
m-messer Sep 25, 2026
686d3d9
Pin the packages at 2848e58 (CriteriaGraph.export_feedback)
m-messer Sep 25, 2026
541162c
Use the renamed lambdafeedback packages
m-messer Sep 25, 2026
3f4dff3
Ignore extracted compareExpressions-packages nested repo
m-messer Sep 11, 2026
08454d7
Merge branch 'main' into refactor/adopt-packages
m-messer Oct 12, 2026
8e27807
Refactor symbolic and quantity contexts for updated lambdafeedback APIs
m-messer Oct 13, 2026
bd9c991
Simplify LaTeX outputs: Remove unnecessary parentheses and update not…
m-messer Oct 13, 2026
9807764
Use SympyParsingConfig.replace for extra unsplittable symbols
m-messer Oct 13, 2026
8f0c5d0
Update lambdafeedback dependencies to v0.3.0-rc.2
m-messer Oct 13, 2026
c7d375b
Remove unused benchmarking scripts, Mermaid documentation files, and …
m-messer Oct 13, 2026
f7a1b32
Add development container setup with Dockerfile and configuration files
m-messer Oct 13, 2026
7a091d9
Remove redundant `pytest.main` calls, outdated imports, and update do…
m-messer Oct 13, 2026
8e95025
Mark slow-running tests with `@pytest.mark.slow` and update healthche…
m-messer Oct 13, 2026
06dd8d1
Removed dockerfile and comments
m-messer Oct 23, 2026
a8425fb
Refactor test structure: move test files to `app/tests/`, update impo…
m-messer Oct 13, 2026
ba973b9
Refactor preview structure: move context-specific preview functions, …
m-messer Oct 13, 2026
9218edf
Refactor symbolic and numerical contexts: extract set equivalence and…
m-messer Oct 14, 2026
1af3e00
Refactor context structure: reorganize symbolic and physical_quantity…
m-messer Oct 15, 2026
bd50534
Refactor graph helpers: extract shared helper functions (`attach_term…
m-messer Oct 15, 2026
7d5f9c4
Refactor symbolic context: remove `set_equivalence.py`, add `graphs` …
m-messer Oct 15, 2026
bbf8068
Refactor physical_quantity context: split logic into separate modules…
m-messer Oct 15, 2026
e3fdaf5
Refactor evaluation module: extract reusable logic into helper functi…
m-messer Oct 15, 2026
f5d7f52
Add reserved keyword validations to evaluation tests and refactor fee…
m-messer Oct 15, 2026
3ea7e0b
Refactor context structure and evaluation logic: introduce `context/d…
m-messer Oct 15, 2026
b3bff03
Refactor operators and evaluation logic: centralize `COMPARISON_OPERA…
m-messer Oct 15, 2026
87879b4
Introduce type annotations for all modules in `app/`, add `mypy.ini` …
m-messer Oct 16, 2026
7d31ec2
Refactor evaluation graph construction: replace repetitive graph init…
m-messer Oct 16, 2026
582fe46
Refactor symbolic comparison: simplify retry logic for expression com…
m-messer Oct 16, 2026
33b3c79
Refactor context handling: replace context dictionaries with `Context…
m-messer Oct 16, 2026
4d4a8b0
Refactor quantity parsing and usage: replace `QuantityDict` with `Par…
m-messer Oct 16, 2026
ca2e2df
Adopt absolute imports across `app/`, replace all relative imports, a…
m-messer Oct 16, 2026
a096a55
Refactor evaluation parameters: replace `FrozenValuesDictionary` with…
m-messer Oct 16, 2026
9ae968d
Centralize type aliases in `app/types.py`, update type hints across t…
m-messer Oct 16, 2026
294d2fe
Refactor error reporting in `parse_reserved_expressions`: introduce `…
m-messer Oct 16, 2026
81f3a7e
Update type aliases in `comparison.py` to use centralized definitions…
m-messer Oct 16, 2026
a14026f
Update lambdafeedback packages to v0.3.0-rc.3 and adopt helpers moved…
m-messer Oct 19, 2026
0eaae11
Refactor `parameters_dict` to `parameters`: update function signature…
m-messer Oct 16, 2026
659ab57
Update dev docs to reflect absolute import path changes in `app/types…
m-messer Oct 16, 2026
cedb336
Spaced around operators
m-messer Oct 23, 2026
c66f5c2
Merge pull request #287 from lambda-feedback/refactor/adopt-packages-rc3
m-messer Oct 23, 2026
11d1bf3
Merge pull request #283 from lambda-feedback/refactor/design-patterns
m-messer Oct 23, 2026
3edde8d
Merge pull request #281 from lambda-feedback/refactor/restructure-app
m-messer Oct 23, 2026
bfc73f4
Sorted spacing around kwargs
m-messer Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/staging-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,24 @@ jobs:
- name: Install dependencies
run: |
python -m venv .venv
.venv/bin/pip install pytest
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/pip install -r app/requirements.txt


- name: Lint with flake8
run: |
source .venv/bin/activate
.venv/bin/pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 ./app --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 ./app --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

- name: Type check with mypy
run: |
source .venv/bin/activate
# configuration (checked files, strictness, untyped dependencies) is in mypy.ini
mypy

- name: Run tests
if: always()
run: |
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/test-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@ jobs:
- name: Install dependencies
run: |
python -m venv .venv
.venv/bin/pip install pytest
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/pip install -r app/requirements.txt

- name: Lint with flake8
run: |
source .venv/bin/activate
.venv/bin/pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 ./app --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 ./app --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

- name: Type check with mypy
run: |
source .venv/bin/activate
# configuration (checked files, strictness, untyped dependencies) is in mypy.ini
mypy

- name: Run tests
if: always()
run: |
Expand Down
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,18 @@ app/benchmark.py
app/profiling_examples.py

.idea/

# Extracted packages live here as their own separate git repo
/compareExpressions-packages/

# OS / editor / local tool files
.DS_Store
.vscode/
.junie/
.mcp.json

# Local virtual environments (e.g. .venv38)
.venv*/

# Test reports written by CI
reports/
16 changes: 10 additions & 6 deletions README.md
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,26 @@ This version is specifically for python, however the ultimate goal is to make si
- [Production API Gateway Integrations](https://eu-west-2.console.aws.amazon.com/apigateway/main/develop/integrations/attach?api=cttolq2oph&integration=qpbgva8&region=eu-west-2&routes=0xsoy4q)

4. Merge commits into the default branch
- This will trigger the `test-and-deploy.yml` workflow, which will build the docker image, push it to a shared ECR repository, then call the backend `grading-function/ensure` route to build the necessary infrastructure to make the function available from the client app.
- This will trigger the `staging-deploy.yml` workflow (and `production-deploy.yml` on release), which will build the docker image, push it to a shared ECR repository, then call the backend `grading-function/ensure` route to build the necessary infrastructure to make the function available from the client app.

5. You are now ready to start developing your function:

- Edit the `app/evaluation.py` file, which ultimately gets called when the function is given the `eval` command
- Edit the `app/evaluation_tests.py` file to add tests which get run:
- Every time you commit to this repo, before the image is built and deployed
- Whenever the `healthcheck` command is supplied to the deployed function
- Edit the `app/docs.md` file to reflect your changes. This file is baked into the function's image, and is made available using the `docs` command. This feature is used to display this function's documentation on our [Documentation](https://lambda-feedback.github.io/Documentation/) website once it's been hooked up!
- Add pytest tests to `app/tests/`. They get run:
- On every pull request and before the image is built and deployed (`pytest` from the repo root)
- Whenever the `healthcheck` command is supplied to the deployed function (via `app/evaluation_tests.py`)
- Edit `app/docs/user.md` and `app/docs/dev.md` to reflect your changes. These files are baked into the function's image, and is made available using the `docs` command. This feature is used to display this function's documentation on our [Documentation](https://lambda-feedback.github.io/Documentation/) website once it's been hooked up!

---

## How it works

The function is built on top of a custom base layer, [BaseEvaluationFunctionLayer](https://github.com/lambda-feedback/BaseEvalutionFunctionLayer), which tools, tests and schema checking relevant to all evaluation functions.

### Packages

Expression parsing, unit handling and the criteria machinery come from the [lambda-feedback-packages](https://github.com/lambda-feedback/lambda-feedback-packages) repository (currently the `v0.3.0-rc.2` release candidate), installed as git dependencies in `app/requirements.txt`. See the "Packages" section of `app/docs/dev.md` for what each package does and how to develop against a local checkout.

### Docker & Amazon Web Services (AWS)

The grading scripts are hosted AWS Lambda, using containers to run a docker image of the app. Docker is a popular tool in software development that allows programs to be hosted on any machine by bundling all its requirements and dependencies into a single file called an **image**.
Expand All @@ -57,7 +61,7 @@ Once the code passes all these tests, it will then be uploaded to AWS and will b
## Pre-requisites
Although all programming can be done through the GitHub interface, it is recommended you do this locally on your machine. To do this, you must have installed:

- Python 3.8 or higher.
- Python 3.12 (the version used by CI and the production image).

- GitHub Desktop or the `git` CLI.

Expand Down
Empty file removed __init__.py
Empty file.
12 changes: 12 additions & 0 deletions app/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Build files
Dockerfile
.dockerignore

# The base image provides the app package's __init__.py and handler
__init__.py

# Local and generated files
**/__pycache__
**/*.py[cod]
**/.DS_Store
**/.pytest_cache
49 changes: 6 additions & 43 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Base image that bundles AWS Lambda Python 3.8 image with some middleware functions
# FROM base-eval-tmp
# FROM rabidsheep55/python-base-eval-layer
# Base image that bundles the AWS Lambda Python image with the lambda-feedback middleware (app.handler)
FROM ghcr.io/lambda-feedback/baseevalutionfunctionlayer:main-3.12

RUN dnf install -y git findutils && dnf clean all
Expand All @@ -13,46 +11,11 @@ ENV PYTHONPATH=/app
COPY requirements.txt .
RUN pip3 install -r requirements.txt

# Copy main scripts
COPY evaluation.py ./app/
COPY evaluation_test.py ./app/
COPY preview.py ./app/
COPY preview_test.py ./app/

# Copy contexts
COPY context/physical_quantity.py ./app/context/
COPY context/symbolic.py ./app/context/

# Copy feedback messages
COPY feedback/physical_quantity.py ./app/feedback/
COPY feedback/symbolic.py ./app/feedback/

# Copy preview implementations
COPY preview_implementations/physical_quantity_preview.py ./app/preview_implementations/
COPY preview_implementations/symbolic_preview.py ./app/preview_implementations/

# Copy tests
COPY tests/example_test.py ./app/tests/
COPY tests/physical_quantity_evaluation_test.py ./app/tests/
COPY tests/physical_quantity_preview_test.py ./app/tests/
COPY tests/slr_quantity_test.py ./app/tests/
COPY tests/symbolic_evaluation_test.py ./app/tests/
COPY tests/symbolic_preview_test.py ./app/tests/

# Copy utility code
COPY utility/criteria_graph_utilities.py ./app/utility/
COPY utility/criteria_parsing.py ./app/utility/
COPY utility/evaluation_result_utilities.py ./app/utility/
COPY utility/expression_utilities.py ./app/utility/
COPY utility/physical_quantity_utilities.py ./app/utility/
COPY utility/preview_utilities.py ./app/utility/
COPY utility/slr_parsing_utilities.py ./app/utility/
COPY utility/syntactical_comparison_utilities.py ./app/utility/
COPY utility/unit_system_conversions.py ./app/utility/

# Copy Documentation
COPY docs/dev.md ./app/docs/dev.md
COPY docs/user.md ./app/docs/user.md
# The healthcheck command runs the pytest suite (see evaluation_tests.py)
RUN pip3 install pytest

# Copy the evaluation function source, tests and docs (exclusions in .dockerignore)
COPY . ./app/

# Set permissions so files and directories can be accessed on AWS
RUN chmod 644 $(find . -type f)
Expand Down
57 changes: 0 additions & 57 deletions app/benchmarking.py

This file was deleted.

5 changes: 5 additions & 0 deletions app/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pytest


def pytest_configure(config: pytest.Config) -> None:
config.addinivalue_line("markers", "slow: excluded from the deployed healthcheck (see evaluation_tests.py)")
35 changes: 35 additions & 0 deletions app/context/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""The `Context` that specialises the evaluation function for one kind of response (symbolic or physical quantity)."""

from __future__ import annotations

from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Any

from lambdafeedback.criteria import CriteriaGraph
from lambdafeedback.slr_parsing import SLRParser

from app.evaluation_parameters import EvaluationParameters
from app.preview.common import Result
from app.result import EvaluationResult
from app.types import Parameters


@dataclass(frozen=True)
class Context:
# Previews the response, as the preview function would
expression_preview: Callable[[str, Parameters], Result]
# Builds the parser for the criteria from the parsed reserved expressions
generate_criteria_parser: Callable[[Mapping[str, Mapping[str, Any]]], SLRParser]
# `(name, expression, parameters)`: the expression string, ready to be parsed
expression_preprocess: Callable[[str, str, Parameters], str]
# `(name, expression, parsing_parameters, evaluation_result)`: the parsed expression
expression_parse: Callable[[str, str, Any, EvaluationResult], Any]
# Parameters used when they are not given
default_parameters: Mapping[str, Any]
# Criteria used when none are given
default_criteria: tuple[str, ...]
# Builds the criteria graph for each parsed criterion
feedback_procedure_generator: Callable[[EvaluationParameters], dict[str, CriteriaGraph]]
# `(parameters, unsplittable_symbols=...)`: the parsing parameters passed to `expression_parse`
parsing_parameters_generator: Callable[..., Any]
9 changes: 9 additions & 0 deletions app/context/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Default evaluation parameters shared by all contexts; each context adds its own on top of these."""

base_default_parameters = {
"complexNumbers": False,
"convention": "equal_precedence",
"elementary_functions": False,
"strict_syntax": False,
"multiple_answers_criteria": "all",
}
46 changes: 46 additions & 0 deletions app/context/graph_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Helpers for building the `CriteriaGraph`s shared by the symbolic and physical quantity contexts."""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from lambdafeedback.criteria import CriteriaGraph, Evaluate, FeedbackStringGenerator, OutputNode, ReachedCriteria


def outcome_tag(label: str, result: bool | None, inputs: Mapping[str, Any] | None = None) -> ReachedCriteria:
"""`{label+"_TRUE": inputs}` if `result` is True, `label+"_FALSE"` if it is False, otherwise `label+"_UNKNOWN"`."""
if result is True:
return {label + "_TRUE": inputs}
elif result is False:
return {label + "_FALSE": inputs}
else:
return {label + "_UNKNOWN": inputs}


def outcome_graph(label: str, summary: str, details: str, evaluate: Evaluate) -> tuple[CriteriaGraph, OutputNode]:
"""A new graph `label` with an end node and a single evaluation node `label`, ready for its outcomes to be attached."""
graph = CriteriaGraph(label)
end = graph.add_end_node()
graph.add_evaluation_node(label, summary=summary, details=details, evaluate=evaluate)
return graph, end


def attach_terminal_criterion(
graph: CriteriaGraph,
evaluation_label: str,
label: str,
end: OutputNode,
summary: str,
details: str,
feedback_string_generator: FeedbackStringGenerator | None = None,
) -> None:
"""Attaches a new criterion `label` to `evaluation_label` and connects it to the `end` node."""
graph.attach_new_criterion(
evaluation_label,
label,
summary=summary,
details=details,
feedback_string_generator=feedback_string_generator
)
graph.attach(label, end.label)
11 changes: 11 additions & 0 deletions app/context/operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""The comparison operators that criteria can use, by the symbol used in the criteria string."""

import operator

COMPARISON_OPERATORS = {
"=": operator.eq,
">": operator.gt,
">=": operator.ge,
"<": operator.lt,
"<=": operator.le,
}
Loading