# Business Rules

| ID | Rule Statement | Enforcement Point | Evidence | Evidence Quality | Exceptions / Notes |
| --- | --- | --- | --- | --- | --- |
| BR-001 | Item names are required and normalized for uniqueness. | `src/services/item_service.py` | `normalize_item_name`, duplicate-name tests | Observed | Duplicate suggestions use suffixes such as `Name (1)`. |
| BR-002 | Item type is constrained to recipe or base food. | `database/schema.sql`, item services | `item.item_type`, tests | Observed | Future customizable item types are deferred in docs. |
| BR-003 | Recipes require yield quantity/unit, ingredients, instructions, and primary cooking method. | `src/services/recipe_service.py` | recipe validation tests; `docs/agent_context/recipe_collection.md` | Observed | Base foods are intentionally lighter weight. |
| BR-004 | Base foods do not require recipe yield, serving fields, or cooking method in MVP. | Schema/tests/docs | `tests/test_db_init.py`; `docs/agent_context/recipe_collection.md` | Observed | Base-food authority fields support later scaling and inventory. |
| BR-005 | Advanced hotel-pan units are allowed for live scaling and Forecasting, not recipe/base-food authoring. | Unit config and validation services | `src/config/units.py`; `docs/agent_context/recipe_collection.md`; route/service tests | Observed | They remain available in Forecasting and live detail views. |
| BR-006 | Workflow statuses are `submitted`, `reviewed`, `approved`, `analyzed`, `live`, and `rejected`. | `src/config/statuses.py`, `src/services/workflow_service.py` | Status config and workflow tests | Observed | `returned to submitter` is represented through status and resubmission flag, not a separate status. |
| BR-007 | Reviewer, dietitian, admin, and super user roles have distinct workflow portal access. | `src/config/roles.py`, `src/services/workflow_service.py` | Portal definitions and policy tests | Observed | Mock auth supplies current user context in MVP. |
| BR-008 | Some workflow actions require reason text, including returns/send-backs/rejections. | `src/services/workflow_service.py` | `requires_reason` action definitions; workflow tests | Observed | Reason labels vary by action. |
| BR-009 | A recipe must have both mass and volume yield data before it can go live. | `transition_item_status` | `src/services/workflow_service.py`; route test `test_go_live_requires_recipe_mass_and_volume_measurements` | Observed | Applies to recipes when action code is `go_live`. |
| BR-010 | Menu concepts, meal periods, days, and menu statuses are controlled lists. | `src/config/menu_builder.py` | Menu config and tests | Observed | Concept order affects display order, not slot identity. |
| BR-011 | Menu slot assignments must use live items. | `src/services/menu_service.py` | `test_replace_menu_slot_items_rejects_non_live_items` | Observed | Supports both recipes and base foods. |
| BR-012 | Forecasting is assignment-oriented; production summary is item-oriented. | `src/services/menu_forecast_service.py` | `docs/agent_context/menu_builder.md`; forecast tests | Observed | One item may roll up across assignments. |
| BR-013 | Forecast case mode stores visible case quantity separately from calculated production yield. | `src/services/menu_forecast_service.py`, schema | Forecast tests and docs | Observed | Advanced case mode can link to recipe ingredient basis. |
| BR-014 | Batch split percentages must total 100. | `src/services/menu_forecast_service.py` | `test_save_menu_forecast_batch_splits_requires_total_100` | Observed | Batch split rows can also carry planned time. |
| BR-015 | Production forecast accuracy is calculated against implied demand, not raw production. | `src/services/production_record_service.py` | `docs/agent_context/menu_builder.md` | Observed | Implied demand equals actual production minus signed end-service variance. |
| BR-016 | Posted production facts exclude draft records. | `src/services/production_record_service.py` | `test_posted_production_facts_exclude_drafts` | Observed | Posted facts contract is versioned as v1. |
| BR-017 | Inventory counts are live operational counts, not finalized accounting periods. | Inventory docs and services | `docs/agent_context/inventory.md`; `README.md` | Observed | Weekly finance/accounting finalization is future scope. |
| BR-018 | Current on hand sums active location item rows after normalizing each/case entry. | `src/services/inventory_service.py` | inventory service tests | Observed | Display may prefer case display for case-counted rows. |
| BR-019 | Inventory catalog identity is separate from Recipe Collection culinary identity. | `database/schema.sql`, bridge service | `inventory_catalog_item`, `inventory_item_match`; inventory docs | Observed | Current count entry still accepts live base-food `item_id` for compatibility. |
| BR-020 | Catalog review default scope is demand-driven, not all live base foods. | `src/services/inventory_bridge_service.py`, docs | `get_inventory_catalog_review_page`; README | Observed | `scope=all` exists for audit/debug. |
| BR-021 | One active preferred substitute is allowed per inventory catalog item. | Schema and bridge service | `idx_inventory_catalog_substitute_preferred`; substitute tests | Observed | Non-preferred active substitutes are available for manual override. |
| BR-022 | Vendor invoice staging supports manual, CSV fixture, vendor API, PDF, and email source types at schema level. | `database/schema.sql` | `inventory_vendor_invoice.source_type` check | Observed | Current implementation supports manual/CSV fixture/dev generation. |
| BR-023 | Invoice line matching currently uses vendor-code/catalog links and manual review. | `src/services/inventory_invoice_service.py` | invoice service tests and inventory docs | Observed | Name/category fallback matching is documented as later work. |
| BR-024 | Invoice placement is not an accounting post or finalized receiving workflow. | Inventory docs and service behavior | `docs/agent_context/inventory.md` | Observed | It seeds a count row using invoice quantity/unit. |
| BR-025 | Planning uses vendor/category preferences when configured and a marked 7-day default when not configured. | `src/services/inventory_ordering_service.py`, `src/services/inventory_usage_service.py` | planning tests and README | Observed | Future item-level vendor resolution is still needed. |

## BA Alignment

- Activity areas: Analyze requirements and define design options; manage requirements lifecycle.
- Techniques used: Business rules analysis, document analysis, interface analysis, acceptance criteria extraction.
