Cobalt Core Apps » Integration Reference

πŸ“Š MoneyThumb β†’ HubSpot Field Map

Comprehensive data map of every HubSpot deal property, custom object, and downstream consumer that MoneyThumb writes to or feeds.

Generated 2026-05-01 Portal 46538270 (Cobalt/Magenta) MT API v1.5

1. Overview #

MoneyThumb (PDF Insights TP, API v1.5) ingests bank statement PDFs and a Plaid asset report and returns a structured scorecard of revenue metrics, account summaries, debt indicators, and per-statement detail. It is a downstream underwriting analysis subsystem β€” never runs during intake.

Once a deal has classified bank statements, the pipeline creates an MT app, uploads PDFs, polls for results, then fans out to:

  • Deal properties β€” orchestration status, revenue statistics (production-final), 3m/6m/all aggregates, and _extracted raw values.
  • Bank Statement custom object (2-37801779) β€” one record per (statement-month Γ— account).
  • MCA Position custom object (2-38036513, internal name lender_metrics) β€” one consolidated record per lender.
  • UW Documents custom object (2-56466936) β€” four MT file outputs (CSV, Excel scorecard, log, summary PDF).
  • Associated Company β€” gap-fill for DBA, address, ZIP+4, primary bank.

Two parallel write paths exist (see Β§2). Property catalog reference: reference/hubspot/hubspot_properties_deals.json.

2. Sync mechanism summary #

MT API β†’ moneyThumbWebhook OR pollMoneyThumbResults
       β†’ results stored at deals/{dealId}/moneythumb_results/latest
       β†’ syncMoneyThumbToHubSpot()  (full-refresh orchestrator)
            β”œβ”€ clearHubspotDeal()                    [archive prior bank statements + MCA positions, blank revenue props]
            β”œβ”€ moneythumbService.makeExcel()         [fetch xlsx scorecard]
            β”œβ”€ upload JSON + XLS to GCS              [public URLs]
            β”œβ”€ syncBankStatementsToHubSpot()         [batch upsert by tid]
            β”œβ”€ syncMcaPositionsToHubSpot()           [batch upsert by lender_name]
            β”œβ”€ extractRevenueStatistics() + computeMoneythumbAggregates()
            β”‚       β†’ PATCH deals/{id}: revenue stats + 3m/6m/all aggregates + average_monthly_revenue__final_
            β”œβ”€ computeCompanyAddressFromAppinfo()    [PATCH company: only fill empty fields]
            └─ enrichmentService.enrichFields()      [Firestore-side enrichment, conf 0.95]

Parallel path (poller only):
       β†’ extractAndStoreDealProperties()             [Firestore deals/{id}.extractedFields.*]
       β†’ syncExtractedFieldsToHubSpot()              [PATCH *_extracted props]
       β†’ enrichCompanyFromMoneyThumb()               [DBA + address gap-fill on company]
       β†’ processMoneyThumbFiles()                    [4 UW Documents]
TriggerEntry pointCalls full orchestrator?Calls extracted-fields sync?
MT webhookfunctions/moneythumb/moneyThumbWebhook.jsyes (syncMoneyThumbToHubSpot)no
2-min pollerfunctions/moneythumb/pollMoneyThumbResults.jsno β€” uses syncExtractedFieldsToHubSpot onlyyes
Manual HTTP syncResultsToHubSpotfunctions/moneythumb/syncResultsToHubSpot.jsyesyes

Practical implication: the canonical MT sync is the orchestrator. The poller writes a strictly smaller set of fields (the _extracted family).

3. RAW EXTRACTED _extracted suffix #

Set by syncExtractedFieldsToHubSpot() (poller + manual sync only). Source = Firestore deals/{id}.extractedFields.*. UW-editable; locked fields are skipped on next sync.

