Appearance
System Architecture
High-Level Overview
mermaid
graph TD
A[Flutter Mobile App] --> B[API Gateway / Express]
C[React Dashboard] --> B
B --> D[Auth Middleware]
B --> E[Credit / Subscription Gate]
B --> F[Pathfinding Engine]
B --> G[Company Service]
B --> H[Representative Verification Service]
F --> I[AdjacencyService]
I --> J1[personalConnectionAdapter]
I --> J2[employmentAdapter]
I --> J3[companyRelAdapter]
I --> J4[knowledgeEdgeAdapter]
I --> J5[userLinkAdapter]
F --> K[edgeScoring]
B --> L[MongoDB Atlas]
B --> M[AI Service / FastAPI]
M --> N[Gemini API]
H --> O[emailService / YeboLink]API Service (pathorah-api)
Express 4 / TypeScript monolith. Organized by feature:
src/
models/ — Mongoose schemas (Company, Branch, Employment, CompanyRelationship, KnowledgeEdge, ...)
services/
pathfinding/
core/ — GraphNode types, BFS engine, AdjacencyService
adapters/ — One adapter per edge kind
scoring/ — edgeScoring (unified), pathScoring
resolvers/ — targetResolver, nodeHydrator
controllers/ — Thin HTTP handlers
routes/ — Express routers
middleware/ — auth, companyAdmin, creditCheck, subscriptionLoad, rateLimit
scripts/ — backfillCompanies, verifyPathfinding (regression tests)AI Service (pathorah-ai)
FastAPI / Python. Dedicated enrichment service — the API calls it, it calls Gemini, it returns suggestions. The API never calls Gemini directly.
app/
api/v1/endpoints/
companies.py — POST /enrich/company
paths.py — path enrichment
warmth.py — warmth signal inference
discover.py — discovery suggestions
services/
gemini_client.py — Gemini API wrapper
entity_prompt_renderer.py — per-node-type prompt templatesPathfinding Engine Architecture
The engine was refactored from two divergent monolithic files into a clean separation of concerns:
Before (legacy)
pathfindingService.ts(893 lines) — local BFS, person-onlysmartPathfindingService.ts(535 lines) — global BFS, different scoringpathScoringService.ts(114 lines) — scoring split across both- Known issues: O(n)
Array.shift(), O(n) path array spreads, two divergent scoring systems,calculateWarmthDecaydefined but never called
After (current)
core/GraphNode.ts— polymorphic{nodeType, nodeId}types and edge kind definitionscore/bfs.ts— heterogeneous BFS with parent-pointer reconstruction (eliminates O(n) spreads), per-depth visited cache, wall-clock timeoutcore/AdjacencyService.ts— fan-out coordinator; expands a node by calling all registered adaptersadapters/personalConnectionAdapter.ts— Connection collection edgesadapters/employmentAdapter.ts— Employment collection edges (emits REPRESENTS/REPRESENTED_BY for rep flags)adapters/companyRelAdapter.ts— CompanyRelationship collection edgesadapters/knowledgeEdgeAdapter.ts— KnowledgeEdge collection edgesadapters/userLinkAdapter.ts— cross-ref between User and Contact nodes sharing an identityscoring/edgeScoring.ts— single unified scorer replacing both legacy systems;calculateWarmthDecayfinally wired inscoring/pathScoring.ts— aggregate path score from edge sequenceresolvers/targetResolver.ts— resolvestargetType+targetIdto aGraphNodeRefresolvers/nodeHydrator.ts— enrichesGraphNodeRefinto displayableGraphNode
Adding a new entity type = one new adapter file, zero changes to BFS or scoring.
Data Layer
MongoDB Atlas with Mongoose ODM. Notable indexes:
Company: text index onname + aliases + description; 2dsphere onheadquarters.coords; sparse unique onslug; sparse ondomainEmployment: partial unique on(userId, companyId, startDate)and(contactId, companyId, startDate);representativeEmailOtpHashwithselect: falseCompanyRelationship: unique on(fromCompanyId, toCompanyId, type, startDate)to prevent duplicate relationship rowsBranch: 2dsphere onlocation.coords; unique(companyId, slug)
Scoped Admin Permissions
companyAdmin middleware checks Employment.isCompanyAdmin per company rather than a global role. This means a user who is a company admin for Acme Corp cannot edit profiles for Globex Corp. Distinct from isRepresentative which is a graph semantics concept, not a permissions concept.