Skip to content

Drafts (Notes) ​

The mobile app calls them Notes. The backend calls them Drafts. They are the same thing β€” a Draft document with source: 'map'.

Drafts are the central intake point for user-generated content. They capture text, attachments, voice recordings, and GPS coordinates, then feed them through an AI parsing pipeline to produce structured records (ParseResults).

Data Model ​

Status Lifecycle ​

  • active β€” draft is being created or edited
  • processing β€” async queue is analyzing attachments + running AI extraction
  • stored β€” finalized, searchable, visible on the map
  • discarded β€” soft-deleted by user
  • error β€” processing pipeline failed

Sources ​

Drafts can originate from four different entry points:

SourceEntry PointTypical Content
mapPOST /drafts/from-map-noteTitle + body + location + photos from the map UI
chatPOST /drafts/conversation/:idAccumulated chat messages + attachments
vapiInbound phone call pipelineAudio transcription from voice calls
manualDirect API callUser-entered text, file uploads

Map Note Flow (Mobile) ​

This is the most common path for mobile users creating notes on the map.

What happens inside createFromMapNote() ​

  1. Geospatial field lookup β€” queries the Field collection with $geoIntersects on the provided GPS coordinates to find which agricultural field the note falls within
  2. Attachment resolution β€” fetches full Attachment documents from MongoDB by the provided IDs
  3. Location metadata β€” builds a DraftLocationPoint with the matched fieldId, fieldName, note title, timestamp, and attachment references
  4. Draft creation β€” calls initDraft() with status: 'processing' and source: 'map'
  5. Async processing β€” enqueues the draft to the chat.attachments.analyze BullMQ queue for attachment analysis and AI parsing

GPS Detection ​

When messages are added to a draft, the service runs GPT-4o-mini to extract explicit GPS coordinates from the text. This only recognizes literal coordinate values β€” it does NOT geocode addresses or place names.

Recognized formats:

  • 49.9728, -98.2804
  • lat: 40.71 lon: -74.00
  • 40.7128Β° N, 74.0060Β° W

GPS is also extracted from photo EXIF metadata when attachments are added.

All detected locations are stored in metadata.locations as GeoJSON Points.

AI Parsing Pipeline ​

The core of draft processing is DraftParseUtil.regenerateParseResults(), which assembles content from multiple sources and sends it to the LLM:

Content assembly:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ messages[]   │──→ message.text lines
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ transcript   │──→ full text content
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ locations    │──→ "Location 1: lat, lng Β±accuracy @timestamp"
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ attachments  │──→ "[ID: xxx] (image): photo.jpg\n<analyzed content>"
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚
        β–Ό
  ParseProcessService.parseTranscript()
        β”‚
        β”œβ”€β”€ TranscriptPreprocessingService (clean/normalize)
        β”œβ”€β”€ IntentDetectionService (LLM classification)
        β”œβ”€β”€ ParseService.parse() (LLM structured extraction)
        β”œβ”€β”€ WorkspaceValidationService (validate fields)
        β”œβ”€β”€ ReferenceResolutionService (resolve field/workspace refs)
        └── ReferenceValidationService (confirm refs exist)
        β”‚
        β–Ό
  ParseResults saved to DB
  Draft updated with parseResultIds

After parsing, the service also:

  • Injects callLogIds from the draft into parse results
  • Injects fieldId from location metadata (if the workspace schema has a field reference column)
  • Maps generic attachment placeholders to real attachment IDs

Controller Endpoints ​

CRUD ​

MethodEndpointPurpose
GET/draftsGet user's drafts (filter by status query param)
GET/drafts/company/:companyIdGet company drafts (optional date range)
GET/drafts/:draftIdGet draft by ID
POST/drafts/from-map-noteCreate note from map (with location)
POST/drafts/conversation/:conversationIdCreate blank draft for chat
DELETE/drafts/:draftIdSoft delete (mark as discarded)
DELETE/drafts/:draftId/permanentHard delete + cascade to ParseResults

Processing & Updates ​

MethodEndpointPurpose
PUT/drafts/:draftId/metadataUpdate locations, tags
PUT/drafts/:draftId/transcriptUpdate transcript, re-parse, refresh summary
PUT/drafts/:draftId/parse-resultsUpdate parsed data directly
POST/drafts/:draftId/storeFinalize draft (status β†’ stored)

Sub-resources ​

MethodEndpointPurpose
GET/drafts/conversation/:id/activeGet active draft for a chat conversation
GET/drafts/conversation/:id/summaryGet draft summary + message count
DELETE/drafts/:id/attachments/:attachmentIdRemove attachment from draft
DELETE/drafts/:id/call-logs/:callLogIdRemove audio reference from draft

Key Files ​

FilePurpose
src/drafts/schemas/draft.schema.tsMongoose model definition
src/drafts/dto/draft.dto.tsZod validation schemas
src/drafts/drafts.controller.tsREST endpoints
src/drafts/draft.service.tsBusiness logic (main service)
src/drafts/utils/draft-parse.util.tsContent assembly + LLM parsing integration
src/drafts/utils/draft-attachment.util.tsAttachment extraction + ID mapping
src/drafts/utils/draft-metadata.util.tsLocation metadata helpers