HubSpot propertyFirestore fieldTypeTransformSet in (file:line)
moneythumb_app_url_extractedmoneythumbAppUrlstringidentityextractedFieldsSyncHandler.js:64
moneythumb_spreadsheet_url_extractedmoneythumbSpreadsheetUrlstringidentityextractedFieldsSyncHandler.js:65
avg_monthly_balance_extractedaverageMonthlyBalancenumberround 2dpextractedFieldsSyncHandler.js:55, sourced syncMoneyThumbResultsToHubSpot.js:283
true_avg_monthly_revenue_extractedtrueAverageMonthlyRevenuenumberround 2dp; from averageMonthlyRevenue6MonthsextractedFieldsSyncHandler.js:56
true_avg_monthly_revenue_last_3_months_extractedtrueAverageMonthlyRevenueLast3Monthsnumberround 2dpextractedFieldsSyncHandler.js:57
true_avg_monthly_revenue_last_4_months_extractedtrueAverageMonthlyRevenueLast4Monthsnumberround 2dpextractedFieldsSyncHandler.js:58
true_avg_monthly_revenue_last_6_months_extractedtrueAverageMonthlyRevenueLast6Monthsnumberround 2dpextractedFieldsSyncHandler.js:59
true_avg_monthly_revenue_last_12_months_extractedtrueAverageMonthlyRevenueLast12Monthsnumberround 2dpextractedFieldsSyncHandler.js:60
nsf_days_extractednsfDaysnumberidentity; from nsfCountextractedFieldsSyncHandler.js:61
low_days_extractedlowDaysnumberidentityextractedFieldsSyncHandler.js:62
credit_counts_extractedcreditCountsnumberidentityextractedFieldsSyncHandler.js:63

4. DERIVED / SYNCED production-final #

Written by syncMoneyThumbToHubSpot() orchestrator in a single PATCH at syncMoneyThumbResultsToHubSpot.js:234.

4a. Status / orchestration

HubSpot propertyTypeSource / valueSet / Cleared
moneythumb_app_idnumberMT API appidsyncMoneyThumbResultsToHubSpot.js:217
moneythumb_app_statusenum'Results Synced' on success; 'Statements Cleared/Reset' on clear; 'Ready for Moneythumb' on intake gating:218 / clearHubspotDeal.js:67 / matchCompanyContact.js:298
moneythumb_status_messageshtmlcompletion message + appId:219 / clearHubspotDeal.js:68
moneythumb_data_as_of_datedatetoday UTC midnight (ms epoch):232 / cleared :70
moneythumb_spreadsheet_urlstringpublic GCS URL for XLS:226 / cleared :69
bank_statements_statusenum'Statements Complete' on sync:220

moneythumb_app_status enum domain: Not Ready for Moneythumb, Ready for Moneythumb, App Created, Upload Started, Upload Complete, Results Synced, Results Refreshed, Statements Cleared/Reset, Error. Bold values are written by the orchestrator/clear; intermediates are written upstream during upload.

4b. Revenue Statistics (deal-level)

Set by extractRevenueStatistics(). Source = scorecard.revenue_statistics[] rows keyed by label.

HubSpot propertyMT labelColumnTransform
revenueRevenuemonthlyparseFloat
true_revenueTrue RevenuemonthlyparseFloat
expensesExpensesmonthlyparseFloat
profitProfitmonthlyparseFloat
balance_days_negativeBalance/Days NegativemonthlyparseFloat
non_true_revenueNon-True RevenuemonthlyparseFloat
true_balanceTrue BalancemonthlyparseFloat
average_net_monthly_revenue__moneythumb_Average Monthly Net RevenuemonthlyparseFloat
minimum_monthly_true_revenueMinimum Monthly True RevenuemonthlyparseFloat
average_monthly_credit_card_revenueAverage Monthly Credit Card RevenuemonthlyparseFloat
average_monthly_factoring_revenueAverage Monthly Factoring RevenuemonthlyparseFloat
combined_days_negativeCombined Days NegativemonthlyparseFloat
low_daysLow DaysannualparseInt
days_with_returns (label "NSF Days")Days with ReturnsannualparseInt
mca_withhold_percent_MCA Withhold PercentannualparseFloat Γ· 100
moneythumb_avg_true_credits_6mnested: scorecard.average_true_revenue_6_month.data Total row β†’ groups[0].trueRevenueAverageparseFloat

4c. Aggregates (3m / 6m / all)

Set by computeMoneythumbAggregates(). Source = per-month combined_account_summary array (oldest β†’ newest).

