> ## Documentation Index
> Fetch the complete documentation index at: https://cona.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Accounting rules engine

# CONA Accounting Rules Engine

## Overview

The CONA Accounting Rules Engine is a sophisticated system that automatically determines the correct General Ledger (GL) dimensions for financial transactions based on configurable business rules. It eliminates the need for manual accounting entries by intelligently matching transaction data against predefined criteria.

## Core Purpose

**Why do we need this?**

* **Automation**: Automatically classify transactions without manual intervention
* **Accuracy**: Reduce human error in GL account assignments
* **Consistency**: Ensure uniform accounting treatment across all transactions
* **Scalability**: Handle thousands of transactions with complex routing logic
* **Auditability**: Maintain clear trails of why specific GL accounts were chosen

## Accounting Dimensions

### What are Accounting Dimensions?

Accounting dimensions are the different ways we can categorize and route financial transactions in the General Ledger (Collection of financial transactions). They represent the "buckets" where transactions should be recorded.
Financial transactions are also called accounting impacts or journal entries.

### Types of Dimensions

1. **GL Accounts** (`account` type)
   * The most common dimension type
   * Represents specific Chart of Accounts (Collection of Account-numbers) entries
   * Examples: "Sales Revenue", "Cost of Goods Sold", "Accounts Receivable"

2. **Custom Dimensions** (other types)
   * Cost centers, departments, projects, etc.
   * Used for additional categorization beyond standard GL accounts

### Dimension Structure

```typescript theme={null}
interface GLDimension {
  id: string;
  label: string; // Human-readable name
  type: "account" | string; // Dimension type
  account_nr?: string; // Account number (for GL accounts)
}
```

## Rules Architecture

### What is a Rule?

A **Rule** is a logical statement that defines when a specific GL dimension should be applied to a transaction. Think of it as an "if-then" statement:

> "**IF** transaction meets these criteria **THEN** assign this GL dimension"

### Rule Structure

```typescript theme={null}
interface Rule {
  id: string;
  criteria: Criteria[]; // List of conditions to check
  gl_account?: GLAccount; // Target GL account (for account dimensions)
  set_gl_dimension?: string; // Target value (for other dimensions)
  is_fallback?: boolean; // Whether this is a fallback rule
  order: number; // Priority order for rule evaluation
}
```

### Rule Types

1. **Standard Rules**
   * Contains specific criteria that must be met
   * Evaluated in order of priority
   * First matching rule wins

2. **Fallback Rules**
   * Special rules that match when no other rules apply
   * Typically use the `all` operator to match everything
   * Prevents transactions from becoming "UNMATCHED"

## Criteria System

### What is a Criterion?

A **Criterion** is a single condition that checks a specific field in the transaction data against an expected value using a logical operator.

### Criterion Structure

```typescript theme={null}
interface Criterion {
  id: string;
  column_id: string; // Which field to check
  operator: string; // How to compare (=, !=, >, contains, etc.)
  value: string; // Expected value to compare against
  column: {
    column_type: {
      field_path: string; // Path to the field in transaction data
      field_type: string; // Data type (string, number, date, etc.)
      nested_column_type_child?: {
        // For nested fields
        field_path: string;
      };
    };
  };
}
```

### Field Path Examples

```typescript theme={null}
// Direct field access
field_path: "total_amount"; // data.total_amount
field_path: "currency"; // data.currency

// Nested object access
field_path: "custom_properties"; // data.custom_properties.shopify_payment_type.value
nested_field_path: "shopify_payment_type";

// Array access (first element)
field_path: "line_items"; // data.line_items[0].tax_rate
nested_field_path: "tax_rate";
```

## Operators

### What are Operators?

**Operators** define how to compare the actual transaction data against the expected criterion value.

### Operator Types

#### Equality Operators

* `=` (equals) - Exact match
* `!=` (not equals) - Does not match

#### Numeric Operators

* `>` (greater than)
* `<` (less than)
* `>=` (greater than or equal)
* `<=` (less than or equal)

#### String Operators

* `*=` (contains) - Field contains the value
* `!*=` (not contains) - Field does not contain the value
* `^=` (starts with) - Field starts with the value
* `!^=` (not starts with) - Field does not start with the value
* `$=` (ends with) - Field ends with the value
* `!$=` (not ends with) - Field does not end with the value

#### Special Operators

* `empty` - Field is empty or null
* `!empty` - Field is not empty
* `all` - Always matches (used in fallback rules)

### Operator Examples

