| name | field-service-objective-designer-configure |
| 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. |
| user-invocable | false |
| owning_team | sfs-setup-experience |
| metadata | {"version":"1.0","domains":["Field Service"],"cliTools":[{"tool":"[Truncated]","semver":">=2.0.0"}]} |
Managing Sfs Service Objective Designer
When to Use This Skill
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. Called after work rule design; delegates to sfs-sobject-create for record creation.
Workflow
Salesforce Field Service – Service Objective Designer
Designs the service objectives for a scheduling policy. This skill collects the objective selection and derives each objective's weight through a structured trade-off interview — one question at a time — and emits a serviceObjectives[] block as output. It does not create any Salesforce records. When complete, delegates to sfs-sobject-create.
Interview phases: Scheduling Policy → Work Rules → Service Objectives (this skill) → Record Creation
This skill covers the service objective phase only. The policy block (from sfs-scheduling-policy-designer) and the workRules[] block (from sfs-work-rule-designer) arrive as context; this skill adds serviceObjectives[] and hands the complete design to sfs-sobject-create.
Background: How SFS Scoring Works
The optimizer assigns penalty points to each candidate schedule. Lower total penalty = better schedule. Each service objective contributes penalty points based on its weight and its own scale (the worst-case scenario for that objective).
Minimize Travel weight is the anchor — its value is set at 1000 and all other weights are derived relative to it. The general formula, common to every objective (stated once — not re-derived per objective):
penaltyPerViolation = max( 1, roundingFn( (1000 × weight) / scale ) ) × finalMultiplier
total_penalty = ceil( violations / granularity ) × penaltyPerViolation
Two consequences of the shared ×1000 internal multiplier:
- It cancels out of every derivation between two objectives — which is why the continuous approximations used in the interview stay valid and every "derive weight_X from weight_Y" formula is clean of any ×1000 term. Where the rounding function isn't exact for integer weights (ASAP's round, Skill Level/Preference's roundInt), small drift is possible — flagged per objective below.
- The
max(1, …) floor guarantees every included objective has some effect even at a very low weight.
One exception: Same Site's final multiplier (×0.01) nets to an effective ×10, not ×1000 — the one place the "just divide by the other objective's rate" shortcut needs adjustment.
Penalty Formulas by Objective
Use these to show math and back-calculate weights. General mechanics (×1000, floor, why derivations stay valid) are above and not repeated.
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.
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 weight ≈0.12.
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.
Precision: round5 exact for integer weights. Flat per-event regardless of time. Effective multiplier is ×10, not ×1000.
Minimize Overtime — Identical mechanics to Minimize Travel (scale 120, round5, weight 1000 → 8333.33333) except two differences: 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 a 120-sec one. penaltyPerViolation_overtime = max(1, round5(1000×weight_overtime/120)) × 1.0. Continuous: ≈ Z × weight_overtime/120; comparable to Travel.
Precision: Floor binds below weight ≈0.12.
Preferred Resource — Scale 1 (binary), roundInt, ×1.0. penaltyPerViolation_preferred = max(1, roundInt(100 × 10.0 × weight_preferred)) × 1.0 (100×10.0 = 1000, just decomposed). At weight 375 → 375,000 pts/violation. Derivation: weight_preferred = T_equiv × (weight_travel/120) — with weight_travel 1000: = T_equiv × 8.333.
Precision: Zero rounding error for integer weights (exact form X_violations × 1000 × weight_preferred). Floor only below weight 0.001.
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.
Precision: Full float, no rounding. Linear — priority 5 = 50% of max.
Skill Level — Scale 10 (10-point skill scale), roundInt. penaltyPerViolation_skill_level = max(1, roundInt(1000×weight_skill_level/10)). Total for skill level S: S × penaltyPerViolation.
Applicability: Multiple skill requirements → SFS averages the scores; no skill requirements → no impact. Least vs. Most Qualified mode (handled in the interview) flips the preferred direction but not the formula.
Precision: Zero rounding error for integer weights. Floor negligible below weight ≈0.01.
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) handled in the interview.
Precision: Zero rounding error for integer weights. Linear in SP.
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.
Precision: Flat per-event — each appointment outside its cluster costs the same regardless of distance.
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 ].
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.
Conversation Flow
Two global rules (govern everything below):
Phase 1: **Round every derived weight up to the next...
Phase 2: Never compare raw weight values across objectives to...
Step 1: Objective Selection
First explain the concept: service objectives are soft scoring criteria that grade candidates who already survived the work rules — unlike work rules, objectives never reject anyone, they just influence which eligible candidate the optimizer prefers. Tell the user that Minimize Travel is always included automatically as the anchor objective, with a fixed weight of 1000 that every other objective's weight gets derived against through trade-off math — they don't need to select it.
Then present and ask about the remaining nine objectives one category at a time, in this fixed order, waiting for the user's selection before moving to the next category:
1. Customer-Experience Objectives — present these three, ask which (if any):
- ASAP — Serve customers as early as possible
- Same Site — If two jobs are at the same place, do them back-to-back
- Group Nearby — Cluster jobs that are geographically close
2. Cost / Efficiency Objectives — present these three (Travel already included), ask which of the remaining two:
- Minimize Travel (already included automatically — anchor, fixed weight 1000)
- Minimize Gaps — Keep technicians continuously busy
- Minimize Overtime — Avoid paying overtime
3. Workforce / Assignment-Quality Objectives — present these four, ask which (if any):
- Preferred Resource — Use the preferred/named technician when possible
- Resource Priority — Prefer higher-priority resources (e.g. staff over contractors)
- Skill Level — Match the right level of expertise to the job
- Skill Preference — Honor preference rankings within a skill type (e.g. language preference)
Per-objective follow-up questions — ask right after the category they belong to is answered, before the next category:
- Minimize Travel (always — ask once, at the end of the Cost/Efficiency category): "Should Minimize Travel also count the legs to and from a resource's home base — the drive from home to the first job, and from the last job back home — or should those legs be excluded from scoring?" Capture as two independent flags:
excludeTravelFromHome and excludeTravelToHome (true = excluded). Default both false.
- Same Site (only if selected): "Should Same Site treat two appointments as the same site only when they share the exact same latitude/longitude — useful for campuses or farms — or use the default grouping (appointments within about one second of travel time)?" Capture as
useExactLocation (true = exact lat/long only). Default false.
- Minimize Gaps (only if selected): Ask for the minimum idle duration their 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."
Once all three categories are answered and follow-ups captured, move to Step 2.
Step 2: Trade-Off Interview
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 is what lets you calculate the weight. After the user answers, solve for the unknown weight by setting the two penalty expressions equal, then round up per the global rules. Always show the math.
Offer concrete preset answers alongside the open question. After the question template, give a small set of ready-made crossover points spanning light/medium/strong preference — so the user can pick one instead of inventing numbers. Always also invite them to describe their own exact trade-off. Presets are a convenience, not a separate calculation path.
ASAP Trade-Off
"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?"
Presets: "15 min travel ≈ 12 hr delay" (mild), "30 min travel ≈ 24 hr delay" (moderate), "60 min travel ≈ 48 hr delay" (strong).
Math (user gives X min travel, Y hr delay):
travel_penalty(X) = asap_penalty(Y × 60)
X × (weight_travel / 120) = (Y × 60) × (weight_asap / 43200)
weight_asap = X × weight_travel × 6 / Y
With weight_travel = 1000: weight_asap = X × 6000 / Y.
Integer-formula validation: confirm penaltyPerViolation = max(1, round(1000 × weight_asap / 43200)), then effective_weight = penaltyPerViolation × 43200 / 1000. If effective_weight differs meaningfully (>5% drift), note it.
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 optimizer behavior.
Same Site Trade-Off
Framing note: Do NOT compare Same Site to travel time — same-site appointments are already at the same location. Compare against ASAP (if selected) or Preferred Resource (if selected).
If ASAP is selected — Same Site vs. ASAP:
"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'?"
Presets: "keep together up to 1 hr later" (weak), "up to 4 hr later" (moderate), "up to 8 hr later" (strong).
Math (delay threshold D_equiv in minutes): weight_same_site = D_equiv × (weight_asap / 43200)
If ASAP NOT selected but Preferred Resource IS — Same Site vs. Preferred Resource:
"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?"
Presets: "equally important" (F = 1), "same-site matters twice as much" (F = 2), "same-site matters half as much" (F = 0.5).
Math: weight_same_site = F × weight_preferred
If neither is selected — fall back to travel comparison:
"How many minutes of extra travel would make it worth splitting same-site appointments?"
Presets: "10 extra minutes", "20 extra minutes", "30 extra minutes".
Math: weight_same_site = T_equiv × (weight_travel / 120)
Minimize Overtime Trade-Off
"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?"
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).
Math (user gives Z min overtime, H hr delay):
overtime_penalty(Z) = asap_penalty(H × 60)
Z × (weight_overtime / 120) = (H × 60) × (weight_asap / 43200)
weight_overtime = weight_asap × H / (Z × 6)
Requires weight_asap calculated first if ASAP is selected.
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 rather have than Z minutes of overtime.
Preferred Resource Trade-Off
"If an appointment has a preferred resource assigned, how important is it to honor that preference? 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'?"
Presets: "15 extra minutes" (light), "30 extra minutes" (moderate), "60 extra minutes" (strong).
Math (travel threshold T_equiv): weight_preferred = T_equiv × (weight_travel / 120)
Group Nearby Trade-Off
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: what is the maximum additional overall travel the user will spend to keep a cluster together?
"What is the maximum amount of additional overall travel you'd be willing to 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."
Presets: "10 extra minutes" (weak), "20 extra minutes" (moderate), "30 extra minutes" (strong).
Math (T_equiv): weight_group_nearby = T_equiv × (weight_travel / 120)
Resource Priority Trade-Off
Background to share:
"The Resource Priority objective lets you rank service resources on a scale of 0–10, where 0 means highest priority (best candidate) and 10 means lowest priority. For example, you might assign internal staff a priority of 1 and contractors a priority of 5 or higher. The optimizer applies a penalty proportional to a resource's priority value — a priority 5 resource incurs 50% of the full objective weight as a penalty, while a priority 0 resource incurs no penalty at all."
"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'?"
Presets: "30 extra minutes" (mild), "60 extra minutes" (moderate), "90 extra minutes" (strong).
Math (T_equiv, staff priority P_high = 1, contractor priority P_low): weight_resource_priority = T_equiv × weight_travel / (12 × (P_low - P_high))
Example (staff 1, contractor 5, T_equiv = 90): = 90 × 1000 / (12 × 4) = 1,875
Skill Level Trade-Off
Background to share:
"The Skill Level objective steers the optimizer toward either the least or most qualified resource that meets an appointment's skill requirements. The penalty is calculated as the resource's raw skill level value multiplied by the objective weight — so a resource with skill level 8 incurs 8× the weight as penalty compared to a skill level 1 resource."
Step 1 — Ask which mode they want:
"Which mode would you like to use?
- Least Qualified — prefers the lowest-skilled resource that still meets the requirement. Good for preserving senior resources for complex jobs, or keeping costs down.
- Most Qualified — prefers the highest-skilled resource available. Good for maximising first-time fix rates or when quality of outcome is the priority."
If Least Qualified:
"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?"
If Most Qualified:
"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?"
Presets: "15 extra minutes" (mild), "30 extra minutes" (moderate), "45 extra minutes" (strong).
Math (same formula regardless of mode): weight_skill_level = T_equiv × weight_travel / (120 × (S_high - S_low))
Example — Least Qualified (S_low=4, S_high=8, T_equiv=30): = 30 × 1000 / (120 × 4) = 62.5 → round up to 63
Skill Preference Trade-Off
Background to share:
"The Skill Preference objective applies when a work order has multiple skill requirements of the same Skill Type, and a preference exists for one skill over another. Each skill requirement has a Skill Priority value from 1 to 10 — where 1 is the most preferred and 10 is the least preferred. The optimizer assigns a penalty proportional to that priority value."
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 work rule with "At Least One Skill Matches (OR)." Ask:
"Which Skill Type does this preference rank within? Give me its Developer Name — it must be the same Skill Type used by a Match Skills work rule set to At Least One Skill Matches (OR), since Skill Preference has no effect under AND matching."
Record as skillType param (a Skill Type Developer Name, maps to {ns}Skill_Type__c on the goal). Flag if the user hasn't defined a Match Skills rule for that Skill Type with OR logic.
"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?"
Presets: "15 extra minutes" (mild), "30 extra minutes" (moderate), "45 extra minutes" (strong).
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))
Example (Spanish priority 1, English priority 6, T_equiv = 45): = 45 × 1000 / (12 × 5) = 750
Minimize Gaps Trade-Off
Minimum gap duration was already captured in Step 1 as params.minGapMinutes (default 30). Don't re-ask it. This section covers the weight math only.
If ASAP is selected — Minimize Gaps vs. ASAP:
"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'?"
Presets: "leave the gap up to 2 hr later" (weak), "up to 4 hr later" (moderate), "up to 8 hr later" (strong).