HubSpot propertySource row fieldAggregationWindow
moneythumb_avg_true_credits_3mtrue_creditsaveragelast 3 months
moneythumb_avg_monthly_deposits_3mtotal_creditsaveragelast 3 months
moneythumb_avg_daily_balance_3mavg_balanceaveragelast 3 months
moneythumb_avg_true_debits_3mtrue_debitsaveragelast 3 months
moneythumb_negative_days_3mdays_negsumlast 3 months
moneythumb_total_nsfs_3mnum_nsfssumlast 3 months
moneythumb_nsf_count_3mnum_nsfssumlast 3 months (duplicate of total_nsfs_3m)
moneythumb_avg_monthly_deposits_6mtotal_creditsaveragelast 6 months
moneythumb_avg_true_credits_alltrue_creditsaverageall months
moneythumb_avg_monthly_deposits_alltotal_creditsaverageall months
moneythumb_average_daily_balanceavg_balanceaverageall months
moneythumb_total_nsfs_allnum_nsfssumall months
moneythumb_beginning_balancestarting_balancefirst rowfirst month
moneythumb_ending_balanceending_balancelast rowlast month
moneythumb_lowest_daily_balancemin_balancemin over rowsall months
moneythumb_lowest_monthly_balanceavg_balancemin over rowsall months
moneythumb_month_with_lowest_balancemonth stringrow with min avg_balanceall months
average_monthly_revenue__final_revenue_statistics[label='True Revenue'].monthly, fallback label='Revenue'identityβ€”

All numeric values rounded to 2 decimal places via round2(). null results dropped from PATCH.

5. MT Reports as UW Documents 2-56466936 #

Created by processMoneyThumbFiles() via syncDocumentToHubSpot(). All four collapse to document_type = 'MoneyThumb Report'.

Firestore docTypeHubSpot document_typeSourceFile name
moneythumb_csvMoneyThumb Reportresults.csvurlMoneyThumb_Analysis_{dealId}.csv
moneythumb_reportMoneyThumb Reportresults.logurlMoneyThumb_Log_{dealId}.txt
moneythumb_excelMoneyThumb ReportmoneythumbService.makeExcel(appId)MoneyThumb_Scorecard_{dealId}.xlsx
moneythumb_summaryMoneyThumb Reportlocally generated (pdfkit)MoneyThumb_Summary_Report_{dealId}.pdf

Rollup: number_of_uw_docs__moneythumb counts these. Final HubSpot UW Document name format: {dealId}-MoneyThumb Report.{ext} with (N) suffix on duplicates.

6. Bank Statement object 2-37801779 #

Created by syncBankStatementsToHubSpot(). One record per (account, statement_month). Existing records matched by moneythumb_tid__base_ and upserted via batch.

PropertySourceNotes
institution_name{accountOwner} - {accountNumber} - {month}composite display key; primary property
accountaccountNumber
statement_monthscorecard month
total_credits / total_debitscombined_account_summary.total_credits / .total_debitsparseFloat
average_balanceavg_balanceparseFloat
starting_balance / ending_balancesame names from MTparseFloat
nsf_counts / credit_counts / debit_countsnum_nsfs / num_credits / num_debitsparseInt; default 0
true_creditstrue_creditsparseFloat
account_ownerfirst accountOwner[] elementtitle-cased
bank_namebankName from statement_summaries
start_date / end_dateISO from statement_summariesUTC-midnight ms-epoch
as_of_datesame as end_date
moneythumb_tid__base_tididempotency key
app_idMT appId
associated_company_id / associated_deal_idresolved via deal→company assoc

Rollup feeders: source for the deal-level n3_month_avg_true_revenue and n4_month_avg_true_revenue rollups (formulas defined in HubSpot UI).

7. MCA Position object 2-38036513 (lender_metrics) #

Created by syncMcaPositionsToHubSpot(). One consolidated record per lender per deal. Existing records matched by moneythumb_app_id + associated_deal_id.

Source aggregation merges three MT inputs: mca_companies[] (totals), monthly_mca[] (per-month), and sections.mca.data (flattened detail). Per-lender aggregates sum monthly amounts/counts, falling back to mca_companies totals when monthly data is missing.

