Module Relationships β
This page maps how the backend's domain modules connect to each other. The Drafts module sits at the center, linking conversations, attachments, audio, geospatial fields, and AI-extracted records.
Relationship Diagram β
Drafts β Conversations β
A Draft can originate from a chat conversation. When it does, the conversation holds a reference to the current working draft.
Conversation.activeDraftId βββ Draft._id
Draft.conversationId βββ Conversation._id (string, not ObjectId)
Draft.messageIds[] βββ ConversationMessage._id[]
Draft.messages[] βββ embedded DraftMessage copies- One conversation has at most one active draft at a time
- When a draft is created from chat,
conversation.activeDraftIdis updated - Messages are copied into the draft as
DraftMessageobjects (denormalized for parsing)
Drafts β Attachments β
Attachments are files uploaded to S3 β images, PDFs, documents, spreadsheets.
Draft.attachments[] βββ embedded attachment metadata
Attachment.draftId? βββ Draft._id (optional back-reference)- Attachments are created independently via the upload flow
- Their IDs are passed when creating a map note (
POST /drafts/from-map-note) - The draft service fetches full
Attachmentdocuments for content assembly - GPS EXIF data from photos is extracted and added to
metadata.locations - During parsing,
DraftAttachmentUtil.mapAttachmentIds()maps generic LLM placeholders to real attachment IDs
Attachment Schema (key fields) β
| Field | Type | Purpose |
|---|---|---|
attachmentType | enum | image, audio, video, document, pdf, spreadsheet, other |
s3Key / s3Bucket | string | S3 storage location |
processingStatus | enum | pending β processing β completed / failed |
uploadingStatus | enum | pending β uploading β completed |
analysisReport | Mixed | Raw response from analysis provider (Reducto, OpenAI Vision) |
metadata.gps | object | GPS coordinates from EXIF data |
Drafts β Call Logs β
Call Logs represent audio recordings and their transcriptions.
Draft.callLogIds[] βββ CallLog._id[]- Audio is uploaded through a separate pipeline (
/call-logs/generate-s3-signed-url) - Transcription runs asynchronously through BullMQ (OpenAI Whisper, Qwen3)
- The
callLogIdis passed when creating the note - During parsing,
callLogIdsare injected into the resulting ParseResults sourcefield on CallLog distinguishes'notes'(transcribe-only) from'file_upload'(transcribe + parse)
CallLog Schema (key fields) β
| Field | Type | Purpose |
|---|---|---|
transcript | string | Speech-to-text output |
source | enum | inbound, outbound, file_upload, notes |
assets | Map | Audio files + transcription metadata per provider |
parseId | ObjectId | Associated ParseResult (if parsed) |
Drafts β Parse Results β
Parse Results are the structured data records extracted by the AI pipeline.
Draft.parseResultIds[] βββ ParseResult._id[]
Draft.parseResults βββ embedded { data, success, count }
ParseResult.draftId βββ Draft._id (back-reference)- Created by
DraftParseUtil.regenerateParseResults()during async processing - Stored both as referenced documents and as an embedded summary on the draft
- When a transcript is re-parsed (
PUT /drafts/:id/transcript), old ParseResults are cascade-deleted and new ones are created - ParseResults link to a Workspace (schema definition) that defines what fields were extracted
ParseResult Schema (key fields) β
| Field | Type | Purpose |
|---|---|---|
draftId | ObjectId | Source draft |
workspaceId | ObjectId | Schema that defines the record structure |
geometry | GeoJSON Point | Location (2dsphere indexed) |
images[] | ObjectId[] | Referenced Attachments |
callLogIds[] | ObjectId[] | Source audio recordings |
searchText | string | Full-text search content |
internalReferenceId | ObjectId | Deduplication key |
Drafts β Fields β
Drafts connect to Fields indirectly through geospatial queries.
Draft.metadata.locations[].properties.fieldId βββ Field._id- When a map note is created,
DraftService.createFromMapNote()runs a$geoIntersectsquery against the Field collection - If the note's GPS coordinates fall within a field polygon, the
fieldIdandfieldNameare stored in the location metadata - During parsing, if the workspace schema has a field reference column, the matched
fieldIdis injected into parse results
Field Schema (key fields) β
| Field | Type | Purpose |
|---|---|---|
geometry | Polygon / MultiPolygon | Field boundary (2dsphere indexed) |
centroid | Point | Center of the field (2dsphere indexed) |
area | number | Field area |
name | string | Display name |
tags[] | string[] | Classification labels |
Drafts β Embeddings β
Vector embeddings enable semantic search across notes.
Draft.embedding βββ number[1536] (OpenAI embedding)
Draft.embeddingModel βββ string (model identifier)- Generated asynchronously via
DraftEventEmitter.emitGeneratedEmbeddingEvent() - Stored directly on the draft document
- Indexed via MongoDB Atlas Search (cosine similarity, filtered by
companyId) - Used for semantic search across a company's notes
Companies β Everything β
All domain entities extend CompanyAwareBase:
*.companyId βββ Company._id
*.visibility βββ 'private' | 'company' | 'public'
*.sharedWithCompanies[] βββ Company._id[]
*.sharedWithUsers[] βββ User._id[]See Multi-Tenancy Architecture for the full access control model.