import MermaidChart from '../../../components/MermaidChart';

Developer Guide: Sales for Mortgages Architecture 🏗️

This document outlines the technical architecture, data model, automation triggers, and configuration patterns for the Valstorm Sales for Mortgages system.

This system relies heavily on App Metadata for configuration and Record Triggers for business logic. It is designed to minimize hardcoding and maximize bulk-processing efficiency.


1. Entity Relationship Model (ERD)

The system distinguishes between the human (Contact), the sales opportunity (Lead), and the financial product (Loan).

<MermaidChart chart={` erDiagram CONTACT ||--o{ LEAD : "Applied For" CONTACT ||--o{ LOAN : "Primary Borrower" CONTACT ||--o{ LOAN : "Co-Borrower" LEAD |o--|| LOAN : "Converted To" LEAD }o--|| CONTACT : "Referred By"

CONTACT { string id PK string email string phone string salesforce_id } LEAD { string id PK string status string loan_purpose date in_contract_date json contact FK } LOAN { string id PK string status json originating_lead FK json primary_borrower FK currency loan_amount percent ltv } `} />


2. Configuration: App Metadata

Business logic is controlled via two primary App Metadata configurations. Modifying these JSON objects allows you to alter system behavior without deploying code.

Config A: Sales for Mortgages

Controls timestamp mapping, lead-to-loan conversion, and loan status logic.

{ "lead_status_date_time_mapping": { "New": "new_lead_date", "In Contract": "in_contract_date", "Won": "won_date" // ... maps Status (Key) to Field API Name (Value) }, "lead_status_date_time_mapping_active": true, "lead_to_loan_creation_active": true, "lead_to_loan_creation_mapping": { "originating_lead": "id", "primary_borrower": "contact", "purpose": "loan_purpose", "property_address": "address" }, "loan_status_date_time_mapping": { "Contract Received": "contract_received_date", "Funded": "funded_date" }, "loan_status_date_time_mapping_active": true }

Config B: Lead - Contact Sync

Controls the identity resolution strategy when new Leads are ingested via API or Webhooks.

{ "active": true, "identity_priority": ["email", "phone"], "sync_mapping": { "first_name": "first_name", "email": "email", "phone": "phone" }, "options": { "create_missing_contact": true } }

3. Automation Logic (Triggers)

Trigger 1: Lead Identity Resolution

File: lead_before_upsert.py (Logic branch) Context: Create/Update

Before a Lead is saved, the system attempts to link it to an existing Contact to prevent data duplication.

  1. Extraction: Scans incoming payload for email or phone based on identity_priority.
  2. Bulk Query: Executes a single SQL query to find matches in the Contact collection using optimized buckets.
  3. Linking:
  • Match Found: Links the Lead to the existing Contact ID.
  • No Match: Creates a new Contact record (using create_records bypassing recursion triggers) and links the Lead to it.

Trigger 2: Lead Lifecycle & Conversion

File: lead_sales_for_mortgages_upsert.py Context: Before Create/Update

This trigger handles timestamping and the critical hand-off to the Loan object.

A. Status Timestamping

  • Logic: Checks lead_status_date_time_mapping.
  • Condition: If status changes AND the target date field is empty.
  • Action: Sets current UTC timestamp to the target field.

B. Loan Generation ("In Contract")

  • Condition: Lead Status changes to "In Contract".
  • Action: Prepares a Loan record payload.
  • Mapping: Uses lead_to_loan_creation_mapping metadata. If metadata is missing, falls back to hardcoded defaults.
  • Naming: Generates Loan Name: {Last Name} - {Street Address} - {Purpose}.
  • Execution: Collects all Loan payloads and executes create_records('loan', ...) in a bulk operation outside the processing loop.

Trigger 3: Loan Processing Logic

File: loan_before_upsert.py Context: Before Create/Update

Handles the underwriting data integrity and feedback loop to the originating Lead.

A. Auto-Naming

  • Condition: If property_address or purpose changes.
  • Format: {Borrower Name} - {Line1, City, State} - {Purpose}.
  • Fallbacks: Handles missing addresses gracefully (No Street, No City).

B. Financial Calculations (LTV)

  • Condition: If loan_amount and purchase_price are present and valid (>0).
  • Formula:
  • Output: Stored as a decimal (e.g., 0.80).

C. Feedback Loop (Loan Lead)

  • Condition: Loan Status changes to Funded or Canceled.

  • Action: Updates the originating_lead record.

  • Funded Lead Status: Won

  • Canceled Lead Status: Preapproved

  • Execution: Uses update_records('lead', ...) in a bulk operation.


4. Picklist Dependencies

The system relies on specific Picklist values to trigger logic. If you modify these Tags, ensure you update the App Metadata JSON accordingly.

Lead Statuses

ValueTrigger Effect
NewTimestamps new_lead_date
In ContractCreates Loan Record
WonTimestamps won_date

Loan Statuses

ValueTrigger Effect
Contract ReceivedDefault status on creation
FundedUpdates originating Lead to Won
CanceledUpdates originating Lead to Preapproved

5. Maintenance & Troubleshooting

Disabling Automation

To stop specific automations without deploying code, set the following keys to false in App Metadata:

  • lead_status_date_time_mapping_active
  • lead_to_loan_creation_active
  • loan_status_date_time_mapping_active

Common Errors

  • Missing Loan Name: Usually caused by a Lead having no Contact linked or no Address when moved to "In Contract". The system defaults to "Unnamed Lead" or "No Street".
  • Duplicate Contacts: Check the Lead - Contact Sync metadata. Ensure identity_priority includes unique identifiers like email or phone.
  • LTV Calculation Fails: Ensure purchase_price is not 0. The trigger catches ZeroDivisionError silently, so the field will simply remain empty.