PropertySourceTransform
lender_name{lenderName} - {dealId}composite to allow same lender across deals
monthhardcoded 'Totals'sentinel for the rollup record
totals_position_ / exclude_position_true / falseflag
deposit_total / net_amount_fundedaggregateMath.abs()
withdrawal_totalaggregateMath.abs()
avg_withdrawal_amount / debit_amountwithdrawal_total / withdrawal_count if count>0, else lastwithdrawalamountMath.abs()
withhold_percentmca_companies.withholdpercent e.g. "15%"strip %, Γ· 100
term_in_daysmca_companies.termparseFloat
debit_frequencymca_companies.withdrawalfrequencymapped: Daily / Weekly / Bi-Weekly / Monthly / Other / Unknown
daily_debit_equivalentdebit_amount Γ· {1, 7, 14, 30}by frequency
monthly_debit_equivalentdaily_debit_equivalent Γ— 30
deposit_count / withdrawal_countaggregateparseInt
last_deposit_date / date_fundedlastdepositdateUTC-midnight ms-epoch
last_withdrawal_datelastwithdrawaldateUTC-midnight ms-epoch
moneythumb_app_id / associated_deal_id / associated_company_ididentifiers

Rollup feeder: source for number_of_associated_mca_positions.

8. HubSpot rollups not written by code #

Computed by HubSpot itself. The integration relies on them but never PATCHes them.

PropertyTypeRolls up fromUsed by
number_of_associated_mca_positionscalculation_rollupMCA Position custom objectcobalt_funding_position derivation (intakeRequiredDocumentStatus.js:39)
number_of_uw_docs__moneythumbcalculation_rollup (presumed)UW Documents where document_type='MoneyThumb Report'intake_required_document_status threshold (intakeRequiredDocumentStatus.js:80,84)
n3_month_avg_true_revenuecalculation_rollupBank Statement objectrevenue/offer calc group (HubSpot UI)
n4_month_avg_true_revenuecalculation_rollupBank Statement objectrevenue/offer calc group (HubSpot UI)
moneythumb_app_urlcalculation_equationmoneythumb_app_idUW UI deep-link to MT app
number_of_uw_docs_statements_calculation_rollupUW Documents where document_type='Bank Statement'dealinformation

Implication: rollup propagation is asynchronous β€” after the orchestrator completes, rollups may briefly show stale values.

9. Status / state properties orchestration #

PropertyTypeDomainWriters
moneythumb_app_statusselect(see above)orchestrator (β†’Results Synced), clear, matchCompanyContact.js (β†’Ready with regression guard)
moneythumb_status_messageshtmlfree textorchestrator + clearHubspotDeal
moneythumb_status_changeloghtmlfree textno in-repo writer found β€” workflow-driven
moneythumb_data_as_of_datedateUTC midnightorchestrator on success; cleared on reset
moneythumb_update_timestampdatetimelast syncno in-repo writer found
moneythumb_sourceselectPlaid Asset Report (Auto), Bank Statements (Auto), Bank Statements (Manual)cleared on reset; no in-repo writer found
moneythumb_refresh_results_boolUW-toggledUW manual flag, consumed by HubSpot workflow
upload_plaid_to_moneythumb_boolUW-toggledUW manual flag, consumed by HubSpot workflow
upload_statements_to_moneythumb_boolUW-toggledUW manual flag, consumed by HubSpot workflow
deal_checklist___moneythumb_mca_stats_completeboolprogress flagno in-repo writer found
deal_checklist___moneythumb_revenue_stats_completeboolprogress flagno in-repo writer found

10. Calculations end-to-end #

10a. average_monthly_revenue__final_ β€” the canonical "verified" revenue

MT scorecard.revenue_statistics[]
   β”œβ”€ row { label: 'True Revenue', monthly: 47235.62 }    ← preferred
   └─ row { label: 'Revenue',      monthly: 51000.00 }    ← fallback
        β”‚
        β–Ό
computeMoneythumbAggregates()    [moneythumb/helpers/computeMoneythumbAggregates.js:117-124]
        β”‚
        β–Ό
PATCH deals/{id} { average_monthly_revenue__final_: 47235.62 }
        β”‚
        β–Ό
offer-calculator GET /getDealData    [functions/offerCalculator/index.js:582,638]
        β”‚
        β–Ό
formData.monthlyRevenue = parseFloat(deal.average_monthly_revenue__final_) || 0
        β”‚
        β–Ό
