Skip to content

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.activeDraftId is updated
  • Messages are copied into the draft as DraftMessage objects (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 Attachment documents 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) ​

FieldTypePurpose
attachmentTypeenumimage, audio, video, document, pdf, spreadsheet, other
s3Key / s3BucketstringS3 storage location
processingStatusenumpending β†’ processing β†’ completed / failed
uploadingStatusenumpending β†’ uploading β†’ completed
analysisReportMixedRaw response from analysis provider (Reducto, OpenAI Vision)
metadata.gpsobjectGPS 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 callLogId is passed when creating the note
  • During parsing, callLogIds are injected into the resulting ParseResults
  • source field on CallLog distinguishes 'notes' (transcribe-only) from 'file_upload' (transcribe + parse)

CallLog Schema (key fields) ​

FieldTypePurpose
transcriptstringSpeech-to-text output
sourceenuminbound, outbound, file_upload, notes
assetsMapAudio files + transcription metadata per provider
parseIdObjectIdAssociated 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) ​

FieldTypePurpose
draftIdObjectIdSource draft
workspaceIdObjectIdSchema that defines the record structure
geometryGeoJSON PointLocation (2dsphere indexed)
images[]ObjectId[]Referenced Attachments
callLogIds[]ObjectId[]Source audio recordings
searchTextstringFull-text search content
internalReferenceIdObjectIdDeduplication 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 $geoIntersects query against the Field collection
  • If the note's GPS coordinates fall within a field polygon, the fieldId and fieldName are stored in the location metadata
  • During parsing, if the workspace schema has a field reference column, the matched fieldId is injected into parse results

Field Schema (key fields) ​

FieldTypePurpose
geometryPolygon / MultiPolygonField boundary (2dsphere indexed)
centroidPointCenter of the field (2dsphere indexed)
areanumberField area
namestringDisplay 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.