@hailer/mcp 1.3.21 → 1.3.32
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.
- package/.claude/skills/create-and-publish-app/SKILL.md +44 -56
- package/.claude/skills/hailer-app-builder/SKILL.md +414 -970
- package/.claude/skills/hailer-app-primitives/SKILL.md +742 -0
- package/.claude/skills/hailer-apps-pictures/SKILL.md +191 -87
- package/.claude/skills/hailer-design-patterns/SKILL.md +317 -0
- package/.claude/skills/hailer-design-system/SKILL.md +202 -149
- package/.claude/skills/hailer-docs-search/SKILL.md +75 -0
- package/.claude/skills/hailer-workflow-archetypes/SKILL.md +301 -0
- package/.claude/skills/insight-join-patterns/SKILL.md +313 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +211 -0
- package/.claude/skills/sdk-activity-patterns/SKILL.md +257 -105
- package/.claude/skills/sdk-function-fields/SKILL.md +253 -492
- package/.claude/skills/sdk-insight-calculations/SKILL.md +364 -0
- package/.claude/skills/sdk-insight-queries/SKILL.md +192 -397
- package/.claude/skills/sdk-ws-config-skill/SKILL.md +265 -720
- package/.claude/skills/tool-response-verification/SKILL.md +143 -0
- package/CLAUDE.md +45 -19
- package/dist/bot/bot.d.ts.map +1 -1
- package/dist/bot/bot.js +21 -1
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/services/helper-prompt.d.ts +1 -1
- package/dist/bot/services/helper-prompt.d.ts.map +1 -1
- package/dist/bot/services/helper-prompt.js +4 -6
- package/dist/bot/services/helper-prompt.js.map +1 -1
- package/dist/bot/services/message-formatter.d.ts.map +1 -1
- package/dist/bot/services/message-formatter.js +2 -1
- package/dist/bot/services/message-formatter.js.map +1 -1
- package/dist/bot/services/system-prompt.d.ts.map +1 -1
- package/dist/bot/services/system-prompt.js +21 -34
- package/dist/bot/services/system-prompt.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/lib/object-id.d.ts +9 -0
- package/dist/lib/object-id.d.ts.map +1 -0
- package/dist/lib/object-id.js +15 -0
- package/dist/lib/object-id.js.map +1 -0
- package/dist/mcp/hailer-rpc.d.ts +3 -0
- package/dist/mcp/hailer-rpc.d.ts.map +1 -1
- package/dist/mcp/hailer-rpc.js +7 -0
- package/dist/mcp/hailer-rpc.js.map +1 -1
- package/dist/mcp/tool-profiles.d.ts +1 -0
- package/dist/mcp/tool-profiles.d.ts.map +1 -1
- package/dist/mcp/tool-profiles.js +21 -4
- package/dist/mcp/tool-profiles.js.map +1 -1
- package/dist/mcp/tools/aliases.d.ts.map +1 -1
- package/dist/mcp/tools/aliases.js +14 -6
- package/dist/mcp/tools/aliases.js.map +1 -1
- package/dist/mcp/tools/app-core.d.ts.map +1 -1
- package/dist/mcp/tools/app-core.js +6 -3
- package/dist/mcp/tools/app-core.js.map +1 -1
- package/dist/mcp/tools/app-marketplace.d.ts.map +1 -1
- package/dist/mcp/tools/app-marketplace.js +7 -2
- package/dist/mcp/tools/app-marketplace.js.map +1 -1
- package/dist/mcp/tools/docs.d.ts +20 -0
- package/dist/mcp/tools/docs.d.ts.map +1 -0
- package/dist/mcp/tools/docs.js +87 -0
- package/dist/mcp/tools/docs.js.map +1 -0
- package/dist/mcp/tools/function-fields.d.ts +17 -0
- package/dist/mcp/tools/function-fields.d.ts.map +1 -0
- package/dist/mcp/tools/function-fields.js +717 -0
- package/dist/mcp/tools/function-fields.js.map +1 -0
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +4 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/workflow.d.ts +0 -1
- package/dist/mcp/tools/workflow.d.ts.map +1 -1
- package/dist/mcp/tools/workflow.js +1 -190
- package/dist/mcp/tools/workflow.js.map +1 -1
- package/dist/public-chat/graduate.d.ts +5 -4
- package/dist/public-chat/graduate.d.ts.map +1 -1
- package/dist/public-chat/graduate.js +226 -76
- package/dist/public-chat/graduate.js.map +1 -1
- package/dist/public-chat/handler.d.ts.map +1 -1
- package/dist/public-chat/handler.js +10 -2
- package/dist/public-chat/handler.js.map +1 -1
- package/dist/public-chat/index.d.ts.map +1 -1
- package/dist/public-chat/index.js +12 -1
- package/dist/public-chat/index.js.map +1 -1
- package/dist/public-chat/session-store.d.ts +22 -1
- package/dist/public-chat/session-store.d.ts.map +1 -1
- package/dist/public-chat/session-store.js +56 -2
- package/dist/public-chat/session-store.js.map +1 -1
- package/dist/public-chat/studio-prewarm.js +1 -1
- package/dist/public-chat/studio-prewarm.js.map +1 -1
- package/dist/public-chat/system-prompt.d.ts +1 -1
- package/dist/public-chat/system-prompt.d.ts.map +1 -1
- package/dist/public-chat/system-prompt.js +161 -83
- package/dist/public-chat/system-prompt.js.map +1 -1
- package/package.json +1 -1
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -398
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hailer-design-patterns
|
|
3
|
+
description: Hailer workspace structural design patterns — when to split into two workflows vs keep flat, parent/child lifecycle shapes, row progression vs row generation, phase rationalization, approval gates, conditional fields, and dashboard composition. Load before planning workspace structure or writing a PRD.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hailer Design Patterns
|
|
7
|
+
|
|
8
|
+
Reference patterns for structuring Hailer workspaces. Apply when planning workflows, writing PRDs, or reviewing workspace architecture.
|
|
9
|
+
|
|
10
|
+
## Workflow Structure
|
|
11
|
+
|
|
12
|
+
**Two Workflows: Parent + Children**
|
|
13
|
+
Common pattern for any entity that groups operational sub-records. Two flavors:
|
|
14
|
+
|
|
15
|
+
- **Header + Rows** — transactional entities with totals: orders/order rows, invoices/invoice rows, quotes/quote lines. Lines aggregate to header (totals via function fields).
|
|
16
|
+
- **Parent + Children via ActivityLink** — entities that contain related records without strict line-item semantics: Customer/Contacts, Project/Tasks, Customer/Sites, Equipment/Maintenance entries.
|
|
17
|
+
|
|
18
|
+
Use either flavor when one record needs to contain or group multiple sub-records. Don't apply to genuinely flat records (weather logs, daily reports, observations, attendance, sensor readings) — those are single-workflow.
|
|
19
|
+
|
|
20
|
+
| Header/Parent | Rows/Children | Links To |
|
|
21
|
+
|--------|------|----------|
|
|
22
|
+
| Sales Orders | Order Rows | Products, Services |
|
|
23
|
+
| Purchase Orders | PO Rows | Products, Suppliers |
|
|
24
|
+
| Invoices | Invoice Rows | (or linked Order Rows) |
|
|
25
|
+
| Quotes | Quote Lines | Products, Services |
|
|
26
|
+
| Projects | Tasks | - |
|
|
27
|
+
| Customers | Contacts | - |
|
|
28
|
+
| Equipment | Maintenance Entries | - |
|
|
29
|
+
|
|
30
|
+
**Why two workflows:** Separate workflows for parent and children enable independent phases, unlimited count, per-record state, aggregation via function fields, and cleaner reporting.
|
|
31
|
+
|
|
32
|
+
**Naming convention (Finnish):** Base + "rivit" suffix
|
|
33
|
+
- Tilaukset -> Tilausrivit
|
|
34
|
+
- Myyntitilaukset -> Myyntitilausrivit
|
|
35
|
+
- Laskut -> Laskurivit
|
|
36
|
+
|
|
37
|
+
**Row Source Flexibility:** Rows don't have to be one workflow. A Sales Order might link to Product Rows + Service Rows + Production Rows if types have different fields or processes.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
**Dataset vs Workflow**
|
|
42
|
+
|
|
43
|
+
| Type | When to Use | Examples | Phase Behavior |
|
|
44
|
+
|------|-------------|----------|----------------|
|
|
45
|
+
| **Dataset** | Static/reference data | Products, Customers, Suppliers | Categories (activities don't transition) |
|
|
46
|
+
| **Workflow** | Lifecycle/state data | Orders, Invoices, Tasks | Phases (activities progress through stages) |
|
|
47
|
+
|
|
48
|
+
- **Dataset:** Activities are categorized but don't "move" - a Product stays in "Electronics" category
|
|
49
|
+
- **Workflow:** Activities progress through phases - an Order moves from "Draft" -> "Confirmed" -> "Shipped"
|
|
50
|
+
|
|
51
|
+
This affects phase design:
|
|
52
|
+
- Datasets: phases = categories (no transitions expected)
|
|
53
|
+
- Workflows: phases = lifecycle stages (transitions matter)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
**Master Data vs Transactional**
|
|
58
|
+
|
|
59
|
+
| Type | enableUnlinkedMode | Examples |
|
|
60
|
+
|------|-------------------|----------|
|
|
61
|
+
| Master data | `true` (independent) | Customers, Products, Suppliers, Employees |
|
|
62
|
+
| Transactional | `false` (requires context) | Orders, Invoices, Quotes, their Rows |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
**Domain Prefixes (Large Workspaces)**
|
|
67
|
+
For 30+ workflows, use prefixes to organize:
|
|
68
|
+
- `INV-` Invoicing (INV-Laskut, INV-Laskurivit)
|
|
69
|
+
- `VH-` Warehouse (VH-Varastopaikat, VH-Nimikkeet)
|
|
70
|
+
- `MYY-` Sales (MYY-Tilaukset, MYY-Tilausrivit)
|
|
71
|
+
- `OST-` Purchasing (OST-Tilaukset, OST-Tilausrivit)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
**Phase-Specific Field Visibility**
|
|
76
|
+
|
|
77
|
+
Different phases can show different fields. Plan this during design:
|
|
78
|
+
|
|
79
|
+
**Workflow example (Orders):**
|
|
80
|
+
| Phase | Fields Shown |
|
|
81
|
+
|-------|--------------|
|
|
82
|
+
| Draft | Customer, Items, Notes |
|
|
83
|
+
| Confirmed | + Confirmed Date, Confirmed By |
|
|
84
|
+
| Shipped | + Ship Date, Tracking Number |
|
|
85
|
+
| Invoiced | + Invoice Number, Invoice Date |
|
|
86
|
+
|
|
87
|
+
**Dataset example (Products):**
|
|
88
|
+
| Category | Fields Shown |
|
|
89
|
+
|----------|--------------|
|
|
90
|
+
| Physical Products | Name, Price, + Dimensions, Weight, Stock |
|
|
91
|
+
| Services | Name, Price, + Hourly Rate, Duration |
|
|
92
|
+
| Both | Name, Description, Price (shared) |
|
|
93
|
+
|
|
94
|
+
Document in the design plan which fields appear in which phases.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
**Phase Rationalization for Apps**
|
|
99
|
+
|
|
100
|
+
When building apps, document which phases to show per user role:
|
|
101
|
+
|
|
102
|
+
| View | Phases Fetched | Rationale |
|
|
103
|
+
|------|----------------|-----------|
|
|
104
|
+
| Sales Dashboard | Active only | Hide internal WIP from sales team |
|
|
105
|
+
| Manager View | Draft + Active | Managers need WIP visibility |
|
|
106
|
+
| Admin Panel | All phases | Full access for admin |
|
|
107
|
+
| Public Portal | Active only | No internal data exposed |
|
|
108
|
+
| Insights/Reports | Active only | Accurate counts, no draft duplicates |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Row Lifecycle Patterns
|
|
113
|
+
|
|
114
|
+
**Pattern A: Rows progress through phases (simpler)**
|
|
115
|
+
```
|
|
116
|
+
Order Row -> "Ordered" -> "Shipped" -> "Invoiced"
|
|
117
|
+
```
|
|
118
|
+
- Same activity, phase changes
|
|
119
|
+
- Invoice workflow links to the rows
|
|
120
|
+
- Works when 1:1 (ordered = shipped = invoiced)
|
|
121
|
+
- Good for simple processes without integrations
|
|
122
|
+
|
|
123
|
+
**Pattern B: Rows generate new activities (complex)**
|
|
124
|
+
```
|
|
125
|
+
Order Row -> creates -> Invoice Row
|
|
126
|
+
-> creates -> Delivery Row
|
|
127
|
+
```
|
|
128
|
+
- Separate workflows for each document type
|
|
129
|
+
- Use when:
|
|
130
|
+
- Partial invoicing (invoice 3 of 5 rows now)
|
|
131
|
+
- Consolidated invoicing (multiple orders -> one invoice)
|
|
132
|
+
- Accounting integration needs clean invoice data
|
|
133
|
+
- Different pricing (order price != invoice price)
|
|
134
|
+
|
|
135
|
+
**Decision Guide**
|
|
136
|
+
|
|
137
|
+
| Scenario | Approach |
|
|
138
|
+
|----------|----------|
|
|
139
|
+
| Simple tracking, no integrations | Rows progress through phases |
|
|
140
|
+
| 1:1 order-to-invoice | Rows progress through phases |
|
|
141
|
+
| Partial deliveries/invoices | Separate row workflows |
|
|
142
|
+
| Accounting integration (Netvisor, etc.) | Separate invoice rows |
|
|
143
|
+
| Multiple orders -> one invoice | Separate invoice rows |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Integration Patterns
|
|
148
|
+
|
|
149
|
+
| Need | Solution |
|
|
150
|
+
|------|----------|
|
|
151
|
+
| Phase cascades (parent -> children, child phase load-bearing) | External automation reading function-field JSON payload (monolith webhook or microservice) |
|
|
152
|
+
| Child reflects parent phase (display only) | Function field on child reading parent's phase via `>` link variable |
|
|
153
|
+
| Simple notifications/external tasks | Zapier webhooks |
|
|
154
|
+
| Complex automations, scheduled jobs | Project Monolith |
|
|
155
|
+
| Accounting sync (Netvisor, Procountor) | Monolith integration |
|
|
156
|
+
| No integration (manual export) | Export insight (CSV) |
|
|
157
|
+
|
|
158
|
+
**Export insight fallback:** When no direct integration, create an insight that formats data for CSV download. User imports manually to accounting.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Document Templates
|
|
163
|
+
|
|
164
|
+
Used for: Invoices, Quotes, Order Confirmations, Delivery Notes, Reports.
|
|
165
|
+
Templates pull data from header workflow + linked rows. Typically PDF format with company branding. See `sdk-document-templates` skill for push format and field reference shapes.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Single Workflow Pattern
|
|
170
|
+
|
|
171
|
+
Not everything needs two workflows. Single workflows work for flat records — entities without sub-items, totals, or per-line state:
|
|
172
|
+
- Simple task tracking (no line items), daily reports
|
|
173
|
+
- Suppliers, inventory items
|
|
174
|
+
- Weather logs, sensor data, observations
|
|
175
|
+
- Attendance records, event logs, meeting notes
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## "Unit of Listing" Pattern for Outbound Marketplace Integrations
|
|
180
|
+
|
|
181
|
+
When a product needs to be listed on an external marketplace, model each listing as its own activity in a dedicated workflow (e.g. `Tavararahaksi myynnit`), linked via ActivityLink back to the source product. Phases map 1:1 to remote endpoint actions: Luonnos → upsert:draft, Myynnissä → upsert:published, Myyty/Peruttu → deactivate.
|
|
182
|
+
|
|
183
|
+
Benefits: enables relisting, per-listing overrides (price, mode, publish timing), keeps the product workflow clean, lifecycle is visible in Hailer UI.
|
|
184
|
+
|
|
185
|
+
Anti-pattern: adding marketplace-specific phases directly to the source product workflow — it pollutes the product schema and prevents multiple concurrent listings.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Feature Composition Patterns
|
|
190
|
+
|
|
191
|
+
How common product patterns from tools like Windmill, n8n, Retool, NocoBase, and Budibase map onto Hailer-native primitives.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### Approval-Step UIs
|
|
196
|
+
|
|
197
|
+
**Hailer-native recipe — phases + ball tracking:**
|
|
198
|
+
|
|
199
|
+
Model the approval gate as two terminal `possibleNextPhase` targets (e.g. Approved, Rejected) branching from a single decision phase (e.g. Pending Approval). Ball tracking hands the active holder — the approver — the activity at that phase:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// phases.ts — auto-assign ball to the approver field on phase enter
|
|
203
|
+
{
|
|
204
|
+
name: 'Pending Approval',
|
|
205
|
+
ballAction: MyWorkflow_FieldIds.approver_user_9f2, // users-type field
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
When the activity enters Pending Approval, the `ball.auto_assigned` signal fires and the ball holder (approver) receives it as a discussion participant. They then move the activity to Approved or Rejected via the Hailer UI (or a custom app button calling `activity.update([{_id, phaseId}], {})`). The binary decision is the phase transition itself — no external webhook URL required.
|
|
210
|
+
|
|
211
|
+
The relevant workflow archetype is **Approval Flow** (`hailer-workflow-archetypes`): intake phase → decision phase with two terminal targets.
|
|
212
|
+
|
|
213
|
+
**Availability:** Ball tracking requires the network-level `ballTracking` license restriction (enabled manually in the database by Hailer staff — no self-serve toggle) plus `enableBallTracking: true` on the workflow. Verify the customer's deployment surfaces the UI before promising an interactive approval button.
|
|
214
|
+
|
|
215
|
+
Implementation skills: `sdk-ws-config-skill` (phase config, `ballAction`), `hailer-workflow-archetypes` (Approval Flow archetype skeleton).
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### Multi-Step Wizards
|
|
220
|
+
|
|
221
|
+
Two complementary approaches, depending on whether the wizard persists state in Hailer or is purely UI-side:
|
|
222
|
+
|
|
223
|
+
**Option A — phase-as-step (state persisted):** Map each wizard step to a workflow phase. The activity advances one phase per completed step via `activity.update([{_id, phaseId: nextPhaseId}], {})`. This is the **Pipeline** archetype (`hailer-workflow-archetypes`): each phase is a named lifecycle stage. Phase-specific field visibility lets each step surface only the relevant fields.
|
|
224
|
+
|
|
225
|
+
**Option B — in-app multi-screen (state in React):** In a custom app, manage wizard state with a `currentStep` variable and render step components conditionally:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// In-app wizard — step state lives in React
|
|
229
|
+
const [step, setStep] = useState<'details' | 'review' | 'confirm'>('details');
|
|
230
|
+
|
|
231
|
+
{step === 'details' && <DetailsStep onNext={() => setStep('review')} />}
|
|
232
|
+
{step === 'review' && <ReviewStep onNext={() => setStep('confirm')} />}
|
|
233
|
+
{step === 'confirm' && <ConfirmStep onSubmit={handleCreate} />}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Push suggestions straight into the same staged state the commit action reads — avoid a separate review zone that a user can skip.
|
|
237
|
+
|
|
238
|
+
A single-activity final `hailer.activity.create()` call fires when the user confirms, carrying all field values collected across steps.
|
|
239
|
+
|
|
240
|
+
Implementation skills: `hailer-workflow-archetypes` (Pipeline archetype, phase transitions), `hailer-app-builder` (wizard pattern, `activity.create` format).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### Conditional Field Visibility
|
|
245
|
+
|
|
246
|
+
Hailer's core workflow UI does not have a native no-code field-level show/hide rule editor. Two available approaches differ by where the logic lives:
|
|
247
|
+
|
|
248
|
+
**App-side conditional render (recommended for form/display logic):** In a custom app, render fields conditionally based on other field values read from the activity or from local form state:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
// Show a follow-up field only when a condition is met
|
|
252
|
+
const fieldValue = activity.fields?.[FIELD_IDS.category] as string;
|
|
253
|
+
|
|
254
|
+
{fieldValue === 'premium' && (
|
|
255
|
+
<FormField label="Premium tier" fieldId={FIELD_IDS.premiumTier} />
|
|
256
|
+
)}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
This requires building a custom app; it does not affect the built-in Hailer activity sidebar.
|
|
260
|
+
|
|
261
|
+
**Function fields (compute / derive, not hide/show):** A `function`-type field can derive a value from other fields synchronously in isolated-vm. Useful for display-only computed values (e.g. show a risk score when a linked field exceeds a threshold), but it does not hide or show other fields — it produces a new computed value.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Computed display field — derives a label from another field's value
|
|
265
|
+
functionVariables: { category: { type: '>', data: [LINK_FIELD_ID, CATEGORY_FIELD_ID] } }
|
|
266
|
+
// body:
|
|
267
|
+
return dep.category === 'premium' ? 'High priority' : 'Standard';
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
For a no-code conditional-show experience without a custom app, the closest option is phase-specific field visibility (phase-level field sets are configurable per workflow phase in the core UI).
|
|
271
|
+
|
|
272
|
+
Implementation skills: `hailer-app-builder` (conditional render, field value reading), `sdk-function-fields` (`>` forward-link variable shape, isolated-vm constraints).
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### Computed Dashboards
|
|
277
|
+
|
|
278
|
+
Define an insight with aggregation SQL. Insights run against the live Hailer SQLite engine:
|
|
279
|
+
|
|
280
|
+
```sql
|
|
281
|
+
-- Pipeline value by phase with CASE bucketing
|
|
282
|
+
SELECT
|
|
283
|
+
CASE
|
|
284
|
+
WHEN d.value < 10000 THEN '1_small'
|
|
285
|
+
WHEN d.value < 100000 THEN '2_mid'
|
|
286
|
+
ELSE '3_large'
|
|
287
|
+
END AS bucket,
|
|
288
|
+
COUNT(*) AS "Kauppoja",
|
|
289
|
+
COALESCE(SUM(d.value), 0) AS "Yhteensä"
|
|
290
|
+
FROM deals d
|
|
291
|
+
GROUP BY bucket
|
|
292
|
+
ORDER BY bucket
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
In a custom app, load the insight via `hailer.insight.data(insightId, {})` and render the result rows as charts or stats cards:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const data = await hailer.insight.data(insightId, { update: true });
|
|
299
|
+
// data.headers: string[]
|
|
300
|
+
// data.rows: (string | number | null)[][]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Implementation skills: `sdk-insight-calculations` (aggregation recipes, CASE patterns), `hailer-app-builder` (insight API in apps, `hailer.insight.data()`, response shape).
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Anti-Patterns
|
|
308
|
+
|
|
309
|
+
### "Draft entity for staging" when resource bindings are write-once-at-creation
|
|
310
|
+
|
|
311
|
+
Creating an entity early (draft) and planning to attach resources to it later only works if the platform supports post-creation attachment. Verify the "fill later" half before designing the flow.
|
|
312
|
+
|
|
313
|
+
**Hailer example:** File IDs bind at `activity.create`. A draft activity + later file attach does not work — files uploaded before activity creation can't be retroactively bound. Instead, defer the activity creation until all uploads complete, then pass the file IDs in the create call.
|
|
314
|
+
|
|
315
|
+
**General rule:** If an entity's resource bindings are write-once-at-creation, don't model staging via that entity. Defer creation, not data. This applies to any platform where attachments, links, or sub-resources can only be set at creation time.
|
|
316
|
+
|
|
317
|
+
Design check: before specifying a "create early, fill later" flow, confirm the API has a working "fill later" endpoint and test it. If it doesn't exist or doesn't work, redesign to collect all data first.
|