# Recommendation Engine Contracts (Rule-Based)

> **No AI. No implementation.** Contracts for a future rule evaluator.  
> ADR: [ADR-023](../architecture/adr/ADR-023.md)  
> Contract version: `recommendation.v1`

---

## 1. Purpose

Produce **recommendations** for bundles, services, modules, features, and pricing from structured business inputs. Outputs never activate entitlements.

---

## 2. Input — RecommendationRequest

| Field | Type | Meaning |
| --- | --- | --- |
| `business_type_code` | string | From Business Type Catalog |
| `company_size` | enum | `solo` \| `small` \| `medium` \| `large` \| `enterprise` |
| `employees` | int? | Headcount hint |
| `branches` | int? | Location count |
| `needs[]` | string[] | Free-form need tags (open registry) |
| `online_sales` | bool | |
| `delivery` | bool | |
| `appointments` | bool | |
| `crm` | bool | |
| `loyalty` | bool | |
| `website` | bool | |
| `locale` | string? | |
| `ext` | object | Additive |

### Need tag examples (open)

`menu`, `pos`, `fleet`, `sms`, `accounting`, `membership`, `forms`, `b2b_pipeline`

---

## 3. Output — RecommendationResult

| Field | Type | Meaning |
| --- | --- | --- |
| `recommended_bundles[]` | RankedRef | `{ code, score, reason }` |
| `recommended_services[]` | RankedRef | `service_identifier` |
| `recommended_modules[]` | RankedRef | `module_key` |
| `recommended_features[]` | RankedRef | `feature_key` |
| `recommended_pricing[]` | RankedRef | `pricing_item_code` |
| `rule_trace[]` | string[] | Which rules fired (debug) |
| `version` | string | `recommendation.v1` |

---

## 4. Rule — RecommendationRule

| Field | Type | Meaning |
| --- | --- | --- |
| `rule_id` | string | |
| `priority` | int | Higher evaluated first / stronger weight |
| `active` | bool | |
| `match` | object | Predicates over RecommendationRequest |
| `emit` | object | What to add to result with `score` + `reason` |
| `stop` | bool | If true, halt further rules after match |

### Match predicates (documented operators)

| Operator | Example |
| --- | --- |
| `eq` | `business_type_code eq restaurant` |
| `gte` | `branches gte 2` |
| `in` | `company_size in [medium, large, enterprise]` |
| `flag_true` | `delivery == true` |
| `needs_contains` | `needs contains fleet` |

### Example rules (illustrative)

| rule_id | Match | Emit |
| --- | --- | --- |
| `r.restaurant.base` | type=restaurant | bundle `bundle.restaurant.starter` score 100 |
| `r.delivery.need` | delivery=true | service `delivery` score 80 |
| `r.crm.need` | crm=true OR type=corporate_company | service `crm` score 80 |
| `r.website.need` | website=true | service `experience` score 70 |
| `r.loyalty.need` | loyalty=true | service `loyalty` score 70 |
| `r.multi.branch` | branches≥3 | prefer `*.growth` bundle variants score +20 |
| `r.enterprise.size` | company_size=enterprise | pricing `price.enterprise.custom` score 90 |

---

## 5. Invariants

1. Rules MUST NOT call Payment, Accounting, or vertical write APIs.
2. Scores are relative; UI may sort but MUST present as recommendations.
3. No ML / LLM required or permitted by this contract version.
