@salesforce/afv-skills 1.49.0 → 1.50.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.
- package/package.json +1 -1
- package/skills/field-service-data-capture-form-deployer-configure/SKILL.md +180 -0
- package/skills/field-service-data-capture-form-deployer-configure/examples/inventory-transfer-spec.json +135 -0
- package/skills/field-service-data-capture-form-deployer-configure/examples/sample-spec.json +140 -0
- package/skills/field-service-data-capture-form-deployer-configure/examples/sectioned-spec.json +64 -0
- package/skills/field-service-data-capture-form-deployer-configure/references/field-types.md +297 -0
- package/skills/field-service-data-capture-form-deployer-configure/references/flow-metadata-json.md +243 -0
- package/skills/field-service-data-capture-form-deployer-configure/references/post-screen-automation.md +127 -0
- package/skills/field-service-data-capture-form-designer-configure/SKILL.md +110 -0
- package/skills/field-service-data-capture-form-designer-configure/references/extraction-from-image.md +244 -0
- package/skills/field-service-data-capture-form-designer-configure/references/extraction-from-prompt.md +222 -0
- package/skills/field-service-data-capture-form-editor-configure/SKILL.md +134 -0
- package/skills/field-service-data-capture-reference-configure/SKILL.md +762 -0
- package/skills/field-service-data-capture-reference-configure/examples/DataCapture_Showcase.flow-meta.xml +2022 -0
- package/skills/field-service-data-capture-reference-configure/examples/Data_Capture_All_Components.flow-meta.xml +2170 -0
- package/skills/field-service-data-capture-reference-configure/examples/Repeater_with_prepopulation.flow-meta.xml +231 -0
- package/skills/field-service-foundation-setup-designer-get/SKILL.md +135 -0
- package/skills/field-service-mobile-branding-configure/SKILL.md +133 -0
- package/skills/field-service-mobile-branding-configure/examples/dark-blue-scheme.json +16 -0
- package/skills/field-service-mobile-branding-configure/examples/salesforce-default-scheme.json +16 -0
- package/skills/field-service-mobile-branding-configure/references/color-fields.md +66 -0
- package/skills/field-service-mobile-branding-configure/references/contrast-validation.md +55 -0
- package/skills/field-service-mobile-branding-configure/references/derivation-methodology.md +83 -0
- package/skills/field-service-objective-designer-configure/SKILL.md +342 -0
- package/skills/field-service-prework-brief-deployer-configure/SKILL.md +83 -0
- package/skills/field-service-scheduling-policy-designer-query/SKILL.md +121 -0
- package/skills/field-service-setup-orchestrator-get/SKILL.md +48 -0
- package/skills/field-service-sobject-create-configure/SKILL.md +56 -0
- package/skills/field-service-voice-to-form-configure/SKILL.md +650 -0
- package/skills/field-service-work-rule-designer-configure/SKILL.md +303 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Derivation Methodology
|
|
2
|
+
|
|
3
|
+
How to translate a brand source (URL, hex codes, brand-guide paste, or color description) into the 14-field scheme defined in [color-fields.md](color-fields.md).
|
|
4
|
+
|
|
5
|
+
## Step 1 — Ingest the brand source
|
|
6
|
+
|
|
7
|
+
**Case A — URL provided**
|
|
8
|
+
|
|
9
|
+
1. Fetch the URL with `WebFetch`.
|
|
10
|
+
2. Extract color references: hex codes (`#RRGGBB`, `#RGB`), RGB values, named colors.
|
|
11
|
+
3. Identify the primary brand color (most prominent — used in headers, hero areas, primary CTAs).
|
|
12
|
+
4. Identify the secondary/interactive color (buttons, links, interactive elements).
|
|
13
|
+
5. Note any neutrals/grays from the brand palette.
|
|
14
|
+
|
|
15
|
+
**Case B — Brand guide text / paste provided**
|
|
16
|
+
|
|
17
|
+
1. Parse for hex codes and named colors.
|
|
18
|
+
2. Identify primary, secondary, neutral, and semantic (error/success) colors.
|
|
19
|
+
3. Note any explicit usage rules ("primary is for CTAs", "never use X on Y").
|
|
20
|
+
|
|
21
|
+
**Case C — Hex codes provided directly**
|
|
22
|
+
|
|
23
|
+
1. Accept as-is.
|
|
24
|
+
2. If only one color provided, ask: "Is this your primary brand color? Do you have a secondary/interactive color?"
|
|
25
|
+
3. Proceed to Step 2 with those anchors.
|
|
26
|
+
|
|
27
|
+
**Case D — Description only (e.g. "we're a red and white company")**
|
|
28
|
+
|
|
29
|
+
1. Ask for at least one specific hex code or a website URL.
|
|
30
|
+
2. Do not guess exact brand colors from descriptions alone.
|
|
31
|
+
|
|
32
|
+
## Step 2 — Identify brand anchors
|
|
33
|
+
|
|
34
|
+
From the brand source, establish two anchors. Everything else derives from them:
|
|
35
|
+
|
|
36
|
+
- **Primary brand color** — the dominant brand color, for non-interactive surfaces and the navbar.
|
|
37
|
+
- **Secondary/interactive color** — the color for buttons and interactive elements.
|
|
38
|
+
|
|
39
|
+
If the brand specifies separate navbar and CTA colors, use the navbar color for `NavbarBackgroundColor` and `PrimaryBrandColor`, and the CTA color for `SecondaryBrandColor`.
|
|
40
|
+
|
|
41
|
+
## Step 3 — Derive the full scheme
|
|
42
|
+
|
|
43
|
+
### Brand & Navigation
|
|
44
|
+
|
|
45
|
+
- `NavbarBackgroundColor` = primary brand anchor.
|
|
46
|
+
- `PrimaryBrandColor` = primary brand anchor (same as navbar — both represent the non-interactive brand surface).
|
|
47
|
+
- `NavbarInvertedColor` = derive from navbar luminance:
|
|
48
|
+
- Dark navbar (luminance < 0.35): `#FFFFFF`
|
|
49
|
+
- Light navbar (luminance ≥ 0.35): `#000000` or darkest brand neutral
|
|
50
|
+
- `SecondaryBrandColor` = secondary/interactive anchor from brand.
|
|
51
|
+
- `BrandInvertedColor` = same logic as `NavbarInvertedColor` applied to `PrimaryBrandColor` (usually `#FFFFFF`).
|
|
52
|
+
|
|
53
|
+
### Contrast Scale
|
|
54
|
+
|
|
55
|
+
Use brand neutrals if provided, otherwise use Salesforce defaults:
|
|
56
|
+
|
|
57
|
+
- `ContrastPrimaryColor` = darkest neutral, or `#000000`.
|
|
58
|
+
- `ContrastSecondaryColor` = dark gray, or `#444444`.
|
|
59
|
+
- `ContrastTertiaryColor` = medium-light gray (icons, lines), or `#9FAAB5`.
|
|
60
|
+
- `ContrastQuaternaryColor` = light gray (dividers, graphics), or `#E6E6EB`.
|
|
61
|
+
- `ContrastQuinaryColor` = very light gray (card surrounds), or `#EEEEEE`.
|
|
62
|
+
- `ContrastInvertedColor` = card backgrounds — almost always `#FFFFFF`.
|
|
63
|
+
|
|
64
|
+
If the brand guide specifies a neutral/gray scale, map those values to the 5 contrast steps in order from darkest to lightest.
|
|
65
|
+
|
|
66
|
+
### Feedback Colors
|
|
67
|
+
|
|
68
|
+
Use brand-specified semantic colors if available, otherwise use Salesforce defaults:
|
|
69
|
+
|
|
70
|
+
- `FeedbackPrimaryColor` = brand's error red, or `#C23934`.
|
|
71
|
+
- `FeedbackSecondaryColor` = brand's success color, or `#13C4A3`.
|
|
72
|
+
- `FeedbackSelectedColor` = `#FFFFFF` (text/icon on selected state — almost always white).
|
|
73
|
+
|
|
74
|
+
## Edge cases
|
|
75
|
+
|
|
76
|
+
- **No secondary color in brand guide** — use a 20% lighter or darker variant of the primary.
|
|
77
|
+
- **Monochrome brand** — use `#000000` primary, `#555555` secondary, standard contrast scale.
|
|
78
|
+
- **Very light primary (pastel)** — flag in the proposal: light navbar will fail contrast. Suggest using a darker brand neutral for the navbar instead.
|
|
79
|
+
- **Multiple `FieldServiceMobileSettings` records** — the apply targets the org-default record (`DeveloperName='Field_Service_Mobile_Settings'`, `IsDefault=true`). If the org has multiple records, query first and confirm which to update.
|
|
80
|
+
|
|
81
|
+
## Output
|
|
82
|
+
|
|
83
|
+
The scheme is a flat JSON object matching the schema in [color-fields.md](color-fields.md). Hold it in memory, validate contrast inline (SKILL.md step 4), then apply it with a single sObject PATCH after user approval (SKILL.md step 6).
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: field-service-objective-designer-configure
|
|
3
|
+
description: "Designs service objectives for a Salesforce Field Service scheduling policy via a structured trade-off interview. Guides the user through objective selection and derives weights by establishing crossover equivalences against Minimize Travel (the anchor). Produces a finalized weight table with penalty-rate interpretation and a plain-English policy summary. Use this skill when a user wants to design or weight Field Service scheduling service objectives; called after work rule design and delegates to sfs-sobject-create for record creation."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
owning_team: sfs-setup-experience
|
|
6
|
+
metadata:
|
|
7
|
+
version: "1.0"
|
|
8
|
+
domains: ["Field Service"]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Salesforce Field Service – Service Objective Designer
|
|
12
|
+
|
|
13
|
+
**Designs the service objectives for a scheduling policy.** Collects objective selection and derives each weight through a structured trade-off interview — one question at a time — and emits a `serviceObjectives[]` block. It creates no Salesforce records; when complete, delegates to `sfs-sobject-create`.
|
|
14
|
+
|
|
15
|
+
**Interview phases:** Scheduling Policy → Work Rules → **Service Objectives** (this skill) → Record Creation
|
|
16
|
+
|
|
17
|
+
The `policy` block (from `sfs-scheduling-policy-designer`) and `workRules[]` block (from `sfs-work-rule-designer`) arrive as context; this skill adds `serviceObjectives[]` and hands the complete design to `sfs-sobject-create`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Background: How SFS Scoring Works
|
|
22
|
+
|
|
23
|
+
The optimizer assigns penalty points to each candidate schedule. Lower total penalty = better schedule. Each objective contributes penalty points based on its weight and its own scale (its worst-case scenario).
|
|
24
|
+
|
|
25
|
+
**Minimize Travel weight is the anchor** — set at 1000; all other weights derive relative to it. The general formula, common to every objective (stated once, not re-derived per objective):
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
penaltyPerViolation = max( 1, roundingFn( (1000 × weight) / scale ) ) × finalMultiplier
|
|
29
|
+
total_penalty = ceil( violations / granularity ) × penaltyPerViolation
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Two consequences of the shared ×1000 internal multiplier:
|
|
33
|
+
|
|
34
|
+
- It cancels out of every derivation between two objectives — which is why the interview's continuous approximations stay valid and every "derive weight_X from weight_Y" formula is clean of any ×1000 term. Where the rounding isn't exact for integer weights (ASAP's round, Skill Level/Preference's roundInt), small drift is possible — flagged per objective below.
|
|
35
|
+
- The `max(1, …)` floor guarantees every included objective has some effect even at a very low weight.
|
|
36
|
+
|
|
37
|
+
**One exception:** Same Site's final multiplier (×0.01) nets to an effective ×10, not ×1000 — the one place the "divide by the other objective's rate" shortcut needs adjustment.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Penalty Formulas by Objective
|
|
42
|
+
|
|
43
|
+
Use these to show math and back-calculate weights. General mechanics (×1000, floor, why derivations stay valid) are above, not repeated.
|
|
44
|
+
|
|
45
|
+
**Minimize Travel (anchor)** — Scale 120 min (2 hr = default `MaxGrade__c`), round5, ×1/60 (per-minute → per-second). `penaltyPerViolation_travel = max(1, round5(1000×weight_travel/120)) × (1/60)`. At weight 1000 → 8333.33333 → 138.88889 pts/sec (whole-second granularity). Continuous: `travel_penalty(X_min) ≈ X × weight_travel/120` — ≈8.333 pts/min at 1000.
|
|
46
|
+
*Precision:* Travel is per-second; Overtime (same scale) is per-minute — a 61-sec trip costs more than a 60-sec one. Floor binds only below ≈0.12.
|
|
47
|
+
|
|
48
|
+
**ASAP** — Scale 43,200 min (30 days), round. `penaltyPerViolation_asap = max(1, round(1000×weight_asap/43200))`. Whole-minute granularity: `asap_penalty(X_sec) = ceil(X_sec/60) × penaltyPerViolation`. Continuous: `asap_penalty(Y_min) ≈ Y × weight_asap/43200`.
|
|
49
|
+
*Precision:* Weights 1–21 are dead — round(1000×21/43200)=0, clamped to 1, so all give the identical 1 pt/min rate. Above that, drift is small (weight 250 → effective 259.2, 3.7%). Validate with the integer formula when precision matters.
|
|
50
|
+
|
|
51
|
+
**Same Site** — Scale 1 (binary), round5, ×0.01 → **effective ×10, the exception**. `penaltyPerViolation_same_site = max(1, round5(1000×weight_same_site)) × 0.01`. At weight 50 → 500 pts/violation.
|
|
52
|
+
*Precision:* round5 exact for integer weights. Flat per-event regardless of time. Effective multiplier is ×10, not ×1000.
|
|
53
|
+
|
|
54
|
+
**Minimize Overtime** — **Identical mechanics to Minimize Travel** (scale 120, round5, weight 1000 → 8333.33333) **except:** final multiplier is ×1.0 not ×1/60, so the rate is per-**minute** (8333.33333 pts/min); and penalty groups into whole minutes — `overtime_penalty(X_sec) = ceil(X_sec/60) × penaltyPerViolation` — so a 61-sec block costs the same as 120-sec. `penaltyPerViolation_overtime = max(1, round5(1000×weight_overtime/120)) × 1.0`. Continuous: `≈ Z × weight_overtime/120`; comparable to Travel.
|
|
55
|
+
*Precision:* Floor binds below ≈0.12.
|
|
56
|
+
|
|
57
|
+
**Preferred Resource** — Scale 1 (binary), roundInt, ×1.0. `penaltyPerViolation_preferred = max(1, roundInt(100 × 10.0 × weight_preferred)) × 1.0` (100×10.0 = 1000). At weight 375 → 375,000 pts/violation. Derivation: `weight_preferred = T_equiv × (weight_travel/120)` — with weight_travel 1000: `= T_equiv × 8.333`.
|
|
58
|
+
*Precision:* Zero rounding error for integer weights (exact form `X_violations × 1000 × weight_preferred`). Floor only below weight 0.001.
|
|
59
|
+
|
|
60
|
+
**Resource Priority** — Scale 10 (priority 0–10; 0 = best, 10 = lowest), no rounding (raw decimal). `penaltyPerViolation_resource_priority = max(1, (weight_resource_priority/10.0) × 1000)`. At weight 1875 → 187,500 pts/priority point. Total for priority P: `P × penaltyPerViolation`. P=0 → 0; P=10 → max.
|
|
61
|
+
*Precision:* Full float, no rounding. Linear — priority 5 = 50% of max.
|
|
62
|
+
|
|
63
|
+
**Skill Level** — Scale 10 (10-point scale), roundInt. `penaltyPerViolation_skill_level = max(1, roundInt(1000×weight_skill_level/10))`. Total for skill level S: `S × penaltyPerViolation`.
|
|
64
|
+
*Applicability:* Multiple skill requirements → SFS averages the scores; none → no impact. Least vs. Most Qualified mode (set in the interview) flips the preferred direction but not the formula.
|
|
65
|
+
*Precision:* Zero rounding error for integer weights. Floor negligible below weight ≈0.01.
|
|
66
|
+
|
|
67
|
+
**Skill Preference** — Scale 10 (skill priority 1–10; 1 = most preferred, 10 = least; null → 10), roundInt. `penaltyPerViolation_skill_preference = max(1, roundInt(1000×weight_skill_preference/10))`. Total for skill priority SP: `SP × penaltyPerViolation`. Applicability (same Skill Type, OR matching on the companion Match Skills rule) set in the interview.
|
|
68
|
+
*Precision:* Zero rounding error for integer weights. Linear in SP.
|
|
69
|
+
|
|
70
|
+
**Group Nearby** — Scale 1 (binary — in cluster or not), round5, ×1.0 (full ×1000, unlike Same Site's ×0.01). `penaltyPerViolation_group_nearby = max(1, round5(1000×weight_group_nearby)) × 1.0`. At weight 167 → 167,000 pts/violation. Derivation: `weight_group_nearby = T_equiv × (weight_travel/120)` — with weight_travel 1000: `= T_equiv × 8.333`.
|
|
71
|
+
*Precision:* Flat per-event — each appointment outside its cluster costs the same regardless of distance.
|
|
72
|
+
|
|
73
|
+
**Minimize Gaps** — Scale 1 (each qualifying idle gap = 1 violation; configurable minimum 30 min–24 hr, shorter gaps uncounted), round. `penaltyPerViolation_gaps = max(1, round(1000×weight_gaps))`. Total: `Σ over routes/shifts [ clump_counter(route) × penaltyPerViolation ]`.
|
|
74
|
+
*Precision:* Zero rounding error for integer weights (exact form `clump_count × 1000 × weight_gaps`). Floor negligible below 0.001. Per gap per route — 3 gaps on one resource = 3× the rate.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Conversation Flow
|
|
79
|
+
|
|
80
|
+
**Two global rules (govern everything below):**
|
|
81
|
+
|
|
82
|
+
1. **Round every derived weight up to the next whole number** — ceiling, not nearest. SFS policies don't accept decimal weights.
|
|
83
|
+
2. **Never compare raw weight values across objectives** to infer priority — always compute and compare penalty rates per unit, since each has its own scale.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Step 1: Objective Selection
|
|
88
|
+
|
|
89
|
+
First explain the concept: service objectives are soft scoring criteria that grade candidates who already survived the work rules — unlike work rules, they never reject anyone, they just influence which eligible candidate the optimizer prefers. Note that Minimize Travel is always included automatically as the anchor, fixed weight 1000, that every other weight derives against through trade-off math — the user need not select it.
|
|
90
|
+
|
|
91
|
+
Then present the remaining nine objectives **one category at a time**, in this fixed order, waiting for the user's selection before the next category:
|
|
92
|
+
|
|
93
|
+
**1. Customer-Experience Objectives** — present these three, ask which (if any):
|
|
94
|
+
- ASAP — Serve customers as early as possible
|
|
95
|
+
- Same Site — If two jobs are at the same place, do them back-to-back
|
|
96
|
+
- Group Nearby — Cluster jobs that are geographically close
|
|
97
|
+
|
|
98
|
+
**2. Cost / Efficiency Objectives** — present these three (Travel already included), ask which of the remaining two:
|
|
99
|
+
- Minimize Travel *(already included automatically — anchor, fixed weight 1000)*
|
|
100
|
+
- Minimize Gaps — Keep technicians continuously busy
|
|
101
|
+
- Minimize Overtime — Avoid paying overtime
|
|
102
|
+
|
|
103
|
+
**3. Workforce / Assignment-Quality Objectives** — present these four, ask which (if any):
|
|
104
|
+
- Preferred Resource — Use the preferred/named technician when possible
|
|
105
|
+
- Resource Priority — Prefer higher-priority resources (e.g. staff over contractors)
|
|
106
|
+
- Skill Level — Match the right level of expertise to the job
|
|
107
|
+
- Skill Preference — Honor preference rankings within a skill type (e.g. language preference)
|
|
108
|
+
|
|
109
|
+
**Per-objective follow-up questions** — ask right after the category they belong to is answered, before the next category:
|
|
110
|
+
|
|
111
|
+
- **Minimize Travel** (always — ask once, at the end of the Cost/Efficiency category): "Should Minimize Travel also count the legs to/from a resource's home base — home to first job, and last job back home — or exclude those from scoring?" Capture as two independent flags: `excludeTravelFromHome` and `excludeTravelToHome` (true = excluded). Default both false.
|
|
112
|
+
- **Same Site** (only if selected): "Should Same Site treat two appointments as the same site only when they share the exact latitude/longitude — useful for campuses or farms — or use the default grouping (within about one second of travel time)?" Capture as `useExactLocation` (true = exact lat/long only). Default false.
|
|
113
|
+
- **Minimize Gaps** (only if selected): Ask the minimum idle duration the company counts as a gap (30 min–24 hr). Capture as `params.minGapMinutes`; default 30. Asking here keeps "what counts as a gap" separate from "how hard to close one."
|
|
114
|
+
|
|
115
|
+
Once all three categories are answered and follow-ups captured, move to Step 2.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Step 2: Trade-Off Interview
|
|
120
|
+
|
|
121
|
+
For each selected objective (other than Minimize Travel), ask a trade-off question. The goal is the crossover point where the user considers the two options equally acceptable — that equivalence lets you calculate the weight. After they answer, solve for the unknown weight by setting the two penalty expressions equal, then round up per the global rules. Always show the math.
|
|
122
|
+
|
|
123
|
+
**Offer concrete preset answers alongside the open question.** After the question template, give a few ready-made crossover points spanning light/medium/strong preference so the user can pick one instead of inventing numbers. Always also invite their own exact trade-off. Presets are a convenience, not a separate calculation path.
|
|
124
|
+
|
|
125
|
+
#### ASAP Trade-Off
|
|
126
|
+
|
|
127
|
+
> "If you could schedule an appointment right now but it would add [X] minutes of travel, versus scheduling it [Y] hours from now with no extra travel — at what point would those feel roughly equivalent to you?"
|
|
128
|
+
|
|
129
|
+
Presets: "15 min travel ≈ 12 hr delay" (mild), "30 min travel ≈ 24 hr delay" (moderate), "60 min travel ≈ 48 hr delay" (strong).
|
|
130
|
+
|
|
131
|
+
Math (user gives X min travel, Y hr delay):
|
|
132
|
+
```text
|
|
133
|
+
travel_penalty(X) = asap_penalty(Y × 60)
|
|
134
|
+
X × (weight_travel / 120) = (Y × 60) × (weight_asap / 43200)
|
|
135
|
+
weight_asap = X × weight_travel × 6 / Y
|
|
136
|
+
```
|
|
137
|
+
With weight_travel = 1000: `weight_asap = X × 6000 / Y`.
|
|
138
|
+
|
|
139
|
+
**Integer-formula validation:** confirm `penaltyPerViolation = max(1, round(1000 × weight_asap / 43200))`, then `effective_weight = penaltyPerViolation × 43200 / 1000`. If it differs meaningfully (>5% drift), note it.
|
|
140
|
+
**Low-weight warning:** if derived weight < 22, warn that SFS clamps the per-minute penalty to 1 (the floor) — any weight 1–21 produces identical behavior.
|
|
141
|
+
|
|
142
|
+
#### Same Site Trade-Off
|
|
143
|
+
|
|
144
|
+
**Framing note:** Do NOT compare Same Site to travel time — same-site appointments are already at the same location. Compare against ASAP or Preferred Resource (if selected).
|
|
145
|
+
|
|
146
|
+
**If ASAP is selected — Same Site vs. ASAP:**
|
|
147
|
+
> "Imagine two appointments at the same site. The optimizer can either: (A) Assign both to the same resource, but they get scheduled [H] hours later than they could be. (B) Split them so they're scheduled right now. At what scheduling delay would you say 'just split them'?"
|
|
148
|
+
|
|
149
|
+
Presets: "keep together up to 1 hr later" (weak), "up to 4 hr later" (moderate), "up to 8 hr later" (strong).
|
|
150
|
+
Math (delay threshold D_equiv in minutes): `weight_same_site = D_equiv × (weight_asap / 43200)`
|
|
151
|
+
|
|
152
|
+
**If ASAP NOT selected but Preferred Resource IS — Same Site vs. Preferred Resource:**
|
|
153
|
+
> "Would you split same-site appointments (different resources) to honor a preferred resource assignment for one of them? Or keep them together even if it means ignoring the preferred resource?"
|
|
154
|
+
|
|
155
|
+
Presets: "equally important" (F = 1), "same-site matters twice as much" (F = 2), "same-site matters half as much" (F = 0.5).
|
|
156
|
+
Math: `weight_same_site = F × weight_preferred`
|
|
157
|
+
|
|
158
|
+
**If neither is selected — fall back to travel comparison:**
|
|
159
|
+
> "How many minutes of extra travel would make it worth splitting same-site appointments?"
|
|
160
|
+
|
|
161
|
+
Presets: "10 extra minutes", "20 extra minutes", "30 extra minutes".
|
|
162
|
+
Math: `weight_same_site = T_equiv × (weight_travel / 120)`
|
|
163
|
+
|
|
164
|
+
#### Minimize Overtime Trade-Off
|
|
165
|
+
|
|
166
|
+
> "If an appointment could be scheduled now but it would use [Z] minutes of overtime, versus scheduling it [H] hours from now during regular hours — when would those feel equally acceptable?"
|
|
167
|
+
|
|
168
|
+
Presets: "15 min OT ≈ 6 hr delay" (avoid OT strongly), "30 min OT ≈ 12 hr delay" (moderate), "60 min OT ≈ 24 hr delay" (accept OT readily).
|
|
169
|
+
|
|
170
|
+
Math (user gives Z min overtime, H hr delay):
|
|
171
|
+
```text
|
|
172
|
+
overtime_penalty(Z) = asap_penalty(H × 60)
|
|
173
|
+
Z × (weight_overtime / 120) = (H × 60) × (weight_asap / 43200)
|
|
174
|
+
weight_overtime = weight_asap × H / (Z × 6)
|
|
175
|
+
```
|
|
176
|
+
*Requires weight_asap calculated first if ASAP is selected.*
|
|
177
|
+
**Overtime vs. Travel fallback (if ASAP not selected):** `weight_overtime = T_equiv × weight_travel / Z`, where T is the equivalent minutes of travel the user would accept over Z minutes of overtime.
|
|
178
|
+
|
|
179
|
+
#### Preferred Resource Trade-Off
|
|
180
|
+
|
|
181
|
+
> "If an appointment has a preferred resource assigned, how important is honoring it? Imagine the preferred resource is available but would require [X] extra minutes of travel — at what point would you say 'just use the closer resource'?"
|
|
182
|
+
|
|
183
|
+
Presets: "15 extra minutes" (light), "30 extra minutes" (moderate), "60 extra minutes" (strong).
|
|
184
|
+
Math (travel threshold T_equiv): `weight_preferred = T_equiv × (weight_travel / 120)`
|
|
185
|
+
|
|
186
|
+
#### Group Nearby Trade-Off
|
|
187
|
+
|
|
188
|
+
**Framing note:** Group Nearby and Minimize Travel are natural competitors — keeping a cluster intact may cost more total travel than breaking it. The trade-off: the maximum additional overall travel the user will spend to keep a cluster together.
|
|
189
|
+
|
|
190
|
+
> "What is the maximum additional overall travel you'd add to the schedule to keep all appointments in a cluster together? If maintaining the cluster costs more than that, the optimizer should break it and save the travel instead."
|
|
191
|
+
|
|
192
|
+
Presets: "10 extra minutes" (weak), "20 extra minutes" (moderate), "30 extra minutes" (strong).
|
|
193
|
+
Math (T_equiv): `weight_group_nearby = T_equiv × (weight_travel / 120)`
|
|
194
|
+
|
|
195
|
+
#### Resource Priority Trade-Off
|
|
196
|
+
|
|
197
|
+
Background to share:
|
|
198
|
+
> "The Resource Priority objective ranks service resources on a scale of 0–10, where 0 = highest priority (best candidate) and 10 = lowest. For example, assign internal staff priority 1 and contractors 5+. The optimizer applies a penalty proportional to priority — a priority 5 resource incurs 50% of the full weight as penalty, a priority 0 resource none."
|
|
199
|
+
|
|
200
|
+
> "Imagine two available resources: Resource A is a staff technician (priority 1) but is [X] minutes further away. Resource B is a contractor (priority [P]) and is the closer option. At what point would you say 'just use the contractor'?"
|
|
201
|
+
|
|
202
|
+
Presets: "30 extra minutes" (mild), "60 extra minutes" (moderate), "90 extra minutes" (strong).
|
|
203
|
+
Math (T_equiv, staff priority P_high = 1, contractor priority P_low): `weight_resource_priority = T_equiv × weight_travel / (12 × (P_low - P_high))`
|
|
204
|
+
Example (staff 1, contractor 5, T_equiv = 90): `= 90 × 1000 / (12 × 4) = 1,875`
|
|
205
|
+
|
|
206
|
+
#### Skill Level Trade-Off
|
|
207
|
+
|
|
208
|
+
Background to share:
|
|
209
|
+
> "The Skill Level objective steers the optimizer toward either the least or most qualified resource that meets an appointment's skill requirements. Penalty = the resource's raw skill level × the objective weight — so a skill level 8 resource incurs 8× the penalty of a skill level 1 resource."
|
|
210
|
+
|
|
211
|
+
**Step 1 — Ask which mode they want:**
|
|
212
|
+
> "Which mode would you like?
|
|
213
|
+
> - **Least Qualified** — prefers the lowest-skilled resource that still meets the requirement. Good for preserving senior resources for complex jobs, or keeping costs down.
|
|
214
|
+
> - **Most Qualified** — prefers the highest-skilled resource available. Good for first-time fix rates or when outcome quality is the priority."
|
|
215
|
+
|
|
216
|
+
**If Least Qualified:**
|
|
217
|
+
> "Imagine two eligible resources: a junior technician (skill level [S_low]) and a senior technician (skill level [S_high]). The senior tech is closer. In Least Qualified mode, the optimizer prefers the junior tech to preserve the senior for harder jobs. How many extra minutes of travel would you accept to route the junior tech?"
|
|
218
|
+
|
|
219
|
+
**If Most Qualified:**
|
|
220
|
+
> "Imagine two eligible resources: a junior technician (skill level [S_low]) and a senior technician (skill level [S_high]). The junior tech is closer. In Most Qualified mode, the optimizer prefers the senior tech. How many extra minutes of travel would you accept to route the senior tech?"
|
|
221
|
+
|
|
222
|
+
Presets: "15 extra minutes" (mild), "30 extra minutes" (moderate), "45 extra minutes" (strong).
|
|
223
|
+
Math (same formula regardless of mode): `weight_skill_level = T_equiv × weight_travel / (120 × (S_high - S_low))`
|
|
224
|
+
Example — Least Qualified (S_low=4, S_high=8, T_equiv=30): `= 30 × 1000 / (120 × 4) = 62.5 → round up to 63`
|
|
225
|
+
|
|
226
|
+
#### Skill Preference Trade-Off
|
|
227
|
+
|
|
228
|
+
Background to share:
|
|
229
|
+
> "The Skill Preference objective applies when a work order has multiple skill requirements of the same Skill Type and a preference exists for one over another. Each requirement has a Skill Priority from 1 to 10 — 1 = most preferred, 10 = least. The optimizer assigns a penalty proportional to that priority."
|
|
230
|
+
|
|
231
|
+
Also capture the **Skill Type** (required for this objective to function). Skill Preference operates on one Skill Type — the family of skills (e.g. Language) evaluated by a companion Match Skills rule with "At Least One Skill Matches (OR)." Ask:
|
|
232
|
+
> "Which Skill Type does this preference rank within? Give me its Developer Name — it must match a Match Skills work rule set to At Least One Skill Matches (OR), since Skill Preference has no effect under AND matching."
|
|
233
|
+
|
|
234
|
+
Record as `skillType` param (Skill Type Developer Name, maps to `{ns}Skill_Type__c` on the goal). Flag if no Match Skills rule with OR logic exists for that Skill Type.
|
|
235
|
+
|
|
236
|
+
> "Imagine a work order where the customer can be served by either a [Skill A]-speaking technician (skill priority [SP_high_pref]) or a [Skill B]-speaking technician (skill priority [SP_low_pref]), but they prefer [Skill A]. The [Skill A] technician is further away. How many extra minutes of travel would you accept to assign the [Skill A] technician?"
|
|
237
|
+
|
|
238
|
+
Presets: "15 extra minutes" (mild), "30 extra minutes" (moderate), "45 extra minutes" (strong).
|
|
239
|
+
Math (T_equiv, SP_high_pref = more preferred, SP_low_pref = less preferred): `weight_skill_preference = T_equiv × weight_travel / (12 × (SP_low_pref - SP_high_pref))`
|
|
240
|
+
Example (Spanish priority 1, English priority 6, T_equiv = 45): `= 45 × 1000 / (12 × 5) = 750`
|
|
241
|
+
|
|
242
|
+
#### Minimize Gaps Trade-Off
|
|
243
|
+
|
|
244
|
+
*Minimum gap duration was captured in Step 1 as `params.minGapMinutes` (default 30). Don't re-ask it. This section covers the weight math only.*
|
|
245
|
+
|
|
246
|
+
**If ASAP is selected — Minimize Gaps vs. ASAP:**
|
|
247
|
+
> "The optimizer can either: (A) Leave a gap in a technician's schedule and schedule a new appointment [H] hours earlier. (B) Compress the schedule to eliminate the gap, but that appointment gets scheduled [H] hours later. At what scheduling delay would you say 'just leave the gap and schedule earlier'?"
|
|
248
|
+
|
|
249
|
+
Presets: "leave the gap up to 2 hr later" (weak), "up to 4 hr later" (moderate), "up to 8 hr later" (strong).
|
|
250
|
+
Math (delay threshold D_equiv in minutes): `weight_gaps = D_equiv × weight_asap / 43200`
|
|
251
|
+
Example (weight_asap = 250, D_equiv = 240 min): `= 240 × 250 / 43200 = 1.389 → ceil to 2`
|
|
252
|
+
|
|
253
|
+
**If ASAP NOT selected — fall back to travel comparison:**
|
|
254
|
+
> "How many extra minutes of travel across the schedule would make it worth leaving a gap in a technician's shift rather than compressing it?"
|
|
255
|
+
|
|
256
|
+
Presets: "10 extra minutes", "20 extra minutes", "30 extra minutes".
|
|
257
|
+
Math: `weight_gaps = T_equiv × weight_travel / 7200`
|
|
258
|
+
|
|
259
|
+
**Integer-formula validation:** confirm `penaltyPerViolation = max(1, round(1000 × weight_gaps))`. For very small weights (below ~1), warn that the max(1,…) floor clamps the penalty to a fixed minimum.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### Step 3: Output the Results
|
|
264
|
+
|
|
265
|
+
After all trade-off questions are answered, present a table with one row per included objective, showing its **weight** and a one-line **rationale** grounded in the trade-off the user stated (e.g. for ASAP, the "X min travel ≈ Y hours delay" crossover; for Same Site, "1 split ≈ D min of ASAP delay"). Minimize Travel is always the anchor at 1000.
|
|
266
|
+
|
|
267
|
+
Then describe **relative priority** using penalty points per unit — not raw weights, since the multiplier differs per objective. Compute each rate: Travel `weight_travel / 120` per minute; ASAP `weight_asap / 43200` per minute of delay; Overtime `weight_overtime / 120` per minute; Same Site `weight_same_site × 10` per event (×10, not ×1000); Preferred Resource and Group Nearby `weight × 1000` per event; Resource Priority `weight × (P / 10)` per resource (show a couple priority tiers); Skill Level `S × weight × 100` per resource (show relevant tiers); Skill Preference `weight × (SP / 10)` per skill assignment (show relevant priorities); Minimize Gaps `weight_gaps × 1000` per gap (flat).
|
|
268
|
+
|
|
269
|
+
Use these rates to explain relative priority in plain English, grounded in the trade-offs the user stated — e.g. "This reflects your stated preference that 30 minutes of travel equals 12 hours of delay." Offer to re-run any trade-off if the implied priority doesn't match expectations.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### Policy at a Glance (2–3 sentence business summary)
|
|
274
|
+
|
|
275
|
+
After presenting the weights and penalty rates, generate a 2–3 sentence business summary of what this policy is optimized for — an executive soundbite for stakeholders who don't care about the math.
|
|
276
|
+
|
|
277
|
+
**How to generate it:**
|
|
278
|
+
|
|
279
|
+
1. Classify objectives by category: Customer-experience (ASAP, Same Site, Group Nearby); Cost/efficiency (Minimize Travel, Gaps, Overtime); Workforce/assignment-quality (Preferred Resource, Resource Priority, Skill Level, Skill Preference).
|
|
280
|
+
2. Determine which category dominates by comparing penalty rates across objectives — the one generating the largest penalties is the dominant focus.
|
|
281
|
+
3. Identify key trade-off tensions from the crossover values stated — these reveal what the policy will sacrifice for what.
|
|
282
|
+
4. Write 2–3 sentences naming a primary and secondary focus, what the optimizer prioritizes in plain terms, and what it's willing to sacrifice up to what threshold. If one category's penalties are 5×+ higher, call it out; if two are close, name them co-primary. Only call the policy "Balanced" if all three categories are within ~2× of each other.
|
|
283
|
+
|
|
284
|
+
Use business language grounded in the user's stated values (e.g. "drive up to 2 hours," "wait up to 24 hours") — not penalty math. This summary is also a good candidate for the scheduling policy record's Description field — suggest it at handoff.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Emit the Build Spec (handoff contract)
|
|
289
|
+
|
|
290
|
+
The skill's final deliverable and sole contract with `sfs-sobject-create`. Once the design is settled (policy settings, work rules, relevance groups, weights), emit **one** structured build spec as a JSON object. `sfs-sobject-create` consumes it verbatim and never re-derives intent, so it must be complete and self-contained.
|
|
291
|
+
|
|
292
|
+
**Hard boundary:** emit **design values only** — no Salesforce object names, field API names, record IDs, composite-API reference syntax, or creation ordering (those belong to the data-layer skill). Describe rules/objectives by their canonical type name (exactly as used here: e.g. Match Skills, Minimize Travel, Resource Priority) plus business parameters. Unknown parameter → omit or mark null; never invent an API field name.
|
|
293
|
+
|
|
294
|
+
```jsonc
|
|
295
|
+
{
|
|
296
|
+
"specVersion": "1.0",
|
|
297
|
+
"policy": {
|
|
298
|
+
"name": "scheduling policy name",
|
|
299
|
+
"description": "free text; include the Policy at a Glance summary",
|
|
300
|
+
"inDayOptimization": true,
|
|
301
|
+
"commitMode": "Always Commit | Rollback"
|
|
302
|
+
},
|
|
303
|
+
"workRules": [{
|
|
304
|
+
"type": "canonical work rule type name (e.g. 'Match Skills')",
|
|
305
|
+
"name": "instance display name — see naming convention below",
|
|
306
|
+
"mandatory": false,
|
|
307
|
+
"params": {}, // business params only. e.g. Service Resource Availability absolute break: breaks:[{mode:'absolute',startClock:'12:00',durationMinutes:30}]; offset break: breaks:[{mode:'offset',earliestStartOffsetMinutes:180,latestEndOffsetMinutes:210,durationMinutes:30}]; Maximum Travel from Home: maxTravelFromHome:45, maxTravelFromHomeType:'Travel Time'
|
|
308
|
+
"relevanceGroup": { "basis": "Service Appointment | Service Territory Member | null", "booleanField": "scoping Boolean field name, or null for policy-wide" }
|
|
309
|
+
}],
|
|
310
|
+
"serviceObjectives": [{
|
|
311
|
+
"type": "canonical objective type name (e.g. 'Minimize Travel')",
|
|
312
|
+
"name": "instance display name — see naming convention below",
|
|
313
|
+
"weight": 1000,
|
|
314
|
+
"params": {}, // objective-specific business params. e.g. Skill Level mode:'Least Qualified'; Resource Priority priorityField concept; Minimize Gaps minGapMinutes; Skill Preference skillType; Minimize Travel excludeTravelFromHome/excludeTravelToHome; Same Site useExactLocation
|
|
315
|
+
"relevanceGroup": { "basis": "Service Appointment | Service Territory Member | null", "booleanField": "scoping Boolean field name, or null for policy-wide" },
|
|
316
|
+
"rationale": "one-line human trace of how the weight was derived (the stated crossover)"
|
|
317
|
+
}],
|
|
318
|
+
"prerequisites": ["human-readable notes the user must satisfy before deployment — e.g. 'Create Boolean field Break_Group_France__c on Service Territory Member and set it true for French resources.'"],
|
|
319
|
+
"notes": "caveats, low-weight-floor warnings, or unresolved ambiguities"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Rules for emitting:**
|
|
324
|
+
|
|
325
|
+
1. **Naming convention** (every rule and objective): `name` = canonical type name + abbreviated policy name, separated by `" - "` — e.g. policy "First test on shorter" → "Match Skills - First test", "Minimize Travel - First test". Abbreviate the policy name (drop filler like "on shorter"/"policy"); don't append it verbatim. *Exception:* the two Arrival Window Match Time rules keep their own names — append the abbreviated policy name rather than replacing with the type: "Arrival Window Start - First test", "Arrival Window End - First test".
|
|
326
|
+
2. **Always include** Minimize Travel at weight 1000 and exactly one Service Resource Availability rule with `"mandatory": true`, even if never discussed. Never emit Earliest Start Permitted or Due Date.
|
|
327
|
+
3. **One instance per subset** — if a rule/objective was split across relevance groups, emit a separate entry per group, each with its own `relevanceGroup`.
|
|
328
|
+
4. **Weights are whole numbers**, already ceiling-rounded per the Conversation Flow rules.
|
|
329
|
+
5. **Parameters are business values, not field mappings** — "a 30-min break between 12:00 and 15:00" goes in as durations/times; the field mapping lives in the data-layer skill.
|
|
330
|
+
6. **Surface prerequisites explicitly** — any relevance-group Boolean field, custom priority field, or skill-type dependency the design assumes goes in `prerequisites` so the user and data-layer skill know it must pre-exist.
|
|
331
|
+
7. **Show the spec and get approval before handing off.** Present the complete build spec as a JSON code block and ask plainly whether it looks good (e.g. "Here's the full build spec — does this look right, or would you like to change anything?"). Wait for explicit confirmation; apply requested edits and re-show. Only after approval proceed to handoff.
|
|
332
|
+
8. **Don't create anything.** Once approved, tell the user it's ready for `sfs-sobject-create`. If asked to "build/deploy/create it," finalize the spec — don't perform CRUD.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Handoff
|
|
337
|
+
|
|
338
|
+
When the user confirms the weights and summary, the objective design phase is complete. **Delegate to `sfs-sobject-create`**, passing the build spec above (with the `policy` from `sfs-scheduling-policy-designer`, `workRules` from `sfs-work-rule-designer`, and `serviceObjectives` from this skill) so it can create the scheduling policy, work rules, and service objectives in dependency order.
|
|
339
|
+
|
|
340
|
+
Do not create records here or start the creation flow — `sfs-sobject-create` owns all Salesforce API calls. Transition message:
|
|
341
|
+
|
|
342
|
+
> "Objective design is complete. Next, I'll hand off to record creation, which will create all the Salesforce records — the scheduling policy, work rules, and service objectives — in the correct order. Ready to proceed?"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: field-service-prework-brief-deployer-configure
|
|
3
|
+
description: "Deploy the deterministic half of Einstein Pre-Work Brief on Field Service Mobile to a target org — prompt template, Lightning Data Service, licenses and permission sets, the Work Order layout field, and a scheduled test Work Order. Use this skill when a user asks to deploy, set up, enable, or configure Einstein Pre-Work Brief on Field Service Mobile."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
metadata:
|
|
6
|
+
version: "1.0"
|
|
7
|
+
domains: ["Field Service"]
|
|
8
|
+
cliTools:
|
|
9
|
+
- tool: ["sf"]
|
|
10
|
+
semver: ">=2.0.0"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Field Service Pre-Work Brief Deployer
|
|
14
|
+
|
|
15
|
+
**Deploy the deterministic half of Einstein Pre-Work Brief on Field Service Mobile via `execute_api`.**
|
|
16
|
+
|
|
17
|
+
This skill takes an org whose Einstein for Field Service add-on is already provisioned and performs every automatable step to stand up Pre-Work Brief: it deploys three metadata artifacts, assigns licenses and permission sets, exposes the Work Order field on the technician's layout, wires a test Work Order scheduled in today's window, and **activates the prompt template via the Connect API**. The workflow is fully automatable end-to-end — there are no irreducibly manual steps. On-device rendering verification (confirming grounding produces job-specific content) is owned by the coordinating Pre-Work Brief skill, not this deploy primitive.
|
|
18
|
+
|
|
19
|
+
This skill is the **judgment-free deploy primitive.** Org diagnosis / routing (STOP if unprovisioned), technician selection, and the fresh-vs-existing test-data decision are resolved by the coordinating skill and passed in as inputs / a choice point.
|
|
20
|
+
|
|
21
|
+
## What it does
|
|
22
|
+
|
|
23
|
+
- **Prompt template** — deploys the `einstein_gpt__fieldServicePreWorkBrief` GenAiPromptTemplate (`Pre_Work_Brief`) as Published.
|
|
24
|
+
- **Lightning Data Service** — enables Lightning Data Service on the Field Service settings (the `lsdkForFieldServiceMobilePref` org preference; without LDS the brief renders blank on mobile).
|
|
25
|
+
- **Licenses + permission sets** — assigns the Einstein for Field Service PSL + permission sets to an admin and a pilot technician.
|
|
26
|
+
- **Field-level access** — deploys `PreWorkBrief_Field_Access` and exposes `WorkOrder.PreWorkBriefPromptTemplate` on the technician's layout with FLS.
|
|
27
|
+
- **Test data** — creates a test Work Order + Service Appointment + Assigned Resource scheduled in today's window, pointed at the deployed template (or points an existing Work Order at it).
|
|
28
|
+
|
|
29
|
+
## Inputs
|
|
30
|
+
|
|
31
|
+
- **Target org** — an org whose Einstein for Field Service add-on is provisioned. If the add-on is absent the org is unprovisioned — **STOP**; the coordinating skill owns this routing.
|
|
32
|
+
- **Pilot technician** — username + user Id, selected by the coordinating skill, with an active `ServiceResource`.
|
|
33
|
+
- **Test-data mode** — `fresh` (create a clean test Work Order chain) or `existing` (point a supplied real Work Order at the template and move its Service Appointment into today's window). Default `fresh`.
|
|
34
|
+
|
|
35
|
+
## Preconditions
|
|
36
|
+
|
|
37
|
+
- The Einstein for Field Service add-on is provisioned (the Einstein for Field Service permission-set license is present).
|
|
38
|
+
- The admin holds Customize Application + Manage Profiles and Permission Sets.
|
|
39
|
+
- Einstein generative AI base setup (including Data 360 grounding) is complete on the org.
|
|
40
|
+
- A pilot technician has been selected and has an active `ServiceResource`.
|
|
41
|
+
|
|
42
|
+
## Happy path (fresh test data)
|
|
43
|
+
|
|
44
|
+
Verify provisioning → assign licenses and permission sets → verify permset assignments → detect existing template → deploy prompt template → verify template deployed → read Field Service settings → deploy LDS setting → verify LDS enabled → deploy field-access permset → read Work Order layout → add field to layout → verify field on layout → create test Work Order → create Service Appointment → assign resource → verify test Work Order scheduled → **resolve template version → activate prompt template (Connect API) → verify activation**.
|
|
45
|
+
|
|
46
|
+
**Existing-test-WO branch** — when test-data-mode is `existing`, skip create-test-workorder / create-service-appointment / assign-resource and run point-existing-workorder instead: update a real Work Order's `PreWorkBriefPromptTemplate` and move its Service Appointment into today's window.
|
|
47
|
+
|
|
48
|
+
Every step is idempotent — it checks org state before it writes, so re-running applies zero changes.
|
|
49
|
+
|
|
50
|
+
## Ordering is load-bearing
|
|
51
|
+
|
|
52
|
+
- **Assign the admin permission sets (including `EinsteinGPTPromptTemplateManager`) BEFORE deploying the prompt template.** If the admin lacks it, the Pre-Work Brief template type silently vanishes from Prompt Builder and the deploy fails with no error message — just a missing dropdown option.
|
|
53
|
+
- **Enable LDS (`lsdkForFieldServiceMobilePref`) before on-device use** or the brief renders blank on mobile.
|
|
54
|
+
|
|
55
|
+
## Gotchas
|
|
56
|
+
|
|
57
|
+
- **`GenAiPromptTemplate` is NOT SOQL/REST queryable.** Detect it and resolve its `0hf`-prefixed Id via the Metadata deep-read by name (`type=GenAiPromptTemplate`, `fullName=Pre_Work_Brief`), not a query.
|
|
58
|
+
- **Send the template deploy bodies as JSON objects, not serialized strings.**
|
|
59
|
+
- **Whole-record write footguns — two different mechanisms, neither a raw MDAPI deploy.** `deploy-lds-setting` and `add-field-to-layout` both write existing whole records, but differently. **LDS** goes through the Field Service settings controller (`saveFieldServiceSettingsConfig`): a per-field `isChanged<Field>` partial-patch whose body **must be wrapped under a top-level `userSettings` key** — `{"userSettings": {"lsdkForFieldServiceMobilePref": true, "isChangedLsdkForFieldServiceMobilePref": true}}`. A flat body (fields at the top level) returns **500 `CONTROLLER_ERROR` NullPointerException (`userSettings is null`)**. **The Work Order layout** is a **Tooling API `Layout.Metadata` round-trip** that IS full-replace: read the current Metadata first (a Tooling `Layout` GET — `read-workorder-layout`'s `describe/layouts` is only for checking placement), add only the new field, and PATCH the whole object back (omitted keys reset to default). On the write, null out `feedLayout` and drop the `ServiceReportRelatedList` related list or the PATCH 400s (see the layout step's notes). This is NOT SOAP MDAPI and NOT `/headless/metadata` (unrouted) — it's Tooling REST over dispatch passthrough.
|
|
60
|
+
|
|
61
|
+
## Activate the prompt template (Connect API)
|
|
62
|
+
|
|
63
|
+
The template deploys **Published, not Active** — until it is activated it does not appear in the runtime catalog and the mobile app fails with *"We hit a snag."* Activation IS programmatic via the Connect API (available since v65.0 / API 258):
|
|
64
|
+
|
|
65
|
+
```http
|
|
66
|
+
PUT /services/data/v67.0/einstein/prompt-templates/{devName}/versions/{versionId}/status?action=activate&ignoreWarnings=false
|
|
67
|
+
Body: {}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **Resolve the `versionId` first.** GET `/services/data/v67.0/einstein/prompt-templates/{devName}` and read `childRelationships.GenAiPromptTemplateVersions[].fields.Id.value` (the `3vN`-prefixed version Id). This GET works even while the template is inactive/absent from the catalog.
|
|
71
|
+
- On success the response is `isSuccessful:true`, `statusCode:"200"`, with an `additionalData.wrappedMap.summary.overallSeverity` of `SAFE`. The template-level `IsActive` flips to `true` and `ActiveVersionId` is populated (the version's own `Status` stays `Published` — "Published" at the version level *is* "Active" at the template level).
|
|
72
|
+
- **Not callable from Apex.** The endpoint is `@ConnectHidden(from=Apex)`; call it through `execute_api` (or `sf api request rest --method PUT`), not `ConnectApi`. This is why the earlier Apex `ConnectApi.EinsteinLLM` and Tooling/metadata attempts failed — along with a wrong URL shape (`/activate` rather than `/versions/{id}/status?action=activate`) and too-early API versions (v62–v66).
|
|
73
|
+
- `verify-activation` — a runtime prompt-template-catalog read (`GET /einstein/prompt-templates?pageSize=200`, confirm `Pre_Work_Brief` now appears) — is chained immediately after to confirm activation landed.
|
|
74
|
+
|
|
75
|
+
*Live-verified against a non-prod org 2026-07-22: `IsActive` `False`→`True`, `ActiveVersionId` null→populated, and the template appeared in the runtime catalog on the same call.*
|
|
76
|
+
|
|
77
|
+
## Scope boundary — on-device rendering
|
|
78
|
+
|
|
79
|
+
On-device verification (the technician opening the Field Service mobile app and confirming the brief renders job-specific content in the Overview tab) is **out of scope for this deploy primitive** and owned by the coordinating Pre-Work Brief skill. Note that `verify-activation` confirms the template is Active, but only on-device rendering confirms Data 360 grounding is actually producing job-specific content — a distinct check the coordinating skill is responsible for. There is no headless surface that returns what the technician sees on the device.
|
|
80
|
+
|
|
81
|
+
## Source
|
|
82
|
+
|
|
83
|
+
Authored from the sf-skills-internal coordinating skill `field-service-prework-brief-configure` (+ its `references/` files) and Salesforce Help for Einstein Pre-Work Brief. Live-validated against a dispatcher org. The harness derives the ordered, typed SOR from this skill on each run.
|