The rule engine is Innova’s first layer of error classification. Every student attempt passes through it before anything else — no network call, no AI inference. The design is grounded in Brown & VanLehn’s (1980) Repair Theory, which established that procedural arithmetic errors are not random: students apply consistent but faulty procedures that can be catalogued. By encoding those known “bugs” as deterministic rules, Innova can classify the majority of errors instantly and with high confidence, reserving the more expensive LLM path for genuinely ambiguous cases.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/vruizz22/innova-backend-serverless/llms.txt
Use this file to discover all available pages before exploring further.
Design
The rule engine is built around the Strategy + Factory pattern.- One TypeScript class per math subdomain implements the
RuleEngineStrategyinterface. RuleEngineFactorymaintains a registry keyed by subdomain code (ARITH_SUB,FRACT_MUL, etc.) and resolves the right strategy at runtime.RuleEngineServiceis the NestJS injectable that orchestrates the call: it receives the subdomain code alongside theCreateAttemptDtopayload and delegates to the resolved strategy.
Supported Topics
The factory registry maps 19 subdomain codes to their concrete strategy classes across 7 math domains.| Subdomain code | Strategy class | Example error types |
|---|---|---|
ARITH_ADD | AdditionCarryStrategy | ARITH_ADD_CARRY_OMITTED_G3, ARITH_ADD_CARRY_WRONG_COLUMN_G3 |
ARITH_SUB | SubtractionBorrowStrategy | ARITH_SUB_BORROW_OMITTED_TENS_G3, ARITH_SUB_BORROW_FROM_ZERO_G3 |
ARITH_MUL | MultiplicationStrategy | ARITH_MUL_ZERO_TIMES_N_EQUALS_N_G3, ARITH_MUL_PARTIAL_NOT_SHIFTED_G5 |
ARITH_DIV | DivisionLongStrategy | ARITH_DIV_DIVISOR_DIVIDEND_SWAPPED_G4, ARITH_DIV_QUOTIENT_ZERO_SKIPPED_G5 |
INT_ADD / INT_SUB / INT_MUL | IntAdditionStrategy, IntSubtractionStrategy, IntMultiplicationStrategy | INT_MUL_NEG_TIMES_NEG_NEG_G7, INT_ADD_DIFF_SIGN_ADDS_MAGNITUDES_G7 |
FRACT_ADDSUB | FractionSameDenomStrategy | FRACT_ADDSUB_SAME_DENOM_ADDS_DENOM_G5, FRACT_ADDSUB_LCD_WRONG_G6 |
FRACT_MUL | FractionMultiplicationStrategy | FRACT_MUL_CROSS_MULTIPLIES_G6, FRACT_MUL_SEEKS_COMMON_DENOM_G6 |
FRACT_DIV | FractionDivisionStrategy | FRACT_DIV_NO_RECIPROCAL_G7, FRACT_DIV_INVERTS_FIRST_FRACTION_G7 |
DEC_ADD / DEC_SUB / DEC_MUL / DEC_DIV | DecimalAdditionStrategy, DecimalSubtractionStrategy, DecimalMultiplicationStrategy, DecimalDivisionStrategy | DEC_ADD_RIGHT_ALIGNED_LIKE_INTEGERS_G5, DEC_MUL_POINT_PLACEMENT_ERROR_G6 |
RATIO_PERCENT / RATIO_PROPORTION | PercentStrategy, ProportionStrategy | Percent and proportion procedure errors |
ALGEBRA_EQ_LINEAR | LinearEquationStrategy | Linear equation solving errors |
POW_POWER / POW_ROOT | PowerLawsStrategy, RootLawsStrategy | Exponent and root law errors |
Error Taxonomy
Error codes follow a structured naming convention:<DOMAIN>_<SUBDOMAIN>_<BUG_NAME>_<GRADE_LEVEL>. For example:
ARITH_SUB_BORROW_OMITTED_TENS_G3— Grade 3 subtraction, borrow omitted in the tens columnARITH_ADD_CARRY_WRONG_COLUMN_G3— Grade 3 addition, carry added to the wrong columnARITH_MUL_PARTIAL_NOT_SHIFTED_G5— Grade 5 multiplication, second partial product not shiftedARITH_DIV_QUOTIENT_ZERO_SKIPPED_G5— Grade 5 long division, interior zero dropped from quotientFRACT_DIV_NO_RECIPROCAL_G7— Grade 7 fraction division, divisor not invertedDEC_MUL_POINT_PLACEMENT_ERROR_G6— Grade 6 decimal multiplication, decimal point misplaced
ARITH_TRANSV_DIGIT_TRANSPOSITION— digits of the answer are a permutation of the correct answerARITH_TRANSV_PLACE_VALUE_ERROR— answer is a factor-of-10 shift of the correct answerARITH_TRANSV_FACT_ERROR— basic arithmetic fact recall error (off by ≤ 2)
error-tags.generated.ts) contains 2 624 active tags across all domains. The rule engine targets a ≥ 75% classification rate on in-scope subdomains; the remainder fall through to UNCLASSIFIED.
UNCLASSIFIED Fallback
When no deterministic rule matches — or when the subdomain code has no registered strategy — the factory returns a built-in fallback:UNCLASSIFIED is enqueued to SQS for asynchronous LLM classification. The two-tier architecture means deterministic classification remains the fast path (< 5 ms, in-process), while AI inference handles only the tail that rules cannot cover.
Strategy Interface
Every strategy implements this interface:evidence array is optional but strongly encouraged — it is logged and surfaced in the admin UI to help validate rule quality.
Example: Subtraction Borrow Strategy
TheSubtractionBorrowStrategy implements eight deterministic rules in priority order. Here is an excerpt showing the first four:
How to Add a Strategy
Implement RuleEngineStrategy
Create a new file in
src/modules/attempts/rule-engine/strategies/. Export a class that implements RuleEngineStrategy, setting subdomainCode to the new subdomain key and writing classify() as a series of prioritized if guards.Register in the factory
Open
factory.ts, import your class, and add an entry to the REGISTRY object:Write unit tests
Add a spec file beside the strategy. Cover at least: correct answer returns
isCorrect: true; each documented error rule returns the right errorType and a confidence ≥ 0.75; the fallback returns UNCLASSIFIED with confidence: 0.The rule engine runs entirely in-process — no database queries, no HTTP calls. End-to-end classification time is consistently under 5 ms per attempt. This is by design: strategies must remain pure functions of the
CreateAttemptDto payload.