```typescript theme={null}
// Exact match
{ operator: "=", value: "shopify", field_path: "sales_channel" }
// Matches when data.sales_channel === "shopify"

// Contains text
{ operator: "*=", value: "refund", field_path: "description" }
// Matches when data.description contains "refund"

// Numeric comparison
{ operator: ">", value: "100", field_path: "total_amount" }
// Matches when data.total_amount > 100

// Empty check
{ operator: "empty", value: "", field_path: "customer_notes" }
// Matches when data.customer_notes is empty
```

## Rule Evaluation Process

### How Rules are Processed

1. **Document Splitting**: Complex documents are split into individual documents per line item
2. **Data Extraction**: Extract field values from transaction data using field paths
3. **Criterion Evaluation**: Check each criterion in the rule against the extracted data
4. **Rule Matching**: Rule matches only if ALL criteria are true
5. **Rule Selection**: First matching rule (by order) determines the GL dimension
6. **Fallback Handling**: If no rules match, use fallback rule if available
7. **Consolidation**: Documents with identical GL dimensions are consolidated back together

### Document Splitting and Consolidation Process

The accounting rules engine processes complex documents through a split-and-consolidate approach:

#### 1. Document Splitting

When a document contains multiple line items (e.g., an invoice with different product types), the system:

* Creates separate documents for each line item
* Each split document inherits the parent document's metadata (customer, date, etc.)
* Each split document contains only one line item for targeted rule evaluation

#### 2. Individual Rule Evaluation

Each split document is processed independently:

* Rules are applied to each document's specific line item data
* Different line items may match different rules and get different GL dimensions
* This allows for precise accounting treatment of mixed transactions

#### 3. Consolidation

After rule evaluation, documents with identical GL dimensions are merged:

* Documents targeting the same GL account are consolidated
* Line items are combined while preserving individual amounts
* This reduces the number of journal entries while maintaining accuracy

#### Example: Mixed Product Invoice

**Original Document:**

```json theme={null}
{
  "id": "INV-001",
  "customer": "ACME Corp",
  "total_amount": 1150.0,
  "line_items": [
    {
      "type": "product",
      "description": "Widget A",
      "amount": 500.0,
      "tax_rate": 0.08
    },
    {
      "type": "shipping",
      "description": "Express Shipping",
      "amount": 50.0,
      "tax_rate": 0.08
    },
    {
      "type": "service",
      "description": "Installation",
      "amount": 600.0,
      "tax_rate": 0.08
    }
  ]
}
```

**After Splitting (3 documents):**

* Document 1: Widget A (product) → Rules evaluate to "Product Sales Revenue"
* Document 2: Express Shipping (shipping) → Rules evaluate to "Shipping Revenue"
* Document 3: Installation (service) → Rules evaluate to "Service Revenue"

**After Consolidation:**

* 3 separate journal entries created (no consolidation because all dimensions are different)
* Each targeting their respective revenue accounts

**Alternative Scenario - Same Dimensions:**
If line items 1 and 3 both matched rules pointing to "General Sales Revenue":

* Documents 1 and 3 would consolidate into one journal entry
* Document 2 would remain separate for "Shipping Revenue"
* Result: 2 journal entries instead of 3

This approach ensures that:

* **Accuracy**: Each line item gets the correct accounting treatment
* **Efficiency**: Unnecessary journal entry proliferation is avoided
* **Flexibility**: Complex documents with mixed line items are handled automatically
* **Auditability**: The split-and-consolidate process is tracked and reversible

## Practical Examples

### Example 1: Simple Sales Revenue Rule

**Business Logic**: "All Shopify sales should go to Sales Revenue account"

```typescript theme={null}
const rule = {
  id: "shopify-sales-revenue",
  order: 1,
  criteria: [
    {
      column_id: "sales_channel",
      operator: "=",
      value: "shopify",
      column: {
        column_type: {
          field_path: "sales_channel",
          field_type: "string",
        },
      },
    },
  ],
  gl_account: {
    id: "4000",
    label: "Sales Revenue",
    account_nr: "4000",
  },
};
```

**Transaction Data**:

```json theme={null}
{
  "sales_channel": "shopify",
  "total_amount": 150.0,
  "currency": "USD"
}
```

**Result**: Matches → assigns to "Sales Revenue" account

### Example 2: Complex Multi-Criteria Rule

**Business Logic**: "Shopify refunds over \$100 should go to Refunds Expense account"

```typescript theme={null}
const rule = {
  id: "shopify-large-refunds",
  order: 1,
  criteria: [
    {
      column_id: "sales_channel",
      operator: "=",
      value: "shopify",
      column: {
        column_type: {
          field_path: "sales_channel",
          field_type: "string",
        },
      },
    },
    {
      column_id: "transaction_type",
      operator: "=",
      value: "refund",
      column: {
        column_type: {
          field_path: "transaction_type",
          field_type: "string",
        },
      },
    },
    {
      column_id: "amount",
      operator: ">",
      value: "100",
      column: {
        column_type: {
          field_path: "total_amount",
          field_type: "number",
        },
      },
    },
  ],
  gl_account: {
    id: "6100",
    label: "Refunds Expense",
    account_nr: "6100",
  },
};
```

