Skip to content

Action: Nexus — update estimated completion date ​

Status: proposed. Port of stagehand-automations/apps/new-nexus-automation (its README records why most steps are the way they are — read it before building). Shared rules (queue, outcomes, MFA, credentials) are in README.md.

Sets the Completion Date on every in-scope sub-job of a project in Auto & General's supplier portal to Prime's estimated completion date (ECD), verifies it from the timeline, then records the watermark in Prime.

Entry points ​

  • Queue nexus, Subject none. Message body = payload.
  • POST /actions/nexus/update-ecd — payload + dryRun?.

Payload ​

json
{ "primeJobId": "<guid>", "primeJobNumber": "<optional>" }

primeJobId required, trimmed, non-empty; primeJobNumber optional, display only. Anything else → reject/invalid_payload.

Dependencies ​

KindWhatOwner
ProducerspEthNexusECDQueueTasks — one message per job whose normalised EndDate differs from LastECD. Today ends in spEthWorkQueueEnqueue_Preview; needs to SELECT rows instead (see README).BlazorApps
Schedulen8n "Nexus ECD Update - Enqueue" (o9JbWWZXjN67NJJj), every 3 h at :11n8n
Queuenexus, duplicate detection window 12 h, MessageId = primeJobId (matches BlazorApps migration 0034_NexusQueueDeduplication12Hours). At-least-once is fine: the write is idempotent (read before, skip if already correct).this repo
Data — readWebAPI GET {WebApi:BaseUrl}/Prime/job/{primeJobId}/ecd → { jobID, clientReference, endDate }; 404 or empty body = no recordBlazorApps WebAPI
Data — writeWebAPI POST {WebApi:BaseUrl}/Prime/job/{primeJobId}/ecdhistory, body { "EcdDate": "<iso>" }, Content-Type: application/json (else 415). Upserts via spPrimeUpsertECDHistory.BlazorApps WebAPI
CredentialsPortals:Nexus:Username/:Password, WebApi:*Key Vault
MFADelivered SMS to one phone. Portals:Nexus:MfaUrl = n8n mfa-as-a-service/get-mfa-latest-email/unity ("Get Latest MFA Code For Service", iTrH8w5mgxZWrlxQ). Send budget account nexus, 3/hour, shared with A&G staff — so the new budget must leave headroom.n8n
BrowserContext Portals:Nexus:ContextId. AU residential proxy (rotating is OK today). The old lane also used a fixed egress IP for the saved login — prove whether Unity cares.Browserbase

Flow ​

  1. Resolve (no browser). GET …/ecd.
    • No record / no endDate / no clientReference → attempt (no_ecd_record, no_usable_date, no_project_reference).
    • endDate ≥ 2099-12-31 (Prime's "no date") → POST …/ecdhistory with that date → complete/sentinel. ~70% of messages end here.
    • Else target = { projectReference: clientReference, date: endDate (date only) }.
  2. Sign in (https://unity.autogeneral.com.au). Route-block {origin}/graphql/v2/clear-site-data* before navigating (the app wipes its own storage otherwise). Go to /pulse/suppliers/projects; signed in = projects list or account name visible. Otherwise the auth0 login at /login: supplier login option → (remembered-account screen: continue) → email → password (maybe separate screens) → reserve send → submit → "remember this browser" → code → submit. Decline passkey enrolment if offered.
  3. Find project. Projects search field: type reference, Enter. Open the row whose reference matches exactly; confirm the reference on the project page (wrong_project).
  4. List sub-jobs. "Jobs" tab; columns Job Type, Job Reference, Assigned to, Last Updated, Status, Actions. Walk pages (cap 25; stop when a page repeats). Keep rows with Job Type "Assess and Repair" or "Quote Only" and Status "Repairs in Progress" (case-insensitive). None → complete/no_targets.
  5. Per sub-job: open via the row's "VIEW" button (not the caret beside it). Page is /pulse/suppliers/jobs/<id>/details and paints blank until reloaded — reload, re-read after 6 s if still blank. Confirm it's the expected sub-job (wrong_sub_job_opened → complete, nothing written).
  6. Write. "OVERVIEW" tab → "Completion Date" (input[type=date] — not "Appointment Date"). Already equal → skip. Else FillAsync("yyyy-MM-dd"), read the value back, (dryRun stops here) press the header "SAVE".
  7. Verify. "TIMELINE" tab; if the table is empty, turn on "SHOW SYSTEM-GENERATED RECORDS" (its own on/off state is unreliable — judge by the rows). Columns Date, Type, Summary, Description, User, Action. Newest "Job field updated" row → its "VIEW" → "View Field Updates" panel shows the new value as yyyy-MM-dd → "CLOSE". Mismatch → attempt/date_not_persisted.
  8. Watermark. Only when every in-scope sub-job is verified or already correct: POST …/ecdhistory → complete/reconciled. Failure → attempt/watermark_unavailable.

Whole-run deadline 14 min (deadline_exceeded); Browserbase session timeout 15 min.

Outcomes ​

OutcomeReasons
completesentinel, no_targets, wrong_sub_job_opened, reconciled
rejectinvalid_payload
attemptirreversible_write_refused (dryRun), wrong_project, date_not_persisted, coverage_incomplete, watermark_unavailable, no_ecd_record, no_usable_date, no_project_reference, send_budget_exhausted, mfa_code_stale, mfa_code_rejected, sign_in_failed, bot_challenge, rate_limited, portal_step_failed, deadline_exceeded

Open / to prove ​

  • Saved login survives in a Browserbase context. The token that matters is in IndexedDB (localforage, ~9 h life). The old worker seeded IndexedDB from /robots.txt before the app booted. If the context keeps the full profile this all disappears; if not, restore via AddInitScript. Prove it first — every failed restore spends an SMS.
  • Capture real locators for every label above from one signed-in session.
  • Order: first action to port.