Skip to main content

Progressive Home SF Sync — Debugging & Known Debts

Reference for developers working on the Salesforce sync flow for Progressive Home. Covers the three debug mechanisms available today, when each one is useful, and the known data-quality debts that need to be resolved before we can fully trust what ends up in Salesforce.


Debug Mechanisms

1. VITE_SF_SYNC_DEV_MODE — Unified dev mode (frontend + backend)

Type: Shared env var — read by both Vite (import.meta.env) and the NestJS gateway (process.env)
Files:

  • apps/fastlane-portal/src/app/pages/carriers/progressive/home/config-driven/step-overrides/checkout/checkout-step.tsx
  • apps/apis/fastlane-api-gateway/src/app/salesforce-sync/services/progressive-home-salesforce-sync-orchestrator.service.ts

When set to true this single flag activates two behaviors simultaneously:

Frontend: Clicking Submit on the Checkout step calls the devTriggerSfSync endpoint and does not execute the actual Progressive bind:

if (import.meta.env.VITE_SF_SYNC_DEV_MODE === 'true') {
const syncResult = await devTriggerSfSync(stepConfig.syncId);
// logs result, sets form error with status, returns early
return;
}

Backend: The LaunchDarkly feature flag check is bypassed — the SF sync always runs regardless of the LD flag state:

if (process.env.VITE_SF_SYNC_DEV_MODE === 'true') {
return true; // skip LD evaluation
}

Use when: You want to test/iterate on the SF sync payload (builders, normalizers, orchestrator) without committing a live insurance policy, or when LD is not configured locally and you need to force-enable the sync.

Production safety: The devTriggerSfSync API endpoint is guarded server-side and returns an error when NODE_ENV === 'production'. This variable must be false or unset in production.


2. SF_SYNC_LOG_PAYLOAD — Verbose PII-containing payload logs

Type: Backend env var
Files affected:

  • apps/apis/fastlane-api-gateway/src/app/salesforce-sync/services/salesforce-sync-client.service.ts
  • apps/apis/fastlane-api-gateway/src/app/salesforce-sync/services/progressive-home-salesforce-sync-orchestrator.service.ts

When set to true, the gateway logs:

  1. The raw JSON sent to the Salesforce microservice (policyRequest + propertyRequests).
  2. A structured human-readable breakdown of the outgoing payload: named insureds, property address, mortgagee details, coverage limits.

Both log blocks are wrapped in:

if (process.env.SF_SYNC_LOG_PAYLOAD === 'true') { ... }

Use when: You need to compare what we're sending to SF against what actually lands in the Salesforce record. Look for [PH-SF-SYNC] and [SF-SYNC][OUTGOING-PAYLOAD] log prefixes in the gateway output.

Production safety: These logs contain full PII (names, addresses, loan numbers). Must be false or unset in production.


Local .env Reference

Both flags should be set to true for full local debugging visibility:

# Dev mode: frontend skips real bind + backend bypasses LaunchDarkly
VITE_SF_SYNC_DEV_MODE=true

# Logs full PII payload to gateway console (names, addresses, raw JSON)
SF_SYNC_LOG_PAYLOAD=true

In production both must be false or absent.


Known Data Debts — Coverage Normalizer Defaults

The following constants in apps/apis/fastlane-api-gateway/src/app/salesforce-sync/mappers/progressive-home-coverage-normalizer.ts inject assumed values into SF when CRN does not store the real data. They produce incorrect or misleading SF records and should be eliminated.

PROGRESSIVE_HOME_MEDICAL_PAYMENTS_DEFAULT = 5000

Problem: CRN's coverages_data does not save the Medical Payments (Coverage F) amount selected by the user. When the field is absent, the normalizer falls back to 5000 and medical_payments_home__c always arrives in SF as '5000' — regardless of what the customer actually chose.

Fix: Save medicalPayments to coverages_data in the portal, or read the actual Coverage F amount from the Progressive HAL API quote/bind response, so this constant is never reached.


PROGRESSIVE_HOME_FOUNDATION_COVERAGE_DEFAULT = true

Problem: CRN does not store the foundation coverage flag. The normalizer defaults it to true, meaning every SF Opportunity receives Foundation_Coverage__c = true even for policies that do not include foundation coverage.

Fix: Confirm with Progressive and the SF team whether:

  • This field should be omitted from the sync when the value is unknown, or
  • The actual boolean can be read from the HAL bind response.

PROGRESSIVE_HOME_LOI_DEFAULT_PCT = 20

Problem: When CRN does not store an explicit Loss-of-Use limit, the normalizer derives it as 20 % of the dwelling limit. The 20 % is a common HO3 standard but is still an assumption — the user may have selected a different value.

Severity: Lower than the others because the derived number is at least proportional to real policy data (dwelling limit). However it is still not the actual selected value.

Fix: Save lossOfUse to coverages_data in the portal so CRN always provides the real figure and this fallback is never needed.


Salesforce Developer Console — Verifying What Landed in SF

After triggering a sync (real or via VITE_SF_SYNC_DEV_MODE), use the SF Developer Console to confirm the Opportunity record was actually updated and the field values match what we sent.

Steps

  1. Log in to Salesforce Sandbox at https://test.salesforce.com
  2. Top-right menu → Developer Console
  3. Open Query Editor (bottom panel) and run SOQL to find the Opportunity:
SELECT Id, policy_number__c, insurance_carrier__c, coverage__c,
medical_payments_home__c, Foundation_Coverage__c,
Water_Backup__c, Sudden_Accidental__c,
Replacement_Cost_Picklist__c, Mortgagee_Bind__c,
Loan_Number__c, first_named_insured__c, qti_bind_date__c
FROM Opportunity
WHERE policy_number__c = '<your-policy-number>'
LIMIT 1
  1. Compare the returned values field-by-field against what the gateway logged under [PH-SF-SYNC] (enable SF_SYNC_LOG_PAYLOAD=true first).

What to look for

SymptomLikely cause
Field is null when it should have a valueBuilder is not setting the field / normalizer returned undefined
Field has a hardcoded value (e.g. 5000, true)One of the coverage normalizer defaults is firing — see Known Debts below
Opportunity not updated at allSF sync did not run — check VITE_SF_SYNC_DEV_MODE, LD flag, and gateway logs for [PH-SF-SYNC]
HTTP error from microserviceCheck SALESFORCE_SERVICE_BASE_URL in .env and that the goosehead-apps SF service is running

Useful: view all recent Opportunities

SELECT Id, policy_number__c, CreatedDate, LastModifiedDate, insurance_carrier__c
FROM Opportunity
ORDER BY LastModifiedDate DESC
LIMIT 20

References

GitLab — Salesforce Microservice

The SALESFORCE_SERVICE_BASE_URL points to the goosehead-apps service, which is the internal microservice that receives our SyncPolicyRequest payload and writes to Salesforce via jsforce.

Script — test-sf-opportunity-update.js (preservado)

Script jsforce de diagnóstico rápido para verificar que el usuario DEV de SF tiene permisos para actualizar un Opportunity, y para probar valores de campos directamente contra el sandbox sin pasar por el gateway.

Requiere: jsforce instalado en goosehead-apps (npm install en ese repo).

# desde el root de goosehead-apps
node scripts/test-sf-opportunity-update.js

Sustituir las constantes en el script con credenciales y el Opportunity ID real del test antes de ejecutar:

/**
* Quick diagnostic script — tests if the DEV SF user can update an Opportunity.
* Run: node scripts/test-sf-opportunity-update.js
*/
const jsforce = require('jsforce');

const SF_USER = 'digitalagentintegration@goosehead.com.uat'; // DEV/sandbox user
const SF_PASS = '<password+security-token>'; // never commit real value
const SF_LOGIN_URL = 'https://test.salesforce.com';
const OPPORTUNITY_ID = '<OpportunityId from SF>'; // e.g. 006TI00000OQ534YAD

async function main() {
const conn = new jsforce.Connection({ loginUrl: SF_LOGIN_URL });

console.log('Connecting to Salesforce...');
await conn.login(SF_USER, SF_PASS);
console.log('Connected. UserInfo:', conn.userInfo);

console.log('\nAttempting Opportunity update...');
const payload = {
Id: OPPORTUNITY_ID,
premium__c: 360786,
pmt_to_carrier__c: '0',
down_payment_amount__c: '0',
policy_fee__c: 0,
agreed_personal_liability__c: '500000',
Replacement_Cost_Picklist__c: 'Yes',
loss_of_use__c: '72157',
medical_payments_home__c: '5000',
Sudden_Accidental__c: 'Up to Dwelling Limit',
};

try {
const result = await conn.sobject('Opportunity').update(payload);
console.log('Result:', JSON.stringify(result, null, 2));
if (!result.success) {
console.error('UPDATE FAILED:', result.errors);
} else {
console.log('UPDATE SUCCEEDED');
}
} catch (err) {
console.error('EXCEPTION:', err.message || err);
}
}

main().catch(console.error);

Uso típico:

  1. Copia el Opportunity ID de SF Developer Console (SOQL query de arriba).
  2. Ajusta el payload con los valores que quieres probar (p.ej. cambiar Sudden_Accidental__c para verificar que el picklist acepta el valor).
  3. Ejecuta — si UPDATE SUCCEEDED el campo acepta ese valor y el usuario tiene permisos. Si UPDATE FAILED revisa result.errors para el mensaje de validación de SF.
caution

No commitear credenciales reales. Este script era de uso local efímero — las credenciales deben leerse de .env o pasarse por variable de entorno si se reutiliza el patrón.

Confluence — SF Post-Bind Sync Validation Matrix

The Safeco Home V3.1 sync validation matrix is the closest documented precedent for how SF post-bind sync is validated across carriers. The field matrix, expected values, and validation steps there are directly applicable as a reference pattern for Progressive Home.

A Progressive Home equivalent of this matrix should be created once the sync is validated end-to-end — it will document the expected SF field values for a known test policy and serve as the acceptance criteria for the feature.

Reference Salesforce Policies (UAT)

These UAT Opportunity records can be used as reference points when validating SF sync behavior and field mapping:


Flow Summary

Submit (Checkout)
└─ VITE_SF_SYNC_DEV_MODE=true? ── YES ──► devTriggerSfSync endpoint (no bind)

NO │
│ ▼
Progressive VITE_SF_SYNC_DEV_MODE or LD flag?
HAL bind │
│ ▼
└────► Orchestrator

├─ Coverage normalizer (⚠ defaults above)
├─ Opportunity builder
├─ Property request builder


SF microservice call

SF_SYNC_LOG_PAYLOAD=true?

YES ──► Logs raw JSON + PII breakdown