@ai-matrx/records 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2715 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/use-cases/alvarado-chen-kitchen-weekly-cooking.ts
24
+ var alvarado_chen_kitchen_weekly_cooking_exports = {};
25
+ __export(alvarado_chen_kitchen_weekly_cooking_exports, {
26
+ default: () => alvarado_chen_kitchen_weekly_cooking_default,
27
+ useCase: () => useCase
28
+ });
29
+ var useCase, alvarado_chen_kitchen_weekly_cooking_default;
30
+ var init_alvarado_chen_kitchen_weekly_cooking = __esm({
31
+ "src/use-cases/alvarado-chen-kitchen-weekly-cooking.ts"() {
32
+ "use strict";
33
+ useCase = {
34
+ id: "alvarado-chen-kitchen-weekly-cooking",
35
+ useCase: "Priya and Daniel Alvarado-Chen cook for a family of five, and they use this to keep the recipes they actually make, the ingredients each one needs, and the weekly shopping list that falls out of the meals they have planned, instead of three notebooks and a text message at the shop.",
36
+ industry: "Household cooking",
37
+ cleanupTag: "use-case:alvarado-chen-kitchen-weekly-cooking",
38
+ business: {
39
+ name: "The Alvarado-Chen Kitchen",
40
+ describes: "A household of five that cooks most nights and shops once a week across three shops.",
41
+ address: {
42
+ line1: "68 Winterbourne Road",
43
+ city: "Oak Park",
44
+ region: "IL",
45
+ postalCode: "60302",
46
+ country: "US"
47
+ },
48
+ phone: "+1 708 555 0126",
49
+ email: "kitchen@alvaradochen.net",
50
+ timezone: "America/Chicago"
51
+ },
52
+ tables: [
53
+ {
54
+ token: "recipes",
55
+ name: "Recipes",
56
+ labelSingular: "Recipe",
57
+ labelPlural: "Recipes",
58
+ type: "entity",
59
+ display: "grid",
60
+ weight: "heavy",
61
+ ordered: false,
62
+ icon: "chef-hat",
63
+ titleField: "title",
64
+ describes: "Every dish the household actually cooks, with who brought it in and how long it takes.",
65
+ fields: [
66
+ { key: "title", label: "Recipe", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
67
+ { key: "category", label: "Course", parityType: "select", choices: ["Breakfast", "Main Course", "Side Dish", "Soup", "Dessert", "Sauce/Condiment"], sensitivity: "internal", contextPolicy: "include" },
68
+ { key: "cuisine", label: "Cuisine", parityType: "select", choices: ["Mexican", "Chinese", "Italian", "American", "Middle Eastern", "Indian"], sensitivity: "internal", contextPolicy: "include" },
69
+ { key: "prep_minutes", label: "Prep time", parityType: "integer", unit: "min", sensitivity: "internal", contextPolicy: "include" },
70
+ { key: "cook_minutes", label: "Cook time", parityType: "integer", unit: "min", sensitivity: "internal", contextPolicy: "include" },
71
+ { key: "servings", label: "Serves", parityType: "integer", sensitivity: "internal", contextPolicy: "include" },
72
+ { key: "source", label: "Where it came from", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
73
+ { key: "contributed_by", label: "Brought in by", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
74
+ { key: "date_added", label: "Added", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" }
75
+ ],
76
+ rows: [
77
+ { key: "recipe-chilaquiles-verdes", values: { "title": "Chilaquiles Verdes", "category": "Breakfast", "cuisine": "Mexican", "prep_minutes": 15, "cook_minutes": 20, "servings": 4, "source": "Grandma Alvarado's recipe box, card #12", "contributed_by": "Rosa Alvarado", "date_added": "2026-01-08" } },
78
+ { key: "recipe-kung-pao-chicken", values: { "title": "Kung Pao Chicken", "category": "Main Course", "cuisine": "Chinese", "prep_minutes": 20, "cook_minutes": 15, "servings": 4, "source": "Adapted from Fuchsia Dunlop's Land of Fish and Rice", "contributed_by": "Wei Chen", "date_added": "2026-01-15" } },
79
+ { key: "recipe-slow-braised-short-rib-ragu", values: { "title": "Slow-Braised Short Rib Ragu", "category": "Main Course", "cuisine": "Italian", "prep_minutes": 25, "cook_minutes": 210, "servings": 6, "source": "Sunday dinner tradition, no written source", "contributed_by": "Marco Alvarado", "date_added": "2026-02-02" } },
80
+ { key: "recipe-mapo-tofu", values: { "title": "Mapo Tofu", "category": "Main Course", "cuisine": "Chinese", "prep_minutes": 10, "cook_minutes": 15, "servings": 3, "source": "Wei's mother, dictated over the phone", "contributed_by": "Wei Chen", "date_added": "2026-02-10" } },
81
+ { key: "recipe-elote-street-corn-salad", values: { "title": "Elote Street Corn Salad", "category": "Side Dish", "cuisine": "Mexican", "prep_minutes": 15, "cook_minutes": 10, "servings": 6, "source": "Farmers market recipe card", "contributed_by": "Rosa Alvarado", "date_added": "2026-03-01" } },
82
+ { key: "recipe-pozole-rojo", values: { "title": "Pozole Rojo", "category": "Soup", "cuisine": "Mexican", "prep_minutes": 30, "cook_minutes": 180, "servings": 8, "source": "Family recipe, three generations", "contributed_by": "Rosa Alvarado", "date_added": "2026-03-14" } },
83
+ { key: "recipe-hot-and-sour-soup", values: { "title": "Hot and Sour Soup", "category": "Soup", "cuisine": "Chinese", "prep_minutes": 15, "cook_minutes": 20, "servings": 4, "source": "Wei Chen's own recipe", "contributed_by": "Wei Chen", "date_added": "2026-03-22" } },
84
+ { key: "recipe-tres-leches-cake", values: { "title": "Tres Leches Cake", "category": "Dessert", "cuisine": "Mexican", "prep_minutes": 30, "cook_minutes": 35, "servings": 12, "source": "Grandma Alvarado's recipe box, card #4", "contributed_by": "Rosa Alvarado", "date_added": "2026-04-05" } },
85
+ { key: "recipe-cacio-e-pepe", values: { "title": "Cacio e Pepe", "category": "Main Course", "cuisine": "Italian", "prep_minutes": 5, "cook_minutes": 15, "servings": 2, "source": "Weeknight standby, no written source", "contributed_by": "Marco Alvarado", "date_added": "2026-04-12" } },
86
+ { key: "recipe-chana-masala", values: { "title": "Chana Masala", "category": "Main Course", "cuisine": "Indian", "prep_minutes": 15, "cook_minutes": 30, "servings": 4, "source": "Learned from neighbor Priya", "contributed_by": "Wei Chen", "date_added": "2026-05-01" } },
87
+ { key: "recipe-salsa-verde-cruda", values: { "title": "Salsa Verde Cruda", "category": "Sauce/Condiment", "cuisine": "Mexican", "prep_minutes": 10, "cook_minutes": 0, "servings": 8, "source": "Grandma Alvarado's recipe box, card #1", "contributed_by": "Rosa Alvarado", "date_added": "2026-05-10" } },
88
+ { key: "recipe-shakshuka", values: { "title": "Shakshuka", "category": "Breakfast", "cuisine": "Middle Eastern", "prep_minutes": 10, "cook_minutes": 25, "servings": 4, "source": "Ottolenghi's Jerusalem, adapted", "contributed_by": "Marco Alvarado", "date_added": "2026-05-18" } }
89
+ ]
90
+ },
91
+ {
92
+ token: "ingredients",
93
+ name: "Ingredients",
94
+ labelSingular: "Ingredient",
95
+ labelPlural: "Ingredients",
96
+ type: "detail",
97
+ display: "list",
98
+ weight: "light",
99
+ ordered: true,
100
+ icon: "carrot",
101
+ titleField: "ingredient_name",
102
+ describes: "What goes into each recipe, in the amounts the recipe is written for.",
103
+ fields: [
104
+ { key: "recipe", label: "Recipe", parityType: "relation", required: true, relationTarget: "recipes", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
105
+ { key: "ingredient_name", label: "Ingredient", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
106
+ { key: "quantity", label: "Quantity", parityType: "integer", sensitivity: "internal", contextPolicy: "include" },
107
+ { key: "unit", label: "Unit", parityType: "select", choices: ["cup", "tbsp", "tsp", "oz", "lb", "clove", "whole", "can", "pinch"], sensitivity: "internal", contextPolicy: "include" },
108
+ { key: "notes", label: "Notes", parityType: "text", sensitivity: "internal", contextPolicy: "include" }
109
+ ],
110
+ rows: [
111
+ { key: "ingredient-corn-tortillas-cut-into-wedges-and-fried", values: { "recipe": "recipe-chilaquiles-verdes", "ingredient_name": "corn tortillas, cut into wedges and fried", "quantity": 12, "unit": "whole", "notes": "day-old tortillas fry crisper" } },
112
+ { key: "ingredient-tomatillos", values: { "recipe": "recipe-chilaquiles-verdes", "ingredient_name": "tomatillos", "quantity": 1, "unit": "lb", "notes": "husked and rinsed" } },
113
+ { key: "ingredient-serrano-peppers", values: { "recipe": "recipe-chilaquiles-verdes", "ingredient_name": "serrano peppers", "quantity": 2, "unit": "whole", "notes": "seeds removed for less heat" } },
114
+ { key: "ingredient-queso-fresco", values: { "recipe": "recipe-chilaquiles-verdes", "ingredient_name": "queso fresco", "quantity": 4, "unit": "oz", "notes": "crumbled for topping" } },
115
+ { key: "ingredient-chicken-thighs-diced", values: { "recipe": "recipe-kung-pao-chicken", "ingredient_name": "chicken thighs, diced", "quantity": 1.5, "unit": "lb", "notes": "boneless, skinless" } },
116
+ { key: "ingredient-dried-red-chilies", values: { "recipe": "recipe-kung-pao-chicken", "ingredient_name": "dried red chilies", "quantity": 8, "unit": "whole", "notes": "adjust to taste" } },
117
+ { key: "ingredient-roasted-peanuts", values: { "recipe": "recipe-kung-pao-chicken", "ingredient_name": "roasted peanuts", "quantity": 0.5, "unit": "cup", "notes": "" } },
118
+ { key: "ingredient-sichuan-peppercorns", values: { "recipe": "recipe-kung-pao-chicken", "ingredient_name": "Sichuan peppercorns", "quantity": 1, "unit": "tsp", "notes": "toasted and cracked" } },
119
+ { key: "ingredient-bone-in-short-ribs", values: { "recipe": "recipe-slow-braised-short-rib-ragu", "ingredient_name": "bone-in short ribs", "quantity": 3, "unit": "lb", "notes": "trimmed of excess fat" } },
120
+ { key: "ingredient-san-marzano-tomatoes", values: { "recipe": "recipe-slow-braised-short-rib-ragu", "ingredient_name": "San Marzano tomatoes", "quantity": 1, "unit": "can", "notes": "28 oz can, crushed by hand" } },
121
+ { key: "ingredient-red-wine", values: { "recipe": "recipe-slow-braised-short-rib-ragu", "ingredient_name": "red wine", "quantity": 1, "unit": "cup", "notes": "something drinkable" } },
122
+ { key: "ingredient-soft-tofu-cubed", values: { "recipe": "recipe-mapo-tofu", "ingredient_name": "soft tofu, cubed", "quantity": 14, "unit": "oz", "notes": "one package" } },
123
+ { key: "ingredient-doubanjiang-fermented-chili-bean-paste", values: { "recipe": "recipe-mapo-tofu", "ingredient_name": "doubanjiang (fermented chili bean paste)", "quantity": 2, "unit": "tbsp", "notes": "Pixian brand preferred" } },
124
+ { key: "ingredient-ground-pork", values: { "recipe": "recipe-mapo-tofu", "ingredient_name": "ground pork", "quantity": 0.5, "unit": "lb", "notes": "" } },
125
+ { key: "ingredient-corn-kernels-charred", values: { "recipe": "recipe-elote-street-corn-salad", "ingredient_name": "corn kernels, charred", "quantity": 4, "unit": "cup", "notes": "grilled or pan-charred" } },
126
+ { key: "ingredient-cotija-cheese", values: { "recipe": "recipe-elote-street-corn-salad", "ingredient_name": "cotija cheese", "quantity": 0.5, "unit": "cup", "notes": "crumbled" } },
127
+ { key: "ingredient-lime", values: { "recipe": "recipe-elote-street-corn-salad", "ingredient_name": "lime", "quantity": 2, "unit": "whole", "notes": "juiced" } },
128
+ { key: "ingredient-pork-shoulder", values: { "recipe": "recipe-pozole-rojo", "ingredient_name": "pork shoulder", "quantity": 3, "unit": "lb", "notes": "cut into large chunks" } },
129
+ { key: "ingredient-hominy", values: { "recipe": "recipe-pozole-rojo", "ingredient_name": "hominy", "quantity": 2, "unit": "can", "notes": "29 oz cans, drained" } },
130
+ { key: "ingredient-guajillo-chilies", values: { "recipe": "recipe-pozole-rojo", "ingredient_name": "guajillo chilies", "quantity": 6, "unit": "whole", "notes": "stemmed and seeded" } },
131
+ { key: "ingredient-wood-ear-mushrooms-dried", values: { "recipe": "recipe-hot-and-sour-soup", "ingredient_name": "wood ear mushrooms, dried", "quantity": 0.25, "unit": "cup", "notes": "rehydrated and sliced" } },
132
+ { key: "ingredient-white-pepper", values: { "recipe": "recipe-hot-and-sour-soup", "ingredient_name": "white pepper", "quantity": 1, "unit": "tsp", "notes": "the 'hot' in hot and sour" } },
133
+ { key: "ingredient-evaporated-milk", values: { "recipe": "recipe-tres-leches-cake", "ingredient_name": "evaporated milk", "quantity": 1, "unit": "can", "notes": "12 oz can" } },
134
+ { key: "ingredient-sweetened-condensed-milk", values: { "recipe": "recipe-tres-leches-cake", "ingredient_name": "sweetened condensed milk", "quantity": 1, "unit": "can", "notes": "14 oz can" } },
135
+ { key: "ingredient-pecorino-romano-grated", values: { "recipe": "recipe-cacio-e-pepe", "ingredient_name": "pecorino romano, grated", "quantity": 1.5, "unit": "cup", "notes": "finely grated, not shredded" } },
136
+ { key: "ingredient-chickpeas", values: { "recipe": "recipe-chana-masala", "ingredient_name": "chickpeas", "quantity": 2, "unit": "can", "notes": "15 oz cans, drained" } },
137
+ { key: "ingredient-garam-masala", values: { "recipe": "recipe-chana-masala", "ingredient_name": "garam masala", "quantity": 1, "unit": "tbsp", "notes": "" } },
138
+ { key: "ingredient-tomatillos-raw", values: { "recipe": "recipe-salsa-verde-cruda", "ingredient_name": "tomatillos, raw", "quantity": 1, "unit": "lb", "notes": "quartered" } },
139
+ { key: "ingredient-eggs", values: { "recipe": "recipe-shakshuka", "ingredient_name": "eggs", "quantity": 6, "unit": "whole", "notes": "cracked directly into sauce" } },
140
+ { key: "ingredient-crushed-tomatoes", values: { "recipe": "recipe-shakshuka", "ingredient_name": "crushed tomatoes", "quantity": 1, "unit": "can", "notes": "28 oz can" } }
141
+ ]
142
+ },
143
+ {
144
+ token: "shopping_lists",
145
+ name: "Shopping lists",
146
+ labelSingular: "Shopping list",
147
+ labelPlural: "Shopping lists",
148
+ type: "entity",
149
+ display: "board",
150
+ weight: "heavy",
151
+ ordered: false,
152
+ icon: "shopping-cart",
153
+ titleField: "list_name",
154
+ describes: "One list per shop per week, built from the recipes planned for that week.",
155
+ fields: [
156
+ { key: "list_name", label: "List", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
157
+ { key: "week_of", label: "Week of", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
158
+ { key: "store", label: "Shop", parityType: "select", choices: ["Fresh Market Co-op", "Lucky Star Asian Grocery", "Mercado Alvarado", "Costco"], sensitivity: "internal", contextPolicy: "include" },
159
+ { key: "status", label: "Status", parityType: "select", choices: ["Planning", "Ready to Shop", "Shopping", "Done"], sensitivity: "internal", contextPolicy: "include" }
160
+ ],
161
+ rows: [
162
+ { key: "list-week-of-jun-1-mexican-night-weekday-soup", values: { "list_name": "Week of Jun 1 \u2014 Mexican night + weekday soup", "week_of": "2026-06-01", "store": "Mercado Alvarado", "status": "Done" } },
163
+ { key: "list-week-of-jun-8-chinese-takeout-at-home", values: { "list_name": "Week of Jun 8 \u2014 Chinese takeout-at-home", "week_of": "2026-06-08", "store": "Lucky Star Asian Grocery", "status": "Done" } },
164
+ { key: "list-week-of-jun-15-sunday-ragu-easy-weeknights", values: { "list_name": "Week of Jun 15 \u2014 Sunday ragu + easy weeknights", "week_of": "2026-06-15", "store": "Fresh Market Co-op", "status": "Done" } },
165
+ { key: "list-week-of-jun-22-birthday-cake-week", values: { "list_name": "Week of Jun 22 \u2014 Birthday cake week", "week_of": "2026-06-22", "store": "Costco", "status": "Ready to Shop" } },
166
+ { key: "list-week-of-jun-29-pozole-for-the-in-laws", values: { "list_name": "Week of Jun 29 \u2014 Pozole for the in-laws", "week_of": "2026-06-29", "store": "Mercado Alvarado", "status": "Planning" } }
167
+ ]
168
+ },
169
+ {
170
+ token: "shopping_list_items",
171
+ name: "List items",
172
+ labelSingular: "List item",
173
+ labelPlural: "List items",
174
+ type: "detail",
175
+ display: "list",
176
+ weight: "light",
177
+ ordered: true,
178
+ icon: "list-checks",
179
+ titleField: "ingredient_name",
180
+ describes: "What is actually on a list, by aisle, and whether it is already in the trolley.",
181
+ fields: [
182
+ { key: "shopping_list", label: "Shopping list", parityType: "relation", required: true, relationTarget: "shopping_lists", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
183
+ { key: "ingredient_name", label: "Ingredient", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
184
+ { key: "quantity", label: "Quantity", parityType: "integer", sensitivity: "internal", contextPolicy: "include" },
185
+ { key: "unit", label: "Unit", parityType: "select", choices: ["cup", "tbsp", "tsp", "oz", "lb", "clove", "whole", "can", "pinch"], sensitivity: "internal", contextPolicy: "include" },
186
+ { key: "aisle", label: "Aisle", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
187
+ { key: "checked", label: "In the trolley", parityType: "select", choices: ["Yes", "No"], sensitivity: "internal", contextPolicy: "include" }
188
+ ],
189
+ rows: [
190
+ { key: "item-tomatillos", values: { "shopping_list": "list-week-of-jun-1-mexican-night-weekday-soup", "ingredient_name": "tomatillos", "quantity": 2, "unit": "lb", "aisle": "Produce", "checked": "Yes" } },
191
+ { key: "item-corn-tortillas", values: { "shopping_list": "list-week-of-jun-1-mexican-night-weekday-soup", "ingredient_name": "corn tortillas", "quantity": 2, "unit": "whole", "aisle": "Bakery", "checked": "Yes" } },
192
+ { key: "item-queso-fresco", values: { "shopping_list": "list-week-of-jun-1-mexican-night-weekday-soup", "ingredient_name": "queso fresco", "quantity": 8, "unit": "oz", "aisle": "Dairy", "checked": "Yes" } },
193
+ { key: "item-chicken-thighs", values: { "shopping_list": "list-week-of-jun-8-chinese-takeout-at-home", "ingredient_name": "chicken thighs", "quantity": 1.5, "unit": "lb", "aisle": "Meat", "checked": "Yes" } },
194
+ { key: "item-dried-red-chilies", values: { "shopping_list": "list-week-of-jun-8-chinese-takeout-at-home", "ingredient_name": "dried red chilies", "quantity": 1, "unit": "oz", "aisle": "International", "checked": "Yes" } },
195
+ { key: "item-sichuan-peppercorns", values: { "shopping_list": "list-week-of-jun-8-chinese-takeout-at-home", "ingredient_name": "Sichuan peppercorns", "quantity": 1, "unit": "oz", "aisle": "International", "checked": "No" } },
196
+ { key: "item-bone-in-short-ribs", values: { "shopping_list": "list-week-of-jun-15-sunday-ragu-easy-weeknights", "ingredient_name": "bone-in short ribs", "quantity": 3, "unit": "lb", "aisle": "Meat", "checked": "Yes" } },
197
+ { key: "item-san-marzano-tomatoes", values: { "shopping_list": "list-week-of-jun-15-sunday-ragu-easy-weeknights", "ingredient_name": "San Marzano tomatoes", "quantity": 2, "unit": "can", "aisle": "Canned Goods", "checked": "Yes" } },
198
+ { key: "item-pecorino-romano", values: { "shopping_list": "list-week-of-jun-15-sunday-ragu-easy-weeknights", "ingredient_name": "pecorino romano", "quantity": 1, "unit": "lb", "aisle": "Dairy", "checked": "No" } },
199
+ { key: "item-evaporated-milk", values: { "shopping_list": "list-week-of-jun-22-birthday-cake-week", "ingredient_name": "evaporated milk", "quantity": 2, "unit": "can", "aisle": "Baking", "checked": "No" } },
200
+ { key: "item-sweetened-condensed-milk", values: { "shopping_list": "list-week-of-jun-22-birthday-cake-week", "ingredient_name": "sweetened condensed milk", "quantity": 2, "unit": "can", "aisle": "Baking", "checked": "No" } },
201
+ { key: "item-pork-shoulder", values: { "shopping_list": "list-week-of-jun-29-pozole-for-the-in-laws", "ingredient_name": "pork shoulder", "quantity": 3, "unit": "lb", "aisle": "Meat", "checked": "No" } },
202
+ { key: "item-hominy", values: { "shopping_list": "list-week-of-jun-29-pozole-for-the-in-laws", "ingredient_name": "hominy", "quantity": 4, "unit": "can", "aisle": "Canned Goods", "checked": "No" } },
203
+ { key: "item-guajillo-chilies", values: { "shopping_list": "list-week-of-jun-29-pozole-for-the-in-laws", "ingredient_name": "guajillo chilies", "quantity": 6, "unit": "oz", "aisle": "International", "checked": "No" } }
204
+ ]
205
+ }
206
+ ],
207
+ relationships: [
208
+ {
209
+ describes: "An ingredient belongs to the recipe it is part of and means nothing without it.",
210
+ fromTable: "ingredients",
211
+ fromField: "recipe",
212
+ toTable: "recipes",
213
+ flavor: "owned",
214
+ cardinality: "one",
215
+ onDelete: "cascade",
216
+ inverseKey: "ingredients"
217
+ },
218
+ {
219
+ describes: "A line on the shopping list belongs to the list it is written on.",
220
+ fromTable: "shopping_list_items",
221
+ fromField: "shopping_list",
222
+ toTable: "shopping_lists",
223
+ flavor: "owned",
224
+ cardinality: "one",
225
+ onDelete: "cascade",
226
+ inverseKey: "items"
227
+ }
228
+ ],
229
+ form: {
230
+ token: "alvarado_chen_add_a_recipe",
231
+ name: "Add a recipe to the family collection",
232
+ describes: "What anyone in the family fills in when a dish is worth keeping \u2014 including the grandparents, which is why it asks who brought it in.",
233
+ audience: "member",
234
+ table: "recipes",
235
+ fields: [
236
+ { fieldKey: "title", label: "What is it called?", required: true },
237
+ { fieldKey: "category", label: "Course", required: true },
238
+ { fieldKey: "cuisine", label: "Cuisine" },
239
+ { fieldKey: "prep_minutes", label: "Prep time in minutes", help: "Chopping and mixing \u2014 everything before it goes on the heat." },
240
+ { fieldKey: "cook_minutes", label: "Cook time in minutes" },
241
+ { fieldKey: "servings", label: "How many does it serve?", required: true },
242
+ { fieldKey: "source", label: "Where did it come from?", help: "A book, a website, or a person. We like knowing." },
243
+ { fieldKey: "contributed_by", label: "Who brought it in?", required: true }
244
+ ],
245
+ submitLabel: "Add it to the collection",
246
+ confirmation: "Added. Put the ingredients in next, and it will start showing up on the shopping list when you plan it for a week."
247
+ },
248
+ pipeline: {
249
+ token: "alvarado_chen_weekly_shop",
250
+ name: "The weekly shop",
251
+ describes: "A list's life is four days long and everybody in the house touches it. The stages exist so nobody buys the same bag of rice twice.",
252
+ table: "shopping_lists",
253
+ stageField: "status",
254
+ stages: [
255
+ { key: "Planning", label: "Planning", describes: "Meals are still being chosen, so the list is still growing. Nobody shops from it yet.", typicalDurationDays: 3 },
256
+ { key: "Ready to Shop", label: "Ready to shop", describes: "The meals are settled and the list is complete. This is the state it is in when somebody picks up their keys.", typicalDurationDays: 1 },
257
+ { key: "Shopping", label: "Shopping", describes: "Somebody is in the shop ticking things off. Anything added now is a genuine omission, not a change of plan.", typicalDurationDays: 1 },
258
+ { key: "Done", label: "Done", describes: "Everything is home. Unticked items roll onto next week rather than being lost." }
259
+ ]
260
+ },
261
+ dashboardQuestion: {
262
+ question: "Which of our recipes can we actually cook on a Tuesday?",
263
+ whyItMatters: "Weeknight cooking fails on time, not on ideas. Knowing the average cook time by course is what stops a two-hour braise being planned for a school night, and it is the number that decides what goes on the list.",
264
+ table: "recipes",
265
+ aggregate: "avg",
266
+ of: "cook_minutes",
267
+ groupBy: "category",
268
+ answerUnit: "minutes"
269
+ },
270
+ documentTemplate: {
271
+ token: "alvarado_chen_recipe_card",
272
+ name: "Printable recipe card",
273
+ describes: "What gets printed and stuck to the fridge door, or sent to someone who asked for the recipe after dinner. It keeps the attribution, because half the collection came from somebody's mother.",
274
+ table: "recipes",
275
+ title: "{{recipe_title}} \u2014 serves {{servings}}",
276
+ body: [
277
+ "{{recipe_title}}",
278
+ "",
279
+ "{{course}} \xB7 {{cuisine}} \xB7 serves {{servings}}",
280
+ "Prep {{prep_time}} minutes, cook {{cook_time}} minutes.",
281
+ "",
282
+ "From {{source}}, brought into the family by {{contributed_by}}.",
283
+ "",
284
+ "Ingredients are on the back. If you halve it, do not halve the salt by the same amount \u2014 taste it instead.",
285
+ "",
286
+ "{{household}} \xB7 printed {{today}}"
287
+ ].join("\n"),
288
+ mergeFields: [
289
+ { key: "recipe_title", from: "title", describes: "The dish." },
290
+ { key: "course", from: "category", describes: "Which part of the meal it is." },
291
+ { key: "cuisine", from: "cuisine", describes: "Where it comes from." },
292
+ { key: "servings", from: "servings", describes: "How many it feeds, so it can be scaled." },
293
+ { key: "prep_time", from: "prep_minutes", describes: "Minutes before it goes on the heat." },
294
+ { key: "cook_time", from: "cook_minutes", describes: "Minutes on the heat." },
295
+ { key: "source", from: "source", describes: "The book, site or person it came from." },
296
+ { key: "contributed_by", from: "contributed_by", describes: "Who brought it into the family." },
297
+ { key: "household", from: "business", describes: "Whose kitchen this is." },
298
+ { key: "today", from: "today", describes: "When the card was printed." }
299
+ ]
300
+ },
301
+ notificationRule: {
302
+ token: "alvarado_chen_list_is_ready",
303
+ name: "The list is ready to shop",
304
+ describes: "The one message this household would genuinely set up: whoever is going to the shop needs to know the list stopped changing, because the whole failure mode is leaving with a list somebody was still editing.",
305
+ when: {
306
+ table: "shopping_lists",
307
+ event: "stage_entered",
308
+ stage: "Ready to Shop"
309
+ },
310
+ audience: "whoever is doing the shop this week",
311
+ channel: "in_app",
312
+ message: "{{list_name}} for the week of {{week_of}} is finished and ready \u2014 {{store}}. Tick things off as you go so nobody buys them twice."
313
+ },
314
+ provenance: {
315
+ kind: "synthesized",
316
+ authoredBy: "data crew (dataset) + lane REAL-DATA (business identity, relationships, form, pipeline, dashboard question, document, notification rule)",
317
+ authoredOn: "2026-09-21",
318
+ noRealPeople: true
319
+ }
320
+ };
321
+ alvarado_chen_kitchen_weekly_cooking_default = useCase;
322
+ }
323
+ });
324
+
325
+ // src/use-cases/birchwood-avenue-renovation-budget.ts
326
+ var birchwood_avenue_renovation_budget_exports = {};
327
+ __export(birchwood_avenue_renovation_budget_exports, {
328
+ default: () => birchwood_avenue_renovation_budget_default,
329
+ useCase: () => useCase2
330
+ });
331
+ var useCase2, birchwood_avenue_renovation_budget_default;
332
+ var init_birchwood_avenue_renovation_budget = __esm({
333
+ "src/use-cases/birchwood-avenue-renovation-budget.ts"() {
334
+ "use strict";
335
+ useCase2 = {
336
+ id: "birchwood-avenue-renovation-budget",
337
+ useCase: "Marguerite Oyelowo is renovating a 1920s house one room at a time, and she uses this to hold every contractor quote against the room it is for and every receipt against the budget she set, so that whether the job is over budget has an answer instead of an argument.",
338
+ industry: "Home renovation",
339
+ cleanupTag: "use-case:birchwood-avenue-renovation-budget",
340
+ business: {
341
+ name: "Birchwood Avenue Renovation",
342
+ describes: "A owner-managed whole-house renovation of a 1920s four-bedroom, run room by room over about a year.",
343
+ address: {
344
+ line1: "417 Birchwood Avenue",
345
+ city: "Providence",
346
+ region: "RI",
347
+ postalCode: "02906",
348
+ country: "US"
349
+ },
350
+ phone: "+1 401 555 0188",
351
+ email: "marguerite@birchwoodreno.net",
352
+ timezone: "America/New_York"
353
+ },
354
+ tables: [
355
+ {
356
+ token: "rooms",
357
+ name: "Rooms",
358
+ labelSingular: "Room",
359
+ labelPlural: "Rooms",
360
+ type: "entity",
361
+ display: "board",
362
+ weight: "heavy",
363
+ ordered: true,
364
+ icon: "door-open",
365
+ titleField: "room_name",
366
+ describes: "The house, room by room: what is being done to each one, when, and for how much.",
367
+ fields: [
368
+ { key: "room_name", label: "Room", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
369
+ { key: "project_scope", label: "Scope of work", parityType: "long_text", sensitivity: "internal", contextPolicy: "include" },
370
+ { key: "target_start", label: "Target start", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
371
+ { key: "target_end", label: "Target finish", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
372
+ { key: "status", label: "Status", parityType: "select", choices: ["Planning", "Quoting", "In Progress", "Complete", "On Hold"], sensitivity: "internal", contextPolicy: "include" },
373
+ { key: "budget", label: "Budget", parityType: "currency", unit: "USD", format: "currency", sensitivity: "confidential", contextPolicy: "summarize" }
374
+ ],
375
+ rows: [
376
+ { key: "room-kitchen", values: { "room_name": "Kitchen", "project_scope": "Full gut renovation: new cabinets, quartz counters, relocate island, new appliances.", "target_start": "2026-04-01", "target_end": "2026-06-15", "status": "Complete", "budget": 48e3 } },
377
+ { key: "room-primary-bathroom", values: { "room_name": "Primary Bathroom", "project_scope": "Replace tub with walk-in shower, new vanity, heated tile floor.", "target_start": "2026-06-20", "target_end": "2026-08-01", "status": "In Progress", "budget": 22500 } },
378
+ { key: "room-basement", values: { "room_name": "Basement", "project_scope": "Finish unfinished basement into family room and home office, add egress window.", "target_start": "2026-08-10", "target_end": "2026-11-15", "status": "Quoting", "budget": 61e3 } },
379
+ { key: "room-guest-bathroom", values: { "room_name": "Guest Bathroom", "project_scope": "Cosmetic refresh: new vanity, paint, light fixtures.", "target_start": "2026-09-01", "target_end": "2026-09-20", "status": "Planning", "budget": 6500 } },
380
+ { key: "room-backyard-deck", values: { "room_name": "Backyard Deck", "project_scope": "Tear off old wood deck, build composite deck with pergola.", "target_start": "2026-05-01", "target_end": "2026-06-01", "status": "Complete", "budget": 18e3 } },
381
+ { key: "room-garage", values: { "room_name": "Garage", "project_scope": "Epoxy floor, built-in storage, new insulated door.", "target_start": "2026-10-01", "target_end": "2026-10-20", "status": "On Hold", "budget": 9500 } }
382
+ ]
383
+ },
384
+ {
385
+ token: "contractors",
386
+ name: "Contractors",
387
+ labelSingular: "Contractor",
388
+ labelPlural: "Contractors",
389
+ type: "entity",
390
+ display: "list",
391
+ weight: "heavy",
392
+ ordered: false,
393
+ icon: "hard-hat",
394
+ titleField: "company_name",
395
+ describes: "Everyone quoting or working on the job, with the licence and insurance details that matter if something goes wrong.",
396
+ fields: [
397
+ { key: "company_name", label: "Company", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
398
+ { key: "trade", label: "Trade", parityType: "select", choices: ["General Contractor", "Plumbing", "Electrical", "Flooring", "Painting", "HVAC", "Carpentry"], sensitivity: "internal", contextPolicy: "include" },
399
+ { key: "contact_name", label: "Contact", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
400
+ { key: "phone", label: "Phone", parityType: "phone", format: "phone", sensitivity: "confidential", contextPolicy: "exclude" },
401
+ { key: "email", label: "Email", parityType: "email", format: "email", sensitivity: "confidential", contextPolicy: "exclude" },
402
+ { key: "license_number", label: "Licence number", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
403
+ { key: "insured", label: "Insured", parityType: "select", choices: ["Yes", "No"], sensitivity: "internal", contextPolicy: "include" }
404
+ ],
405
+ rows: [
406
+ { key: "contractor-ferro-sons-construction", values: { "company_name": "Ferro & Sons Construction", "trade": "General Contractor", "contact_name": "Dominic Ferro", "phone": "555-330-1001", "email": "dominic@northshoremail.com", "license_number": "GC-88213-OH", "insured": "Yes" } },
407
+ { key: "contractor-redline-plumbing-co", values: { "company_name": "Redline Plumbing Co.", "trade": "Plumbing", "contact_name": "Tanya Iversen", "phone": "555-330-1002", "email": "tanya@northshoremail.com", "license_number": "PL-44029-OH", "insured": "Yes" } },
408
+ { key: "contractor-voltway-electric", values: { "company_name": "Voltway Electric", "trade": "Electrical", "contact_name": "Emeka Osayande", "phone": "555-330-1003", "email": "emeka@northshoremail.com", "license_number": "EL-90117-OH", "insured": "Yes" } },
409
+ { key: "contractor-hearthstone-flooring", values: { "company_name": "Hearthstone Flooring", "trade": "Flooring", "contact_name": "Bianca Moretti", "phone": "555-330-1004", "email": "bianca@northshoremail.com", "license_number": "FL-22850-OH", "insured": "Yes" } },
410
+ { key: "contractor-clearcoat-painting", values: { "company_name": "Clearcoat Painting", "trade": "Painting", "contact_name": "Sam Delacroix", "phone": "555-330-1005", "email": "sam@northshoremail.com", "license_number": "PT-10442-OH", "insured": "No" } },
411
+ { key: "contractor-northgate-hvac", values: { "company_name": "Northgate HVAC", "trade": "HVAC", "contact_name": "Renata Kowalczyk", "phone": "555-330-1006", "email": "renata@northshoremail.com", "license_number": "HV-77302-OH", "insured": "Yes" } },
412
+ { key: "contractor-millbrook-carpentry", values: { "company_name": "Millbrook Carpentry", "trade": "Carpentry", "contact_name": "Julius Wexford", "phone": "555-330-1007", "email": "julius@northshoremail.com", "license_number": "CP-55671-OH", "insured": "Yes" } }
413
+ ]
414
+ },
415
+ {
416
+ token: "quotes",
417
+ name: "Quotes",
418
+ labelSingular: "Quote",
419
+ labelPlural: "Quotes",
420
+ type: "ledger",
421
+ display: "list",
422
+ weight: "heavy",
423
+ ordered: false,
424
+ icon: "file-text",
425
+ titleField: "quote_amount",
426
+ describes: "What each trade said each room would cost, and whether that price is still good.",
427
+ fields: [
428
+ { key: "room", label: "Room", parityType: "relation", required: true, relationTarget: "rooms", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
429
+ { key: "contractor", label: "Contractor", parityType: "relation", required: true, relationTarget: "contractors", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
430
+ { key: "quote_amount", label: "Quote", parityType: "currency", unit: "USD", format: "currency", required: true, sensitivity: "confidential", contextPolicy: "summarize" },
431
+ { key: "quote_date", label: "Quoted on", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
432
+ { key: "status", label: "Status", parityType: "select", choices: ["Pending", "Accepted", "Rejected", "Expired"], sensitivity: "internal", contextPolicy: "include" },
433
+ { key: "valid_until", label: "Valid until", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" }
434
+ ],
435
+ rows: [
436
+ { key: "quote-46500-0", values: { "room": "room-kitchen", "contractor": "contractor-ferro-sons-construction", "quote_amount": 46500, "quote_date": "2026-02-14", "status": "Accepted", "valid_until": "2026-03-14" } },
437
+ { key: "quote-3200-0", values: { "room": "room-kitchen", "contractor": "contractor-redline-plumbing-co", "quote_amount": 3200, "quote_date": "2026-02-20", "status": "Accepted", "valid_until": "2026-03-20" } },
438
+ { key: "quote-2800-0", values: { "room": "room-kitchen", "contractor": "contractor-voltway-electric", "quote_amount": 2800, "quote_date": "2026-02-20", "status": "Accepted", "valid_until": "2026-03-20" } },
439
+ { key: "quote-21000-0", values: { "room": "room-primary-bathroom", "contractor": "contractor-ferro-sons-construction", "quote_amount": 21e3, "quote_date": "2026-05-10", "status": "Accepted", "valid_until": "2026-06-10" } },
440
+ { key: "quote-4100-0", values: { "room": "room-primary-bathroom", "contractor": "contractor-redline-plumbing-co", "quote_amount": 4100, "quote_date": "2026-05-11", "status": "Accepted", "valid_until": "2026-06-11" } },
441
+ { key: "quote-58000-0", values: { "room": "room-basement", "contractor": "contractor-ferro-sons-construction", "quote_amount": 58e3, "quote_date": "2026-07-20", "status": "Pending", "valid_until": "2026-08-20" } },
442
+ { key: "quote-12500-0", values: { "room": "room-basement", "contractor": "contractor-millbrook-carpentry", "quote_amount": 12500, "quote_date": "2026-07-22", "status": "Pending", "valid_until": "2026-08-22" } },
443
+ { key: "quote-6800-0", values: { "room": "room-basement", "contractor": "contractor-northgate-hvac", "quote_amount": 6800, "quote_date": "2026-07-25", "status": "Pending", "valid_until": "2026-08-25" } },
444
+ { key: "quote-1800-0", values: { "room": "room-guest-bathroom", "contractor": "contractor-hearthstone-flooring", "quote_amount": 1800, "quote_date": "2026-07-28", "status": "Pending", "valid_until": "2026-08-28" } },
445
+ { key: "quote-17200-0", values: { "room": "room-backyard-deck", "contractor": "contractor-millbrook-carpentry", "quote_amount": 17200, "quote_date": "2026-03-05", "status": "Accepted", "valid_until": "2026-04-05" } },
446
+ { key: "quote-19800-0", values: { "room": "room-backyard-deck", "contractor": "contractor-ferro-sons-construction", "quote_amount": 19800, "quote_date": "2026-03-02", "status": "Rejected", "valid_until": "2026-04-02" } },
447
+ { key: "quote-2100-0", values: { "room": "room-garage", "contractor": "contractor-clearcoat-painting", "quote_amount": 2100, "quote_date": "2026-01-15", "status": "Expired", "valid_until": "2026-02-15" } }
448
+ ]
449
+ },
450
+ {
451
+ token: "purchases",
452
+ name: "Purchases",
453
+ labelSingular: "Purchase",
454
+ labelPlural: "Purchases",
455
+ type: "ledger",
456
+ display: "list",
457
+ weight: "heavy",
458
+ ordered: false,
459
+ icon: "receipt",
460
+ titleField: "item_description",
461
+ describes: "Every pound spent against the project, so the budget is a fact rather than a feeling.",
462
+ fields: [
463
+ { key: "room", label: "Room", parityType: "relation", required: true, relationTarget: "rooms", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
464
+ { key: "item_description", label: "What was bought", parityType: "text", required: true, sensitivity: "internal", contextPolicy: "include" },
465
+ { key: "vendor", label: "Vendor", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
466
+ { key: "purchase_date", label: "Bought on", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
467
+ { key: "amount", label: "Amount", parityType: "currency", unit: "USD", format: "currency", sensitivity: "confidential", contextPolicy: "summarize" },
468
+ { key: "category", label: "Category", parityType: "select", choices: ["Materials", "Fixtures", "Labor", "Permits", "Appliances", "Design/Design Fees"], sensitivity: "internal", contextPolicy: "include" },
469
+ { key: "receipt_number", label: "Receipt", parityType: "text", sensitivity: "internal", contextPolicy: "include" }
470
+ ],
471
+ rows: [
472
+ { key: "purchase-custom-shaker-cabinets-painted-alabaster", values: { "room": "room-kitchen", "item_description": "Custom shaker cabinets, painted 'Alabaster'", "vendor": "Millbrook Cabinetry Supply", "purchase_date": "2026-04-05", "amount": 14200, "category": "Materials", "receipt_number": "RCT-10021" } },
473
+ { key: "purchase-quartz-countertop-calacatta-gold-42-sq-ft", values: { "room": "room-kitchen", "item_description": "Quartz countertop, 'Calacatta Gold' 42 sq ft", "vendor": "Stone Depot", "purchase_date": "2026-04-18", "amount": 5900, "category": "Materials", "receipt_number": "RCT-10022" } },
474
+ { key: "purchase-range-refrigerator-dishwasher-package", values: { "room": "room-kitchen", "item_description": "Range, refrigerator, dishwasher package", "vendor": "Coldstream Appliance Center", "purchase_date": "2026-04-22", "amount": 8700, "category": "Appliances", "receipt_number": "RCT-10023" } },
475
+ { key: "purchase-building-permit-kitchen-remodel", values: { "room": "room-kitchen", "item_description": "Building permit \u2014 kitchen remodel", "vendor": "City of Fairmont Permits Office", "purchase_date": "2026-03-28", "amount": 450, "category": "Permits", "receipt_number": "RCT-10024" } },
476
+ { key: "purchase-ferro-sons-final-labor-invoice", values: { "room": "room-kitchen", "item_description": "Ferro & Sons \u2014 final labor invoice", "vendor": "Ferro & Sons Construction", "purchase_date": "2026-06-15", "amount": 15800, "category": "Labor", "receipt_number": "RCT-10025" } },
477
+ { key: "purchase-walk-in-shower-kit-with-frameless-glass", values: { "room": "room-primary-bathroom", "item_description": "Walk-in shower kit with frameless glass", "vendor": "Stone Depot", "purchase_date": "2026-06-25", "amount": 4300, "category": "Fixtures", "receipt_number": "RCT-10026" } },
478
+ { key: "purchase-heated-floor-mat-system-45-sq-ft", values: { "room": "room-primary-bathroom", "item_description": "Heated floor mat system, 45 sq ft", "vendor": "WarmFloor Direct", "purchase_date": "2026-06-28", "amount": 1650, "category": "Materials", "receipt_number": "RCT-10027" } },
479
+ { key: "purchase-floating-double-vanity-60-inch", values: { "room": "room-primary-bathroom", "item_description": "Floating double vanity, 60 inch", "vendor": "Millbrook Cabinetry Supply", "purchase_date": "2026-07-02", "amount": 2200, "category": "Fixtures", "receipt_number": "RCT-10028" } },
480
+ { key: "purchase-composite-decking-600-sq-ft-weathered-teak", values: { "room": "room-backyard-deck", "item_description": "Composite decking, 600 sq ft, 'Weathered Teak'", "vendor": "Deckmaster Supply", "purchase_date": "2026-05-05", "amount": 8900, "category": "Materials", "receipt_number": "RCT-10029" } },
481
+ { key: "purchase-cedar-pergola-kit-12x14", values: { "room": "room-backyard-deck", "item_description": "Cedar pergola kit, 12x14", "vendor": "Deckmaster Supply", "purchase_date": "2026-05-08", "amount": 3200, "category": "Materials", "receipt_number": "RCT-10030" } },
482
+ { key: "purchase-millbrook-carpentry-labor-and-install", values: { "room": "room-backyard-deck", "item_description": "Millbrook Carpentry \u2014 labor and install", "vendor": "Millbrook Carpentry", "purchase_date": "2026-06-01", "amount": 4900, "category": "Labor", "receipt_number": "RCT-10031" } },
483
+ { key: "purchase-design-consultation-fixture-layout", values: { "room": "room-guest-bathroom", "item_description": "Design consultation \u2014 fixture layout", "vendor": "Bianca Moretti Interiors", "purchase_date": "2026-07-30", "amount": 350, "category": "Design/Design Fees", "receipt_number": "RCT-10032" } }
484
+ ]
485
+ }
486
+ ],
487
+ relationships: [
488
+ {
489
+ describes: "A quote is for exactly one room, and it is meaningless once that room is out of scope.",
490
+ fromTable: "quotes",
491
+ fromField: "room",
492
+ toTable: "rooms",
493
+ flavor: "owned",
494
+ cardinality: "one",
495
+ onDelete: "cascade",
496
+ inverseKey: "quotes"
497
+ },
498
+ {
499
+ describes: "A quote comes from one contractor, who goes on existing after the quote is rejected.",
500
+ fromTable: "quotes",
501
+ fromField: "contractor",
502
+ toTable: "contractors",
503
+ flavor: "referenced",
504
+ cardinality: "one",
505
+ onDelete: "restrict",
506
+ inverseKey: "quotes_given"
507
+ },
508
+ {
509
+ describes: "A purchase is charged against the room it was bought for, which is how the budget stays honest.",
510
+ fromTable: "purchases",
511
+ fromField: "room",
512
+ toTable: "rooms",
513
+ flavor: "owned",
514
+ cardinality: "one",
515
+ onDelete: "cascade",
516
+ inverseKey: "purchases"
517
+ }
518
+ ],
519
+ form: {
520
+ token: "birchwood_contractor_details",
521
+ name: "Contractor details and insurance",
522
+ describes: "What a trade fills in before they are allowed to quote. It asks for the licence number and the insurance because those are the two things nobody can produce after something goes wrong.",
523
+ audience: "public",
524
+ table: "contractors",
525
+ fields: [
526
+ { fieldKey: "company_name", label: "Company name", required: true },
527
+ { fieldKey: "trade", label: "Trade", required: true },
528
+ { fieldKey: "contact_name", label: "Who should I ask for?", required: true },
529
+ { fieldKey: "phone", label: "Best number", required: true },
530
+ { fieldKey: "email", label: "Email", required: true },
531
+ { fieldKey: "license_number", label: "Licence number", required: true, help: "I check this with the state board before accepting a quote." },
532
+ { fieldKey: "insured", label: "Do you carry liability insurance?", required: true, help: "I will ask for the certificate before work starts." }
533
+ ],
534
+ submitLabel: "Send my details",
535
+ confirmation: "Thank you. I will check the licence and come back to you about which rooms I would like quoted, usually within a few days."
536
+ },
537
+ pipeline: {
538
+ token: "birchwood_room_progress",
539
+ name: "Room by room",
540
+ describes: "Where each room stands. The board exists because a whole-house renovation is really five small projects at different stages, and the only way to stay sane is to look at them side by side.",
541
+ table: "rooms",
542
+ stageField: "status",
543
+ stages: [
544
+ { key: "Planning", label: "Planning", describes: "Scope is still being decided. Nothing has been priced, so the budget figure is a guess.", typicalDurationDays: 21 },
545
+ { key: "Quoting", label: "Quoting", describes: "Trades are pricing it. Leaves when a quote is accepted \u2014 or when every quote comes back over budget and the scope goes back to Planning.", typicalDurationDays: 21 },
546
+ { key: "In Progress", label: "In progress", describes: "Work has started and receipts are landing against this room. This is where the budget is actually won or lost.", typicalDurationDays: 45 },
547
+ { key: "On Hold", label: "On hold", describes: "Stopped for money, a permit or a lead time. Named honestly so it is not mistaken for progress.", typicalDurationDays: 30 },
548
+ { key: "Complete", label: "Complete", describes: "Finished, paid and signed off. The room's spend is final and can be compared against what was budgeted." }
549
+ ]
550
+ },
551
+ dashboardQuestion: {
552
+ question: "What have I actually spent on each room so far?",
553
+ whyItMatters: "The budget was set room by room, so the only useful comparison is room by room. This is the number that decides whether the next room starts on schedule or waits, and it is the one thing a spreadsheet stops being trusted for once there are more than about thirty receipts.",
554
+ table: "purchases",
555
+ aggregate: "sum",
556
+ of: "amount",
557
+ groupBy: "room",
558
+ answerUnit: "USD"
559
+ },
560
+ documentTemplate: {
561
+ token: "birchwood_quote_acceptance",
562
+ name: "Quote acceptance",
563
+ describes: "Sent to a contractor when their price is accepted. It restates the number, the room and the date the quote was given, because a renovation dispute is almost always about which version of the price was agreed.",
564
+ table: "quotes",
565
+ title: "Accepting your quote of {{amount}}",
566
+ body: [
567
+ "Thank you for quoting.",
568
+ "",
569
+ "I am accepting your price of {{amount}}, quoted on {{quoted_on}} and valid until {{valid_until}}, for the work we discussed.",
570
+ "",
571
+ "Before anything starts I will need a copy of your certificate of insurance. Materials I have bought myself are already on site; anything else is yours to supply and appears on your invoice, not on my receipts.",
572
+ "",
573
+ "If the scope changes once the wall is open \u2014 and on a house this age it usually does \u2014 please price the change and send it to me before carrying it out. I would rather approve a second number than argue about a bigger one later.",
574
+ "",
575
+ "{{project}}",
576
+ "{{project_phone}}",
577
+ "{{today}}"
578
+ ].join("\n"),
579
+ mergeFields: [
580
+ { key: "amount", from: "quote_amount", describes: "The accepted price, restated so there is one version of it." },
581
+ { key: "quoted_on", from: "quote_date", describes: "Which quote this is, when a trade has given several." },
582
+ { key: "valid_until", from: "valid_until", describes: "The date the price was good until." },
583
+ { key: "project", from: "business", describes: "The project name." },
584
+ { key: "project_phone", from: "business", describes: "The number to call back on." },
585
+ { key: "today", from: "today", describes: "The date of acceptance." }
586
+ ]
587
+ },
588
+ notificationRule: {
589
+ token: "birchwood_quote_expiring",
590
+ name: "A quote is about to expire",
591
+ describes: "Quotes are good for thirty days and a renovation moves slower than that, so the default outcome is re-pricing everything at a worse number. A week's warning is what prevents it, and it is the first thing any homeowner would automate.",
592
+ when: {
593
+ table: "quotes",
594
+ event: "due",
595
+ field: "valid_until",
596
+ daysBefore: 7
597
+ },
598
+ audience: "the homeowner",
599
+ channel: "email",
600
+ message: "The quote of {{quote_amount}} expires on {{valid_until}} \u2014 about a week from now. Accept it, or ask for it to be held, before it has to be re-priced."
601
+ },
602
+ provenance: {
603
+ kind: "synthesized",
604
+ authoredBy: "data crew (dataset) + lane REAL-DATA (business identity, relationships, form, pipeline, dashboard question, document, notification rule)",
605
+ authoredOn: "2026-09-21",
606
+ noRealPeople: true
607
+ }
608
+ };
609
+ birchwood_avenue_renovation_budget_default = useCase2;
610
+ }
611
+ });
612
+
613
+ // src/use-cases/harbor-dental-new-patient-intake.ts
614
+ var harbor_dental_new_patient_intake_exports = {};
615
+ __export(harbor_dental_new_patient_intake_exports, {
616
+ default: () => harbor_dental_new_patient_intake_default,
617
+ useCase: () => useCase3
618
+ });
619
+ var useCase3, harbor_dental_new_patient_intake_default;
620
+ var init_harbor_dental_new_patient_intake = __esm({
621
+ "src/use-cases/harbor-dental-new-patient-intake.ts"() {
622
+ "use strict";
623
+ useCase3 = {
624
+ id: "harbor-dental-new-patient-intake",
625
+ useCase: "Renata Okonkwo runs the front desk at Harbor Dental Group, a six-chair family dental practice, and she uses this to take a new patient from the first phone call through forms, insurance verification and a booked first visit without anything falling between the cracks.",
626
+ industry: "Dentistry",
627
+ cleanupTag: "use-case:harbor-dental-new-patient-intake",
628
+ business: {
629
+ name: "Harbor Dental Group",
630
+ legalName: "Harbor Dental Group, PLLC",
631
+ describes: "A six-chair family dental practice doing general dentistry, hygiene and same-day crowns.",
632
+ address: {
633
+ line1: "1840 Waterfront Parkway",
634
+ line2: "Suite 210",
635
+ city: "Bellingham",
636
+ region: "WA",
637
+ postalCode: "98225",
638
+ country: "US"
639
+ },
640
+ phone: "+1 360 555 0142",
641
+ website: "https://harbordentalgroup.com",
642
+ email: "frontdesk@harbordentalgroup.com",
643
+ timezone: "America/Los_Angeles"
644
+ },
645
+ tables: [
646
+ {
647
+ token: "patient",
648
+ name: "Patients",
649
+ labelSingular: "Patient",
650
+ labelPlural: "Patients",
651
+ type: "entity",
652
+ display: "list",
653
+ weight: "heavy",
654
+ ordered: false,
655
+ icon: "user-round",
656
+ titleField: "full_name",
657
+ describes: "Everyone the practice treats, from the first enquiry through to an active chart.",
658
+ fields: [
659
+ {
660
+ key: "full_name",
661
+ label: "Patient name",
662
+ parityType: "text",
663
+ required: true,
664
+ sensitivity: "confidential",
665
+ contextPolicy: "summarize"
666
+ },
667
+ {
668
+ key: "chart_number",
669
+ label: "Chart number",
670
+ parityType: "text",
671
+ required: true,
672
+ sensitivity: "internal",
673
+ contextPolicy: "include",
674
+ help: "Assigned when the chart is opened. Printed on every claim and referral."
675
+ },
676
+ {
677
+ key: "date_of_birth",
678
+ label: "Date of birth",
679
+ parityType: "datetime",
680
+ format: "date",
681
+ sensitivity: "confidential",
682
+ contextPolicy: "exclude",
683
+ help: "Carriers verify eligibility on name plus date of birth, so it has to be exact."
684
+ },
685
+ {
686
+ key: "phone",
687
+ label: "Mobile phone",
688
+ parityType: "phone",
689
+ format: "phone",
690
+ sensitivity: "confidential",
691
+ contextPolicy: "exclude"
692
+ },
693
+ {
694
+ key: "email",
695
+ label: "Email",
696
+ parityType: "email",
697
+ format: "email",
698
+ sensitivity: "confidential",
699
+ contextPolicy: "exclude"
700
+ },
701
+ {
702
+ key: "street",
703
+ label: "Street address",
704
+ parityType: "text",
705
+ sensitivity: "confidential",
706
+ contextPolicy: "exclude"
707
+ },
708
+ {
709
+ key: "city",
710
+ label: "City",
711
+ parityType: "text",
712
+ sensitivity: "internal",
713
+ contextPolicy: "include"
714
+ },
715
+ {
716
+ key: "region",
717
+ label: "State",
718
+ parityType: "text",
719
+ sensitivity: "internal",
720
+ contextPolicy: "include"
721
+ },
722
+ {
723
+ key: "postal_code",
724
+ label: "ZIP",
725
+ parityType: "text",
726
+ sensitivity: "internal",
727
+ contextPolicy: "include"
728
+ },
729
+ {
730
+ key: "intake_stage",
731
+ label: "Intake stage",
732
+ parityType: "select",
733
+ required: true,
734
+ sensitivity: "internal",
735
+ contextPolicy: "include",
736
+ choices: [
737
+ "Enquiry",
738
+ "Forms sent",
739
+ "Forms received",
740
+ "Insurance verified",
741
+ "First visit booked",
742
+ "Active patient"
743
+ ],
744
+ help: "Where this person is between the first call and a chart the hygienist can work from."
745
+ },
746
+ {
747
+ key: "insurance_carrier",
748
+ label: "Insurance carrier",
749
+ parityType: "select",
750
+ sensitivity: "confidential",
751
+ contextPolicy: "summarize",
752
+ choices: ["Delta Dental", "Cigna Dental", "MetLife", "Guardian", "Aetna", "Self-pay"]
753
+ },
754
+ {
755
+ key: "member_id",
756
+ label: "Member ID",
757
+ parityType: "text",
758
+ sensitivity: "restricted",
759
+ contextPolicy: "exclude",
760
+ help: "Never leaves the practice. Needed to verify eligibility before the first visit."
761
+ },
762
+ {
763
+ key: "primary_dentist",
764
+ label: "Primary dentist",
765
+ parityType: "select",
766
+ sensitivity: "internal",
767
+ contextPolicy: "include",
768
+ choices: ["Dr. Alina Reyes", "Dr. Marcus Webb"]
769
+ },
770
+ {
771
+ key: "allergies",
772
+ label: "Known allergies",
773
+ parityType: "multi_select",
774
+ multi: true,
775
+ sensitivity: "confidential",
776
+ contextPolicy: "summarize",
777
+ choices: [
778
+ "Penicillin",
779
+ "Latex",
780
+ "Codeine",
781
+ "Sulfa drugs",
782
+ "Local anaesthetic",
783
+ "None reported"
784
+ ],
785
+ help: "Read out loud at chairside before any injection."
786
+ },
787
+ {
788
+ key: "balance_due",
789
+ label: "Balance due",
790
+ parityType: "currency",
791
+ unit: "USD",
792
+ format: "currency",
793
+ sensitivity: "confidential",
794
+ contextPolicy: "summarize"
795
+ },
796
+ {
797
+ key: "referred_by",
798
+ label: "How they found us",
799
+ parityType: "select",
800
+ sensitivity: "internal",
801
+ contextPolicy: "include",
802
+ choices: [
803
+ "Existing patient referral",
804
+ "Insurance directory",
805
+ "Web search",
806
+ "Walk-in",
807
+ "Employer benefits fair"
808
+ ]
809
+ },
810
+ {
811
+ key: "front_desk_notes",
812
+ label: "Front desk notes",
813
+ parityType: "long_text",
814
+ sensitivity: "confidential",
815
+ contextPolicy: "on_request"
816
+ }
817
+ ],
818
+ rows: [
819
+ {
820
+ key: "patient-rivera-marisol",
821
+ values: {
822
+ full_name: "Marisol Rivera",
823
+ chart_number: "HD-10428",
824
+ date_of_birth: "1988-03-17",
825
+ phone: "+1 360 555 0184",
826
+ email: "m.rivera@northshoremail.com",
827
+ street: "4417 Chuckanut Ridge Road",
828
+ city: "Bellingham",
829
+ region: "WA",
830
+ postal_code: "98229",
831
+ intake_stage: "Active patient",
832
+ insurance_carrier: "Delta Dental",
833
+ member_id: "DD-884201773",
834
+ primary_dentist: "Dr. Alina Reyes",
835
+ allergies: ["Penicillin"],
836
+ balance_due: 0,
837
+ referred_by: "Existing patient referral",
838
+ front_desk_notes: "Prefers early morning. Two children also seen here."
839
+ }
840
+ },
841
+ {
842
+ key: "patient-oyelaran-tunde",
843
+ values: {
844
+ full_name: "Tunde Oyelaran",
845
+ chart_number: "HD-10431",
846
+ date_of_birth: "1975-11-02",
847
+ phone: "+1 360 555 0219",
848
+ email: "toyelaran@baysidedesignco.com",
849
+ street: "902 Eldridge Avenue",
850
+ city: "Bellingham",
851
+ region: "WA",
852
+ postal_code: "98225",
853
+ intake_stage: "Active patient",
854
+ insurance_carrier: "Cigna Dental",
855
+ member_id: "CG-4471209",
856
+ primary_dentist: "Dr. Marcus Webb",
857
+ allergies: ["None reported"],
858
+ balance_due: 142.5,
859
+ referred_by: "Insurance directory",
860
+ front_desk_notes: "Travels for work; wants visits batched into one afternoon."
861
+ }
862
+ },
863
+ {
864
+ key: "patient-halvorsen-britt",
865
+ values: {
866
+ full_name: "Britt Halvorsen",
867
+ chart_number: "HD-10455",
868
+ date_of_birth: "1992-07-26",
869
+ phone: "+1 360 555 0177",
870
+ email: "britt.halvorsen@cascadiaoutfitters.com",
871
+ street: "215 Harris Avenue",
872
+ city: "Bellingham",
873
+ region: "WA",
874
+ postal_code: "98225",
875
+ intake_stage: "First visit booked",
876
+ insurance_carrier: "MetLife",
877
+ member_id: "ML-63902118",
878
+ primary_dentist: "Dr. Alina Reyes",
879
+ allergies: ["Latex"],
880
+ balance_due: 0,
881
+ referred_by: "Web search",
882
+ front_desk_notes: "Latex allergy flagged to the back office; nitrile only."
883
+ }
884
+ },
885
+ {
886
+ key: "patient-nakamura-kenji",
887
+ values: {
888
+ full_name: "Kenji Nakamura",
889
+ chart_number: "HD-10460",
890
+ date_of_birth: "1964-01-09",
891
+ phone: "+1 360 555 0263",
892
+ email: "k.nakamura@harborviewcpa.com",
893
+ street: "1130 Yew Street",
894
+ city: "Bellingham",
895
+ region: "WA",
896
+ postal_code: "98229",
897
+ intake_stage: "Insurance verified",
898
+ insurance_carrier: "Guardian",
899
+ member_id: "GD-20117744",
900
+ primary_dentist: "Dr. Marcus Webb",
901
+ allergies: ["Codeine"],
902
+ balance_due: 0,
903
+ referred_by: "Employer benefits fair",
904
+ front_desk_notes: "Eligibility confirmed through December. Waiting on his schedule to book."
905
+ }
906
+ },
907
+ {
908
+ key: "patient-abadi-leila",
909
+ values: {
910
+ full_name: "Leila Abadi",
911
+ chart_number: "HD-10462",
912
+ date_of_birth: "1997-05-30",
913
+ phone: "+1 360 555 0308",
914
+ email: "leila.abadi@westwindstudio.com",
915
+ street: "77 Fairhaven Parkway",
916
+ city: "Bellingham",
917
+ region: "WA",
918
+ postal_code: "98225",
919
+ intake_stage: "Forms received",
920
+ insurance_carrier: "Aetna",
921
+ member_id: "AE-55823094",
922
+ primary_dentist: "Dr. Alina Reyes",
923
+ allergies: ["None reported"],
924
+ balance_due: 0,
925
+ referred_by: "Existing patient referral",
926
+ front_desk_notes: "Forms in. Needs eligibility checked before we offer a slot."
927
+ }
928
+ },
929
+ {
930
+ key: "patient-delacroix-yvon",
931
+ values: {
932
+ full_name: "Yvon Delacroix",
933
+ chart_number: "HD-10466",
934
+ date_of_birth: "1981-09-14",
935
+ phone: "+1 360 555 0341",
936
+ email: "yvon.delacroix@pugetsoundfreight.com",
937
+ street: "3320 Northwest Avenue",
938
+ city: "Bellingham",
939
+ region: "WA",
940
+ postal_code: "98225",
941
+ intake_stage: "Forms sent",
942
+ insurance_carrier: "Delta Dental",
943
+ member_id: "DD-884977120",
944
+ primary_dentist: "Dr. Marcus Webb",
945
+ allergies: ["None reported"],
946
+ balance_due: 0,
947
+ referred_by: "Insurance directory",
948
+ front_desk_notes: "Packet emailed Tuesday. Follow up if nothing back by Friday."
949
+ }
950
+ },
951
+ {
952
+ key: "patient-strand-ingrid",
953
+ values: {
954
+ full_name: "Ingrid Strand",
955
+ chart_number: "HD-10470",
956
+ date_of_birth: "1955-12-04",
957
+ phone: "+1 360 555 0392",
958
+ email: "ingrid.strand@northshoremail.com",
959
+ street: "618 Alabama Street",
960
+ city: "Bellingham",
961
+ region: "WA",
962
+ postal_code: "98225",
963
+ intake_stage: "Active patient",
964
+ insurance_carrier: "Self-pay",
965
+ member_id: "",
966
+ primary_dentist: "Dr. Alina Reyes",
967
+ allergies: ["Sulfa drugs", "Local anaesthetic"],
968
+ balance_due: 880,
969
+ referred_by: "Walk-in",
970
+ front_desk_notes: "On a payment plan for the crown. Anaesthetic sensitivity \u2014 Dr. Reyes is aware."
971
+ }
972
+ },
973
+ {
974
+ key: "patient-mbeki-sipho",
975
+ values: {
976
+ full_name: "Sipho Mbeki",
977
+ chart_number: "HD-10473",
978
+ date_of_birth: "2001-02-21",
979
+ phone: "+1 360 555 0415",
980
+ email: "sipho.mbeki@wwu-alumni.com",
981
+ street: "1502 Indian Street",
982
+ city: "Bellingham",
983
+ region: "WA",
984
+ postal_code: "98225",
985
+ intake_stage: "Enquiry",
986
+ insurance_carrier: "Self-pay",
987
+ member_id: "",
988
+ primary_dentist: "Dr. Marcus Webb",
989
+ allergies: ["None reported"],
990
+ balance_due: 0,
991
+ referred_by: "Web search",
992
+ front_desk_notes: "Called about a chipped incisor. Quoted the emergency exam fee."
993
+ }
994
+ },
995
+ {
996
+ key: "patient-kowalczyk-agata",
997
+ values: {
998
+ full_name: "Agata Kowalczyk",
999
+ chart_number: "HD-10477",
1000
+ date_of_birth: "1986-06-11",
1001
+ phone: "+1 360 555 0448",
1002
+ email: "a.kowalczyk@evergreenlabsupply.com",
1003
+ street: "2245 Cornwall Avenue",
1004
+ city: "Bellingham",
1005
+ region: "WA",
1006
+ postal_code: "98225",
1007
+ intake_stage: "Active patient",
1008
+ insurance_carrier: "Cigna Dental",
1009
+ member_id: "CG-4488317",
1010
+ primary_dentist: "Dr. Alina Reyes",
1011
+ allergies: ["None reported"],
1012
+ balance_due: 64,
1013
+ referred_by: "Existing patient referral",
1014
+ front_desk_notes: "Night guard impressions taken; lab case out."
1015
+ }
1016
+ },
1017
+ {
1018
+ key: "patient-ferreira-joao",
1019
+ values: {
1020
+ full_name: "Jo\xE3o Ferreira",
1021
+ chart_number: "HD-10481",
1022
+ date_of_birth: "1970-08-19",
1023
+ phone: "+1 360 555 0472",
1024
+ email: "joao.ferreira@harborlinemarine.com",
1025
+ street: "840 Marine Drive",
1026
+ city: "Bellingham",
1027
+ region: "WA",
1028
+ postal_code: "98225",
1029
+ intake_stage: "Insurance verified",
1030
+ insurance_carrier: "MetLife",
1031
+ member_id: "ML-63988402",
1032
+ primary_dentist: "Dr. Marcus Webb",
1033
+ allergies: ["Penicillin", "Latex"],
1034
+ balance_due: 0,
1035
+ referred_by: "Employer benefits fair",
1036
+ front_desk_notes: "Two allergies on file. Wants the first available Thursday."
1037
+ }
1038
+ },
1039
+ {
1040
+ key: "patient-whitehorse-dara",
1041
+ values: {
1042
+ full_name: "Dara Whitehorse",
1043
+ chart_number: "HD-10484",
1044
+ date_of_birth: "1994-04-07",
1045
+ phone: "+1 360 555 0501",
1046
+ email: "dara.whitehorse@salishseaarts.com",
1047
+ street: "506 Girard Street",
1048
+ city: "Bellingham",
1049
+ region: "WA",
1050
+ postal_code: "98225",
1051
+ intake_stage: "First visit booked",
1052
+ insurance_carrier: "Guardian",
1053
+ member_id: "GD-20166018",
1054
+ primary_dentist: "Dr. Alina Reyes",
1055
+ allergies: ["None reported"],
1056
+ balance_due: 0,
1057
+ referred_by: "Web search",
1058
+ front_desk_notes: "Booked for the new patient exam and a cleaning back to back."
1059
+ }
1060
+ },
1061
+ {
1062
+ key: "patient-oconnell-brigid",
1063
+ values: {
1064
+ full_name: "Brigid O'Connell",
1065
+ chart_number: "HD-10489",
1066
+ date_of_birth: "1959-10-23",
1067
+ phone: "+1 360 555 0533",
1068
+ email: "brigid.oconnell@northshoremail.com",
1069
+ street: "3901 Sunset Drive",
1070
+ city: "Bellingham",
1071
+ region: "WA",
1072
+ postal_code: "98229",
1073
+ intake_stage: "Active patient",
1074
+ insurance_carrier: "Delta Dental",
1075
+ member_id: "DD-885013366",
1076
+ primary_dentist: "Dr. Marcus Webb",
1077
+ allergies: ["Codeine"],
1078
+ balance_due: 310.75,
1079
+ referred_by: "Existing patient referral",
1080
+ front_desk_notes: "Crown seat next month. Balance is the patient portion from the last claim."
1081
+ }
1082
+ },
1083
+ {
1084
+ key: "patient-tran-hieu",
1085
+ values: {
1086
+ full_name: "Hieu Tran",
1087
+ chart_number: "HD-10492",
1088
+ date_of_birth: "1990-12-15",
1089
+ phone: "+1 360 555 0566",
1090
+ email: "hieu.tran@baysidedesignco.com",
1091
+ street: "1215 Lakeway Drive",
1092
+ city: "Bellingham",
1093
+ region: "WA",
1094
+ postal_code: "98229",
1095
+ intake_stage: "Forms received",
1096
+ insurance_carrier: "Aetna",
1097
+ member_id: "AE-55901287",
1098
+ primary_dentist: "Dr. Alina Reyes",
1099
+ allergies: ["None reported"],
1100
+ balance_due: 0,
1101
+ referred_by: "Insurance directory",
1102
+ front_desk_notes: "Medical history mentions a heart murmur \u2014 confirm premedication with Dr. Reyes."
1103
+ }
1104
+ },
1105
+ {
1106
+ key: "patient-bergstrom-annika",
1107
+ values: {
1108
+ full_name: "Annika Bergstr\xF6m",
1109
+ chart_number: "HD-10496",
1110
+ date_of_birth: "1983-02-08",
1111
+ phone: "+1 360 555 0590",
1112
+ email: "annika.bergstrom@cascadiaoutfitters.com",
1113
+ street: "705 Samish Way",
1114
+ city: "Bellingham",
1115
+ region: "WA",
1116
+ postal_code: "98225",
1117
+ intake_stage: "Enquiry",
1118
+ insurance_carrier: "Cigna Dental",
1119
+ member_id: "CG-4491550",
1120
+ primary_dentist: "Dr. Marcus Webb",
1121
+ allergies: ["None reported"],
1122
+ balance_due: 0,
1123
+ referred_by: "Walk-in",
1124
+ front_desk_notes: "Came in over lunch asking about whitening. Packet not sent yet."
1125
+ }
1126
+ }
1127
+ ]
1128
+ },
1129
+ {
1130
+ token: "visit",
1131
+ name: "Visits",
1132
+ labelSingular: "Visit",
1133
+ labelPlural: "Visits",
1134
+ type: "entity",
1135
+ display: "calendar",
1136
+ weight: "heavy",
1137
+ ordered: true,
1138
+ icon: "calendar-clock",
1139
+ titleField: "summary",
1140
+ describes: "Every booked chair appointment, what it is for, who is in the chair and who is treating.",
1141
+ fields: [
1142
+ {
1143
+ key: "summary",
1144
+ label: "Appointment",
1145
+ parityType: "text",
1146
+ required: true,
1147
+ sensitivity: "internal",
1148
+ contextPolicy: "include"
1149
+ },
1150
+ {
1151
+ key: "patient",
1152
+ label: "Patient",
1153
+ parityType: "relation",
1154
+ required: true,
1155
+ relationTarget: "patient",
1156
+ relationMax: 1,
1157
+ sensitivity: "confidential",
1158
+ contextPolicy: "summarize"
1159
+ },
1160
+ {
1161
+ key: "appointment_at",
1162
+ label: "Date and time",
1163
+ parityType: "datetime",
1164
+ required: true,
1165
+ sensitivity: "internal",
1166
+ contextPolicy: "include"
1167
+ },
1168
+ {
1169
+ key: "duration_minutes",
1170
+ label: "Chair time",
1171
+ parityType: "integer",
1172
+ unit: "min",
1173
+ sensitivity: "internal",
1174
+ contextPolicy: "include"
1175
+ },
1176
+ {
1177
+ key: "procedure",
1178
+ label: "Procedure",
1179
+ parityType: "select",
1180
+ sensitivity: "confidential",
1181
+ contextPolicy: "summarize",
1182
+ choices: [
1183
+ "New patient exam",
1184
+ "Adult prophylaxis",
1185
+ "Child prophylaxis",
1186
+ "Composite filling",
1187
+ "Crown preparation",
1188
+ "Crown seat",
1189
+ "Root canal",
1190
+ "Extraction",
1191
+ "Emergency exam"
1192
+ ]
1193
+ },
1194
+ {
1195
+ key: "provider",
1196
+ label: "Provider",
1197
+ parityType: "select",
1198
+ sensitivity: "internal",
1199
+ contextPolicy: "include",
1200
+ choices: [
1201
+ "Dr. Alina Reyes",
1202
+ "Dr. Marcus Webb",
1203
+ "Priya Chandra, RDH",
1204
+ "Tomas Lindgren, RDH"
1205
+ ]
1206
+ },
1207
+ {
1208
+ key: "operatory",
1209
+ label: "Operatory",
1210
+ parityType: "select",
1211
+ sensitivity: "internal",
1212
+ contextPolicy: "include",
1213
+ choices: ["Op A", "Op B", "Op C", "Op D", "Op E", "Op F"]
1214
+ },
1215
+ {
1216
+ key: "visit_status",
1217
+ label: "Status",
1218
+ parityType: "select",
1219
+ required: true,
1220
+ sensitivity: "internal",
1221
+ contextPolicy: "include",
1222
+ choices: ["Scheduled", "Confirmed", "Checked in", "Completed", "No-show", "Cancelled"]
1223
+ },
1224
+ {
1225
+ key: "fee",
1226
+ label: "Fee",
1227
+ parityType: "currency",
1228
+ unit: "USD",
1229
+ format: "currency",
1230
+ sensitivity: "confidential",
1231
+ contextPolicy: "summarize"
1232
+ }
1233
+ ],
1234
+ rows: [
1235
+ {
1236
+ key: "visit-rivera-prophy-sep",
1237
+ values: {
1238
+ summary: "Adult cleaning \u2014 M. Rivera",
1239
+ patient: "patient-rivera-marisol",
1240
+ appointment_at: "2026-09-08T08:00:00-07:00",
1241
+ duration_minutes: 50,
1242
+ procedure: "Adult prophylaxis",
1243
+ provider: "Priya Chandra, RDH",
1244
+ operatory: "Op B",
1245
+ visit_status: "Completed",
1246
+ fee: 138
1247
+ }
1248
+ },
1249
+ {
1250
+ key: "visit-oyelaran-crown-prep",
1251
+ values: {
1252
+ summary: "Crown prep, lower right \u2014 T. Oyelaran",
1253
+ patient: "patient-oyelaran-tunde",
1254
+ appointment_at: "2026-09-10T13:30:00-07:00",
1255
+ duration_minutes: 90,
1256
+ procedure: "Crown preparation",
1257
+ provider: "Dr. Marcus Webb",
1258
+ operatory: "Op D",
1259
+ visit_status: "Completed",
1260
+ fee: 1240
1261
+ }
1262
+ },
1263
+ {
1264
+ key: "visit-strand-crown-seat",
1265
+ values: {
1266
+ summary: "Crown seat \u2014 I. Strand",
1267
+ patient: "patient-strand-ingrid",
1268
+ appointment_at: "2026-09-14T10:00:00-07:00",
1269
+ duration_minutes: 60,
1270
+ procedure: "Crown seat",
1271
+ provider: "Dr. Alina Reyes",
1272
+ operatory: "Op A",
1273
+ visit_status: "Completed",
1274
+ fee: 880
1275
+ }
1276
+ },
1277
+ {
1278
+ key: "visit-kowalczyk-prophy",
1279
+ values: {
1280
+ summary: "Adult cleaning \u2014 A. Kowalczyk",
1281
+ patient: "patient-kowalczyk-agata",
1282
+ appointment_at: "2026-09-15T09:00:00-07:00",
1283
+ duration_minutes: 50,
1284
+ procedure: "Adult prophylaxis",
1285
+ provider: "Tomas Lindgren, RDH",
1286
+ operatory: "Op C",
1287
+ visit_status: "Completed",
1288
+ fee: 138
1289
+ }
1290
+ },
1291
+ {
1292
+ key: "visit-oconnell-filling",
1293
+ values: {
1294
+ summary: "Composite filling, upper left \u2014 B. O'Connell",
1295
+ patient: "patient-oconnell-brigid",
1296
+ appointment_at: "2026-09-16T14:00:00-07:00",
1297
+ duration_minutes: 60,
1298
+ procedure: "Composite filling",
1299
+ provider: "Dr. Marcus Webb",
1300
+ operatory: "Op D",
1301
+ visit_status: "Completed",
1302
+ fee: 412
1303
+ }
1304
+ },
1305
+ {
1306
+ key: "visit-mbeki-emergency",
1307
+ values: {
1308
+ summary: "Emergency exam, chipped incisor \u2014 S. Mbeki",
1309
+ patient: "patient-mbeki-sipho",
1310
+ appointment_at: "2026-09-17T16:30:00-07:00",
1311
+ duration_minutes: 30,
1312
+ procedure: "Emergency exam",
1313
+ provider: "Dr. Marcus Webb",
1314
+ operatory: "Op E",
1315
+ visit_status: "No-show",
1316
+ fee: 95
1317
+ }
1318
+ },
1319
+ {
1320
+ key: "visit-rivera-filling",
1321
+ values: {
1322
+ summary: "Composite filling, lower left \u2014 M. Rivera",
1323
+ patient: "patient-rivera-marisol",
1324
+ appointment_at: "2026-09-18T08:30:00-07:00",
1325
+ duration_minutes: 60,
1326
+ procedure: "Composite filling",
1327
+ provider: "Dr. Alina Reyes",
1328
+ operatory: "Op A",
1329
+ visit_status: "Completed",
1330
+ fee: 386
1331
+ }
1332
+ },
1333
+ {
1334
+ key: "visit-halvorsen-new-patient",
1335
+ values: {
1336
+ summary: "New patient exam \u2014 B. Halvorsen",
1337
+ patient: "patient-halvorsen-britt",
1338
+ appointment_at: "2026-09-23T09:00:00-07:00",
1339
+ duration_minutes: 60,
1340
+ procedure: "New patient exam",
1341
+ provider: "Dr. Alina Reyes",
1342
+ operatory: "Op A",
1343
+ visit_status: "Confirmed",
1344
+ fee: 215
1345
+ }
1346
+ },
1347
+ {
1348
+ key: "visit-halvorsen-prophy",
1349
+ values: {
1350
+ summary: "Adult cleaning \u2014 B. Halvorsen",
1351
+ patient: "patient-halvorsen-britt",
1352
+ appointment_at: "2026-09-23T10:00:00-07:00",
1353
+ duration_minutes: 50,
1354
+ procedure: "Adult prophylaxis",
1355
+ provider: "Priya Chandra, RDH",
1356
+ operatory: "Op B",
1357
+ visit_status: "Confirmed",
1358
+ fee: 138
1359
+ }
1360
+ },
1361
+ {
1362
+ key: "visit-whitehorse-new-patient",
1363
+ values: {
1364
+ summary: "New patient exam \u2014 D. Whitehorse",
1365
+ patient: "patient-whitehorse-dara",
1366
+ appointment_at: "2026-09-24T11:00:00-07:00",
1367
+ duration_minutes: 60,
1368
+ procedure: "New patient exam",
1369
+ provider: "Dr. Alina Reyes",
1370
+ operatory: "Op A",
1371
+ visit_status: "Scheduled",
1372
+ fee: 215
1373
+ }
1374
+ },
1375
+ {
1376
+ key: "visit-whitehorse-prophy",
1377
+ values: {
1378
+ summary: "Adult cleaning \u2014 D. Whitehorse",
1379
+ patient: "patient-whitehorse-dara",
1380
+ appointment_at: "2026-09-24T12:00:00-07:00",
1381
+ duration_minutes: 50,
1382
+ procedure: "Adult prophylaxis",
1383
+ provider: "Tomas Lindgren, RDH",
1384
+ operatory: "Op C",
1385
+ visit_status: "Scheduled",
1386
+ fee: 138
1387
+ }
1388
+ },
1389
+ {
1390
+ key: "visit-oconnell-crown-seat",
1391
+ values: {
1392
+ summary: "Crown seat \u2014 B. O'Connell",
1393
+ patient: "patient-oconnell-brigid",
1394
+ appointment_at: "2026-10-06T14:30:00-07:00",
1395
+ duration_minutes: 60,
1396
+ procedure: "Crown seat",
1397
+ provider: "Dr. Marcus Webb",
1398
+ operatory: "Op D",
1399
+ visit_status: "Scheduled",
1400
+ fee: 1180
1401
+ }
1402
+ },
1403
+ {
1404
+ key: "visit-ferreira-new-patient",
1405
+ values: {
1406
+ summary: "New patient exam \u2014 J. Ferreira",
1407
+ patient: "patient-ferreira-joao",
1408
+ appointment_at: "2026-10-08T15:00:00-07:00",
1409
+ duration_minutes: 60,
1410
+ procedure: "New patient exam",
1411
+ provider: "Dr. Marcus Webb",
1412
+ operatory: "Op E",
1413
+ visit_status: "Scheduled",
1414
+ fee: 215
1415
+ }
1416
+ },
1417
+ {
1418
+ key: "visit-nakamura-new-patient",
1419
+ values: {
1420
+ summary: "New patient exam \u2014 K. Nakamura",
1421
+ patient: "patient-nakamura-kenji",
1422
+ appointment_at: "2026-10-09T08:30:00-07:00",
1423
+ duration_minutes: 60,
1424
+ procedure: "New patient exam",
1425
+ provider: "Dr. Marcus Webb",
1426
+ operatory: "Op D",
1427
+ visit_status: "Scheduled",
1428
+ fee: 215
1429
+ }
1430
+ },
1431
+ {
1432
+ key: "visit-tran-child-prophy",
1433
+ values: {
1434
+ summary: "Child cleaning \u2014 H. Tran, daughter",
1435
+ patient: "patient-tran-hieu",
1436
+ appointment_at: "2026-10-13T16:00:00-07:00",
1437
+ duration_minutes: 40,
1438
+ procedure: "Child prophylaxis",
1439
+ provider: "Priya Chandra, RDH",
1440
+ operatory: "Op B",
1441
+ visit_status: "Scheduled",
1442
+ fee: 112
1443
+ }
1444
+ },
1445
+ {
1446
+ key: "visit-kowalczyk-cancelled",
1447
+ values: {
1448
+ summary: "Night guard delivery \u2014 A. Kowalczyk",
1449
+ patient: "patient-kowalczyk-agata",
1450
+ appointment_at: "2026-09-19T11:30:00-07:00",
1451
+ duration_minutes: 30,
1452
+ procedure: "Adult prophylaxis",
1453
+ provider: "Tomas Lindgren, RDH",
1454
+ operatory: "Op C",
1455
+ visit_status: "Cancelled",
1456
+ fee: 0
1457
+ }
1458
+ }
1459
+ ]
1460
+ },
1461
+ {
1462
+ token: "insurance_claim",
1463
+ name: "Insurance claims",
1464
+ labelSingular: "Claim",
1465
+ labelPlural: "Claims",
1466
+ type: "ledger",
1467
+ display: "list",
1468
+ weight: "heavy",
1469
+ ordered: false,
1470
+ icon: "file-text",
1471
+ titleField: "claim_number",
1472
+ describes: "What was billed to a carrier for a completed visit, and what came back.",
1473
+ fields: [
1474
+ {
1475
+ key: "claim_number",
1476
+ label: "Claim number",
1477
+ parityType: "text",
1478
+ required: true,
1479
+ sensitivity: "internal",
1480
+ contextPolicy: "include"
1481
+ },
1482
+ {
1483
+ key: "visit",
1484
+ label: "Visit",
1485
+ parityType: "relation",
1486
+ required: true,
1487
+ relationTarget: "visit",
1488
+ relationMax: 1,
1489
+ sensitivity: "confidential",
1490
+ contextPolicy: "summarize"
1491
+ },
1492
+ {
1493
+ key: "carrier",
1494
+ label: "Carrier",
1495
+ parityType: "select",
1496
+ sensitivity: "internal",
1497
+ contextPolicy: "include",
1498
+ choices: ["Delta Dental", "Cigna Dental", "MetLife", "Guardian", "Aetna"]
1499
+ },
1500
+ {
1501
+ key: "submitted_on",
1502
+ label: "Submitted",
1503
+ parityType: "datetime",
1504
+ format: "date",
1505
+ sensitivity: "internal",
1506
+ contextPolicy: "include"
1507
+ },
1508
+ {
1509
+ key: "billed_amount",
1510
+ label: "Billed",
1511
+ parityType: "currency",
1512
+ unit: "USD",
1513
+ format: "currency",
1514
+ sensitivity: "confidential",
1515
+ contextPolicy: "summarize"
1516
+ },
1517
+ {
1518
+ key: "allowed_amount",
1519
+ label: "Carrier allowed",
1520
+ parityType: "currency",
1521
+ unit: "USD",
1522
+ format: "currency",
1523
+ sensitivity: "confidential",
1524
+ contextPolicy: "summarize"
1525
+ },
1526
+ {
1527
+ key: "patient_portion",
1528
+ label: "Patient portion",
1529
+ parityType: "currency",
1530
+ unit: "USD",
1531
+ format: "currency",
1532
+ sensitivity: "confidential",
1533
+ contextPolicy: "summarize"
1534
+ },
1535
+ {
1536
+ key: "claim_status",
1537
+ label: "Claim status",
1538
+ parityType: "select",
1539
+ required: true,
1540
+ sensitivity: "internal",
1541
+ contextPolicy: "include",
1542
+ choices: ["Prepared", "Submitted", "In review", "Paid", "Denied", "Appealed"]
1543
+ }
1544
+ ],
1545
+ rows: [
1546
+ {
1547
+ key: "claim-rivera-prophy",
1548
+ values: {
1549
+ claim_number: "HD-CLM-24118",
1550
+ visit: "visit-rivera-prophy-sep",
1551
+ carrier: "Delta Dental",
1552
+ submitted_on: "2026-09-09",
1553
+ billed_amount: 138,
1554
+ allowed_amount: 138,
1555
+ patient_portion: 0,
1556
+ claim_status: "Paid"
1557
+ }
1558
+ },
1559
+ {
1560
+ key: "claim-oyelaran-crown-prep",
1561
+ values: {
1562
+ claim_number: "HD-CLM-24123",
1563
+ visit: "visit-oyelaran-crown-prep",
1564
+ carrier: "Cigna Dental",
1565
+ submitted_on: "2026-09-11",
1566
+ billed_amount: 1240,
1567
+ allowed_amount: 1097.5,
1568
+ patient_portion: 142.5,
1569
+ claim_status: "Paid"
1570
+ }
1571
+ },
1572
+ {
1573
+ key: "claim-kowalczyk-prophy",
1574
+ values: {
1575
+ claim_number: "HD-CLM-24131",
1576
+ visit: "visit-kowalczyk-prophy",
1577
+ carrier: "Cigna Dental",
1578
+ submitted_on: "2026-09-16",
1579
+ billed_amount: 138,
1580
+ allowed_amount: 74,
1581
+ patient_portion: 64,
1582
+ claim_status: "Paid"
1583
+ }
1584
+ },
1585
+ {
1586
+ key: "claim-oconnell-filling",
1587
+ values: {
1588
+ claim_number: "HD-CLM-24136",
1589
+ visit: "visit-oconnell-filling",
1590
+ carrier: "Delta Dental",
1591
+ submitted_on: "2026-09-17",
1592
+ billed_amount: 412,
1593
+ allowed_amount: 310.75,
1594
+ patient_portion: 310.75,
1595
+ claim_status: "In review"
1596
+ }
1597
+ },
1598
+ {
1599
+ key: "claim-rivera-filling",
1600
+ values: {
1601
+ claim_number: "HD-CLM-24140",
1602
+ visit: "visit-rivera-filling",
1603
+ carrier: "Delta Dental",
1604
+ submitted_on: "2026-09-19",
1605
+ billed_amount: 386,
1606
+ allowed_amount: 386,
1607
+ patient_portion: 0,
1608
+ claim_status: "Submitted"
1609
+ }
1610
+ },
1611
+ {
1612
+ key: "claim-strand-crown-seat",
1613
+ values: {
1614
+ claim_number: "HD-CLM-24127",
1615
+ visit: "visit-strand-crown-seat",
1616
+ carrier: "Delta Dental",
1617
+ submitted_on: "2026-09-15",
1618
+ billed_amount: 880,
1619
+ allowed_amount: 0,
1620
+ patient_portion: 880,
1621
+ claim_status: "Denied"
1622
+ }
1623
+ },
1624
+ {
1625
+ key: "claim-oyelaran-resubmit",
1626
+ values: {
1627
+ claim_number: "HD-CLM-24144",
1628
+ visit: "visit-oyelaran-crown-prep",
1629
+ carrier: "Cigna Dental",
1630
+ submitted_on: "2026-09-20",
1631
+ billed_amount: 142.5,
1632
+ allowed_amount: 0,
1633
+ patient_portion: 142.5,
1634
+ claim_status: "Appealed"
1635
+ }
1636
+ },
1637
+ {
1638
+ key: "claim-oconnell-crown-predetermination",
1639
+ values: {
1640
+ claim_number: "HD-CLM-24149",
1641
+ visit: "visit-oconnell-crown-seat",
1642
+ carrier: "Delta Dental",
1643
+ submitted_on: "2026-09-21",
1644
+ billed_amount: 1180,
1645
+ allowed_amount: 0,
1646
+ patient_portion: 0,
1647
+ claim_status: "Prepared"
1648
+ }
1649
+ }
1650
+ ]
1651
+ }
1652
+ ],
1653
+ relationships: [
1654
+ {
1655
+ describes: "A visit belongs to the patient who sits in the chair, and it dies with their chart.",
1656
+ fromTable: "visit",
1657
+ fromField: "patient",
1658
+ toTable: "patient",
1659
+ flavor: "owned",
1660
+ cardinality: "one",
1661
+ onDelete: "cascade",
1662
+ inverseKey: "visits"
1663
+ },
1664
+ {
1665
+ describes: "A claim bills exactly one completed visit, and it cannot outlive the visit it bills.",
1666
+ fromTable: "insurance_claim",
1667
+ fromField: "visit",
1668
+ toTable: "visit",
1669
+ flavor: "owned",
1670
+ cardinality: "one",
1671
+ onDelete: "cascade",
1672
+ inverseKey: "claims"
1673
+ }
1674
+ ],
1675
+ form: {
1676
+ token: "harbor_dental_new_patient_intake",
1677
+ name: "Harbor Dental new-patient intake",
1678
+ describes: "The packet the front desk emails after the first call. The patient fills it in before they arrive so the hygienist is not taking a medical history in the chair.",
1679
+ audience: "public",
1680
+ table: "patient",
1681
+ fields: [
1682
+ { fieldKey: "full_name", label: "Your full legal name", required: true, help: "As it appears on your insurance card." },
1683
+ { fieldKey: "date_of_birth", label: "Date of birth", required: true },
1684
+ { fieldKey: "phone", label: "Mobile number", required: true, help: "We text appointment reminders to this number." },
1685
+ { fieldKey: "email", label: "Email", required: true },
1686
+ { fieldKey: "street", label: "Street address", required: true },
1687
+ { fieldKey: "city", label: "City", required: true },
1688
+ { fieldKey: "region", label: "State", required: true },
1689
+ { fieldKey: "postal_code", label: "ZIP", required: true },
1690
+ { fieldKey: "insurance_carrier", label: "Dental insurance carrier", help: "Choose Self-pay if you do not have dental coverage." },
1691
+ { fieldKey: "member_id", label: "Member ID", help: "On the front of your card. We use it to check your benefits before your visit." },
1692
+ { fieldKey: "allergies", label: "Any known allergies", help: "Select all that apply. We read this out before any injection." },
1693
+ { fieldKey: "referred_by", label: "How did you hear about us?" }
1694
+ ],
1695
+ submitLabel: "Send my intake form",
1696
+ confirmation: "Thank you \u2014 we have your forms. We will check your benefits with your carrier and call you within two business days to find a first appointment that works."
1697
+ },
1698
+ pipeline: {
1699
+ token: "harbor_dental_intake_pipeline",
1700
+ name: "New-patient intake",
1701
+ describes: "The six steps between a stranger calling and a chart the hygienist can work from. Renata works this board every morning.",
1702
+ table: "patient",
1703
+ stageField: "intake_stage",
1704
+ stages: [
1705
+ {
1706
+ key: "Enquiry",
1707
+ label: "Enquiry",
1708
+ describes: "They called or walked in. Nothing has been sent yet. Moves on when the intake packet goes out.",
1709
+ typicalDurationDays: 1
1710
+ },
1711
+ {
1712
+ key: "Forms sent",
1713
+ label: "Forms sent",
1714
+ describes: "The packet is with them. Chase at three days; most people fill it in the evening they get it.",
1715
+ typicalDurationDays: 3
1716
+ },
1717
+ {
1718
+ key: "Forms received",
1719
+ label: "Forms received",
1720
+ describes: "Medical history is in. Anything flagged goes to the treating dentist before we book.",
1721
+ typicalDurationDays: 1
1722
+ },
1723
+ {
1724
+ key: "Insurance verified",
1725
+ label: "Insurance verified",
1726
+ describes: "Eligibility and remaining annual maximum confirmed with the carrier, so nobody is surprised by a bill.",
1727
+ typicalDurationDays: 2
1728
+ },
1729
+ {
1730
+ key: "First visit booked",
1731
+ label: "First visit booked",
1732
+ describes: "Exam and cleaning are on the schedule and confirmed. Moves on the day they are seen.",
1733
+ typicalDurationDays: 14
1734
+ },
1735
+ {
1736
+ key: "Active patient",
1737
+ label: "Active patient",
1738
+ describes: "Seen at least once and on recall. They leave this stage only if they transfer out."
1739
+ }
1740
+ ]
1741
+ },
1742
+ dashboardQuestion: {
1743
+ question: "How many people have sent their forms back but still are not on the schedule?",
1744
+ whyItMatters: "Everyone sitting between 'Forms received' and 'First visit booked' is someone who already chose us and is quietly going cold. If the number climbs above about six, Renata blocks an hour to call them before the week gets away.",
1745
+ table: "patient",
1746
+ aggregate: "count",
1747
+ groupBy: "intake_stage",
1748
+ filter: "intake_stage is 'Forms received' or 'Insurance verified'",
1749
+ answerUnit: "patients"
1750
+ },
1751
+ documentTemplate: {
1752
+ token: "harbor_dental_welcome_letter",
1753
+ name: "New patient welcome letter",
1754
+ describes: "Goes out the moment a first visit is booked. It confirms who they will see, what to bring and what the first appointment actually involves, which is the single biggest reason people no-show.",
1755
+ table: "patient",
1756
+ title: "Welcome to {{practice_name}}, {{patient_name}}",
1757
+ body: [
1758
+ "Dear {{patient_name}},",
1759
+ "",
1760
+ "Thank you for choosing {{practice_name}}. Your chart is open under number {{chart_number}} and you will be seeing {{dentist}}.",
1761
+ "",
1762
+ "Your first appointment is a full examination with digital x-rays, followed by a cleaning with one of our hygienists. Together they take about two hours, so please allow for that.",
1763
+ "",
1764
+ "Please bring a photo ID and your {{carrier}} card. We have already checked your benefits, so we can tell you what is covered before any work is agreed \u2014 we do not start treatment you have not said yes to.",
1765
+ "",
1766
+ "If anything about your health has changed since you filled in your forms, tell the front desk when you arrive. It matters more than people expect.",
1767
+ "",
1768
+ "We look forward to meeting you.",
1769
+ "",
1770
+ "{{practice_name}}",
1771
+ "{{practice_phone}}",
1772
+ "{{today}}"
1773
+ ].join("\n"),
1774
+ mergeFields: [
1775
+ { key: "patient_name", from: "full_name", describes: "How we address them in the letter." },
1776
+ { key: "chart_number", from: "chart_number", describes: "So the letter and the chart agree if they call about it." },
1777
+ { key: "dentist", from: "primary_dentist", describes: "The dentist they are assigned to." },
1778
+ { key: "carrier", from: "insurance_carrier", describes: "Tells them which card to bring." },
1779
+ { key: "practice_name", from: "business", describes: "The practice name." },
1780
+ { key: "practice_phone", from: "business", describes: "The front desk number." },
1781
+ { key: "today", from: "today", describes: "The date the letter went out." }
1782
+ ]
1783
+ },
1784
+ notificationRule: {
1785
+ token: "harbor_dental_visit_reminder",
1786
+ name: "Day-before appointment reminder",
1787
+ describes: "A no-show costs the practice the whole chair hour and the hygienist's time. A text the day before is the cheapest thing that prevents it, and it is the first automation any practice turns on.",
1788
+ when: {
1789
+ table: "visit",
1790
+ event: "due",
1791
+ field: "appointment_at",
1792
+ daysBefore: 1
1793
+ },
1794
+ audience: "the patient in the chair",
1795
+ channel: "sms",
1796
+ message: "Harbor Dental: a reminder that you are booked with {{provider}} tomorrow at {{appointment_time}}. Reply C to confirm or call 360 555 0142 if you need to move it."
1797
+ },
1798
+ provenance: {
1799
+ kind: "synthesized",
1800
+ authoredBy: "lane REAL-DATA (records use-case library)",
1801
+ authoredOn: "2026-09-21",
1802
+ noRealPeople: true
1803
+ }
1804
+ };
1805
+ harbor_dental_new_patient_intake_default = useCase3;
1806
+ }
1807
+ });
1808
+
1809
+ // src/use-cases/ridgeline-physical-therapy-plan-of-care.ts
1810
+ var ridgeline_physical_therapy_plan_of_care_exports = {};
1811
+ __export(ridgeline_physical_therapy_plan_of_care_exports, {
1812
+ default: () => ridgeline_physical_therapy_plan_of_care_default,
1813
+ useCase: () => useCase4
1814
+ });
1815
+ var useCase4, ridgeline_physical_therapy_plan_of_care_default;
1816
+ var init_ridgeline_physical_therapy_plan_of_care = __esm({
1817
+ "src/use-cases/ridgeline-physical-therapy-plan-of-care.ts"() {
1818
+ "use strict";
1819
+ useCase4 = {
1820
+ id: "ridgeline-physical-therapy-plan-of-care",
1821
+ useCase: "Sasha Lindqvist runs Ridgeline Physical Therapy single-handed, and she uses this to carry a patient from a physician's referral through a course of visits to a treatment goal that is either met or honestly written off, without losing track of who is overdue for a re-evaluation.",
1822
+ industry: "Physical therapy",
1823
+ cleanupTag: "use-case:ridgeline-physical-therapy-plan-of-care",
1824
+ business: {
1825
+ name: "Ridgeline Physical Therapy",
1826
+ legalName: "Ridgeline Physical Therapy, LLC",
1827
+ describes: "A solo outpatient physical therapy practice doing orthopaedic and post-surgical rehabilitation.",
1828
+ address: {
1829
+ line1: "3114 Foothill Boulevard",
1830
+ line2: "Suite B",
1831
+ city: "Boise",
1832
+ region: "ID",
1833
+ postalCode: "83702",
1834
+ country: "US"
1835
+ },
1836
+ phone: "+1 208 555 0173",
1837
+ website: "https://ridgelinept.com",
1838
+ email: "frontdesk@ridgelinept.com",
1839
+ timezone: "America/Boise"
1840
+ },
1841
+ tables: [
1842
+ {
1843
+ token: "patients",
1844
+ name: "Patients",
1845
+ labelSingular: "Patient",
1846
+ labelPlural: "Patients",
1847
+ type: "entity",
1848
+ display: "list",
1849
+ weight: "heavy",
1850
+ ordered: false,
1851
+ icon: "user-round",
1852
+ titleField: "full_name",
1853
+ describes: "Everyone on the caseload, from referral through discharge.",
1854
+ fields: [
1855
+ { key: "full_name", label: "Patient name", parityType: "text", required: true, sensitivity: "confidential", contextPolicy: "summarize" },
1856
+ { key: "date_of_birth", label: "Date of birth", parityType: "datetime", format: "date", sensitivity: "confidential", contextPolicy: "exclude" },
1857
+ { key: "phone", label: "Phone", parityType: "phone", format: "phone", sensitivity: "confidential", contextPolicy: "exclude" },
1858
+ { key: "email", label: "Email", parityType: "email", format: "email", sensitivity: "confidential", contextPolicy: "exclude" },
1859
+ { key: "referring_physician", label: "Referring physician", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
1860
+ { key: "primary_diagnosis", label: "Primary diagnosis", parityType: "text", sensitivity: "confidential", contextPolicy: "summarize" },
1861
+ { key: "insurance_provider", label: "Insurance", parityType: "select", choices: ["Aetna", "Blue Cross Blue Shield", "UnitedHealthcare", "Cigna", "Medicare", "Self-pay"], sensitivity: "confidential", contextPolicy: "summarize" },
1862
+ { key: "intake_date", label: "Intake date", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
1863
+ { key: "status", label: "Status", parityType: "select", choices: ["Active", "Discharged", "On Hold", "Referred Out"], sensitivity: "internal", contextPolicy: "include" }
1864
+ ],
1865
+ rows: [
1866
+ { key: "patient-nora-castellan", values: { "full_name": "Nora Castellan", "date_of_birth": "1978-03-14", "phone": "555-201-3341", "email": "nora.castellan@northshoremail.com", "referring_physician": "Dr. Amina Reyes", "primary_diagnosis": "Rotator cuff tendinopathy, right shoulder", "insurance_provider": "Aetna", "intake_date": "2026-06-02", "status": "Active" } },
1867
+ { key: "patient-devon-ashworth", values: { "full_name": "Devon Ashworth", "date_of_birth": "1991-11-02", "phone": "555-201-3342", "email": "devon.ashworth@northshoremail.com", "referring_physician": "Dr. Marcus Feld", "primary_diagnosis": "ACL reconstruction, post-op (6 weeks)", "insurance_provider": "Blue Cross Blue Shield", "intake_date": "2026-06-05", "status": "Active" } },
1868
+ { key: "patient-priya-vantana", values: { "full_name": "Priya Vantana", "date_of_birth": "1965-07-22", "phone": "555-201-3343", "email": "priya.vantana@northshoremail.com", "referring_physician": "Dr. Amina Reyes", "primary_diagnosis": "Chronic low back pain, L4-L5 disc bulge", "insurance_provider": "Medicare", "intake_date": "2026-05-19", "status": "Active" } },
1869
+ { key: "patient-isaiah-broome", values: { "full_name": "Isaiah Broome", "date_of_birth": "2002-01-30", "phone": "555-201-3344", "email": "isaiah.broome@northshoremail.com", "referring_physician": "Dr. Lena Ostrowski", "primary_diagnosis": "Grade II ankle sprain, left", "insurance_provider": "UnitedHealthcare", "intake_date": "2026-07-01", "status": "Discharged" } },
1870
+ { key: "patient-marguerite-solano", values: { "full_name": "Marguerite Solano", "date_of_birth": "1954-09-08", "phone": "555-201-3345", "email": "marguerite.solano@northshoremail.com", "referring_physician": "Dr. Marcus Feld", "primary_diagnosis": "Total knee replacement, post-op (3 weeks)", "insurance_provider": "Medicare", "intake_date": "2026-06-20", "status": "Active" } },
1871
+ { key: "patient-tobias-reinholt", values: { "full_name": "Tobias Reinholt", "date_of_birth": "1988-04-17", "phone": "555-201-3346", "email": "tobias.reinholt@northshoremail.com", "referring_physician": "Dr. Lena Ostrowski", "primary_diagnosis": "Cervical radiculopathy, C6", "insurance_provider": "Cigna", "intake_date": "2026-07-10", "status": "Active" } },
1872
+ { key: "patient-odalys-fenmore", values: { "full_name": "Odalys Fenmore", "date_of_birth": "1997-12-25", "phone": "555-201-3347", "email": "odalys.fenmore@northshoremail.com", "referring_physician": "Dr. Amina Reyes", "primary_diagnosis": "Patellofemoral pain syndrome, bilateral", "insurance_provider": "Self-pay", "intake_date": "2026-07-15", "status": "On Hold" } },
1873
+ { key: "patient-wendell-okafor", values: { "full_name": "Wendell Okafor", "date_of_birth": "1972-02-11", "phone": "555-201-3348", "email": "wendell.okafor@northshoremail.com", "referring_physician": "Dr. Marcus Feld", "primary_diagnosis": "Plantar fasciitis, right heel", "insurance_provider": "Aetna", "intake_date": "2026-06-28", "status": "Active" } },
1874
+ { key: "patient-camille-duprez", values: { "full_name": "Camille Duprez", "date_of_birth": "1983-06-04", "phone": "555-201-3349", "email": "camille.duprez@northshoremail.com", "referring_physician": "Dr. Lena Ostrowski", "primary_diagnosis": "Lumbar spinal stenosis", "insurance_provider": "Blue Cross Blue Shield", "intake_date": "2026-05-30", "status": "Active" } },
1875
+ { key: "patient-griffin-alderete", values: { "full_name": "Griffin Alderete", "date_of_birth": "1995-10-19", "phone": "555-201-3350", "email": "griffin.alderete@northshoremail.com", "referring_physician": "Dr. Amina Reyes", "primary_diagnosis": "Achilles tendinopathy, left", "insurance_provider": "UnitedHealthcare", "intake_date": "2026-07-18", "status": "Referred Out" } }
1876
+ ]
1877
+ },
1878
+ {
1879
+ token: "appointments",
1880
+ name: "Visits",
1881
+ labelSingular: "Visit",
1882
+ labelPlural: "Visits",
1883
+ type: "entity",
1884
+ display: "calendar",
1885
+ weight: "heavy",
1886
+ ordered: true,
1887
+ icon: "calendar-clock",
1888
+ titleField: "visit_type",
1889
+ describes: "Every booked appointment, what kind of visit it is and whether it happened.",
1890
+ fields: [
1891
+ { key: "patient", label: "Patient", parityType: "relation", required: true, relationTarget: "patients", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
1892
+ { key: "appointment_date", label: "Date", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
1893
+ { key: "appointment_time", label: "Time", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
1894
+ { key: "visit_type", label: "Visit type", parityType: "select", required: true, choices: ["Initial Evaluation", "Follow-up", "Re-evaluation", "Discharge Visit"], sensitivity: "internal", contextPolicy: "include" },
1895
+ { key: "therapist_name", label: "Therapist", parityType: "text", sensitivity: "internal", contextPolicy: "include" },
1896
+ { key: "duration_minutes", label: "Length", parityType: "integer", unit: "min", sensitivity: "internal", contextPolicy: "include" },
1897
+ { key: "status", label: "Status", parityType: "select", choices: ["Scheduled", "Completed", "No-show", "Cancelled"], sensitivity: "internal", contextPolicy: "include" },
1898
+ { key: "notes", label: "Visit notes", parityType: "long_text", sensitivity: "confidential", contextPolicy: "on_request" }
1899
+ ],
1900
+ rows: [
1901
+ { key: "appointment-01", values: { "patient": "patient-nora-castellan", "appointment_date": "2026-06-02", "appointment_time": "09:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 60, "status": "Completed", "notes": "AROM shoulder flexion limited to 110 deg, pain 6/10 with overhead reach." } },
1902
+ { key: "appointment-02", values: { "patient": "patient-nora-castellan", "appointment_date": "2026-06-09", "appointment_time": "09:00", "visit_type": "Follow-up", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 45, "status": "Completed", "notes": "Progressing isometrics, pain down to 4/10." } },
1903
+ { key: "appointment-03", values: { "patient": "patient-nora-castellan", "appointment_date": "2026-06-16", "appointment_time": "09:00", "visit_type": "Follow-up", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 45, "status": "Scheduled", "notes": "" } },
1904
+ { key: "appointment-04", values: { "patient": "patient-devon-ashworth", "appointment_date": "2026-06-05", "appointment_time": "13:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Quad lag present, effusion mild. Cleared for closed-chain work." } },
1905
+ { key: "appointment-05", values: { "patient": "patient-devon-ashworth", "appointment_date": "2026-06-12", "appointment_time": "13:00", "visit_type": "Follow-up", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 45, "status": "Completed", "notes": "Quad lag resolving, full passive extension achieved." } },
1906
+ { key: "appointment-06", values: { "patient": "patient-devon-ashworth", "appointment_date": "2026-06-19", "appointment_time": "13:00", "visit_type": "Follow-up", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 45, "status": "No-show", "notes": "Patient did not call." } },
1907
+ { key: "appointment-07", values: { "patient": "patient-priya-vantana", "appointment_date": "2026-05-19", "appointment_time": "10:30", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Positive slump test on right, centralization with extension." } },
1908
+ { key: "appointment-08", values: { "patient": "patient-priya-vantana", "appointment_date": "2026-06-02", "appointment_time": "10:30", "visit_type": "Follow-up", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 45, "status": "Completed", "notes": "Symptoms centralizing, tolerating prone press-ups." } },
1909
+ { key: "appointment-09", values: { "patient": "patient-isaiah-broome", "appointment_date": "2026-07-01", "appointment_time": "15:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Grade II sprain, moderate swelling, weight-bearing as tolerated." } },
1910
+ { key: "appointment-10", values: { "patient": "patient-isaiah-broome", "appointment_date": "2026-07-22", "appointment_time": "15:00", "visit_type": "Discharge Visit", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 30, "status": "Completed", "notes": "Full ROM restored, single-leg balance 30s, discharged to home program." } },
1911
+ { key: "appointment-11", values: { "patient": "patient-marguerite-solano", "appointment_date": "2026-06-20", "appointment_time": "11:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Knee flexion 85 deg, using front-wheel walker." } },
1912
+ { key: "appointment-12", values: { "patient": "patient-marguerite-solano", "appointment_date": "2026-06-27", "appointment_time": "11:00", "visit_type": "Follow-up", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 45, "status": "Completed", "notes": "Flexion improved to 100 deg." } },
1913
+ { key: "appointment-13", values: { "patient": "patient-marguerite-solano", "appointment_date": "2026-07-04", "appointment_time": "11:00", "visit_type": "Follow-up", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 45, "status": "Scheduled", "notes": "" } },
1914
+ { key: "appointment-14", values: { "patient": "patient-tobias-reinholt", "appointment_date": "2026-07-10", "appointment_time": "08:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Positive Spurling's test right, numbness in C6 dermatome." } },
1915
+ { key: "appointment-15", values: { "patient": "patient-wendell-okafor", "appointment_date": "2026-06-28", "appointment_time": "16:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Sasha Lindqvist, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Point tenderness at medial calcaneal tubercle, worse first step in AM." } },
1916
+ { key: "appointment-16", values: { "patient": "patient-camille-duprez", "appointment_date": "2026-05-30", "appointment_time": "09:30", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Symptoms improve with flexion-biased posture, neurogenic claudication at 2 blocks." } },
1917
+ { key: "appointment-17", values: { "patient": "patient-griffin-alderete", "appointment_date": "2026-07-18", "appointment_time": "14:00", "visit_type": "Initial Evaluation", "therapist_name": "Dr. Marcus Reid, DPT", "duration_minutes": 60, "status": "Completed", "notes": "Referred to orthopedic surgeon for imaging before continuing PT." } }
1918
+ ]
1919
+ },
1920
+ {
1921
+ token: "treatment_plans",
1922
+ name: "Treatment plans",
1923
+ labelSingular: "Treatment plan",
1924
+ labelPlural: "Treatment plans",
1925
+ type: "entity",
1926
+ display: "board",
1927
+ weight: "heavy",
1928
+ ordered: false,
1929
+ icon: "clipboard-list",
1930
+ titleField: "diagnosis",
1931
+ describes: "The goal each patient is working toward, and whether they have met it.",
1932
+ fields: [
1933
+ { key: "patient", label: "Patient", parityType: "relation", required: true, relationTarget: "patients", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
1934
+ { key: "diagnosis", label: "Diagnosis", parityType: "text", required: true, sensitivity: "confidential", contextPolicy: "summarize" },
1935
+ { key: "goal", label: "Goal", parityType: "long_text", sensitivity: "confidential", contextPolicy: "summarize" },
1936
+ { key: "start_date", label: "Start date", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
1937
+ { key: "target_date", label: "Target date", parityType: "datetime", format: "date", sensitivity: "internal", contextPolicy: "include" },
1938
+ { key: "status", label: "Status", parityType: "select", choices: ["Active", "Met", "Modified", "Discontinued"], sensitivity: "internal", contextPolicy: "include" }
1939
+ ],
1940
+ rows: [
1941
+ { key: "plan-nora-castellan", values: { "patient": "patient-nora-castellan", "diagnosis": "Rotator cuff tendinopathy, right shoulder", "goal": "Restore pain-free overhead reach to 160 deg flexion within 6 weeks.", "start_date": "2026-06-02", "target_date": "2026-07-14", "status": "Active" } },
1942
+ { key: "plan-devon-ashworth", values: { "patient": "patient-devon-ashworth", "diagnosis": "ACL reconstruction, post-op", "goal": "Return to single-leg squat and stair descent without compensation by week 12.", "start_date": "2026-06-05", "target_date": "2026-08-28", "status": "Active" } },
1943
+ { key: "plan-priya-vantana", values: { "patient": "patient-priya-vantana", "diagnosis": "Chronic low back pain, L4-L5 disc bulge", "goal": "Centralize symptoms and tolerate 30-minute walk without radiating pain.", "start_date": "2026-05-19", "target_date": "2026-07-19", "status": "Active" } },
1944
+ { key: "plan-isaiah-broome", values: { "patient": "patient-isaiah-broome", "diagnosis": "Grade II ankle sprain, left", "goal": "Full weight-bearing, return to recreational basketball.", "start_date": "2026-07-01", "target_date": "2026-07-22", "status": "Met" } },
1945
+ { key: "plan-marguerite-solano", values: { "patient": "patient-marguerite-solano", "diagnosis": "Total knee replacement, post-op", "goal": "Achieve 110 deg flexion and walk without assistive device by week 8.", "start_date": "2026-06-20", "target_date": "2026-08-15", "status": "Active" } },
1946
+ { key: "plan-tobias-reinholt", values: { "patient": "patient-tobias-reinholt", "diagnosis": "Cervical radiculopathy, C6", "goal": "Resolve numbness, restore full cervical rotation.", "start_date": "2026-07-10", "target_date": "2026-08-21", "status": "Active" } },
1947
+ { key: "plan-wendell-okafor", values: { "patient": "patient-wendell-okafor", "diagnosis": "Plantar fasciitis, right heel", "goal": "Pain-free first step in the morning, return to daily 3-mile walk.", "start_date": "2026-06-28", "target_date": "2026-08-09", "status": "Active" } },
1948
+ { key: "plan-camille-duprez", values: { "patient": "patient-camille-duprez", "diagnosis": "Lumbar spinal stenosis", "goal": "Increase walking tolerance to 6 blocks without claudication.", "start_date": "2026-05-30", "target_date": "2026-08-01", "status": "Active" } }
1949
+ ]
1950
+ },
1951
+ {
1952
+ token: "exercises_prescribed",
1953
+ name: "Home exercises",
1954
+ labelSingular: "Home exercise",
1955
+ labelPlural: "Home exercises",
1956
+ type: "detail",
1957
+ display: "list",
1958
+ weight: "light",
1959
+ ordered: true,
1960
+ icon: "dumbbell",
1961
+ titleField: "exercise_name",
1962
+ describes: "What each patient was told to do between visits, and how it progresses.",
1963
+ fields: [
1964
+ { key: "treatment_plan", label: "Treatment plan", parityType: "relation", required: true, relationTarget: "treatment_plans", relationMax: 1, sensitivity: "internal", contextPolicy: "include" },
1965
+ { key: "exercise_name", label: "Exercise", parityType: "select", required: true, choices: ["Pendulum Exercise", "Scapular Retraction", "Straight Leg Raise", "Quadriceps Sets", "Heel Slides", "Terminal Knee Extension", "Prone Press-ups (McKenzie)", "Cat-Camel Stretch", "Clamshells", "Bridging", "Standing Hip Abduction", "Wall Sits", "Calf Raises", "Hamstring Curls", "Ankle Pumps", "Chin Tucks"], sensitivity: "internal", contextPolicy: "include" },
1966
+ { key: "sets", label: "Sets", parityType: "integer", sensitivity: "internal", contextPolicy: "include" },
1967
+ { key: "reps", label: "Reps", parityType: "integer", sensitivity: "internal", contextPolicy: "include" },
1968
+ { key: "frequency_per_week", label: "Times per week", parityType: "integer", sensitivity: "internal", contextPolicy: "include" },
1969
+ { key: "progression_notes", label: "Progression notes", parityType: "long_text", sensitivity: "confidential", contextPolicy: "on_request" }
1970
+ ],
1971
+ rows: [
1972
+ { key: "exercises_prescribed-01", values: { "treatment_plan": "plan-nora-castellan", "exercise_name": "Pendulum Exercise", "sets": 3, "reps": 15, "frequency_per_week": 7, "progression_notes": "Started week 1, pain-free arc, add light weight at week 3." } },
1973
+ { key: "exercises_prescribed-02", values: { "treatment_plan": "plan-nora-castellan", "exercise_name": "Scapular Retraction", "sets": 3, "reps": 12, "frequency_per_week": 5, "progression_notes": "Using resistance band, increase band tension at week 4." } },
1974
+ { key: "exercises_prescribed-03", values: { "treatment_plan": "plan-devon-ashworth", "exercise_name": "Quadriceps Sets", "sets": 3, "reps": 10, "frequency_per_week": 7, "progression_notes": "Hold 5 seconds, progressing to straight leg raise next visit." } },
1975
+ { key: "exercises_prescribed-04", values: { "treatment_plan": "plan-devon-ashworth", "exercise_name": "Straight Leg Raise", "sets": 3, "reps": 10, "frequency_per_week": 6, "progression_notes": "No lag observed, add ankle weight 2lb." } },
1976
+ { key: "exercises_prescribed-05", values: { "treatment_plan": "plan-devon-ashworth", "exercise_name": "Terminal Knee Extension", "sets": 3, "reps": 12, "frequency_per_week": 5, "progression_notes": "Using resistance band anchored behind knee." } },
1977
+ { key: "exercises_prescribed-06", values: { "treatment_plan": "plan-priya-vantana", "exercise_name": "Prone Press-ups (McKenzie)", "sets": 2, "reps": 10, "frequency_per_week": 6, "progression_notes": "Symptoms centralizing, hold end range 2 seconds." } },
1978
+ { key: "exercises_prescribed-07", values: { "treatment_plan": "plan-priya-vantana", "exercise_name": "Cat-Camel Stretch", "sets": 2, "reps": 10, "frequency_per_week": 7, "progression_notes": "Morning stiffness reduced." } },
1979
+ { key: "exercises_prescribed-08", values: { "treatment_plan": "plan-isaiah-broome", "exercise_name": "Ankle Pumps", "sets": 3, "reps": 20, "frequency_per_week": 7, "progression_notes": "Early edema control, progressed to standing balance week 2." } },
1980
+ { key: "exercises_prescribed-09", values: { "treatment_plan": "plan-marguerite-solano", "exercise_name": "Heel Slides", "sets": 3, "reps": 10, "frequency_per_week": 7, "progression_notes": "Flexion improving 5 deg/week." } },
1981
+ { key: "exercises_prescribed-10", values: { "treatment_plan": "plan-marguerite-solano", "exercise_name": "Quadriceps Sets", "sets": 4, "reps": 10, "frequency_per_week": 7, "progression_notes": "Hold 5 seconds, no extensor lag." } },
1982
+ { key: "exercises_prescribed-11", values: { "treatment_plan": "plan-tobias-reinholt", "exercise_name": "Chin Tucks", "sets": 3, "reps": 10, "frequency_per_week": 6, "progression_notes": "Reduces referred symptoms into arm during session." } },
1983
+ { key: "exercises_prescribed-12", values: { "treatment_plan": "plan-wendell-okafor", "exercise_name": "Calf Raises", "sets": 3, "reps": 15, "frequency_per_week": 5, "progression_notes": "Eccentric emphasis, 3-second lower." } },
1984
+ { key: "exercises_prescribed-13", values: { "treatment_plan": "plan-camille-duprez", "exercise_name": "Bridging", "sets": 3, "reps": 12, "frequency_per_week": 5, "progression_notes": "Flexion-biased, tolerating well." } },
1985
+ { key: "exercises_prescribed-14", values: { "treatment_plan": "plan-camille-duprez", "exercise_name": "Standing Hip Abduction", "sets": 3, "reps": 12, "frequency_per_week": 5, "progression_notes": "Added ankle weight this week." } }
1986
+ ]
1987
+ }
1988
+ ],
1989
+ relationships: [
1990
+ {
1991
+ describes: "A visit belongs to the patient who attends it, and it dies with their chart.",
1992
+ fromTable: "appointments",
1993
+ fromField: "patient",
1994
+ toTable: "patients",
1995
+ flavor: "owned",
1996
+ cardinality: "one",
1997
+ onDelete: "cascade",
1998
+ inverseKey: "visits"
1999
+ },
2000
+ {
2001
+ describes: "A treatment plan belongs to the patient it sets a goal for.",
2002
+ fromTable: "treatment_plans",
2003
+ fromField: "patient",
2004
+ toTable: "patients",
2005
+ flavor: "owned",
2006
+ cardinality: "one",
2007
+ onDelete: "cascade",
2008
+ inverseKey: "treatment_plans"
2009
+ },
2010
+ {
2011
+ describes: "A home exercise is prescribed under exactly one treatment plan and ends with it.",
2012
+ fromTable: "exercises_prescribed",
2013
+ fromField: "treatment_plan",
2014
+ toTable: "treatment_plans",
2015
+ flavor: "owned",
2016
+ cardinality: "one",
2017
+ onDelete: "cascade",
2018
+ inverseKey: "exercises"
2019
+ }
2020
+ ],
2021
+ form: {
2022
+ token: "ridgeline_new_patient_referral",
2023
+ name: "Ridgeline new-patient referral intake",
2024
+ describes: "What a referred patient fills in before the initial evaluation, so the first visit is spent examining them rather than typing.",
2025
+ audience: "public",
2026
+ table: "patients",
2027
+ fields: [
2028
+ { fieldKey: "full_name", label: "Your full name", required: true },
2029
+ { fieldKey: "date_of_birth", label: "Date of birth", required: true },
2030
+ { fieldKey: "phone", label: "Best number to reach you", required: true },
2031
+ { fieldKey: "email", label: "Email", required: true },
2032
+ { fieldKey: "referring_physician", label: "Who referred you?", required: true, help: "The doctor's name on your referral, so we can send them your plan of care." },
2033
+ { fieldKey: "primary_diagnosis", label: "What are you coming in for?", help: "In your own words is fine \u2014 we will write the clinical version." },
2034
+ { fieldKey: "insurance_provider", label: "Insurance", help: "Choose Self-pay if you would rather not bill insurance." }
2035
+ ],
2036
+ submitLabel: "Send my referral details",
2037
+ confirmation: "Thank you \u2014 we have your details. We will verify your benefits and call you within one business day to book your initial evaluation, which takes about an hour."
2038
+ },
2039
+ pipeline: {
2040
+ token: "ridgeline_plan_of_care",
2041
+ name: "Plan of care",
2042
+ describes: "What happens to a treatment goal once it is written. Sasha reviews this board every Friday, because a plan that sits in Active past its target date is the one that quietly fails.",
2043
+ table: "treatment_plans",
2044
+ stageField: "status",
2045
+ stages: [
2046
+ { key: "Active", label: "Active", describes: "The patient is working toward the goal and attending visits. Leaves when the goal is met, changed or abandoned.", typicalDurationDays: 42 },
2047
+ { key: "Modified", label: "Modified", describes: "Re-evaluation changed the goal or the timeline \u2014 progress was faster or slower than predicted.", typicalDurationDays: 21 },
2048
+ { key: "Met", label: "Met", describes: "The measured goal was reached. This is what discharge is built on and what the referring physician is told." },
2049
+ { key: "Discontinued", label: "Discontinued", describes: "Stopped without meeting the goal \u2014 the patient withdrew, was referred out, or their insurance ran out. Written down honestly rather than left Active." }
2050
+ ]
2051
+ },
2052
+ dashboardQuestion: {
2053
+ question: "How many plans are still Active past the date I said they would be finished?",
2054
+ whyItMatters: "A plan sitting Active past its target date means a patient is being treated without anyone re-checking whether the treatment is working. Every one of those needs a re-evaluation booked this week, or a decision to change the goal.",
2055
+ table: "treatment_plans",
2056
+ aggregate: "count",
2057
+ groupBy: "status",
2058
+ filter: "status is 'Active' and target_date is before today",
2059
+ answerUnit: "treatment plans"
2060
+ },
2061
+ documentTemplate: {
2062
+ token: "ridgeline_plan_of_care_letter",
2063
+ name: "Plan of care letter to the referring physician",
2064
+ describes: "Goes back to the doctor who made the referral, at the start of care and again at discharge. Physicians refer again when they hear what happened to the last patient they sent, which makes this letter the practice's whole marketing plan.",
2065
+ table: "treatment_plans",
2066
+ title: "Plan of care \u2014 {{diagnosis}}",
2067
+ body: [
2068
+ "Thank you for the referral.",
2069
+ "",
2070
+ "Following evaluation I have started a course of treatment for {{diagnosis}}.",
2071
+ "",
2072
+ "Goal: {{goal}}",
2073
+ "",
2074
+ "Care began on {{start_date}} and I expect to reach that goal by {{target_date}}. The plan is currently {{status}}. The patient is on a home exercise programme between visits, and I re-evaluate objectively rather than by report.",
2075
+ "",
2076
+ "I will write again at discharge with the measured outcome. If anything in the history changes at your end, please tell me \u2014 it usually changes the plan.",
2077
+ "",
2078
+ "{{practice_name}}",
2079
+ "{{practice_phone}}",
2080
+ "{{today}}"
2081
+ ].join("\n"),
2082
+ mergeFields: [
2083
+ { key: "diagnosis", from: "diagnosis", describes: "What is being treated." },
2084
+ { key: "goal", from: "goal", describes: "The measurable goal, in the words the physician will be told at discharge." },
2085
+ { key: "start_date", from: "start_date", describes: "When care began." },
2086
+ { key: "target_date", from: "target_date", describes: "When the goal is expected to be met." },
2087
+ { key: "status", from: "status", describes: "Where the plan stands today." },
2088
+ { key: "practice_name", from: "business", describes: "The practice name." },
2089
+ { key: "practice_phone", from: "business", describes: "The number the physician's office calls back on." },
2090
+ { key: "today", from: "today", describes: "The date the letter went out." }
2091
+ ]
2092
+ },
2093
+ notificationRule: {
2094
+ token: "ridgeline_plan_past_target",
2095
+ name: "A plan has passed its target date",
2096
+ describes: "The one automation a solo therapist genuinely needs: nobody else is watching the caseload, so a goal that quietly slips past its date has to come and find her.",
2097
+ when: {
2098
+ table: "treatment_plans",
2099
+ event: "overdue",
2100
+ field: "target_date"
2101
+ },
2102
+ audience: "the treating therapist",
2103
+ channel: "in_app",
2104
+ message: "{{diagnosis}} has passed its target date of {{target_date}} and is still {{status}}. Book a re-evaluation or change the goal."
2105
+ },
2106
+ provenance: {
2107
+ kind: "synthesized",
2108
+ authoredBy: "data crew (dataset) + lane REAL-DATA (business identity, relationships, form, pipeline, dashboard question, document, notification rule)",
2109
+ authoredOn: "2026-09-21",
2110
+ noRealPeople: true
2111
+ }
2112
+ };
2113
+ ridgeline_physical_therapy_plan_of_care_default = useCase4;
2114
+ }
2115
+ });
2116
+
2117
+ // src/use-cases/index.ts
2118
+ var use_cases_exports = {};
2119
+ __export(use_cases_exports, {
2120
+ ALLOW_PRAGMA: () => ALLOW_PRAGMA,
2121
+ MIN_ROWS_IN_PRIMARY_TABLE: () => MIN_ROWS_IN_PRIMARY_TABLE,
2122
+ PLACEHOLDER_PATTERNS: () => PLACEHOLDER_PATTERNS,
2123
+ RESOLVABLE_FIELD_TYPE_WORDS: () => RESOLVABLE_FIELD_TYPE_WORDS,
2124
+ USE_CASE_LOADERS: () => USE_CASE_LOADERS,
2125
+ assertUseCase: () => assertUseCase,
2126
+ describePlaceholder: () => describePlaceholder,
2127
+ findPlaceholders: () => findPlaceholders,
2128
+ listUseCases: () => listUseCases,
2129
+ loadAllUseCases: () => loadAllUseCases,
2130
+ loadUseCase: () => loadUseCase,
2131
+ summarize: () => summarize,
2132
+ useCaseFor: () => useCaseFor,
2133
+ useCaseIds: () => useCaseIds,
2134
+ validateUseCase: () => validateUseCase
2135
+ });
2136
+ module.exports = __toCommonJS(use_cases_exports);
2137
+
2138
+ // src/use-cases/placeholders.ts
2139
+ var ALLOW_PRAGMA = /matrx-real-data:allow\s+\S+/i;
2140
+ var PLACEHOLDER_PATTERNS = [
2141
+ {
2142
+ id: "acme",
2143
+ test: /\bacme\b/gi,
2144
+ why: "'Acme' is the cartoon of a company and tells the owner nothing about whether the product works.",
2145
+ instead: "Name the real business the use case is about \u2014 'Harbor Dental Group', 'Blue Ridge Recycling'.",
2146
+ // RFC 8555. "ACME account", "ACME challenge", "Let's Encrypt" — a real
2147
+ // protocol that happens to share the cartoon's name.
2148
+ unlessLineMatches: /let'?s encrypt|rfc\s*8555|certificate|\bacme\s+(?:account|protocol|challenge|directory|client|server|ca)\b|\b(?:tls|ssl|dns-01|http-01)\b/i
2149
+ },
2150
+ {
2151
+ id: "metasyntactic",
2152
+ test: /\bfoo\b|["'`](?:bar|baz|qux|quux)["'`]|\bfoobar\b/gi,
2153
+ // A BAR CHART is not a metasyntactic variable. `kind: "bar"` is the chart
2154
+ // type this platform draws most often.
2155
+ unlessLineMatches: /\bkind\b|\bchart\b|\bseries\b|\baxis\b|\bstacked\b|\bhistogram\b/i,
2156
+ why: "foo / bar / baz are stand-ins for data nobody thought about.",
2157
+ instead: "Use the practitioner's own word for the thing \u2014 a field is 'po_number', not 'foo'."
2158
+ },
2159
+ {
2160
+ id: "lorem-ipsum",
2161
+ test: /\blorem\b|\bipsum\b|\bdolor sit amet\b/gi,
2162
+ why: "Lorem ipsum is a picture of text, not text. A screenshot of it proves nothing.",
2163
+ instead: "Write the sentence this business would really send."
2164
+ },
2165
+ {
2166
+ id: "zzz-prefix",
2167
+ test: /\bzzz+[\s_-]/gi,
2168
+ why: "A 'ZZZ \u2026' name exists to sort test rows to the bottom and to be deleted; it makes every screen look broken.",
2169
+ instead: "Give it the use case's own name and find it for cleanup by the organization's cleanup tag."
2170
+ },
2171
+ {
2172
+ id: "test-table",
2173
+ test: /\btest[\s_-]table\b|\bmy[\s_-]table\b|\bsample[\s_-]table\b/gi,
2174
+ why: "'Test table' is not a thing any business has.",
2175
+ instead: "Name the table the way the practitioner says it: 'Work orders', 'Patients', 'Pickup routes'."
2176
+ },
2177
+ {
2178
+ id: "test-organization",
2179
+ test: /\btest[\s_-]org(?:anization|anisation)?\b|\bdemo[\s_-]co(?:mpany|rp)?\b|\bsample[\s_-]co(?:mpany|rp)?\b/gi,
2180
+ why: "A test organization with a test name makes every screenshot worthless.",
2181
+ instead: "Give the organization the use case's business name; tag it in settings for cleanup."
2182
+ },
2183
+ {
2184
+ id: "sequential-names",
2185
+ test: /\b(?:job|item|row|record|table|field|form|task|order|client|customer|product|user|thing|entry|note|project)[\s_-]0*\d{1,4}\b/gi,
2186
+ why: "'Job 001', 'Item 3', 'Client 12' are counters wearing a name. Nothing in a real business is called that.",
2187
+ instead: "Use the real identifier the business uses \u2014 an invoice number, a chart number, a route name."
2188
+ },
2189
+ {
2190
+ id: "example-domain",
2191
+ test: /@example\.(?:com|org|net)\b|\b[a-z0-9-]+\.example\b|\bexample\.(?:com|org|net)\b/gi,
2192
+ why: "A '.example' address reads as fake on the screen the owner is looking at.",
2193
+ instead: "Synthesize a plausible domain for the business \u2014 'frontdesk@harbordentalgroup.com'. Invented, never a real company's."
2194
+ },
2195
+ {
2196
+ id: "doe-names",
2197
+ test: /\bjohn doe\b|\bjane doe\b|\bdana whitfield\b|\btest user\b|\btest patient\b|\btest client\b/gi,
2198
+ why: "A repeated stand-in person is the tell that nobody modelled the domain.",
2199
+ instead: "Synthesize varied, plausible people who belong to nobody \u2014 and never copy a real person."
2200
+ },
2201
+ {
2202
+ // THE CHAIR'S RULING, 2026-09-21. People in a template are fictional but
2203
+ // REALISTIC, and a realistic person's mailbox is a mailbox — not a mailbox
2204
+ // with a counter welded to the end of it. `linda.reyes0@…`,
2205
+ // `wei.singh.m34@…` are the tell that a loop wrote the row, and the owner
2206
+ // reads a whole column of them as one fake person repeated.
2207
+ //
2208
+ // NARROW ON PURPOSE: it fires only when the local part is name-shaped
2209
+ // (two or more words joined by . _ or -) AND ends in a bare counter. That
2210
+ // is exactly the generator's signature and leaves the real mailboxes a
2211
+ // business has — `support365@`, `ap2@`, `frontdesk@` — alone.
2212
+ id: "suffixed-mailbox",
2213
+ test: /\b[a-z]+(?:[._-][a-z]+)+\d{1,3}@/gi,
2214
+ why: "A mailbox whose name ends in a counter (linda.reyes0@, wei.singh.m34@) is a generated row wearing a person's name.",
2215
+ instead: "Give each person their own mailbox on the fictional business's own domain \u2014 'linda.reyes@handsandhopealliance.org' \u2014 and vary the people instead of numbering one."
2216
+ },
2217
+ {
2218
+ // THE CHAIR'S RULING, 2026-09-21. "Test fixture" is a CLASSIFICATION, and a
2219
+ // classification lives in the organization's kind/metadata — the
2220
+ // `test_fixture` flag in its settings, which is how cleanup finds it. It is
2221
+ // never part of a NAME, because a name is what the owner reads on the
2222
+ // screen and "Birchwood Ave Renovation (test fixture)" tells him the
2223
+ // product is a rehearsal.
2224
+ //
2225
+ // The settings KEY is the right place, so a line that is writing the key is
2226
+ // exempt; a line that is writing the words into text is not.
2227
+ id: "fixture-in-name",
2228
+ test: /\(\s*test\s+fixture\s*\)|\btest[\s_-]fixture\b|\bfixture[\s_-]org(?:anization|anisation)?\b/gi,
2229
+ unlessLineMatches: /["'`]?test_fixture["'`]?\s*[:=]|\bp_settings\b|\bcleanupTag\b|\bsettings\.[a-z_]*fixture\b/i,
2230
+ why: "'Test fixture' is a classification, not a name. In a name it tells the owner the screen he is looking at is a rehearsal.",
2231
+ instead: "Name the organization after the business the use case is about, and carry the classification in its settings (`test_fixture: true`), which is also how cleanup finds it."
2232
+ },
2233
+ {
2234
+ id: "keyboard-mash",
2235
+ test: /\basdf+\b|\bqwerty\b|\bxxx+\b|\btodo name\b|\bfixme\b|\btbd\b/gi,
2236
+ why: "Keyboard mash and TODO markers are unfinished data shipped as if it were finished.",
2237
+ instead: "Write the value the business would really have there."
2238
+ }
2239
+ ];
2240
+ function findPlaceholders(text) {
2241
+ const hits = [];
2242
+ const lines = text.split("\n");
2243
+ for (const line of lines) {
2244
+ if (ALLOW_PRAGMA.test(line)) continue;
2245
+ for (const pattern of PLACEHOLDER_PATTERNS) {
2246
+ if (pattern.unlessLineMatches?.test(line)) continue;
2247
+ pattern.test.lastIndex = 0;
2248
+ for (const match of line.matchAll(pattern.test)) {
2249
+ hits.push({
2250
+ patternId: pattern.id,
2251
+ match: match[0],
2252
+ why: pattern.why,
2253
+ instead: pattern.instead
2254
+ });
2255
+ }
2256
+ }
2257
+ }
2258
+ return hits;
2259
+ }
2260
+ function describePlaceholder(hit) {
2261
+ return `placeholder data "${hit.match}" (${hit.patternId}): ${hit.why} ${hit.instead}`;
2262
+ }
2263
+
2264
+ // src/store.generated.ts
2265
+ var STORAGE_MODES = ["light", "heavy"];
2266
+ var PARITY_FIELD_TYPES = [
2267
+ { parity_type: "attachment", behavior: "relation", made_of: "REC-31: a File record reached through a relation, target the kernel `File` Table" },
2268
+ { parity_type: "checkbox", behavior: "boolean", made_of: "a ticked or unticked answer, stored as a REAL boolean \u2014 and a record that never answered holds neither, so filters and group-by see three states, not two" },
2269
+ { parity_type: "currency", behavior: "range", made_of: "FLD-N-1: a number whose UNIT is the currency and whose format is currency \u2014 on the Field, never in presentation" },
2270
+ { parity_type: "datetime", behavior: "range", made_of: "a range of kind date or datetime \u2014 the dated modifier says the VALUES are dated, which is a different question" },
2271
+ { parity_type: "email", behavior: "text", made_of: "text whose format is email" },
2272
+ { parity_type: "formula", behavior: "formula", made_of: "FLD-9: a declared computation, on read or on write, evaluated by custom.rule_eval" },
2273
+ { parity_type: "lookup", behavior: "formula", made_of: "a Value read THROUGH a relation \u2014 config.via names the relation field, config.pick the Value on the far side" },
2274
+ { parity_type: "member", behavior: "relation", made_of: "a person: a relation whose target is the kernel `Person` Table" },
2275
+ { parity_type: "multi_select", behavior: "list", made_of: "many choices from the same Table \u2014 the multi modifier, never a second behaviour" },
2276
+ { parity_type: "percent", behavior: "range", made_of: "a number whose unit is % and whose format is percent" },
2277
+ { parity_type: "phone", behavior: "text", made_of: "text whose format is phone" },
2278
+ { parity_type: "rollup", behavior: "formula", made_of: "an aggregate ALONG a relation \u2014 config.via, config.of, config.agg; far-side ids are taken DISTINCT, so nothing is counted twice" },
2279
+ { parity_type: "select", behavior: "list", made_of: "one choice from a Table shown as a list (FLD-5/FLD-6)" },
2280
+ { parity_type: "url", behavior: "text", made_of: "text whose format is url, with the pattern Rule that makes the format enforceable" }
2281
+ ];
2282
+
2283
+ // src/vocabulary.ts
2284
+ var TABLE_TYPES = [
2285
+ "entity",
2286
+ "detail",
2287
+ "reference",
2288
+ "ledger",
2289
+ "restricted",
2290
+ "system",
2291
+ "deprecated"
2292
+ ];
2293
+ var RELATION_FLAVORS = ["owned", "referenced"];
2294
+ var ON_DELETE = ["cascade", "set_null", "restrict"];
2295
+ var RELATION_CARDINALITIES = ["one", "many"];
2296
+ var FIELD_SENSITIVITIES = ["public", "internal", "confidential", "restricted"];
2297
+ var CONTEXT_POLICIES = ["include", "summarize", "exclude", "on_request"];
2298
+ var TABLE_DISPLAYS = ["list", "grid", "board", "calendar", "timeline"];
2299
+
2300
+ // src/use-cases/validate.ts
2301
+ var PLAIN_TYPE_WORDS = [
2302
+ "number",
2303
+ "integer",
2304
+ "decimal",
2305
+ "float",
2306
+ "long_text",
2307
+ "string"
2308
+ ];
2309
+ var RESOLVABLE_FIELD_TYPE_WORDS = [
2310
+ .../* @__PURE__ */ new Set([
2311
+ ...PARITY_FIELD_TYPES.map((t) => t.parity_type),
2312
+ ...PARITY_FIELD_TYPES.map((t) => t.behavior),
2313
+ ...PLAIN_TYPE_WORDS
2314
+ ])
2315
+ ];
2316
+ var CHOICE_TYPES = /* @__PURE__ */ new Set(["select", "multi_select", "list"]);
2317
+ var RELATION_TYPES = /* @__PURE__ */ new Set(["relation", "attachment", "member"]);
2318
+ var MIN_ROWS_IN_PRIMARY_TABLE = 5;
2319
+ function fieldsByKey(table) {
2320
+ return new Map(table.fields.map((f) => [f.key, f]));
2321
+ }
2322
+ var FIXTURE_IN_NAME = /\(\s*test\s+fixture\s*\)|\btest[\s_-]fixture\b|^\s*fixtures?\b/i;
2323
+ function nameFields(useCase5) {
2324
+ const out = [
2325
+ ["business.name", useCase5.business?.name],
2326
+ ["business.legalName", useCase5.business?.legalName],
2327
+ ["form.name", useCase5.form?.name],
2328
+ ["pipeline.name", useCase5.pipeline?.name]
2329
+ ];
2330
+ useCase5.tables?.forEach((table, ti) => {
2331
+ out.push([`tables[${ti}].name`, table.name]);
2332
+ out.push([`tables[${ti}].labelSingular`, table.labelSingular]);
2333
+ out.push([`tables[${ti}].labelPlural`, table.labelPlural]);
2334
+ });
2335
+ return out;
2336
+ }
2337
+ function validateUseCase(useCase5) {
2338
+ const problems = [];
2339
+ const say = (at, says) => problems.push({ at, says });
2340
+ const sentence = (useCase5.useCase ?? "").trim();
2341
+ if (sentence.length < 40) {
2342
+ say(
2343
+ "useCase",
2344
+ `The use case needs ONE full sentence at the top saying who the user is, what business they run, and what job this feature does for them. This one is ${sentence.length} characters, which is not that sentence.`
2345
+ );
2346
+ }
2347
+ if (sentence && !/[.!?]$/.test(sentence)) {
2348
+ say("useCase", "The use-case sentence should read as a sentence and end in a full stop.");
2349
+ }
2350
+ for (const hit of findPlaceholders(JSON.stringify(useCase5, null, 1))) {
2351
+ say(useCase5.id, describePlaceholder(hit));
2352
+ }
2353
+ for (const [at, value] of nameFields(useCase5)) {
2354
+ if (!value) continue;
2355
+ if (FIXTURE_IN_NAME.test(value)) {
2356
+ say(
2357
+ at,
2358
+ `"${value}" carries the test-fixture classification in a name. Name it after the business this use case is about, and carry the classification where it belongs \u2014 \`test_fixture: true\` in the organization's settings, which is also how cleanup finds it (never by a junk name).`
2359
+ );
2360
+ }
2361
+ }
2362
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(useCase5.id ?? "")) {
2363
+ say("id", `The id "${useCase5.id}" should be kebab-case, like "harbor-dental-new-patient-intake".`);
2364
+ }
2365
+ if (!useCase5.cleanupTag) {
2366
+ say(
2367
+ "cleanupTag",
2368
+ "Every use case needs a cleanup tag: it is written into the test organization's settings so cleanup finds it by tag and never by a junk name."
2369
+ );
2370
+ }
2371
+ if (useCase5.provenance?.noRealPeople !== true) {
2372
+ say(
2373
+ "provenance.noRealPeople",
2374
+ "State in the data that every person, address, phone and mailbox here is invented and belongs to nobody."
2375
+ );
2376
+ }
2377
+ if (useCase5.provenance?.kind === "public-data" && !useCase5.provenance.source) {
2378
+ say("provenance.source", "A use case drawn from public data has to name the dataset it came from.");
2379
+ }
2380
+ if (!useCase5.tables?.length) {
2381
+ say("tables", "A use case needs at least one table \u2014 the business's own records.");
2382
+ return { ok: false, problems };
2383
+ }
2384
+ const tablesByToken = new Map(useCase5.tables.map((t) => [t.token, t]));
2385
+ if (tablesByToken.size !== useCase5.tables.length) {
2386
+ say("tables", "Two tables share a token. Each table's token is its address and has to be unique.");
2387
+ }
2388
+ useCase5.tables.forEach((table, ti) => {
2389
+ const at = `tables[${ti}] (${table.token})`;
2390
+ if (!/^[a-z][a-z0-9_]*$/.test(table.token ?? "")) {
2391
+ say(`${at}.token`, `"${table.token}" should be snake_case and start with a letter.`);
2392
+ }
2393
+ if (!TABLE_TYPES.includes(table.type)) {
2394
+ say(`${at}.type`, `"${table.type}" is not one of the seven table types: ${TABLE_TYPES.join(", ")}.`);
2395
+ }
2396
+ if (!TABLE_DISPLAYS.includes(table.display)) {
2397
+ say(`${at}.display`, `"${table.display}" is not one of: ${TABLE_DISPLAYS.join(", ")}.`);
2398
+ }
2399
+ if (!STORAGE_MODES.includes(table.weight)) {
2400
+ say(`${at}.weight`, `"${table.weight}" is not one of: ${STORAGE_MODES.join(", ")}.`);
2401
+ }
2402
+ const keyed = fieldsByKey(table);
2403
+ if (keyed.size !== table.fields.length) {
2404
+ say(`${at}.fields`, "Two fields share a key. A field key is unique within its table.");
2405
+ }
2406
+ if (!keyed.has(table.titleField)) {
2407
+ say(
2408
+ `${at}.titleField`,
2409
+ `The title field "${table.titleField}" is not one of this table's fields \u2014 without it a record cannot be a chip.`
2410
+ );
2411
+ }
2412
+ table.fields.forEach((field, fi) => {
2413
+ const fat = `${at}.fields[${fi}] (${field.key})`;
2414
+ if (!/^[a-z][a-z0-9_]*$/.test(field.key ?? "")) {
2415
+ say(`${fat}.key`, `"${field.key}" should be snake_case and start with a letter.`);
2416
+ }
2417
+ if (!RESOLVABLE_FIELD_TYPE_WORDS.includes(field.parityType)) {
2418
+ say(
2419
+ `${fat}.parityType`,
2420
+ `The store does not resolve the type word "${field.parityType}". Use one of: ${RESOLVABLE_FIELD_TYPE_WORDS.join(", ")}.`
2421
+ );
2422
+ }
2423
+ if (!FIELD_SENSITIVITIES.includes(field.sensitivity)) {
2424
+ say(
2425
+ `${fat}.sensitivity`,
2426
+ `The store refuses a field that does not say how sensitive its values are. Use one of: ${FIELD_SENSITIVITIES.join(", ")}.`
2427
+ );
2428
+ }
2429
+ if (!CONTEXT_POLICIES.includes(field.contextPolicy)) {
2430
+ say(
2431
+ `${fat}.contextPolicy`,
2432
+ `The store refuses a field that does not say whether an agent may see its values. Use one of: ${CONTEXT_POLICIES.join(", ")}.`
2433
+ );
2434
+ }
2435
+ if (CHOICE_TYPES.has(field.parityType) && !field.choices?.length) {
2436
+ say(
2437
+ `${fat}.choices`,
2438
+ "A choice field has to list the business's real words, in the order they really happen \u2014 'Scheduled, Confirmed, Seen, No-show'."
2439
+ );
2440
+ }
2441
+ if (RELATION_TYPES.has(field.parityType) && field.relationTarget && !tablesByToken.has(field.relationTarget)) {
2442
+ say(`${fat}.relationTarget`, `It points at the table "${field.relationTarget}", which this use case does not declare.`);
2443
+ }
2444
+ if (field.parityType === "currency" && !field.unit) {
2445
+ say(`${fat}.unit`, "A currency field carries its currency as the unit \u2014 the unit changes the meaning.");
2446
+ }
2447
+ });
2448
+ const rowKeys = /* @__PURE__ */ new Set();
2449
+ table.rows.forEach((row, ri) => {
2450
+ const rat = `${at}.rows[${ri}] (${row.key})`;
2451
+ if (!row.key) say(`${rat}.key`, "Every row needs a stable local key so relationships can point at it.");
2452
+ if (rowKeys.has(row.key)) say(`${rat}.key`, `The row key "${row.key}" is used twice in this table.`);
2453
+ rowKeys.add(row.key);
2454
+ for (const valueKey of Object.keys(row.values ?? {})) {
2455
+ if (!keyed.has(valueKey)) {
2456
+ say(`${rat}.values.${valueKey}`, `This row sets "${valueKey}", which the table does not declare as a field.`);
2457
+ }
2458
+ }
2459
+ for (const field of table.fields) {
2460
+ if (field.required && (row.values?.[field.key] === void 0 || row.values?.[field.key] === null)) {
2461
+ say(`${rat}.values.${field.key}`, `"${field.label}" is required, and this row leaves it empty.`);
2462
+ }
2463
+ const value = row.values?.[field.key];
2464
+ if (value !== void 0 && field.choices?.length && !field.multi) {
2465
+ if (typeof value === "string" && !field.choices.includes(value)) {
2466
+ say(
2467
+ `${rat}.values.${field.key}`,
2468
+ `"${value}" is not one of "${field.label}"'s choices (${field.choices.join(", ")}).`
2469
+ );
2470
+ }
2471
+ }
2472
+ }
2473
+ });
2474
+ });
2475
+ const primary = useCase5.tables[0];
2476
+ if (primary && primary.rows.length < MIN_ROWS_IN_PRIMARY_TABLE) {
2477
+ say(
2478
+ `tables[0] (${primary.token}).rows`,
2479
+ `${primary.rows.length} rows makes the screen look empty. Seed at least ${MIN_ROWS_IN_PRIMARY_TABLE} \u2014 dozens is better.`
2480
+ );
2481
+ }
2482
+ const resolveField = (tableToken, fieldKey) => tablesByToken.get(tableToken)?.fields.find((f) => f.key === fieldKey);
2483
+ useCase5.relationships?.forEach((rel, i) => {
2484
+ const at = `relationships[${i}]`;
2485
+ if (!tablesByToken.has(rel.fromTable)) {
2486
+ say(`${at}.fromTable`, `There is no table "${rel.fromTable}" in this use case.`);
2487
+ return;
2488
+ }
2489
+ if (!tablesByToken.has(rel.toTable)) {
2490
+ say(`${at}.toTable`, `There is no table "${rel.toTable}" in this use case.`);
2491
+ }
2492
+ const field = resolveField(rel.fromTable, rel.fromField);
2493
+ if (!field) {
2494
+ say(`${at}.fromField`, `The table "${rel.fromTable}" does not declare a field "${rel.fromField}".`);
2495
+ } else if (!RELATION_TYPES.has(field.parityType)) {
2496
+ say(
2497
+ `${at}.fromField`,
2498
+ `"${rel.fromField}" is a ${field.parityType} field, so it cannot hold a relation. Declare it as a relation.`
2499
+ );
2500
+ } else if (field.relationTarget && field.relationTarget !== rel.toTable) {
2501
+ say(
2502
+ `${at}.fromField`,
2503
+ `The field points at "${field.relationTarget}" but the relationship says "${rel.toTable}". They have to agree.`
2504
+ );
2505
+ }
2506
+ if (!RELATION_FLAVORS.includes(rel.flavor)) {
2507
+ say(`${at}.flavor`, `"${rel.flavor}" is not one of: ${RELATION_FLAVORS.join(", ")}.`);
2508
+ }
2509
+ if (!RELATION_CARDINALITIES.includes(rel.cardinality)) {
2510
+ say(`${at}.cardinality`, `"${rel.cardinality}" is not one of: ${RELATION_CARDINALITIES.join(", ")}.`);
2511
+ }
2512
+ if (!ON_DELETE.includes(rel.onDelete)) {
2513
+ say(`${at}.onDelete`, `"${rel.onDelete}" is not one of: ${ON_DELETE.join(", ")}.`);
2514
+ }
2515
+ if (!rel.describes || rel.describes.length < 15) {
2516
+ say(`${at}.describes`, "Say the domain truth in a sentence \u2014 'an invoice belongs to the job it bills'.");
2517
+ }
2518
+ const target = tablesByToken.get(rel.toTable);
2519
+ const targetKeys = new Set(target?.rows.map((r) => r.key) ?? []);
2520
+ tablesByToken.get(rel.fromTable)?.rows.forEach((row, ri) => {
2521
+ const value = row.values?.[rel.fromField];
2522
+ if (value === void 0 || value === null) return;
2523
+ for (const ref of Array.isArray(value) ? value : [value]) {
2524
+ if (typeof ref === "string" && !targetKeys.has(ref)) {
2525
+ say(
2526
+ `tables (${rel.fromTable}).rows[${ri}].values.${rel.fromField}`,
2527
+ `It points at the row "${ref}", which does not exist in "${rel.toTable}".`
2528
+ );
2529
+ }
2530
+ }
2531
+ });
2532
+ });
2533
+ const form = useCase5.form;
2534
+ if (!form) {
2535
+ say("form", "A use case owes a form a practitioner in this business would really put in front of someone.");
2536
+ } else {
2537
+ if (!tablesByToken.has(form.table)) {
2538
+ say("form.table", `The form collects into "${form.table}", which this use case does not declare.`);
2539
+ } else {
2540
+ form.fields?.forEach((ff, i) => {
2541
+ if (!resolveField(form.table, ff.fieldKey)) {
2542
+ say(`form.fields[${i}]`, `The form asks for "${ff.fieldKey}", which "${form.table}" does not declare.`);
2543
+ }
2544
+ });
2545
+ }
2546
+ if (!form.fields?.length) say("form.fields", "The form has to ask for something.");
2547
+ if (!form.confirmation) say("form.confirmation", "Say what the person sees after they submit, in real words.");
2548
+ }
2549
+ const pipeline = useCase5.pipeline;
2550
+ if (!pipeline) {
2551
+ say("pipeline", "A use case owes the real lifecycle a record moves through in this business.");
2552
+ } else {
2553
+ const stageField = resolveField(pipeline.table, pipeline.stageField);
2554
+ if (!tablesByToken.has(pipeline.table)) {
2555
+ say("pipeline.table", `The pipeline runs on "${pipeline.table}", which this use case does not declare.`);
2556
+ } else if (!stageField) {
2557
+ say("pipeline.stageField", `"${pipeline.table}" does not declare a field "${pipeline.stageField}".`);
2558
+ } else if (!stageField.choices?.length) {
2559
+ say("pipeline.stageField", `"${pipeline.stageField}" has to be a choice field so a record can hold a stage.`);
2560
+ } else {
2561
+ pipeline.stages?.forEach((stage, i) => {
2562
+ if (!stageField.choices?.includes(stage.key)) {
2563
+ say(
2564
+ `pipeline.stages[${i}]`,
2565
+ `The stage "${stage.key}" is not one of "${pipeline.stageField}"'s choices (${stageField.choices?.join(", ")}).`
2566
+ );
2567
+ }
2568
+ });
2569
+ }
2570
+ if ((pipeline.stages?.length ?? 0) < 3) {
2571
+ say("pipeline.stages", "A lifecycle worth showing has at least three stages the staff actually say out loud.");
2572
+ }
2573
+ }
2574
+ const question = useCase5.dashboardQuestion;
2575
+ if (!question) {
2576
+ say("dashboardQuestion", "A use case owes the question this business's owner really asks on Monday morning.");
2577
+ } else {
2578
+ if (!question.question?.trim().endsWith("?")) {
2579
+ say("dashboardQuestion.question", "Write it as the question they would actually say, ending in a question mark.");
2580
+ }
2581
+ if (!tablesByToken.has(question.table)) {
2582
+ say("dashboardQuestion.table", `It reads "${question.table}", which this use case does not declare.`);
2583
+ } else {
2584
+ if (question.aggregate !== "count" && !question.of) {
2585
+ say("dashboardQuestion.of", `A ${question.aggregate} has to name the field it aggregates.`);
2586
+ }
2587
+ for (const [prop, key] of [
2588
+ ["of", question.of],
2589
+ ["groupBy", question.groupBy]
2590
+ ]) {
2591
+ if (key && !resolveField(question.table, key)) {
2592
+ say(`dashboardQuestion.${prop}`, `"${question.table}" does not declare a field "${key}".`);
2593
+ }
2594
+ }
2595
+ }
2596
+ if (!question.whyItMatters) {
2597
+ say("dashboardQuestion.whyItMatters", "Say what they DO differently depending on the answer.");
2598
+ }
2599
+ }
2600
+ const doc = useCase5.documentTemplate;
2601
+ if (!doc) {
2602
+ say("documentTemplate", "A use case owes a document this business really sends.");
2603
+ } else {
2604
+ if (!tablesByToken.has(doc.table)) {
2605
+ say("documentTemplate.table", `It is built from "${doc.table}", which this use case does not declare.`);
2606
+ }
2607
+ const declared = new Set(doc.mergeFields?.map((m) => m.key) ?? []);
2608
+ const used = new Set(
2609
+ [...`${doc.title}
2610
+ ${doc.body}`.matchAll(/\{\{\s*([a-z0-9_.]+)\s*\}\}/gi)].map((m) => m[1]).filter((key) => key !== void 0)
2611
+ );
2612
+ for (const key of used) {
2613
+ if (!declared.has(key)) {
2614
+ say("documentTemplate.body", `The document uses {{${key}}} but does not declare it as a merge field.`);
2615
+ }
2616
+ }
2617
+ for (const merge of doc.mergeFields ?? []) {
2618
+ if (!used.has(merge.key)) {
2619
+ say("documentTemplate.mergeFields", `It declares {{${merge.key}}} but the document never uses it.`);
2620
+ }
2621
+ const isSpecial = merge.from === "business" || merge.from === "today" || merge.from.startsWith("business.");
2622
+ if (!isSpecial && tablesByToken.has(doc.table) && !resolveField(doc.table, merge.from)) {
2623
+ say(
2624
+ "documentTemplate.mergeFields",
2625
+ `{{${merge.key}}} reads "${merge.from}", which "${doc.table}" does not declare (use a field key, or "business"/"today").`
2626
+ );
2627
+ }
2628
+ }
2629
+ if ((doc.body ?? "").length < 80) {
2630
+ say("documentTemplate.body", "Write the prose this business would really send, not a stub.");
2631
+ }
2632
+ }
2633
+ const rule = useCase5.notificationRule;
2634
+ if (!rule) {
2635
+ say("notificationRule", "A use case owes the one automatic message this business sets up on day one.");
2636
+ } else {
2637
+ if (!tablesByToken.has(rule.when.table)) {
2638
+ say("notificationRule.when.table", `It watches "${rule.when.table}", which this use case does not declare.`);
2639
+ } else {
2640
+ if (rule.when.field && !resolveField(rule.when.table, rule.when.field)) {
2641
+ say("notificationRule.when.field", `"${rule.when.table}" does not declare a field "${rule.when.field}".`);
2642
+ }
2643
+ if (rule.when.event === "stage_entered" && !rule.when.stage) {
2644
+ say("notificationRule.when.stage", "A stage trigger has to say which stage it fires on.");
2645
+ }
2646
+ if ((rule.when.event === "due" || rule.when.event === "overdue") && !rule.when.field) {
2647
+ say("notificationRule.when.field", "A due trigger has to name the date field it watches.");
2648
+ }
2649
+ }
2650
+ if (!rule.message) say("notificationRule.message", "Write the message this business would really send.");
2651
+ if (!rule.audience) say("notificationRule.audience", "Say who it reaches, in the business's own words.");
2652
+ }
2653
+ return { ok: problems.length === 0, problems };
2654
+ }
2655
+ function assertUseCase(useCase5) {
2656
+ const verdict = validateUseCase(useCase5);
2657
+ if (verdict.ok) return;
2658
+ const lines = verdict.problems.map((p) => ` \xB7 ${p.at}: ${p.says}`);
2659
+ throw new Error(
2660
+ `The use case "${useCase5.id}" is not ready to seed \u2014 ${verdict.problems.length} problem(s):
2661
+ ${lines.join("\n")}`
2662
+ );
2663
+ }
2664
+
2665
+ // src/use-cases/registry.ts
2666
+ var USE_CASE_LOADERS = {
2667
+ "alvarado-chen-kitchen-weekly-cooking": () => Promise.resolve().then(() => (init_alvarado_chen_kitchen_weekly_cooking(), alvarado_chen_kitchen_weekly_cooking_exports)),
2668
+ "birchwood-avenue-renovation-budget": () => Promise.resolve().then(() => (init_birchwood_avenue_renovation_budget(), birchwood_avenue_renovation_budget_exports)),
2669
+ "harbor-dental-new-patient-intake": () => Promise.resolve().then(() => (init_harbor_dental_new_patient_intake(), harbor_dental_new_patient_intake_exports)),
2670
+ "ridgeline-physical-therapy-plan-of-care": () => Promise.resolve().then(() => (init_ridgeline_physical_therapy_plan_of_care(), ridgeline_physical_therapy_plan_of_care_exports))
2671
+ };
2672
+ function useCaseIds() {
2673
+ return Object.keys(USE_CASE_LOADERS).sort();
2674
+ }
2675
+ async function loadUseCase(id) {
2676
+ const loader = USE_CASE_LOADERS[id];
2677
+ if (!loader) {
2678
+ throw new Error(
2679
+ `There is no use case registered as "${id}". Registered: ${useCaseIds().join(", ")}.`
2680
+ );
2681
+ }
2682
+ const module2 = await loader();
2683
+ return module2.useCase;
2684
+ }
2685
+ async function loadAllUseCases() {
2686
+ return Promise.all(useCaseIds().map((id) => loadUseCase(id)));
2687
+ }
2688
+ function summarize(useCase5) {
2689
+ return {
2690
+ id: useCase5.id,
2691
+ useCase: useCase5.useCase,
2692
+ industry: useCase5.industry,
2693
+ businessName: useCase5.business.name,
2694
+ tableCount: useCase5.tables.length,
2695
+ rowCount: useCase5.tables.reduce((total, table) => total + table.rows.length, 0)
2696
+ };
2697
+ }
2698
+ async function listUseCases() {
2699
+ return (await loadAllUseCases()).map(summarize);
2700
+ }
2701
+ function useCaseFor(consumer) {
2702
+ const ids = useCaseIds();
2703
+ let hash = 0;
2704
+ for (let i = 0; i < consumer.length; i += 1) {
2705
+ hash = hash * 31 + consumer.charCodeAt(i) >>> 0;
2706
+ }
2707
+ const id = ids[hash % ids.length];
2708
+ if (!id) {
2709
+ throw new Error(
2710
+ `No use cases are registered, so there is nothing to give "${consumer}". Add one to USE_CASE_LOADERS.`
2711
+ );
2712
+ }
2713
+ return id;
2714
+ }
2715
+ //# sourceMappingURL=index.cjs.map