heroValue / property_budget_aed |
propertyPreference.targetAmount + propertyPreference.currency |
ApplicationPropertyPreference |
targetAmount, currency |
Use a currency field rather than encoding AED in the name. |
foundProperty, found_property, has_found_property |
application.propertySearchStatus |
ApplicationPropertyPreference |
propertySearchStatus |
Map a positive answer to identified. Map a negative answer to searching or not_started based on the UI wording. |
mortgageStatus |
application.mortgageSearchStatus |
Application |
mortgageSearchStatus |
Use this as an intake/search state: has the applicant started, found, or not needed a mortgage path? Actual lender decisions and offers live under lender submissions. |
timeline, property_timeline |
propertyPreference.purchaseTimeline |
ApplicationPropertyPreference |
purchaseTimeline |
Use purchaseTimeline in the payload. Avoid propertyTimeline because the timeline is specifically the buyer's purchase intent. |
areaMulti, property_area, areas |
propertyPreference.locationData |
ApplicationPropertyPreference |
locationData[] items: id, name, address, pslCode, addressId, parentPslCode, addressPslCode, locationConfidence, locationConfidenceScore |
Use the existing location structure; area-picker UI names should not become backend field names. |
propertyType, property_type |
propertyPreference.preferredPropertyTypes |
ApplicationPropertyPreference |
preferredPropertyTypes: propertyType[] |
Use plural because clients can select multiple property types. |
bedrooms, bathrooms |
propertyPreference.preferredBedrooms, propertyPreference.preferredBathrooms |
ApplicationPropertyPreference |
preferredBedrooms: number[], preferredBathrooms: number[] |
Preferences are selected count options, not a min/max range. Use scalar bedrooms and bathrooms only on a selected property. |
residency, uae_residency |
parties[].identity.residencies[] |
ApplicationPartyIdentity / ApplicationPartyResidency |
countryCode, residencyStatus, residencyBasis, startedOn, endedOn, documents[] |
For lending, residency is an applicant/borrower disclosure used for eligibility. UAE should be a country value, not part of the field name. |
nationality |
parties[].identity.nationalities[] |
ApplicationPartyIdentity / ApplicationPartyNationality |
countryCode, status, isPrimary, startedOn, endedOn, documents[] |
A party can disclose more than one nationality/citizenship for one application. Reusable person-level nationality can preload the form, but source links stay internal. |
incomeInput, monthly_income, fixed_income_aed, commissionInput, variable_income, variable_income_aed |
parties[].incomeSources[].amount, parties[].incomeSources[].type |
IncomeSource |
type, amount, currency, cadence, isVariable |
Use one income-source pattern for salary, commission, bonus, business income, and other repeatable income. |
householdIncomeInput, household_income_total |
affordabilityAssessment.inputSnapshot.householdIncomeTotal |
AffordabilityAssessment |
householdIncomeTotal, currency, calculatedAt |
Use this as a calculated or displayed affordability input when individual party incomes are already collected. Do not let it replace party-owned income-source rows. |
partner_income_monthly_aed, partnerCommInput, partner_commission_monthly_aed |
parties[].incomeSources[].amount, scoped by parties[].role = co_applicant |
IncomeSource |
type, amount, currency, cadence |
Partner is a relationship label; co-applicant is the application capacity. If the partner is not legally applying, store the person as a related contact instead of an applicant party. |
fullName, full_name, emailInput, contact_email, phoneInput, contact_phone, co_app_full_name, co_app_email, co_app_phone |
parties[].person.firstName, middleName, lastName, legalFullName, contactMethods[] |
Person, PersonIdentityProfile, ContactMethod |
firstName, middleName, lastName, legalFullName, type, value, normalizedValue |
Collect structured name parts where possible; keep legalFullName for markets/documents that require the exact full name. |
phone_otp_verified |
parties[].contactMethods[].verifiedAt |
ContactMethod |
verifiedAt |
Use timestamp rather than boolean. |
cost_breakdown_confirmed, cost_breakdown_confirmed_at |
application.acknowledgements[].acknowledgedAt |
ApplicationAcknowledgement |
type, acknowledgedAt |
Application acknowledgement event. |
service_selection |
application.selectedServices[].serviceCode |
SelectedService |
serviceCode, selectedAt |
Use only if Sooner has selectable service bundles. Lender products, decisions, and offers should remain on lender-submission records. |
employment_type, employment_type_other, employer_name, role_name, job_title, role_seniority, seniority_level, employer_tenure, employer_tenure_bucket, business_years |
parties[].employment[] |
Employment |
employmentType, employmentTypeOtherText, employerNameText, jobTitle, seniorityLevel, startedOn, tenureMonths, tenureBucket, businessYears |
Prefer startedOn when exact dates are available. Use buckets or calculated months when the form only asks for a range. |
industry_id, industry_code, industry_other, role_family_id, occupation_family_code, role_family_other, employer_ownership_type, employer_size_band, regulated_or_licensed |
parties[].employment[] |
Employment |
industryId, industryOtherText, roleFamilyId, roleFamilyOtherText, employerOwnershipType, employerSizeBand, isRegulatedOrLicensed |
Use backend reference data for business-facing industries and occupation families. Keep *OtherText scoped to the same employment row. |
has_existing_mortgage, mortgage_payment, mortgage_monthly_aed |
parties[].liabilities[].type, monthlyPayment.amount, monthlyPayment.currency |
Liability |
type = mortgage, monthlyPaymentAmount, currency |
Allows more than one mortgage liability. |
has_rent, rent_payment |
parties[].housingExpenses[].type, monthlyPayment.amount, monthlyPayment.currency |
HousingExpense |
type = rent, monthlyPaymentAmount, currency |
Rent is an obligation, not a loan. |
has_personal_loan, personal_loan_payment, has_car_loan, car_loan_payment, has_credit_card, credit_card_payment, credit_card_total_limit, credit_card_outstanding |
parties[].liabilities[] |
Liability |
type, monthlyPaymentAmount, limitAmount, balanceAmount, currency |
Use repeatable liabilities for personal loans, vehicle loans, credit cards, overdrafts, and other obligations. |
other_monthly_commitments, other_monthly_commitments_aed |
parties[].liabilities[] |
Liability |
type = other, monthlyPaymentAmount, currency, description |
Prefer a known liability type such as lease, support_obligation, or overdraft when the form can identify it. |
savings / savings_band |
parties[].assets[].amountBand |
Asset |
type = liquid_savings, amount, amountBand, currency |
Use exact amount if collected later. |
property_usage_raw |
propertyPreference.intendedUse |
ApplicationPropertyPreference |
intendedUse |
Capture occupancy/use intent with values such as primary_residence, secondary_residence, buy_to_let, investment_property, and other. Do not suffix raw unless a normalized pair exists. |
down_payment_source |
application.fundingSources[].type |
FundingSource |
type, amount, currency |
Supports AML/provider workflows later. |
down_payment_source_other |
application.fundingSources[].otherText |
FundingSource |
otherText |
Only populate this when the selected source type is other. |
aecb_score, aecb_band |
parties[].creditProfile |
CreditProfile |
provider = aecb, source = self_reported, selfReportedScore, selfReportedBand, declaredAt |
This is applicant-declared credit standing. Official bureau data belongs to CreditCheck, CreditReport, and credit-score child records. |
has_co_applicant, co_app_*, co_applicant_* |
parties[] with role = co_applicant |
ApplicationParty |
role = co_applicant, plus the same identity, contact, employment, income, liability, housing expense, asset, credit profile, consent, and document child records used by the primary applicant |
The gate controls whether a second party is collected. Durable state is the presence of a co-applicant party, not a standalone boolean. |
has_specific_property |
propertyPreference.propertySearchStatus |
ApplicationPropertyPreference |
propertySearchStatus |
This is a search-state answer from the form. It should not create a separate durable property model. |
listing_url, building_or_sub_community, exact_price, ready_to_move_in, freehold_status, property_usage |
propertyPreference.metadata.specific_property |
ApplicationPropertyPreference.metadata |
specific_property.source_url, location_text, stated_amount, availability_status, tenure_type, intended_use |
Only keep these if the intake flow needs to remember what the applicant typed. Store persisted metadata keys as lower snake case. |
property_options, locked_option_id, property_options_complete |
application.metadata.forms.property_options |
Application.metadata |
forms.property_options, forms.locked_option_id, forms.property_options_completed_at |
Treat as UI/progressive form state unless Sooner later decides to productize recommendations. |
ownership_split |
application.partyRelationships[].ownershipSharePercent |
ApplicationPartyRelationship |
ownershipSharePercent, financialContributionPercent |
Capture applicant-party intent only. Do not model property title or property interests in this document. |
trade_license_number, po-biz-license |
parties[].employment[].tradeLicenseNumberText or institution.identifiers[] |
Employment / InstitutionIdentifier |
tradeLicenseNumberText or identifierType = trade_license, identifierValue, countryCode |
Use an institution identifier when the business is normalized; otherwise keep a self-employed employment snapshot value. |
reliable_submission_consent |
application.consents[].capturedAt |
ConsentEvent |
type, status, capturedAt |
Submission accuracy confirmation. |
rejection_reason, rejection_other |
mortgageOffer.response or lenderSubmission.response |
MortgageOfferResponse / LenderSubmission |
response = rejected, reasonCode, reasonOtherText, respondedAt |
Scope rejection feedback to the offer or lender path being rejected, not to the whole person record. |