calculator.js β†’ suggested_offer_amount, recommended_term, etc.

This is the field that drives the offer calculator's monthly revenue input for both Cobalt and Magenta. The lone numeric MT field consumed by the calc.

Note: average_monthly_revenue__sync_ (group self-reported) is the self-reported revenue, never written by MT code. The offer calculator uses __final_.

10b. cobalt_funding_position

MCA Position custom-object records (created by syncMcaPositionsToHubSpot)
        β”‚  (HubSpot async rollup)
        β–Ό
deal.number_of_associated_mca_positions  (calculation_rollup)
        β”‚
        β–Ό
deriveFundingPosition(mcaCount)    [intakeRequiredDocumentStatus.js:35]
        β”‚
        β–Ό
position = min(mcaCount + 1, 6)  β†’  '1'..'6'
        β”‚
        β–Ό
recomputeAndSyncIntakeRequiredDocumentStatus()    [intakeRequiredDocumentStatus.js:165-181]
        β”‚
        β–Ό
PATCH deals/{id} { cobalt_funding_position: <derived> }   ← ONLY when current value is empty
        β”‚
        β–Ό
offer-calculator: fundingPosition = parseInt(deal.cobalt_funding_position) || 1
        β”‚
        β–Ό
HubDB cfs_funding_position lookup β†’ term_deduction, max_term, sp_deduction

Critical rule: UW may set cobalt_funding_position manually. The derivation must never overwrite a non-empty value. Regression test at intakeRequiredDocumentStatus.test.js:334.

10c. NSF / overdraft / negative-day chain

The Firestore extracted-fields path also computes a synthetic bankAnalysisRiskScore + bankAnalysisRiskFactors in extractFinancialMetrics(), but these go to Firestore only β€” NOT pushed to any HubSpot deal property by current sync handlers.

11. Consumers / dependents #

11a. Offer Calculator

  • average_monthly_revenue__final_ β€” sole MT-derived input for Cobalt CFS + Magenta MF (configs/cobalt.config.js:73, configs/magenta.config.js:70, index.js:582,638).
  • cobalt_funding_position β€” input via cobalt.config.js:76, index.js:585,644. Derived from MCA-Position rollup when empty.

11b. intake_required_document_status aggregate

intakeRequiredDocumentStatus.js:80-87 requires for complete:

  • All five association sub-statuses are complete
  • number_of_associated_uw_docs__applications >= 1
  • number_of_associated_uw_docs__bank_statements >= 3
  • number_of_uw_docs__moneythumb >= 1
  • moneythumb_app_status === 'Results Synced'

If MT is configured but never completes, the deal cannot reach complete intake status.

11c. Intake-time gating (matchCompanyContact)

matchCompanyContact.js:278-318 and :430-466: when the deal becomes ready for MT, set moneythumb_app_status='Ready for Moneythumb' β€” but only if the current value is not in the in-progress/done set. Prevents intake re-runs from regressing a completed MT workflow.

11d. Document classification

geminiClassifier.js and genkit/classifyDocument.js recognize moneythumb_report. Files matching moneythumb, scorecard, or summary_report classified directly without LLM (classifyDocuments.js:69-70).

11e. Deal-stage advancement

No code in this repo directly advances a deal stage based on MT fields. Stage advancement is HubSpot-workflow driven.

12. Firestore companion data (not HubSpot) #

PathContents
moneythumb_apps/{dealId}App metadata: appId, appNumber, documentIds, webhookUrl, status
moneythumb_apps/{dealId}/versions/{ISO}Per-sync snapshot: scorecard URL, JSON URL
moneythumb_jobs/{appId}Job lifecycle: status, statementsUploaded, completedAt, completionMethod
deals/{dealId}/moneythumb_results/latestFull MT results JSON
deals/{dealId}/moneythumb_results/company_enrichmentCompany-enrichment audit trail
deals/{dealId}.extractedFields.*Per-field value with lastSyncedToHubSpot, locked, confidence
deals/{dealId}.moneythumbBankStatementIdsHubSpot Bank Statement IDs (cleanup on next refresh)
deals/{dealId}.moneythumbMcaPositionIdsHubSpot MCA Position IDs (cleanup)
deals/{dealId}.moneythumbResultsSyncedAtISO timestamp of last orchestrator run

