# Field Service Scheduling Policy Designer Query

> Designs the four policy-level settings for a Salesforce Field Service scheduling policy — name, optimization mode (In-Day vs Global), commit mode, and description — and delegates to work rule design when complete. Use this skill when a user wants to design, configure, or set up a Field Service scheduling policy's policy-level settings.

- Skill: `gabrielmoreira/field-service-scheduling-policy-designer-query` (Agent Skill)
- Install (CLI): `npx skillmds@latest add gabrielmoreira/field-service-scheduling-policy-designer-query`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gabrielmoreira/field-service-scheduling-policy-designer-query/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: gabrielmoreira (https://skillmd.com/u/gabrielmoreira)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/gabrielmoreira/field-service-scheduling-policy-designer-query

---


# Managing Sfs Scheduling Policy Designer

## When to Use This Skill

Use this skill to reason about and design a complete Salesforce Field Service (SFS) scheduling policy — the policy's intent, work rules that filter candidate resources, relevance groups that scope rules and objectives, and service objectives with mathematically consistent optimization weight values. Trigger whenever the user mentions a scheduling policy, work rules, service objectives, relevance groups, In-Day Optimization, Commit Mode, field service optimization, penalty points, or wants to configure or tune a Salesforce Field Service scheduling policy. Walk the user through defining requirements (work rules), scoping (relevance groups), and trade-off questions to derive consistent weights for each objective, then emit a structured build spec that a separate data-layer skill turns into records. This skill designs the policy; it does NOT create, read, update, or delete any records.

## Workflow

# Salesforce Field Service – Scheduling Policy Designer (Business Logic, Condensed)

This skill helps the user **design a complete Salesforce Field Service scheduling policy** end to end and then hands the design off — as a structured *build spec* — delegating all record operations to the **`sfs-sobject-create`** skill. This skill owns *what* the policy should be and *why*; it never touches *how* records are created.

**Interview sequence (REQUIRED):** Run interviews IN THIS ORDER and make sure not to skip any question, completing each before moving to the next:

### Phase 1: **Scheduling Policy Interview** — Collect policy settings (name...



### Phase 2: **Work Rule Design Interview** — Ask one question...



### Phase 3: **Service Objective Interview** — Group trade-off questions by...



### Phase 4: **The scheduling policy definition** — name, description, In-Day...



### Phase 5: **Work rules** — the hard filters that decide...



### Phase 6: **Relevance groups** — optionally scope a work rule...



### Phase 7: **Service objectives + weights** — the soft scoring that grades the surviving options. This is the deepest part of the skill

a conversational trade-off interview back-calculates **weight values** from the penalty math so all objectives are calibrated on a consistent scale.

### Phase 8: **The build spec** — a structured, machine-readable summary...



**Rules vs. objectives — the core mental model:** *Work rules are hard filters* — they reject any resource or slot that violates them, and are always applied **before** objectives. *Service objectives are soft scoring* — they grade the candidates that survive the rules and never reject anyone. If a user wants "must have X," that's a work rule. If they want "prefer X," that's an objective. Rules shrink the candidate list; objectives rank what's left.

**Terminology:** Always call the product **Field Service** when talking to the user — never use the abbreviation "FSL," even though it appears in this skill's own internal notes as shorthand for the managed package.

**Scope boundary — design only, no CRUD:** This skill produces *decisions and values*, never records. It has **no** knowledge of Salesforce object/field API names, composite API structure, or record-creation order, and it must not attempt to create, read, update, or delete anything. When the design is complete, it emits the build spec and delegates all record operations to the **`sfs-sobject-create`** skill. If the user asks "now create it" / "deploy this," produce (or finalize) the build spec and hand off to `sfs-sobject-create`.

**How to use this skill:** If the user just wants weights, go straight to *Conversation Flow* (the trade-off interview) — that behavior is unchanged. If they want to design or reason about the whole policy, work through the sections in order: policy definition → work rules → relevance groups → objectives/weights → build spec.

**Source of truth:** The **calculations and weight-derivation math in this skill are authoritative** — they reflect the true internal SFS optimizer behavior and take precedence over Salesforce public documentation, which is less precise about the math. The documentation links in *Live Documentation References* are for **background and behavioral details that may change over time** — fetch them for current details, but never let them override the penalty formulas in this skill.

---

## Defining a Scheduling Policy

A scheduling policy bundles **work rules** and **service objectives** together and tells the optimizer how to schedule. This skill decides the policy's *settings and intent*; the data-layer skill creates the actual record. When helping a user design one, collect these policy-level decisions first — each maps to a field the data-layer skill will populate.

**How to run this section conversationally:** Ask about each setting **one at a time** — Name, then In-Day Optimization vs. Global, then Commit Mode, then Description — never bundle multiple questions into a single message; wait for the user's answer before asking the next. Keep this section scoped strictly to the policy-level settings below — **do not** mention work rules, the mandatory Service Resource Availability rule, or any other rules-related framing notes here; that framing belongs only once the conversation reaches the Work Rules section that follows. This skill always builds a **fully custom policy** from the user's own requirements: never offer, suggest, or ask whether the user wants to start from one of Salesforce's standard starter policies or any other template (see *Starting points* below — background only, not a conversational option). Always call the product **Field Service** — never use the abbreviation "FSL" in any interaction with the user.

**Core settings:**

- **Scheduling Policy Name** — the display name for the policy. Tip: if In-Day Optimization is enabled, Salesforce recommends putting "In-Day" in the name so it's easy to identify when dispatching or optimizing.
- **Description** — a free-text description of the policy's intent. Good place to record the "Policy at a Glance" summary this skill generates in Step 3.
- **In-Day Optimization** (boolean) — when true, the policy uses **in-day** optimization instead of **global** optimization. Global runs for hours across the full horizon; in-day is time-boxed for last-minute changes (**up to 5 minutes with Enhanced Scheduling and Optimization, up to 10 minutes without it**) and can be triggered by dispatchers or a scheduled job.
- **Commit Mode** — governs what happens when a dispatcher (or a scheduling operation) changes the schedule *while* a global/in-day/resource optimization is already running and the two conflict. Two values:
  - **Always Commit** — apply the change even if it conflicts with the in-progress optimization.
  - **Rollback** — reject/undo the conflicting change to protect the optimization run.

Capture each of these as a value in the build spec's `policy` object — do not worry about how the record is stored; that is the data-layer skill's job.

**Mandatory rules (every policy) — this is the one canonical statement of this rule; every other mention in this skill just points back here:**
- **Service Resource Availability** — **always include exactly one** in the design and the build spec (`mandatory: true`), **regardless of what the user asks for**. The Field Service managed package does **not** create this rule automatically, so it must always be emitted for the data-layer skill to create. If the user gives no availability details, emit a baseline Service Resource Availability rule anyway (no breaks, no overtime/travel flags) so the policy is valid.
- **Earliest Start Permitted** and **Due Date** (Match Time rules) — **do not include these in the build spec.** The Field Service managed package creates them automatically when the scheduling policy is created. Do not design, emit, or ask about them; they are provisioned for you.

**Starting points (background only — not a conversational option):** Salesforce ships four standard starter policies — **Customer First, High Intensity, Soft Boundaries, Emergency**. This is background knowledge only; **never suggest or offer these to the user as a jumping-off point** — every policy this skill designs is built fresh from the user's own stated requirements, not modeled on a template.

> For behavioral background on these settings, fetch: `service.pfs_scheduling.htm` (see *Live Documentation References*).

---

## Work Rules

**Work rules are hard filters.** They refine the candidate list for a service appointment by **rejecting any service resource that violates a rule**. They are always applied **before** service objectives, and objectives only ever score the resources that survive the rules. If a requirement is "must," it's a work rule; if it's "prefer," it's an objective.

### Database rules vs. Apex rules (why it matters for performance)

- **Database rules** filter at the SOQL-query level — disqualified resources never come back from the database, so they're cheap. They're aggregated into one query and applied in no particular order.
- **Apex rules** run *after* the initial query, iterating over every returned candidate (like a for-loop) to validate — this has a real CPU cost, compounded by objective grading that runs afterward.
- **Guidance:** include **at least one database rule** so Appointment Booking / Get Candidates start from a small candidate pool. Aim to narrow to roughly **~20 candidates** with database rules before Apex rules and objectives run.

### The 16 work rule types

Each row: **name — engine (DB/Apex) — what it does.** "DB w/ ESO" means the rule runs at the database level *only when Enhanced Scheduling and Optimization is enabled*, and as Apex otherwise.

| Work Rule | Engine | What it does |
|---|---|---|
| **Service Resource Availability** | Apex — **MANDATORY** | Ensures a resource is actually available: respects operating hours, travel, breaks, absences, and existing assignments. Also enforces capacity for capacity-based resources. Configurable breaks, gaps, overtime, and travel-to/from-home. |
| **Match Time Rule** | Apex | Constrains the scheduling window from an appointment's date/time fields. Ships with standard rules **Earliest Start Permitted** and **Due Date** (both **mandatory**), plus Scheduled Start / Scheduled End (arrival-window based). |
| **Match Skills** | Apex | Matches an appointment's skill requirements to a resource's assigned skills; can enforce skill level. Skill Type Logic is **All Skills Match (AND)** (default) or **At Least One Skill Matches (OR)** (OR requires ESO). |
| **Match Fields** | DB w/ ESO | Matches one appointment field to one resource field (1:1). Use Extended Match instead when comparing one appointment field against multiple resource values. |
| **Match Boolean** | DB w/ ESO | Requires a checkbox field on the resource to be true (or false). **Max 5 per policy.** Ships with standard "Active Resources." |
| **Extended Match** | Database | Custom-criteria matching via a junction object linking an appointment field to a related list on the resource (e.g., serviceable postal codes, product lines). |
| **Match Territory** | Database | Restricts to resources who are **primary or relocation** members of the appointment's service territory. |
| **Working Territories** | Database | Enforces **primary and secondary** service territory memberships. |
| **Maximum Travel from Home** | Database | Caps distance/travel time between a resource's home base and any assigned appointment. |
| **Required Resources** | DB w/ ESO | Forces assignment to a resource marked **Required** (Resource Preference on the WO/WOLI). Very restrictive. |
| **Excluded Resources** | DB w/ ESO | Prevents assigning a resource marked **Excluded** on the WO/WOLI. |
| **Count Rule** | Apex | Caps assignments, hours, or a custom value per resource per day (e.g., ≤8 scheduled hours, ≤N items on a truck). Time Resolution is **Daily**. Up to 10 custom-field count rules per policy. |
| **Work Capacity** | Apex — **ESO only** | Enforces per-territory Work Capacity Limit records (e.g., cap install work at 80% of territory capacity). |
| **Service Appointment Visiting Hours** | Apex | Enforces customer operating hours / allowed visit windows (e.g., weekdays 8 AM–noon). All-or-nothing rule (no relevance groups). |
| **TimeSlot Designated Work** | Apex | Reserves a time slot/shift for a specific work type — only that type schedules in that window. All-or-nothing rule (no relevance groups). |
| **Service Crew Resources Availability** | Apex | Ensures a crew-type resource is only assigned when the crew meets the parent record's minimum crew size. |

> **Doc naming note:** the docs' "Considerations" list once refers to "Enhanced Match" in the baseline-database-rules list — there is **no** rule type called Enhanced Match; it means **Extended Match**. Treat as a documentation typo.

### Step 1 of building rules: define your requirements

| If the requirement is… | Use this work rule |
|---|---|
| Resources need specific skills / proficiency levels | **Match Skills**; **Extended Match** |
| Non-skill matching factors (e.g., serviceable postal codes) | **Match Boolean**; **Match Skills**; **Extended Match** |
| Breaks during the day / specific or multiple breaks | **Service Resource Availability** (+ work rule entries for multiple breaks, ESO) |
| Can work go into overtime? Can resources travel outside working hours? | **Service Resource Availability** |
| Max number / duration of appointments per resource per day | **Count Rule** |
| A specific resource **must** be assigned | **Required Resources** |
| A specific resource **must not** be assigned | **Excluded Resources** |
| Customer has specific service-call time windows | **Service Appointment Visiting Hours** |
| Ensure crew-type resources get scheduled | **Service Crew Resources Availability** |
| Appointments have arrival windows / required arrival times | **Match Time Rule** |
| Cap travel distance from home / control travel cost across large territories | **Maximum Travel from Home** — ask whether the cap is by **distance** or by **travel time** (see note below) |
| Assign specific work types to a resource for part/all of a day | **TimeSlot Designated Work** |
| Restrict work to the resource's **primary and secondary** service territory memberships | **Working Territories** |

**Arrival Window Match Time rules (opt-in default pair).** If the user wants appointments to honor customer **arrival windows**, ask them to confirm, and if they agree, emit **two** Match Time rules with these exact default settings — do not ask the user to fill these in, they are the standard arrival-window configuration:

| Setting | Rule A | Rule B |
|---|---|---|
| Name | `Arrival Window Start` | `Arrival Window End` |
| Service Schedule Time Property | `SchedStartTime` | `SchedStartTime` |
| Service Time Operator | `Later than or Equal to` | `Before or Equal to` |
| Service Time Property | `ArrivalWindowStartTime` | `ArrivalWindowEndTime` |
| Pass Empty Values | `true` | `true` |

Emit each as a Match Time work rule whose `params` carry those four values (keys `serviceScheduleTimeProperty`, `serviceTimeOperator`, `serviceTimeProperty`, `passEmptyValues`). These are separate from the mandatory Earliest Start Permitted / Due Date rules (never emitted — see *Mandatory rules*).

**Interpreting and emitting break times (clock time vs. shift-start offset):** A Service Resource Availability rule expresses breaks in one of two shapes, and **this skill decides the shape and emits the values the data-layer skill needs** — the data-layer skill never receives a clock time it has to convert. Choose the shape as follows:

- **One fixed daily break at an absolute clock time** (e.g. "30 minutes at 12:00 every day") → emit it as an **absolute** break: `{ "mode": "absolute", "startClock": "12:00", "durationMinutes": 30 }`. No offset math, no working-day start needed.
- **One or more breaks defined as an offset from the start of the working day** (e.g. "a 30-minute lunch starting 3 hours into the shift") → emit each as an **offset** break, in **minutes from the start of the working day**: `{ "mode": "offset", "earliestStartOffsetMinutes": 180, "latestEndOffsetMinutes": 240, "durationMinutes": 30 }`. All three of `earliestStartOffsetMinutes`, `latestEndOffsetMinutes`, and `durationMinutes` are **mandatory** for an offset break. **When the user states the break in offset terms already** (e.g. "3 hours after the start of day"), that offset *is* `earliestStartOffsetMinutes` (180) — **do not ask for the working-day start; you don't need it.**
- **Breaks given as clock times but there is more than one** (e.g. "15 minutes at 10:00 and 30 minutes at 12:00") → you must use the **offset** shape, which means **converting each clock time into an offset from the start of the working day**. You cannot do that without knowing when the day starts, so **ask the user for the shift / working-day start**, then compute `earliestStartOffsetMinutes = (break start clock − day start)` in minutes for each break. Never assume the day starts at midnight (that would turn "10:00" into a wrong 600-minute offset). *(Only ask for the day start in this clock-time case — never when the break is already expressed as an offset.)*

**Always emit `latestEndOffsetMinutes` for every offset break — ask for it directly, do not fabricate a window.** The earliest start comes from what the user stated (an offset, or a converted clock time) — **do not invent ± tolerance windows around it** (no "±1 hour" / "±2 hour" options). If the user only gave a break start (or a duration with no stated finish-by), **ask them plainly for the latest the break may end**, phrased in the *same terms they used*: if they gave an offset ("starts 3 hours after start of day"), ask "what is the latest it may end, as time after the start of the day?" and convert (e.g. "4 hours after start" → 240); if they gave a clock time, ask for a clock time and convert with the day start. If the user says the break is fixed/pinned with no flex, set `latestEndOffsetMinutes = earliestStartOffsetMinutes + durationMinutes`. Never emit an offset break missing any of the three fields, and never guess the latest-end. If a break requirement is too involved to convert reliably, ask clarifying questions or recommend the manual approach rather than guessing.

**Maximum Travel from Home — establish the cap type, not the unit.** When a requirement caps how far a resource may travel from home, **ask the user whether the cap is by distance or by travel time** — the two are configured differently and the data-layer skill needs to know which. Emit the value and the type in `params`: `{ "maxTravelFromHome": 50, "maxTravelFromHomeType": "Distance" | "Travel Time" }`. Interpret the user's phrasing to set the *type* — "50 miles"/"50 km" ⇒ `Distance`, "45 minutes" ⇒ `Travel Time` — but **do not ask for or emit a distance unit (miles vs km): it is not part of the work-rule config**; the unit is governed by the org's locale/distance settings elsewhere, so the rule stores only the number. For `Travel Time` the value is minutes. Never emit the cap value without its type.

### Step 2 of building rules: per-rule parameter questions

Once the requirements funnel (Step 1) has selected which rules to build, ask the specifics for each selected rule. The **`Emit` line gives the exact `params` keys** to put in the build spec — use these key names verbatim so the data-layer skill can map them to fields. Only ask about rules the user actually selected; keep the rest out of the conversation.

All rule types below have a **fully-defined question → param path** — the data-layer fields are confirmed against the Field Service managed-package model.

- **Service Resource Availability** *(always present)* — ask: "Can work run into **overtime**?" (yes/no) · "Can resources **travel outside working hours** to/from home?" — if **yes**, ask **up to how many minutes** of travel are allowed **from home** (to the first job) and **to home** (from the last job); treat "no limit" as **120** (Field Service's effective unlimited ceiling), and if the answer is **no**, both are **0** · and the **breaks** interview (see *Interpreting and emitting break times* above). Emit: `{ "enableOvertime": true|false, "travelFromHomeMinutes": <number>, "travelToHomeMinutes": <number>, "breaks": [ … ] }`. **`travelFromHomeMinutes`/`travelToHomeMinutes` are minute values, not booleans** — `0` = no travel outside working hours, `120` = effectively unlimited, or the specific cap the user gives. If the user gives no availability detail, emit a baseline rule (`enableOvertime: false`, travel-minute keys and `breaks` omitted).
- **Match Time (arrival windows)** — ask only "Should appointments honor customer **arrival windows**?" If yes, emit the two default rules from the *Arrival Window* table above (no further questions).
- **Match Skills** — **gate first, don't assume it's needed.** Ask: "**Do work orders require resources with relevant skills or specific proficiency levels?**" If **no**, do not emit a Match Skills rule at all. If **yes**, emit the rule and ask the follow-up: "Should the resource also **meet a minimum skill level** (proficiency), or is simply *having* the skill enough?" Emit: `{ "matchSkillLevel": true|false }`. **Do not ask about skill-type AND/OR logic** — this skill deliberately does not configure `skillTypeLogic`.
- **Match Fields** — ask: "Which **Service Appointment field** must match which **Service Resource field**, and with what **operator**?" Emit: `{ "serviceProperty": "<appointment field API name>", "resourceProperty": "<resource field API name>", "booleanOperator": "=" }`. The operator is one of the symbols **`=`, `>=`, `<=`, `>`, `<`** (default `=`). (These field names are the customer's own fields — pass them through as given.)
- **Match Boolean** — ask: "Which **checkbox field on the resource** must be true (or false)?" Emit: `{ "resourceProperty": "<resource field API name>", "value": true|false }`. Remind the user there is a **max of 5 Match Boolean rules per policy**.
- **Maximum Travel from Home** — see the cap-type note above. Emit: `{ "maxTravelFromHome": <number>, "maxTravelFromHomeType": "Distance"|"Travel Time" }`.
- **Working Territories** — ask: "Restrict resources to their **primary and secondary** service territory memberships?" This is the **only** territory-scoping rule the interview emits — there is **no separate Match Territory question** (by design, no interview path produces a standalone Match Territory rule). No user parameters — selecting the rule is enough. Emit: `{ "workingLocationEnablePrimary": true }` (the standard fixed value; the data-layer skill maps it to `{ns}Working_Location_Enable_Primary__c`). *(If a user explicitly needs primary/relocation-only scoping — the classic Match Territory behavior — advise them it must be configured manually; the interview does not emit it.)*
- **Service Crew Resources Availability** — first ask the **gating question**: "**Do you need to ensure crew-type resources get scheduled?**" If **no**, do **not** emit this rule at all — there is nothing to create. If **yes**, emit the rule and ask its two parameters:
  - **Consider Service Crew Membership** (yes/no) — "Should the rule evaluate **individual crew members'** availability and skills, not just the crew record?" → `considerCrewMembership`.
  - **Maximum Additional Service Resources** (optional number) — "What is the **maximum number of extra resources beyond the base crew** that can be pulled in to meet the minimum crew size?" → `maxAdditionalResources`.

Emit: `{ "considerCrewMembership": true|false, "maxAdditionalResources": <number> }` (omit `maxAdditionalResources` if the user leaves it blank).
- **Extended Match** *(advisory only — the data-layer skill will not build this)* — Extended Match needs a **custom junction object plus fields** that no skill in this pair creates (that is schema DDL the user must own). So **do not emit it as a normal work rule with mappable `params`.** Instead, when a requirement calls for custom-criteria matching (e.g. serviceable postal codes, product lines), **advise the user what to set up**, and record it under `prerequisites` — do not put it in `workRules[]` for the data layer to create. The setup guidance to give the user: (1) create a **junction object** linking **Service Resource** to the matched object, with **exactly two Master-Detail** relationships (one to Service Resource, one to the matched object) — the packaged trigger requires exactly two or the rule fails; (2) a **Service Appointment field of type Lookup** that drives the match; (3) a **reference field on the junction** matched against it. Tell the user that once those exist, the Extended Match work rule must be created and configured **manually in Setup** (Field Service Settings → Scheduling → Work Rules), because pointing a rule at objects/fields that are created outside this design is beyond what the data-layer skill does. Capture the intended objects/fields in `prerequisites` as advisory text; **do not** emit a `params` mapping.
- **Count Rule** — ask: "Is there a **maximum number or duration of appointments per resource per day**?" If yes, have the user describe the limit in plain terms, then classify it into three values:
  - **`countBy`** — one of `"Appointments"` (a count of jobs, e.g. "no more than 8 jobs per tech per day"), `"Hours"` (a duration cap, e.g. "≤ 6 working hours of appointments per day"), or `"Custom"` (sum of a custom numeric field on the Service Appointment, e.g. "≤ 100 units on a truck").
  - **`maxValue`** — the numeric cap (for `Hours`, convert the limit to **hours**).
  - **`fieldHint`** — **only for `Custom`** (and optionally `Hours`): the **Service Appointment field to sum**. Set `null` for a plain `Appointments` count.

Emit: `{ "countBy": "Appointments"|"Hours"|"Custom", "maxValue": <number>, "fieldHint": "<SA field API name>"|null }`. When `countBy` is `Custom` (or `Hours` against a specific field), add a **prerequisite** note: *"Confirm the summed field `<fieldHint>` exists on Service Appointment."* The Time Resolution is always **Daily** and the counted object is always **Service Appointment** — the data-layer skill sets both automatically, so do not ask about them. Reminder: up to **10 custom-field count rules** per policy.

**Selection-only rules** — the rule record itself has no config fields; the behavior comes from data on other objects. For each: ask a plain gate question, emit `{}` if yes (or skip entirely if no, where noted), and record the referenced data as a **prerequisite**.

- **Required Resources / Excluded Resources** — ask: "Should the policy **force** assignment to a resource marked *Required* (or **prevent** one marked *Excluded*) on the work order / WOLI?" Emit `{}`. These key off **Resource Preference** records on the work order — note that as a prerequisite (the data must exist).
- **Work Capacity** *(ESO only)* — ask: "Cap work at what limit (Hours or Percentage of capacity), for which **territory** and which **work-type/appointment attribute**?" The limits themselves live on standard **`WorkCapacityLimit`** records per territory (a prerequisite set up outside the policy); adding the rule to the policy just enables enforcement. Capture the intent and note the prerequisite; the rule record itself has no config fields.
- **Service Appointment Visiting Hours** — **gate only; do not ask for the windows.** Ask: "Should appointments be restricted to **customer visiting hours**?" (yes/no). The **windows are per-account data**, not a single policy-wide value: they live on an **`OperatingHours`** record referenced from the Work Order (populated from the Account), so there is nothing to collect at design time. If **yes**, emit `{}` and record a **generic prerequisite** ("each account must have its visiting/operating hours populated; the Work Order's Visiting Hours must resolve from the account"). **Never ask the user to state the allowed windows** — they differ per account. (No relevance-group scoping.)
- **TimeSlot Designated Work** — **gate only.** Ask: "Do you need to **reserve specific time slots / shifts for particular types of work**?" (yes/no). Phrase it as "types of work," **not** "Work Type" — *Work Type* is a specific Salesforce object, and saying it implies the reservation can only key off that object, when the designation is broader. The slot↔work reservation lives on standard **`TimeSlot`** data (a prerequisite), so there is nothing to collect at design time. If **yes**, emit `{}` and record a **generic prerequisite** ("time slots must be configured to designate the intended work"). (No relevance-group scoping.) **Do not ask the user to enumerate slot→work mappings** — that is data setup.

> For per-rule configuration fields and gotchas, fetch the specific rule sub-page under `service.pfs_optimization_theory_work_rules.htm` (see *Live Documentation References*).

---

## Relevance Groups

A **relevance group** scopes a single work rule or service objective so it applies **only to certain appointments or resources**, instead of the whole policy. This lets one policy hold different logic for different work or resource types — e.g., different break/travel limits for part-time vs. full-time employees, or expedited scheduling for high-value accounts.

### How it works, and the division of labor

A relevance group is driven by a **Boolean (true/false) field**. Every standard or custom Boolean field on the **Service Appointment** and **Service Territory Member** objects is selectable in the rule/objective's Relevance Group dropdown. On a work rule or objective, you pick the limiting Boolean field; the rule/objective then applies **only** to records where that field is true. The two bases are just the two objects the Boolean can live on:

- **Service Appointment basis** — scopes by the **appointment** being evaluated (e.g., a formula checkbox "Platinum Account" that's true when the related account tier is Platinum). Use to target appointment *types*.
- **Service Territory Member basis** — scopes by the **service territory member** of the resource being evaluated (e.g., "Part-Time" / "Full-Time" checkboxes). Use to target resource *populations*. Only **primary and relocation** memberships are supported — not secondary.

**In the design:** decide the *basis* (Service Appointment vs. Service Territory Member) and the *name of the Boolean field* that identifies the subset, then record both on the rule/objective in the build spec (`relevanceGroup: { basis, booleanField }`). This skill only decides which field scopes which rule/objective — wiring the group onto the record is the data-layer skill's concern.

**The Boolean field itself is a prerequisite the user must own**, not something either skill in this pair creates: creating the field is a schema change (DDL), and setting its true/false values on records needs a flow, formula, trigger, or data load. So **advise the user** to, before deploying: (1) create one custom Boolean field on the appropriate object (Service Territory Member for a resource subset, Service Appointment for a work subset) per subset; (2) populate it true for exactly the records in that subset (via flow/formula/import), keeping subsets mutually exclusive where the rule type requires it. Then reference that field by name in the build spec so the data-layer skill can attach it as the relevance group.

### Worked example (the canonical pattern)

To apply a **Match Boolean** rule (or any scoping) to only certain appointment types: identify (or have the user create) a Boolean field on the Service Appointment that is true for those appointments, and name it as the rule's relevance group in the build spec. The rule then applies **only** to appointments where the field is true. The same pattern with a Service Territory Member Boolean (e.g., Part-Time vs. Full-Time) lets you design two copies of the **Maximum Travel from Home** rule with different limits — one scoped to part-timers, one to full-timers.

For objectives, the classic example is combining **ASAP** with a relevance group: a formula checkbox "Platinum Account" on the appointment, an ASAP objective named "Expedite Platinum Accounts" scoped to it with a **high weight**, so platinum jobs schedule sooner (accepting more travel). Give scoped high-priority objectives a clearly higher weight, or the engine may prefer not to schedule the appointment at all because of its ASAP penalty.

### Scoping any rule or objective to a subset (the general rule)

**This applies to every work rule and every service objective — not just breaks or availability.** Whenever a request says a rule or objective should apply to **only certain resources** or **only certain work**, rather than the whole policy, the mechanism is a **relevance group**. Watch for this signal and reach for it every time:

- **"…applies to certain types of resources"** (e.g. "resources in France", "part-time techs", "senior engineers", "the install crew") → create a **Service Territory Member relevance group**: a Boolean field on Service Territory Member that is true for exactly those resources, selected as the rule/objective's relevance group.
- **"…applies to certain types of work"** (e.g. "installation jobs", "platinum-account appointments", "emergency work orders", "jobs over 2 hours") → create a **Service Appointment relevance group**: a Boolean field on Service Appointment that is true for exactly those appointments, selected as the rule/objective's relevance group.

**Always check whether the specific rule/objective supports the basis you need.** Not every rule/objective can be scoped by Service Territory Member, and not every one can be scoped by Service Appointment. Before recommending a relevance group, **consult the relevance-group documentation's support matrix** (`service.pfs_relevance_groups.htm`) for that exact rule or objective, and **consider only Enhanced Scheduling & Optimization (ES&O)** support — ignore the legacy/non-Enhanced columns. If the needed basis isn't supported for that rule/objective under ES&O, say so and suggest the closest supported alternative instead of inventing one.

**When two subsets each need a *different* configuration of the same rule/objective, design one instance per subset — never one merged instance.** A single rule/objective instance applies to every record it covers, so you cannot express "France gets pattern A, everyone else gets pattern B" in one instance. Instead specify **one instance per subset, each scoped by its own distinct Boolean field**, and keep the groups **mutually exclusive** (for single-coverage rule types like Service Resource Availability, an overlap throws an error — see *Key rules and gotchas*). Example: "Resources in France get a 15-minute break at 10 AM and a lunch between 12 and 2; all other resources get a 30-minute break between 12 and 3" needs two Service Resource Availability rules — "Availability – France" scoped to a `Break_Group_France__c` STM checkbox, "Availability – Standard" scoped to a `Break_Group_Standard__c` STM checkbox — each carrying only its own group's breaks, with every resource landing in exactly one group.

**When the scenario is genuinely too complex, recommend a manual design.** If a request combines multiple subsets, ambiguous values, and relevance-group fields that don't exist yet, don't force a single merged instance into the spec. Walk the user through the relevance-group design above and let them refine it (or spec only the parts that are unambiguous), rather than emitting a plausible-looking but wrong merged rule/objective. Prefer **asking clarifying questions** (which subset? resources or work? what Boolean field identifies each? is that basis supported for this rule/objective under ES&O?) over guessing.

### Key rules and gotchas

- **Relevance groups must be mutually exclusive.** If two rules with relevance groups overlap, the more restrictive one wins — and for **Service Resource Availability**, an overlap throws an **error**. Each resource must be covered by exactly one Service Resource Availability rule.
- **Additive rule types** — can appear multiple times and legitimately cover the same resources/appointments: **Count Rule, Extended Match, Match Boolean, Match Fields, Match Time**.
- **Single-coverage rule types** — a resource/appointment must be covered by at most one instance at a time: **Match Skills, Match Territory, Maximum Travel from Home, Required Resources, Service Crew Resource Availability, Service Resource Availability, Working Territories**. (Also: don't cover a resource by both Match Territory and Working Territories at once.)
- **All-or-nothing rules — no relevance groups:** **TimeSlot Designated Work** and **Service Appointment Visiting Hours**. Also **Work Capacity** doesn't support relevance groups.
- **Objectives** can be repeated with relevance groups too, but a record must not meet the criteria for two objectives of the same type at once — **except Resource Priority**, which can legitimately apply twice to the same appointments if each instance points to a **different** resource priority field (e.g., "Primary Priority" and "Secondary Priority"/"Tenure").
- Support for whether a rule/objective can be scoped by appointment vs. territory member **differs between the Enhanced and non-Enhanced engines** — verify the support tables when scoping. **Group Nearby** and **Same Site** objectives do **not** support relevance groups at all.

> For the full support matrices and setup steps, fetch: `service.pfs_relevance_groups.htm` (see *Live Documentation References*).

---

## 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 by the user and all other weights are derived relative to it.

**The general formula, common to every objective below** (stated once here so it isn't re-derived nine times):
```text
penaltyPerViolation = max( 1, roundingFn( (1000 × weight) / scale ) ) × finalMultiplier
total_penalty       = ceil( violations / granularity ) × penaltyPerViolation
```
Every objective applies the same **×1000 internal multiplier**, then its own scale, rounding function, and final multiplier. Two consequences follow, and are *not* repeated under each objective below:

- **The ×1000 multiplier is uniform**, so it cancels out of every crossover/derivation equation between two objectives — this is *why* the simple continuous approximations (`penalty ≈ units × weight/scale`) used throughout the trade-off interview stay valid, and why every "derive weight_X from weight_Y" formula below is clean of any ×1000 term. Where the rounding function is *not* exact for integer weights (ASAP's `round`, Skill Level/Preference's `roundInt` against a scale that doesn't divide evenly), a small amount of drift is possible — flagged per objective under *Precision notes* where it matters.
- **The `max(1, …)` floor** guarantees every included objective has *some* effect even at a very low weight. Each objective's Precision notes flag the specific weight range where this floor actually binds.

**The one exception to the uniform multiplier is Same Site**, whose final multiplier (`×0.01`) nets to an *effective* ×10 rather than ×1000 — called out explicitly under its formula below, because it's the one place the "just divide by the other objective's rate" shortcut doesn't apply without adjustment.

---

## Penalty Formulas by Objective

Use these formulas throughout the conversation to show math and back-calculate weights. Each objective states it

…(truncated)
