These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

Data model — AiLocationImportBatch

Header record for an AI-driven location import session; pairs with AiLocationImportItem rows that hold the individual generated drafts the user reviews before importing.

See Backend — AI services for the full import flow, job architecture, and Google Gemini call details.


Table: ai_location_import_batches

ColumnTypeNullableDefaultDescription
idbigint (PK)NoAuto-increment
workspace_idbigint (FK → workspaces)NoOwning workspace; cascade delete
finder_idbigint (FK → finders)YesnullScoped to a specific finder; nulled on finder delete (added in migration 2026_04_21)
user_idbigint (FK → users)NoUser who initiated the batch; cascade delete
statusvarcharNodraftState machine value — see Status enum below
source_answersjsonNoGuided-questionnaire answers posted by the user (brand, business type, location count, geography, etc.) used to build the Gemini prompt
model_namevarcharYesnullGemini model used to generate this batch (e.g. gemini-2.5-flash-lite) — stored for reproducibility
prompt_versionvarcharNov1Prompt template version — current value is v4
usage_metadatajsonYesnullToken-count data returned by Gemini — see usage_metadata shape
total_itemsint (unsigned)No0Total number of draft items in this batch
approved_itemsint (unsigned)No0Items the user explicitly approved
rejected_itemsint (unsigned)No0Items the user explicitly rejected
imported_itemsint (unsigned)No0Items successfully written to locations
failed_itemsint (unsigned)No0Items that failed during the import step
created_attimestampNoLaravel standard
updated_attimestampNoLaravel standard

Indexes: (workspace_id, status), (user_id, status)


Table: ai_location_import_items

One row per generated location draft. Belongs to a batch via batch_id.

ColumnTypeNullableDefaultDescription
idbigint (PK)NoAuto-increment
batch_idbigint (FK → ai_location_import_batches)NoParent batch; cascade delete
statusvarcharNopending_reviewItem state machine value — see Item status enum
raw_model_outputjsonYesnullRaw object for this location as returned by Gemini before normalization
normalized_payloadjsonNoNormalized location data after post-processing and Google Places enrichment
user_payloadjsonYesnullEdits the user made to normalized_payload before approving
warningsjsonYesnullArray of non-blocking warning strings (e.g. “phone format unrecognized”)
validation_errorsjsonYesnullArray of validation errors that prevent import
confidencevarcharYesnullConfidence score: high, medium, or low — see scoring rules below
import_resultjsonYesnullResult written after the import step (e.g. {location_id: 42} on success, error detail on failure)
created_attimestampNo
updated_attimestampNo

Indexes: (batch_id, status)


Relations

  • AiLocationImportBatch::workspace()belongsTo(Workspace::class)
  • AiLocationImportBatch::user()belongsTo(User::class)
  • AiLocationImportBatch::items()hasMany(AiLocationImportItem::class, 'batch_id')->orderBy('id')
  • AiLocationImportItem::batch()belongsTo(AiLocationImportBatch::class, 'batch_id')

Status enum

Batch statuses

ValueMeaning
draftDefault; batch has items pending review or approval
importedAll items have been imported (none remain pending or approved)
discardedBatch was deleted by the user (DELETE /ai-location-imports/{id})

Item status enum

ValueMeaning
pending_reviewDefault; awaiting user decision
approvedUser approved the draft
rejectedUser rejected the draft
importedSuccessfully written to locations
failedImport step wrote to this item but the location create failed

JSON columns

source_answers

The questionnaire payload from the user — keys vary but typically include fields like business type, location count, geography, language, and optional brand context. These are embedded verbatim into the Gemini prompt by buildPrompt().

usage_metadata

Token usage extracted from Gemini’s usageMetadata response object. Shape:

{ "promptTokenCount": 1234, "candidatesTokenCount": 567, "totalTokenCount": 1801, "thoughtsTokenCount": 0, "toolUsePromptTokenCount": 0, "raw": { ... } }

The original Gemini usageMetadata object is preserved under raw. Cost can be estimated using the GEMINI_2_5_FLASH_LITE_INPUT_PER_MILLION / OUTPUT_PER_MILLION env vars.

normalized_payload and user_payload (items)

normalized_payload is the output of the Google Places enrichment step applied to Gemini’s raw output. Fields: name, address, city, state, zip, country, phone, website, lat, lng, tags, categories, custom_fields, and optional hours.

user_payload overlays the user’s edits onto normalized_payload — the import step merges both and uses user_payload values where present.

import_result (items)

On success: {"location_id": 42}. On failure: an object with the error detail. Null until the apply step runs.


Confidence scoring

Computed per item by AiLocationImportController:

ScoreCondition
lowAny validation_errors present
high≥ 6 core fields filled and ≤ 1 warning
mediumAll other cases

The generate step creates a Process record of type ai_location_import_generate; the apply step creates one of type ai_location_import_apply. The Process.related_type / related_id columns point back to this batch. See Data model — Process for the state machine.


Route surface (for reference)

MethodPathNotes
GET/api/ai-location-importsList batches
POST/api/ai-location-importsCreate batch + dispatch generate job
GET/api/ai-location-imports/{id}Single batch with items
PATCH/api/ai-location-imports/{id}/items/{itemId}Approve/reject/edit a draft item
POST/api/ai-location-imports/{id}/importAuto-approve valid pending items, run apply job synchronously
DELETE/api/ai-location-imports/{id}Set status to discarded