enrichmentService.enrichFields(dealId, enrichFields, 'moneythumb', 0.95) writes a parallel store with confidence scoring β€” Firestore-side only.

13. Gotchas / nuances #

  1. Two write paths, different field sets. Webhook→orchestrator writes the production-final values + 3m/6m/all aggregates + average_monthly_revenue__final_. Poller writes only the _extracted family. If only _extracted values are populated, the webhook never fired (or syncResultsToHubSpot was not invoked).
  2. moneythumb_nsf_count_3m and moneythumb_total_nsfs_3m are identical. Both sum(last_3.num_nsfs). Pick one consumer-side; the duplication is intentional for backwards-compat.
  3. clearHubspotDeal does NOT blank the 3m/6m/all aggregates or balance fields. Only blanks revenue-statistics group + moneythumb_avg_true_credits_6m. If the new sync produces fewer rows, older 3m/6m/all values may persist as stale.
  4. HubSpot rollup propagation lag. number_of_associated_mca_positions, number_of_uw_docs__moneythumb, and n3/n4_month_avg_true_revenue update asynchronously. A sync that just completed may show stale rollups for tens of seconds.
  5. cobalt_funding_position is gap-fill only. UW manual values must never be overwritten by the derivation. Verified by regression test.
  6. moneythumb_app_status regression guard. Intake-side handlers will not reset to Ready if the status is in App Created, Upload Started, Upload Complete, Results Synced, Results Refreshed, or Error.
  7. average_monthly_revenue__final_ group is verified_info, not moneythumb. Intentional β€” the canonical UW input regardless of upstream source.
  8. Historic enum values may differ. moneythumb_status_changelog may include older labels like Results Synced to HubSpot. Catalog updated; changelog stays historical.
  9. number_of_uw_docs__moneythumb is missing from the cached property catalog but exists in the live portal. Run npm run hubspot:sync:properties to refresh.
  10. MT prefixes owner names with "DBA " β€” the company-enrichment path strips it and title-cases (enrichCompanyFromMoneyThumb.js:56).
  11. PDFs under 50 KB are silently rejected by MT. Pre-validate at upload β€” see playbook LESSONS #3.
  12. _extracted properties are UW-editable. Each PATCH first reads metadata.locked and skips locked fields.

14. Open questions #

  1. Who writes moneythumb_status_changelog? No in-repo PATCH found. Likely a HubSpot workflow appending each moneythumb_app_status change. Confirm with portal admin.
  2. Who writes moneythumb_update_timestamp? No in-repo writer.
  3. Who writes moneythumb_source? Cleared by clearHubspotDeal.js:71 but never set in code. Presumably a workflow inferring source from upload toggles.
  4. Who writes moneythumb_report_pdf? String labeled "Moneythumb Report PDF" in catalog. Whether this stores a URL to the summary UW Document is unclear from code.
  5. Who writes deal_checklist___moneythumb_mca_stats_complete / _revenue_stats_complete? Likely workflow-driven.
  6. Should clearHubspotDeal also blank the 3m/6m/all aggregates and balance fields? Currently does not β€” gap if next sync produces fewer months.
  7. Are _extracted and _synced versions ever expected to differ? In principle they should track unless UW edits the _extracted and locks it. No automated reconciliation report.

15. Reference quick-look #

Custom object IDs
  • Bank Statement: 2-37801779
  • MCA Position: 2-38036513 (internal name lender_metrics)
  • UW Documents: 2-56466936
Pipelines
  • CFS Deal: 813089417 β†’ CFS UW Docs: 860640945 (stage 1284463963)
  • MF Deal: 142412149 β†’ MF UW Docs: 860639775 (stage 1284472687)
MT API
  • Base: https://insights.moneythumb.com/api/v1.5
  • Header: MT-Product: pdfinsightstp
  • Secret: GCP Secret Manager MONEYTHUMB_API_TOKEN
Documentation
  • Source markdown: docs/integrations/moneythumb-field-map.md
  • Playbook: playbooks/moneythumb-playbook/INTEGRATION.md
  • Production status: docs/MoneyThumb-Production-Status.md
  • Blueprint Β§7.2: docs/blueprint/COBALT_BLUEPRINT.md