-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Creating Rules
This page is an introduction to creating new Oppia rules. An Oppia rule takes a reader's response, normalizes it, and evaluates it against a predicate. A card's interaction will correspond to a set of rules, and the first rule that is triggered by the response determines what Oppia will display to the reader next.
Rules are listed in extensions/interactions/rule_templates.json
, where they are grouped by interaction. For example, here are the rules for the MathEquationInput
interaction:
{
...
"MathEquationInput": {
"MatchesExactlyWith": {
"description": "matches exactly with {{x|MathEquation}} {{y|PositionOfTerms}}"
},
"IsEquivalentTo": {
"description": "is equivalent to {{x|MathEquation}}"
},
"ContainsSomeOf": {
"description": "contains at least one of the terms present in {{x|MathEquation}} {{y|PositionOfTerms}}"
},
"OmitsSomeOf": {
"description": "omits at least one of the terms present in {{x|MathEquation}} {{y|PositionOfTerms}}"
},
"MatchesWithGeneralForm": {
"description": "matches the form of {{x|MathEquation}} with placeholders {{y|SetOfAlgebraicIdentifier}}"
}
},
...
}
Rule descriptions can specify rule inputs with curly braces. For an input p
of type Type
, the description would include {{p|Type}}
. The types are defined as classes in extensions/objects/models/objects.py
, and each class provides a normalize
method that transforms the input into a canonical form.
Each rule is implemented in the interaction's rules service as a method (evaluation function). In extensions/interactions/MathEquationInput/directives/math-equation-input-rules.service.ts
, the MathEquationInputRulesService
class defines a MatchesExactlyWith
method, an IsEquivalentTo
method, and so on. Each method takes exactly two arguments: answer
, which is the correct answer, and inputs
, an object with the rule inputs as attributes. For example the inputs
argument to the MatchesWithGeneralForm
method of MathEquationInputRulesService
would have two attributes: x
and y
. The types for these inputs are defined in extensions/interactions/rule-input-defs.ts
and extensions/interactions/answer-defs.ts
.
The return value from a rule is always a boolean that describes whether answer
and inputs
obey the rule. For example, the IsEquivalentTo
rule returns whether the user's inputs are equivalent to the creator-configured answer.
For example, suppose we have a rule IsBetweenInclusive
with the description is at least {{x|Real}} and at most {{y|Real}}
. Then our evaluation function could be:
export class MyInteractionRulesService {
IsBetweenInclusive(
answer: MyInteractionAnswer,
inputs: MyInteractionInputs): boolean {
return inputs.x <= answer <= inputs.y
}
}
Note that the evaluation function must not use any external information, for example the reader's current card. The inputs
and answer
must be sufficient to determine whether the rule is satisfied.
Suppose you have just created a new interaction, say MyInteraction
, and now you want to define the rules associated with it. To do so, follow these steps:
- In
extensions/interactions/rule_templates.json
, add a dictionary under the keyMyInteraction
. You can skip this step if you are adding a rule to an existing interaction. - For each rule you want to create add an appropriate key that names the rule. The corresponding value should be a dictionary with a single key,
description
, whose value is a string describing the rule. The string should contain rule inputs as substrings of the form{{inputName|InputType}}
. See the descriptions section above for details. - Add a corresponding evaluation function to the interaction's rules service, as described in the evaluation functions section above.
- Add tests for the evaluation function by creating a
*.spec.ts
file for the rules service. Create a separate test for each rule and check the various corner cases to make sure that rules are defined correctly. You must do this for any new rule that you create so that you can ensure that the rule works properly and returns the correct results on a variety of inputs.
If you would like to contribute a rule to Oppia, please feel free to do so, but we recommend that you talk to us first! This helps ensure that work is not duplicated, and that the added rules are useful for creators and learners. We also welcome suggestions for new rules.
Have an idea for how to improve the wiki? Please help make our documentation better by following our instructions for contributing to the wiki.
Core documentation
- Oppia's mission
- Code of Conduct
- Get involved!
- How to report a bug
- Google Summer of Code 2024
- Hacktoberfest 2024
Developing Oppia
- FAQs
- How to get help
- Getting started with the project
- How the codebase is organized
- Making your first PR
- Debugging
- Testing
- Codebase policies and processes
- Guidelines for launching new features
- Guidelines for making an urgent fix (hotfix)
- Testing jobs and other features on production
- Guidelines for Developers with Write Access to the Oppia Repository
- Release schedule and other information
- Revert and Regression Policy
- Privacy aware programming
- Code review:
- Project organization:
- QA Testing:
- Design docs:
- Team-Specific Guides
- LaCE/CD:
- Developer Workflow:
Developer Reference
- Oppiabot
- Git cheat sheet
- Frontend
- Backend
- Backend Type Annotations
- Writing state migrations
- Calculating statistics
- Storage models
- Coding for speed in GAE
- Adding a new page
- Adding static assets
- Wipeout Implementation
- Notes on NDB Datastore transactions
- How to handle merging of change lists for exploration properties
- Instructions for editing roles or actions
- Protocol buffers
- Webpack
- Third-party libraries
- Extension frameworks
- Oppia-ml Extension
- Mobile development
- Performance testing
- Build process
- Best practices for leading Oppia teams
- Past Events