Skip to content

Action: Endata — set quote rates ​

Status: proposed. Blocked on Endata MFA recovery enrolment (work item 1745). Port of stagehand-automations/apps/new-endata-quote-rates. Shared rules are in README.md.

Sets the rate and margin of every line item on an Endata claim's quote from an emailed CSV, then re-reads the whole table and compares every row.

Entry points ​

  • Queue endata.
  • POST /actions/endata/quote-rates — payload + dryRun?.

Payload ​

json
{ "claimNumber": "<string|number>", "blobName": "endata-quote-rates-<6 digits>-<claim>.json" }
{ "claimNumber": "<string|number>", "lineItems": [ { "costCentre": "...", "rate": "123.45" } ] }
  • claimNumber string or number, trimmed, non-empty.
  • lineItems (≥ 1) wins over blobName; neither → reject/invalid_payload. Each item's costCentre is the first present of costCentre / cost_centre / description; rate string or number.
  • Blob document: { estimatorEmail?, lineItems }.

Dependencies ​

KindWhatOwner
Producern8n "Endata Quote Rates - Enqueue": Outlook trigger polls one folder every minute, groups CSV rows by claim (header aliases), uploads the blob (POST …/EndataStorage/{blobName}, multipart file, credential "Ethos Prod WebAPI"), then EXEC spEthWorkQueueEnqueue_Preview directly → switches to POST /queue/endata on this API. No BlazorApps producer proc.n8n
Queueendata. Old dedupe key sent is claimNumber (docs say blob name — the workflow wins). Proposed MessageId = blobName (a corrected CSV for the same claim is a new request), window 1 h. At-least-once is fine: writes are idempotent.this repo
Data — readWebAPI GET {WebApi:BaseUrl}/EndataStorage/{urlencoded blobName}, Basic auth, Accept: */*. Body is JSON despite a text/csv content type. Non-2xx (incl. 404) → attempt/line_items_unavailable. Retry 3× on transport errors.BlazorApps WebAPI
Data — writeNone to Ethos; the portal is the only write.—
CredentialsPortals:Endata:Username/:Password, WebApi:*Key Vault
MFAComputed TOTP. Portals:Endata:MfaUrl = n8n POST /webhook/mfa-as-a-service/otp/get-code/Endata (credential "OTP Service Auth"); secret decrypted from dbo.EthMFAOtpAccounts. Accept if seconds_remaining >= 20. No send budget.n8n
BrowserContext Portals:Endata:ContextId. Old lane exits through the static-IP proxy VM ([ip], tinyproxy, steel-proxy-url) — Endata probably allowlists it, so Portals:Endata:Proxy points at the same VM.Browserbase + proxy VM
SettingsEndata:MarginRate = 20, Endata:Server = CHU, Endata:Company = Ethos Building Pty Ltd, Application = ENData (constant)config

Flow ​

  1. Line items. Inline, or fetch the blob. Fetch failure → attempt/line_items_unavailable.
  2. Sign in on https://www.endataclaims.com (sign-in path /NewWebsite/Solid/Login); workspace on https://endata-c.endataclaims.com/. Cookies/storage span the apex endataclaims.com. Signed in = top nav has both Dashboard and Claims (the marketing page with HOME / ABOUT US = signed out). Screens: cookie banner → email → ("Continue") → password → ("Continue with the account … used last time") → decline enrolment → TOTP code → "ENData Portal" dialog: choose Server (the press sometimes doesn't register and shows no selection — check Company/Application appear), Company, Application, confirm.
  3. Find claim. Nav "Claims" → list search box: type claimNumber → run the search. Read each result's insurer reference; exactly one exact match, else attempt/claim_not_found. Press that row's insurer reference cell → Quote tab → re-check the reference (wrong_claim).
  4. Read the table. Columns Cost Centre, Description, Unit, Qty, Rate Ex, Margin, Subtotal, Action (only while the quote is editable). A line item = a row with an Action control (headings like "BATHROOM - 2.70 x 1.74" and totals aren't). Single page asserted (147 rows seen).
  5. Gates (before any write):
    • Row count == CSV count, from two agreeing readings → else reject/row_count_mismatch.
    • Descriptions match by position: lowercase both, split into [a-z0-9]+ runs, CSV runs must appear in order in the row text (a CSV with no runs passes) → else reject/row_description_mismatch.
  6. Write each differing row (dryRun stops before the first): row's Action control → dialog: "margin percentage" = MarginRate, "rate excluding GST" = rate → read both boxes back → Update → dialog closed (else Escape, rate_not_set).
  7. Verify. Re-read the whole table without the targets; compare every row → complete/reconciled. Some rows wrong → attempt/partially_written (not_persisted).

Deadline 14 min; session timeout 15 min.

Outcomes ​

OutcomeReasons
completereconciled
rejectinvalid_payload, row_count_mismatch, row_description_mismatch
attemptpartially_written, line_items_unavailable, claim_not_found, wrong_claim, rate_not_set, not_persisted, coverage_incomplete, irreversible_write_refused (dryRun), mfa_code_stale, mfa_code_rejected, sign_in_failed, bot_challenge, rate_limited, portal_step_failed, deadline_exceeded

Open / blockers ​

  • MFA enrolment (work item 1745) — no sign-in until done.
  • No teardown URL known ("until somebody has watched this portal navigate to one"); watch for storage wipes when proving the saved login.
  • The queue has never carried a message — no live run has exercised steps 3–7. Capture locators from the first signed-in session.
  • Order: second, after Nexus.