Skip to content

Action: Auto & General — create Prime job from Crunchworks (AB#2132) ​

Status: proposed design. Nothing here is built. Field mapping and click paths wait for the screen recording (see Open questions). Shared rules (queue, outcomes, MFA, credentials) are in BrowserActions-Actions.md; the Unity sign-in facts are in BrowserActions-nexus-update-ecd.md.

When Auto & General (A&G) assigns Ethos an Assess And Repair job, FreshDesk (EthosClaims) gets a notification email with a link to the job in A&G's supplier portal ("Crunchworks", unity.autogeneral.com.au). Today someone re-keys the job into Prime. This action does it: read the job in the portal, create the customer contacts and the Prime job, and note the Prime job on the ticket.

The trigger email ​

Both attached examples (claims 12249928001, 12214202201) are identical but for the claim number:

  • From noreply@mail.claims.autogeneral.com.au, to autoandgeneral@ethosbuilding.com.au.
  • Subject A&G Assess And Repair Claim #<claim ref> (11 digits in both samples).
  • Body: "A new Assess And Repair Job has been assigned to Ethos from Auto & General for claim #<claim ref>", an "Access job details here" link (the Unity job URL, …/pulse/suppliers/jobs/<guid>/details) and "Or via the Nexus platform".
  • The same inbox already feeds FreshDesk rule A&G Assess And Repair (ag-assess-repair, FreshDeskInbox-Spec §6.3).

Proposed flow ​

FreshDesk Claims "Ticket Creation" -> WebhookReceiver -> FreshDesk queue -> FreshDeskWebhookConsumer (WebAPI, exists)
   1. subject/body match? -> extract claim ref + job URL           [WebAPI, new step]
   2. job already in Prime (by claim ref)? -> stop, note on ticket [WebAPI]
   3. send { ticketId, claimRef, jobUrl } to queue sbq-agcreatejob, scheduled HoldMinutes ahead  [WebAPI]
 -> AgCreateJobWorker (BrowserActions, QueueConsumer)
   4. sign in to Unity (saved login, else MFA from the store), open jobUrl
   5. read the job fields into a snapshot                          [Playwright]
   6. POST snapshot to WebAPI  api/Prime/ag-create-job             [WebAPI]
        a. re-check Prime for the claim ref (under a lock on claimRef)
        b. create contacts, then relationships, then the job
        c. add the Prime job to the FreshDesk ticket (private note)

Why this split:

  • Steps 1–3 in WebAPI: the ticket is already fetched there for the Prime job note (AB#2033), JobReferenceExtractor and spPrimeFindJobsByReference already exist, and it already has FreshDesk and Prime clients. The new step runs for created Claims tickets only, like the job note. No new receiver or n8n flow.
  • Browser work in BrowserActions: the portal session, MFA and Playwright live there. It sends the snapshot to WebAPI and never holds Prime credentials, as RepairLine's submit-job plan already does (BrowserActions-repairline-submit-job.md).
  • Prime writes in WebAPI, not in the browser app: Prime tokens and CommonPrimeAPI are WebAPI's (BrowserActions never references WebAPI, calls it over HTTP). One endpoint does contacts, job and the ticket note, so the "contacts first, then job" order and the duplicate guard sit in one place.

Step 1: detection ​

Match (case-insensitive) subject ^A&G Assess And Repair Claim #(\d{8,}) and the body phrase from the work item; take the claim ref from the subject and check it equals the body's. Job URL: first https://unity.autogeneral.com.au/pulse/suppliers/jobs/<guid>/details in the HTML description (the plain-text description may drop the href). No URL, or refs disagree → no queue message, private note "A&G job notification not processed: <reason>", so a person does it by hand. Use the strict regex rather than the rule table: the rule table (AB#2120) decides triage handling, this decides whether a browser touches a portal.

Step 2: does the job exist? ​

Rule (decided): a ClientReference is unique per client, so a job exists when primeJobs.ClientReference = the claim ref and ClientId = the A&G contact (405B08A4-95F7-4CB4-A3A4-BB952BCC929D, "Auto & General Holdings Pty Ltd"), in any status (closed and cancelled count). A job for another client with the same number is not a duplicate. spPrimeFindJobsByReference does not return ClientId (and searches every reference column), so this needs a small new proc, spPrimeJobExistsForClient @ClientId, @ClientReference (migration 0068), returning the job id, number and status. Found → note on the ticket ("Prime job J… already exists") and stop. The database is the only source: Prime's API is not searched. The check is repeated inside the create endpoint (6a) under the claim-ref lock, just before writing. The database can lag Prime, so a hold (below) and an after-the-fact watch (step 7) cover the gap.

Hold before processing. The queue message is sent with ScheduledEnqueueTime = now + AgCreateJob:HoldMinutes (default 5 minutes), so nothing opens the portal until then. It lets the Prime webhook sync catch up with anything staff created straight away, and gives staff a short head start if they want to do it by hand. The check runs at ticket time (to stop the obvious case without queueing anything) and again inside the create endpoint (6a) after the hold. The cost is that every job takes at least that long; turn it down to 0 if A&G's acceptance time makes that matter (Q13). The duplicate watch in step 7 covers what the hold misses.

Steps 4–5: portal read ​

  • Queue sbq-agcreatejob-prod-australiasoutheast-001: duplicate detection on, window 7 days, MessageId = ag-create-job:<claimRef> (a re-sent ticket or a FreshDesk retry drops out). TTL 1 day, lock 5 min (auto-renewed), max delivery 10, dead-letter on expiry. Created once per environment like the RepairLine queue.
  • AgCreateJobWorker is a CommonServiceBus.QueueConsumer subclass (like RepairLineSyncDatesWorker): MaxConcurrentCalls = 1, one replica, off unless AgCreateJob:ConsumerEnabled. attempt → re-sent with backoff, reject → dead-letter.
  • Session and sign-in (to be tested). Same login as the Nexus ECD action (nexus-update-ecd.md); A&G are migrating suppliers from Nexus to the new Crunchworks portal (unity.autogeneral.com.au), so this action should be built against Crunchworks and the Nexus spec's sign-in facts re-checked against it. The hope is that a persistent Browserbase context (Portals:Unity:ContextId) keeps the user signed in so MFA is rare. Unproven, and it depends on how Browserbase IPs behave: RepairLine binds its login to the client IP and its cookie is session-only, so on Browserbase's rotating AU proxy every new session needed a fresh emailed code and neither the context nor restored cookies helped (BrowserActions-Spec.md, tested 11 Oct 2026). Phase 1 tests Crunchworks the same way: sign in once, close, open a new session on the same context (with the rotating proxy, then a fixed-IP/sticky external proxy if it fails), and see whether it is still signed in; also whether the token lives in IndexedDB (Nexus's did, ~9 h life) and survives. Outcomes: (a) context works, so most runs need no MFA; (b) only with a fixed IP, so Portals:Unity:Proxy is needed; (c) neither, so use the RepairLine pattern: one long-lived session (60 min cap), one code per session, jobs processed one at a time in it.
  • MFA is read from dbo.EthMFAAsAService row Unity through WebAPI GET api/Mfa/Unity (MfaCodeStore-Spec), floor-then-poll as RepairLine does. The work item's direct SQL is not needed: the endpoint exists, and BrowserActions does not need the table. Unity sends the code by SMS to a shared phone, 3 sends/hour shared with A&G staff, so the send budget (spBrowserActionsMfaReserveSend, not built) is required.
  • Read. Open jobUrl, confirm the page shows the expected claim ref (wrong_job), then read the fields. Prefer reading the page's own GraphQL responses (/graphql/v2, passive WaitForResponse, adds no traffic) over scraping labels, with DOM as fallback; decided from the recording (Q7). Nothing is typed or clicked in Unity beyond navigating and expanding sections, so there is nothing to undo in the portal.
  • Human-paced, one session per account, AU residential proxy, as every portal action.

Step 6: the create endpoint (WebAPI) ​

POST api/Prime/ag-create-job, Basic auth, body { ticketId, claimRef, jobUrl, snapshot, dryRun? }. State is kept in a new table so a retry resumes rather than duplicates:

dbo.ethAGCreateJob: ClaimRef (PK), TicketId, JobUrl, Status (Reading|ContactsCreated|JobCreating|JobCreated|Noted|Verified|DuplicateFound|JobCreateUnknown|Failed), VerifyDueAt, SnapshotJson, ContactIdsJson, PrimeJobId, PrimeJobNumber, LastError, CreatedAt, UpdatedAt.

  1. Take sp_getapplock on the claim ref; check spPrimeJobExistsForClient (the database only). Exists → record PrimeJobId, skip to the note.
  2. Create each contact (IPrimeContactService.CreateAndUpsertContact shape; the customer is an individual; extra people only if Q9 says so), record the ids in the row before going on.
  3. Create the job (IPrimeJobService.CreateJob, PrimeJobCreateModel): client = A&G contact, ClientReference = claim ref, customer + site address, description/notes from the snapshot, assigned user, job type, workflow and status. Record PrimeJobId immediately. The Prime ids hard-coded in CreateJobFromPayloadAsync belong to another flow and must not be reused blindly (Q8).
  4. Contact relationships to the job (CreateContactRelationship, as TriageContactSyncService).
  5. FreshDesk private note: Prime job number, link, status (the AB#2033 note format, built by the same code), and set the Prime job number on the ticket if that custom field exists (FreshDeskInbox-Spec §6.2).
  6. Respond { outcome, reason, primeJobId, primeJobNumber }. Prime errors with a stale version or a 429 are attempt; a Prime 422 on the payload is reject plus a ticket note.

Case manager (decided): not set on create. The job is created with the Integrations user as assignee and no case manager, as the RepairLine flow does; the FreshDesk rules (AB#2120 and the rule engine behind it) assign the case manager later. caseManagerId is optional on PrimeJobCreateAttributes, so nothing is needed in the payload. For reference, Laura Westelynck is B497E0B4-847F-4634-AA6B-C5110B41B60E if a fixed owner is ever wanted.

Step 7: duplicate watch (WebAPI) ​

The check before creating can miss a job that staff made by hand seconds earlier (the database lags Prime's webhook). So after the note, the state row gets VerifyDueAt = now + AgCreateJob:VerifyMinutes and status Noted. A WebAPI background service (off unless AgCreateJob:Enabled) polls for Noted rows past VerifyDueAt, runs spPrimeJobExistsForClient and counts the jobs with this claim ref for A&G:

  • One job → status Verified, nothing else.
  • More than one → status DuplicateFound, a private ticket note naming both job numbers ("Possible duplicate: J… and J… for claim #… Check and cancel one") and a queued email to the system owner. Nothing is deleted or cancelled automatically: a person decides which job is kept.

It polls the row, not a delayed message, so it survives a restart and needs no browser. The hold (step 3) and this watch together narrow the gap: the hold lets the sync catch up before the create, the watch catches whatever still got through.

Failure handling ​

  • attempt outcomes (portal down, MFA stale, budget spent, Prime 429) retry on the consumer's backoff (10/30/90/120 min), max 5, as RepairLine.
  • Dead-letter or reject → WebAPI adds a private ticket note "Automatic job creation failed: <reason>. Create manually", and the existing escalation path (system owner) picks it up. Nothing sits silent.
  • Safe re-runs: duplicate detection on the queue, the claim-ref lock, and the per-step state row mean the worst case of a crash is a resumed run, not a second job. A job created in Prime but not noted is finished by the retry (JobCreated → Noted).

Safety switches ​

  • AgCreateJob:Enabled (WebAPI producer, default false) and AgCreateJob:ConsumerEnabled (BrowserActions, default false).
  • AgCreateJob:DryRun (default true): runs steps 1–5 and the endpoint up to its checks, logs the snapshot and the Prime payload it would send, writes nothing to Prime or FreshDesk. Same pattern as ITProvisioning and the FreshDesk rules.
  • Prod must not point at test Prime. Dev DB Prime tokens are stale, so local runs test only the non-Prime parts.

Settings reused from the RepairLine submit-job automation ​

Read from stagehand-automations/apps/new-repair-line-submit-job (the n8n workflow "Repairline submit job - create Prime job", which calls WebAPI api/Prime/...). It is the closest existing flow: portal job in, Prime contact + job out. What it uses, and what we do with it:

SettingRepairLine valueFor A&G
Workflow (Prime's own workflowId: the set of statuses a job moves through, required by Prime's create-job call; not our orchestration)74646B7F-48E5-49FB-AA17-9E7F2C9259C3, hard-coded in CreateJobFromPayloadAsyncConfirmed (owner): 74646B7F-48E5-49FB-AA17-9E7F2C9259C3. An existing A&G job in the Prime mirror (client 405B08A4-…) already carries this workflow, so it is confirmed; held as a constant, not a per-job input
Initial status66834CF1-B1FE-A0BD-D1FB-6144C07E9ADF = Job Registered (BookingJobStatus.cs)Reuse
Job type id4b8481c6-694b-11f0-8228-0a39957d26b4 (hard-coded in PrimeController.CreateJob and the service; the payload's own jobTypeId is ignored)Confirmed (owner): use as is
Job type text (jobType, notes Job Type: …)the portal's Request Type (default Quote)Assess And Repair
Assigned userfixed 7788BD72-A414-4553-874A-21CC33A8A12ALooked up in the dev DB: Prime user "Ethos Admin Team" (IT@ethosbuilding.com.au), active, role Integrations, 188 jobs assigned, none as case manager. It is the automation account, so jobs land in a shared queue and a person (or the later FreshDesk case-manager assignment) takes them over. Reuse; it is not a person's name, so the job is not owned by anyone until a case manager is set (Q10)
Clientlooked up by insurer text via GET api/Prime/contact/GetInstructingClient?text=; fallback "Sedgwick - Other" 2AD123B8-…Fixed: AgCreateJob__ClientId (A&G Holdings). No lookup, no fallback: a missing client is a failure, never a job under another client
Customer contactGET api/Prime/GetContactInformation?contactName=<full name> reuses an existing contact if found, else POST api/Prime/contact/createContact with contactType: "Customer", status: "active", isIndividual: true, preferredContactNumber: "Mobile", mobileNumber and workNumber both the phone, email only if present, notes: "Created via Ethos Automation - <portal>.", address with country: "Australia", postalSameAsAddress: trueSame shape. Reusing a contact by name alone can attach the job to the wrong person (two "John Smith"s); propose match on name and phone or email, else create (Q9)
Job bodyclientReference, clientId, description, incidentDate, jobType, workflowId, statusId, assignedId, notes, siteSameAsCustomerAddress: true, customer{…}, address, postalAddress (site = customer address)Same; the site address comes from the portal job, and is separate from the customer's if the portal has both
DescriptionBlock: "Customer contacts" (name, phone, email), "Address(es)", then Job Status, Request Type, Job Information, Date of Loss, Cause Of Loss, Loss DescriptionSame layout so estimators see a familiar job, plus the Unity job URL
Incident datethe portal's Date of Loss (accepts d/M/yyyy and ISO, calendar-checked); else today (Sydney), with a "(could not be read; job records today)" noteSame rule
First-contact datebusiness day (Sydney, weekend rolls to Monday), typed back into RepairLineNot needed unless Crunchworks asks for it (Q11)

Lessons carried over (their incident history is in that workflow's notes):

  • createJob is not idempotent and WebAPI's endpoint turns every failure, including a Prime 5xx or timeout, into the same 400 { error }. A failed or timed-out create therefore can't be told from one that committed. RepairLine seals before the create and never retries. Our equivalent: the state row moves to JobCreating before the Prime POST; a clean failure before Prime (validation) goes back to retryable; anything ambiguous becomes JobCreateUnknown, is not retried automatically, and raises a ticket note for a person to check Prime for the claim ref (the mirror sees it after the next Prime webhook sync).
  • Parse lookup responses as text, treat an empty or non-JSON 2xx as a failure and never let it throw past the handler (their message 479747B5 died that way).
  • Only the contact create needs the "does one already exist" check; the contact create is safe to retry, the job create is not.
  • Dates and policy live in code with a test, not in a workflow node.

Configuration (proposed) ​

Variable (Azure format)AppNotes
AgCreateJob__EnabledWebAPItrue to queue A&G tickets
AgCreateJob__DryRunWebAPItrue until verified
AgCreateJob__ClientIdWebAPI405B08A4-95F7-4CB4-A3A4-BB952BCC929D (A&G client contact)
AgCreateJob__HoldMinutesWebAPIMinutes to hold a new message before the browser starts, so the Prime sync catches up and staff get first go. Default 5
AgCreateJob__VerifyMinutesWebAPIMinutes after creation before the duplicate watch runs. Default 10
ServiceBus__Queues__AgCreateJobWebAPI, BrowserActionssbq-agcreatejob-prod-australiasoutheast-001
AgCreateJob__ConsumerEnabledBrowserActionstrue on the one production host only
Portals__Unity__UsernameBrowserActionsMason Davies' account (Q3), secret
Portals__Unity__PasswordBrowserActionssecret; App Service setting / Key Vault reference, not a database column
Portals__Unity__ContextIdBrowserActionsBrowserbase context (if the spike shows it keeps the login)
Portals__Unity__MfaServiceProviderNameBrowserActionsUnity (appsettings)

FreshDesk__Claims__Domain/ApiKey, WebApi__*, ServiceBus__*, Browserbase__* already exist on their apps.

What is reused vs new ​

ReuseNew
FreshDeskWebhookConsumer Claims branch, JobReferenceExtractor, spPrimeFindJobsByReference, FreshDeskClient private notedetection step + AgCreateJobProducer (WebAPI)
QueueConsumer, ServiceBusQueue, Browserbase.OpenAsync, RepairLine browser/MFA patternAgCreateJobWorker, UnityBrowser, UnityPortal (BrowserActions)
CommonPrimeAPI contact/job/relationship services (the shapes the RepairLine flow already posts)ag-create-job endpoint + service, dbo.ethAGCreateJob + procs (migration 0068+)
api/Mfa/UnityUnity MFA send budget (shared with Nexus)

Proposed timing ​

PhaseWhatNeeds
0 (now)This design, questions answeredBusiness answers (below)
1Unity spike: signed-in locators, does a Browserbase context keep the login across sessions (rotating vs fixed IP), what the job page exposesThe recording, an account that can sign in
2Field mapping signed off (portal field → Prime field → default)Recording + Prime job-type/status/client ids
3WebAPI: detection, queue, endpoint, state table, dry-run; unit tests on the two sample emailsPhase 2
4BrowserActions worker + Unity portal steps, dry-run against live ticketsPhases 1, 3
5Dry-run in prod for about a week: compare the would-be Prime payload with what staff create by hand—
6Switch on (DryRun=false), watch the first several jobsSign-off

Phases 3 and 4 are each roughly one PR. Phase 1 gates everything that touches the browser; start it as soon as the recording lands, because the login-persistence result decides whether MFA is a once-per-session or per-job cost. Deploys: WebAPI, BrowserActions, DbMigrations (Test then Prod).

Open questions ​

  1. Scope — answered: Assess And Repair only for now. The same sender also sends make safe, restoration and quote-only notifications; those stay manual and are out of scope until separately requested. The detection regex is keyed to the Assess And Repair subject so nothing else can match.
  2. Duplicate rule — answered: ClientReference is unique per client, so the check is claim ref + A&G client, any status. A&G client contact id: 405B08A4-95F7-4CB4-A3A4-BB952BCC929D (Auto & General Holdings Pty Ltd), held as a config setting AgCreateJob__ClientId.
  3. Account — answered: the same login the Nexus action uses (Mason Davies). Still true that the login, the Nexus ECD action (until retired) and any person using it share one MFA send budget (3/hour), so keep one session and serialise.
  4. Login persistence — to be tested in phase 1 (see Session and sign-in), including how Browserbase IPs affect it. If it fails, expect one SMS per browser session.
  5. Password storage: proposed App Service setting / Key Vault reference, not the database. OK?
  6. Mirror lag — answered: the duplicate check reads only the database, so it can miss a job created by hand just before the Prime sync lands. Mitigated two ways: the message is held AgCreateJob__HoldMinutes before processing (step 3) and a duplicate watch runs after creation (step 7).
  7. Reading the portal: OK to read the page's own GraphQL responses (passive), or DOM labels only? Decided from the recording.
  8. Prime constants — answered: workflow 74646B7F-…, job type id 4b8481c6-…, status Job Registered, assigned user the Integrations account. Filter tags and custom fields: out of scope for now.
  9. Contacts — matching answered: reuse an existing Prime contact only on name and phone or email, else create. Still open: who is created (policyholder only, or also other people the portal lists)?
  10. Assignment — answered: no case manager on create; the Integrations user is the assignee and the FreshDesk rules assign the case manager.
  11. Where does the result go: private note only, or also set the ticket's Prime Job Number/ID custom fields and move the ticket (status/agent)? Does the A&G "job accepted" step in Unity (if any) stay manual?
  12. Failure owner: who is told when creation fails: the system owner via the ticket (proposed) or an email to a named person?
  13. Volume and hours: how many A&G Assess And Repair jobs per day, and is there a time limit from assignment to a Prime job (A&G acceptance SLA)? This sets retry timing.
  14. Test environment: is there any non-prod Prime to dry-run creates against, or is production dry-run the only verification? (Dev DB Prime tokens are stale.)
  15. Recording: please include in it the login, finding the job from the link, every screen that holds a field you copy, what to do when the link has expired or the job is already accepted/cancelled, and any "accept"/"reject" decision the portal asks of us.