**Transaction Data**:

```json theme={null}
{
  "sales_channel": "shopify",
  "transaction_type": "refund",
  "total_amount": 250.0,
  "currency": "USD"
}
```

**Result**: Matches → assigns to "Refunds Expense" account

### Example 3: Nested Field Access

**Business Logic**: "Transactions with specific payment methods should go to different accounts"

```typescript theme={null}
const rule = {
  id: "credit-card-payments",
  order: 1,
  criteria: [
    {
      column_id: "payment_method",
      operator: "=",
      value: "credit_card",
      column: {
        column_type: {
          field_path: "custom_properties",
          field_type: "string",
          nested_column_type_child: {
            field_path: "shopify_payment_method",
          },
        },
      },
    },
  ],
  gl_account: {
    id: "1200",
    label: "Credit Card Receivables",
    account_nr: "1200",
  },
};
```

**Transaction Data**:

```json theme={null}
{
  "total_amount": 150.0,
  "custom_properties": {
    "shopify_payment_method": {
      "label": "Payment Method",
      "value": "credit_card"
    }
  }
}
```

**Result**: Matches → assigns to "Credit Card Receivables" account

### Example 4: Fallback Rule

**Business Logic**: "Any unmatched transactions should go to Unmatched Revenue"

```typescript theme={null}
const fallbackRule = {
  id: "unmatched-fallback",
  order: 999,
  is_fallback: true,
  criteria: [
    {
      column_id: "catch_all",
      operator: "all",
      value: "",
      column: {
        column_type: {
          field_path: "total_amount",
          field_type: "number",
        },
      },
    },
  ],
  gl_account: {
    id: "4999",
    label: "Unmatched Revenue",
    account_nr: "4999",
  },
};
```

**Result**: Always matches if no other rules match

## Advanced Features

### Line Item Validation

For transactions with multiple line items, the system can validate against specific line item properties:

```typescript theme={null}
// Check if any line item has type "shipping"
const criterion = {
  column_id: "line_item_type",
  operator: "=",
  value: "shipping",
  column: {
    column_type: {
      field_path: "line_items",
      field_type: "array",
      nested_column_type_child: {
        field_path: "type",
      },
    },
  },
};
```

### Custom Property Support

CONA supports custom properties with special handling for the `{label, value}` format:

```typescript theme={null}
// Custom property stored as: {label: "Tax Rate", value: "0.08"}
// The system automatically extracts the .value for comparison
```

## Best Practices

### Rule Design

1. **Order Matters**: More specific rules should have lower order numbers
2. **Always Have Fallbacks**: Include fallback rules to prevent UNMATCHED transactions
3. **Test Thoroughly**: Use the rule diagnostic tools to validate logic
4. **Keep It Simple**: Avoid overly complex criteria when possible

### Performance Considerations

1. **Field Path Caching**: The system caches field path computations
2. **Rule Ordering**: Most common rules should be evaluated first
3. **Criterion Efficiency**: Use exact matches when possible over contains/regex

### Error Handling

1. **Validation**: Rules are validated before saving
2. **Diagnostics**: Built-in tools help identify configuration issues
3. **Logging**: Detailed logs show why specific rules matched or didn't match

## Troubleshooting

### Common Issues

1. **Rules Not Matching**
   * Check field paths are correct
   * Verify data types match expected values
   * Use diagnostic tools to see actual vs expected values

2. **UNMATCHED Transactions**
   * Add fallback rules with `all` operator
   * Check rule order and specificity

3. **Performance Issues**
   * Optimize rule ordering
   * Reduce complexity of criteria
   * Check for unnecessary nested field access

### Debugging Tools

The system provides diagnostic functions to help troubleshoot:

```typescript theme={null}
// Analyze rules and identify issues
const diagnosis = diagnoseRules(rules, transactionData);

// Check individual rule matching
const isMatch = validateRule(transactionData, rule);
```

## Integration Points

### Data Flow

1. **Transaction Import**: Data comes from integrations (Shopify, PayPal, etc.)
2. **Rule Evaluation**: Rules engine processes transaction data
3. **GL Assignment**: Determined dimensions are assigned to transactions
4. **Journal Entries**: Final GL entries are created for accounting system

### API Integration

* Rules are configured via the posting matrix UI
* Rule evaluation happens automatically during transaction processing
* Results are stored and auditable

This accounting rules engine provides the foundation for automated, accurate, and scalable financial transaction processing in CONA.
