Accounting Import Events (Amplitude)

Accounting Import Events (Amplitude) #

The AI accounting import (apps/webapp/src/features/entity-setting/components/accounting/v2) emits a snake_case event set to the existing Amplitude project. Extend this catalogue rather than inventing parallel names.

How it is wired #

  • Names and the per-event property allow-list: …/accounting/v2/constants/tracking-events.ts.
  • Emitter: …/accounting/v2/tracking/use-import-tracking.ts, which wraps the house useTracking() hook (shared/components/monitor/hooks/use-tracking.tsx) so every event stays behind Cookiebot consent. Never import @amplitude/analytics-browser in the feature — a test fails the build if you do.
  • sanitizeProps (…/tracking/payload.ts) drops any property the event does not allow-list, caps arrays at 20 items and strings at 200 characters, and buckets file sizes. The allow-list is the privacy boundary, enforced at runtime rather than by review.

Shared properties #

Stamped onto every event: import_flow_id, entity_id, organization_id, user_id, source_software, source_is_other, has_integration, mode (ai_import | prefill), model_tier, flow_variant, plus category: accounting_import.

import_flow_id is minted when an upload settles (or when the prefill screen opens) and is the join key for the whole funnel — a re-upload starts a new id.

Events #

Funnel #

Event Fires when Notable properties
upload_screen_viewed Upload screen opens has_prefill_data
source_software_selected Software picked changed_from_default
upload_onboarding_started Upload settles, AI runs fire file_count, file_extension(s), estimated_rows, size_bucket, has_csv, has_excel, is_reupload
upload_file_rejected File fails validation error_code, file_extension, size_bucket
upload_file_removed File removed from the pool file_extension, remaining_count
prefill_mode_shown Connected-integration variant provider, has_prefill_data
manual_entry_chosen Falls back to the V1 form stage, duration_seconds
onboarding_flow_completed Save succeeds duration_seconds, total_records, records_<table>, correction_count_total, retry_count, failed_run_count, recovery_used, steps_revisited
onboarding_flow_abandoned Back / manual entry / discarding re-upload / navigate away / unmount reason, last_step_reached, last_step_table, tables_accepted, unsaved_records

AI quality #

Event Fires when Notable properties
ai_run_started A category’s run acquires its gate slot gate_wait_ms, retry_token, pool_version
ai_run_completed Model result validated duration_ms, field_count, steps_streamed
ai_run_failed Run settles with no result duration_ms, error_kind
ai_run_retried Retry pressed retry_index, was_encode_retry
import_encode_failed Pool encode throws (corrupt/protected workbook) file_count, has_excel
ai_mapping_suggested Suggestion becomes visible (single commit point) confidence_score, confidence_min, low_confidence_count, assumed_count, filled_count, field_count, row_count, needs_normalization, duration_ms
ai_mapping_corrected Per corrected column, at step accept field_name, correction_count, rows_changed, correction_kind, was_assumed, field_confidence, and for controlled-vocabulary columns only original_suggestion / final_selection
ai_normalization_started / _completed Normalisation pass row_count, mapped_field_count, outcome (applied | schema_invalid | settled_without_result)
ai_missing_fields_reported Run reports missing fields missing_field_count, missing_fields (tofu field keys)

Review behaviour #

Event Fires when Notable properties
review_step_viewed / _accepted / _revisited Wizard navigation step_index, time_on_step_ms, edited_before_accept, correction_count, corrections_unavailable
validation_error_shown Active step invalid (deduped per failure) missing_fields, invalid_fields, error_row_count
sheet_first_edited First divergence from the AI’s sheet ms_since_suggestion
expanded_ai_suggestion Chain-of-thought section opened step_category, step_count, is_live_turn
recovery_guide_requested / _shown Missing-field guide missing_field_count, source_count, has_video, duration_ms
recovery_source_clicked Guide link clicked host (hostname only), is_video
save_failed Save rejected error_kind, duplicate_field, records_attempted

Deliberately not tracked #

Cell values, raw file names, contact details (names, emails, tax IDs), the AI summary and chain-of-thought text, and extractedData rows never reach Amplitude. The one exception is ai_mapping_corrected’s original_suggestion / final_selection, which are only attached for columns whose values come from lists we own (account type, currency, tax rate, tag parent, default account) — see CONTROLLED_FIELDS in …/tracking/corrections.ts.

How corrections are derived #

There is no explicit mapping UI: the AI writes into the review sheets and the user edits cells. At each step accept, the live sheet is diffed against the snapshot taken when the AI’s sheet was merged. Rows are paired on each table’s identity column so sorting is not mistaken for mass corrections, and if more than 75% of rows changed (on a sheet of at least four rows) the diff reports corrections_unavailable instead of noise.

The accepted-import record #

On a successful save the client also POSTs a diagnostic record to /api/accounting-import/log, which validates it and writes one structured log line (label: accounting_import_record) to Datadog. It carries file metadata, per-table mapping details (tofu field, source header, confidence, assumed/filled flags), validation results and correction counts — no cell values. Retention is Datadog log retention; if this record ever needs to be queried months later it should move to a bonsai table.

Reading the data #

  • Events are gated on Cookiebot statistics/marketing consent, so every count is a sample, not a census. Note it on any dashboard.
  • Nothing is emitted in local dev (shouldMonitorEnvironment); the hook logs Tracking action: <name> at debug level instead, which is how you verify wiring locally.
  • duration_seconds starts when the upload settles, not when the file picker opens.

Session replay #

Replay runs at sampleRate: 1. consent-manager.ts blocks .bonsai-spreadsheet (the grid canvas) and masks .bonsai-import-conversation plus any [data-amp-mask] element (the uploaded file list), so recordings show how the UI behaved without shipping the customer’s books.