# Classification rules specification
Pair: [user guide](../../user/accounting/rules.md). UI is the full drawer in classify.php/classifier.js; private classification_rules.php and classification_rule_actions.php. Tables: classification_rules, classification_rule_conditions, classification_rule_targets, classification_rule_events, classification_rule_control_events; classification audit records business/allocation review context too.
## Matching and precedence
cf_rules orders priority descending, then ID ascending; conditions by condition_order/ID. cf_match requires active rule, resolved scope account|statement_import|global and at least one condition. All conditions AND together. Fields: description, amount, source_name, account_name, account_identifier, currency. Text operators contains/equals/starts_with/ends_with trim and lowercase values. Numeric amount operators require numeric inputs and compare signed amounts; equals uses floating tolerance 0.000001.
cf_suggestions skips rules with missing targets or any inactive target. For each unsaved field, first matching available target wins. Saved fields are not replaced. First relevant matching rule supplies allocation suggestion, from statement default or explicit business; overlapping target/allocation precedence is implementation behaviour requiring deliberate tests, not a semantic guarantee that one rule owns every field.
## Creation and preview
rule_preview and rule_create accept {statement_id,name,scope,conditions:[{match_field,match_operator,match_value}],draft,business_mode,apply_existing}. Name nonempty <=200 bytes; 1–20 conditions, nonempty value <=500 bytes. Numeric operators only amount with numeric value. Use and Category both required; ordinary classification validation applies. business_mode statement|explicit; explicit requires a business.
Preview returns total matching count and first 100 rows across stored data subject to scope. Client requires preview before enabling Save rule; server revalidates create but does not require a preview token. Create locks through transaction, inserts condition/target records and starts in Suggest mode via database default. Conditions and targets are immutable through this UI; replacement preserves history.
## Controls and history
rule_control {id,active,mode:suggest|commit} locks row, validates resolved scope when enabling, updates controls and writes before/after control audit. rule_history {id} returns up to 200 classification events, counts by event type and up to 200 matching allocation-review audit entries. Current allocation-history retrieval searches suggestion JSON text for the rule ID; see implementation limitations, not a claim of a normalized relationship.
## Commit versus explicit apply
Automatic commit during import/load requires statement manual-review off, complete draft and commit-mode suggestions for unsaved required fields. Load never auto-commits previously saved rows, including partial user saves.
apply_existing is a separate explicit action atomic with creation. ac_apply_rule_existing locks statements, matches rows, and requires current latest allocation/payment touching unclassified holding. Treat existing Unspecified fields as open; preserve manually specified fields. Fill available targets/business details; validate and require completeness before cf_write with rule_applied_existing. It is not enabled by simply saving a Suggest rule without the checkbox.
Custom category deletion resets matching rule targets and logs control history. Rules with Unspecified category cannot complete a row; names/activation/mode are retained. Historical acceptance/override events remain unchanged.
## Verification
All-condition matching; signed numeric comparisons; scope isolation; priority conflicts; inactive target suppresses rule; new Suggest rule not auto-committed; explicit Commit obeys statement manual-review and saved-row protection; explicit apply only eligible unresolved current allocations, preserves manual fields/completed rows; preview count can exceed displayed 100; replacement keeps history.