Drafts (Notes) β
The mobile app calls them Notes. The backend calls them Drafts. They are the same thing β a
Draftdocument withsource: '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:
| Source | Entry Point | Typical Content |
|---|---|---|
map | POST /drafts/from-map-note | Title + body + location + photos from the map UI |
chat | POST /drafts/conversation/:id | Accumulated chat messages + attachments |
vapi | Inbound phone call pipeline | Audio transcription from voice calls |
manual | Direct API call | User-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() β
- Geospatial field lookup β queries the
Fieldcollection with$geoIntersectson the provided GPS coordinates to find which agricultural field the note falls within - Attachment resolution β fetches full
Attachmentdocuments from MongoDB by the provided IDs - Location metadata β builds a
DraftLocationPointwith the matchedfieldId,fieldName, note title, timestamp, and attachment references - Draft creation β calls
initDraft()withstatus: 'processing'andsource: 'map' - Async processing β enqueues the draft to the
chat.attachments.analyzeBullMQ 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.2804lat: 40.71 lon: -74.0040.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 parseResultIdsAfter parsing, the service also:
- Injects
callLogIdsfrom the draft into parse results - Injects
fieldIdfrom location metadata (if the workspace schema has a field reference column) - Maps generic attachment placeholders to real attachment IDs
Controller Endpoints β
CRUD β
| Method | Endpoint | Purpose |
|---|---|---|
GET | /drafts | Get user's drafts (filter by status query param) |
GET | /drafts/company/:companyId | Get company drafts (optional date range) |
GET | /drafts/:draftId | Get draft by ID |
POST | /drafts/from-map-note | Create note from map (with location) |
POST | /drafts/conversation/:conversationId | Create blank draft for chat |
DELETE | /drafts/:draftId | Soft delete (mark as discarded) |
DELETE | /drafts/:draftId/permanent | Hard delete + cascade to ParseResults |
Processing & Updates β
| Method | Endpoint | Purpose |
|---|---|---|
PUT | /drafts/:draftId/metadata | Update locations, tags |
PUT | /drafts/:draftId/transcript | Update transcript, re-parse, refresh summary |
PUT | /drafts/:draftId/parse-results | Update parsed data directly |
POST | /drafts/:draftId/store | Finalize draft (status β stored) |
Sub-resources β
| Method | Endpoint | Purpose |
|---|---|---|
GET | /drafts/conversation/:id/active | Get active draft for a chat conversation |
GET | /drafts/conversation/:id/summary | Get draft summary + message count |
DELETE | /drafts/:id/attachments/:attachmentId | Remove attachment from draft |
DELETE | /drafts/:id/call-logs/:callLogId | Remove audio reference from draft |
Key Files β
| File | Purpose |
|---|---|
src/drafts/schemas/draft.schema.ts | Mongoose model definition |
src/drafts/dto/draft.dto.ts | Zod validation schemas |
src/drafts/drafts.controller.ts | REST endpoints |
src/drafts/draft.service.ts | Business logic (main service) |
src/drafts/utils/draft-parse.util.ts | Content assembly + LLM parsing integration |
src/drafts/utils/draft-attachment.util.ts | Attachment extraction + ID mapping |
src/drafts/utils/draft-metadata.util.ts | Location metadata helpers |
Related β
- Notes & Audio Upload β the upload architecture (attachment + audio pipelines)
- Module Relationships β how Drafts connect to Conversations, Fields, Attachments, etc.
- Queues & Processing β the async pipeline details