Technical Architecture
Technology Stack
The platform combines a Python AI backend, two Next.js web applications, a React Native mobile application, and a set of data and AI services.
Component |
Technology |
Version |
Role |
|---|---|---|---|
API backend |
FastAPI |
0.109+ |
REST server and orchestration |
Generative AI |
Google Gemini (via OpenRouter) |
2.0 Flash |
Recommendation generation |
OCR |
GLM-4.5V |
4.5 |
Text extraction from scanned documents |
Vector database |
Qdrant |
1.7+ |
Semantic search |
Embeddings |
Ollama |
latest |
Vectorization (1024 dimensions) |
Relational DB |
PostgreSQL |
13+ |
Memory, history, and platform data |
Web frontend / partners |
Next.js + React |
15 / 19 |
Web user interfaces |
Mobile app |
React Native + Expo |
0.81 / SDK 54 |
Mobile user interface |
Language (backend) |
Python |
3.10+ |
Engine development |
High-Level Architecture
graph TB
subgraph Clients["CLIENTS"]
Web["Web Frontend<br/>(Next.js)"]
Mobile["Mobile App<br/>(React Native / Expo)"]
Partners["Partners Portal<br/>(Next.js)"]
end
subgraph Backend["BACKEND"]
API["FastAPI<br/>REST API"]
Engine["AI Recommendation Engine<br/>(gemini_agent)"]
Memory["Agent Memory<br/>(connection pool v2.0)"]
end
subgraph AISvc["AI SERVICES"]
Ollama["Ollama bge-m3<br/>Embeddings (1024D)"]
Qdrant["Qdrant<br/>Vector DB"]
Gemini["Gemini 2.0 Flash<br/>via OpenRouter"]
end
subgraph Data["DATA STORES"]
PG["PostgreSQL<br/>profiles, history, audit"]
end
Web -->|HTTPS / JSON| API
Mobile -->|HTTPS / JSON| API
Partners -->|HTTPS / JSON| API
API --> Engine
Engine --> Ollama
Engine --> Qdrant
Engine --> Gemini
Engine --> Memory
Memory --> PG
classDef client fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#000
classDef backend fill:#FFF3E0,stroke:#F57C00,stroke-width:2px,color:#000
classDef ai fill:#F3E5F5,stroke:#8E24AA,stroke-width:2px,color:#000
classDef data fill:#E8F5E9,stroke:#2E7D32,stroke-width:2px,color:#000
class Web,Mobile,Partners client
class API,Engine,Memory backend
class Ollama,Qdrant,Gemini ai
class PG data
Figure 2. High-level architecture — clients, API layer, AI services, and data stores.
Clients (web, mobile, and partner portal) talk to the backend API over HTTPS. The API orchestrates the AI recommendation engine, which in turn calls the embedding service (Ollama), the vector store (Qdrant), the generative model (Gemini via OpenRouter), and the relational database (PostgreSQL) for persistence and history.
Domain Model
The core domain entities and their relationships are captured in the class
diagram below. The central entities are User / Beneficiary,
SkillProfile, EconomicOpportunity / TrainingProgram,
Recommendation, and Application.
%% IOM Digital Tool - Class Diagram
%% Main entities and relationships - Official IOM Terminology
classDiagram
%% ============================================
%% USER MANAGEMENT
%% ============================================
class User {
+UUID user_id
+String email
+String password_hash
+String first_name
+String last_name
+String phone_number
+String preferred_language
+UserStatus status
+DateTime created_at
+DateTime updated_at
+Integer profile_completion_pct
+login()
+logout()
+updateProfile()
+verifyEmail()
}
class Beneficiary {
+UUID beneficiary_id
+BeneficiaryCategory category
+String origin_country
+String origin_region
+Date date_of_return
+Date date_of_displacement
+MigrationStatus migration_status
+EducationLevel education_level
+FamilyStatus family_status
+Integer number_of_children
+Integer dependents_count
+EmploymentStatus employment_status
+String monthly_income_range
+JSON vulnerabilities
+UUID current_location_id
+HousingStatus housing_status
+completeProfile()
+uploadDocument()
+getRecommendations()
}
class SkillProfile {
+UUID skill_profile_id
+UUID beneficiary_id
+JSON skills
+JSON work_experience
+JSON certifications
+JSON soft_skills_rating
+JSON interests
+JSON career_goals
+Date assessment_date
+addSkill()
+addExperience()
+assessCompetency()
}
class Organization {
+UUID organization_id
+String name
+OrganizationType type
+String sector
+String description
+String website
+String contact_email
+String contact_phone
+Boolean verified
+Date verification_date
+submitOpportunity()
+updateOpportunity()
+viewStatistics()
}
%% ============================================
%% ECONOMIC OPPORTUNITIES (AGGREGATED)
%% ============================================
class EconomicOpportunity {
+UUID opportunity_id
+OpportunityType type
+String title
+String description
+String sector
+UUID organization_id
+UUID location_id
+String external_url
+OpportunityStatus status
+Date published_at
+Date deadline
+JSON requirements
+JSON benefits
+String source
+getDetails()
+redirectToExternal()
}
class JobOpportunity {
+String contract_type
+Decimal salary_min
+Decimal salary_max
+String experience_required
+String education_level
+JSON skills_required
}
class TrainingProgram {
+String duration
+Decimal cost
+String certification
+Date start_date
+Integer available_places
+String delivery_mode
+JSON prerequisites
}
class AGRInitiative {
+Decimal investment_required
+Decimal subsidy_available
+String business_model
+JSON support_provided
+Decimal estimated_revenue
}
class Financing {
+Decimal amount_min
+Decimal amount_max
+Decimal interest_rate
+String repayment_period
+JSON eligibility_criteria
}
%% ============================================
%% AI-POWERED RECOMMENDATION SYSTEM
%% ============================================
class Recommendation {
+UUID recommendation_id
+UUID beneficiary_id
+UUID opportunity_id
+Decimal match_score
+ConfidenceLevel confidence_level
+JSON reasoning
+JSON scoring_breakdown
+DateTime generated_at
+String algorithm_version
+Boolean viewed
+Boolean clicked
+Boolean applied
+Integer feedback_rating
+String feedback_comment
+calculateMatchScore()
+explainReasoning()
+recordFeedback()
}
class AIModel {
+UUID model_id
+String model_name
+String version
+Date trained_at
+JSON hyperparameters
+Decimal accuracy
+generateRecommendations()
+calculateSimilarity()
+performPredictiveAnalytics()
+learn()
}
%% ============================================
%% TRACKING & APPLICATIONS
%% ============================================
class Application {
+UUID application_id
+UUID beneficiary_id
+UUID opportunity_id
+ApplicationStatus status
+String external_url
+DateTime redirected_at
+DateTime submitted_at
+DateTime updated_at
+JSON notes
+trackStatus()
+updateStatus()
}
class SavedOpportunity {
+UUID saved_id
+UUID user_id
+UUID opportunity_id
+DateTime saved_at
+String notes
}
%% ============================================
%% NOTIFICATIONS & ALERTS
%% ============================================
class Notification {
+UUID notification_id
+UUID user_id
+NotificationType type
+String title
+String message
+NotificationChannel channel
+Priority priority
+Boolean read
+DateTime sent_at
+DateTime read_at
+send()
+markAsRead()
}
class NotificationPreference {
+UUID preference_id
+UUID user_id
+JSON opportunity_types
+JSON sectors
+JSON notification_channels
+Integer location_radius_km
+Decimal min_match_score
+String frequency
+Boolean enabled
}
class Alert {
+UUID alert_id
+UUID user_id
+UUID opportunity_id
+DateTime triggered_at
+Boolean sent
+String channel_used
}
%% ============================================
%% LOCATION & GEOSPATIAL
%% ============================================
class Location {
+UUID location_id
+String country
+String region
+String department
+String municipality
+String address
+Decimal latitude
+Decimal longitude
+Geometry geom
+calculateDistance()
}
%% ============================================
%% DOCUMENTS
%% ============================================
class Document {
+UUID document_id
+UUID beneficiary_id
+DocumentType type
+String file_path
+String file_name
+Integer file_size
+String mime_type
+DateTime uploaded_at
+upload()
+download()
+delete()
}
%% ============================================
%% ANALYTICS
%% ============================================
class AnalyticsEvent {
+UUID event_id
+UUID user_id
+EventType event_type
+EntityType entity_type
+UUID entity_id
+JSON metadata
+DateTime timestamp
+track()
}
class ActivityLog {
+UUID log_id
+UUID user_id
+String action
+String entity_type
+UUID entity_id
+JSON changes
+DateTime created_at
}
%% ============================================
%% GDPR CONSENT
%% ============================================
class ConsentRecord {
+UUID consent_id
+UUID user_id
+ConsentType consent_type
+Boolean granted
+DateTime granted_at
+DateTime revoked_at
+String purpose
+recordConsent()
+revokeConsent()
}
%% ============================================
%% RELATIONSHIPS
%% ============================================
%% User relationships
User "1" --> "0..1" Beneficiary : is a
User "1" --> "0..1" Organization : represents
User "1" --> "0..*" Notification : receives
User "1" --> "0..1" NotificationPreference : has
User "1" --> "0..*" ConsentRecord : has
User "1" --> "0..*" ActivityLog : generates
User "1" --> "0..*" AnalyticsEvent : generates
%% Beneficiary relationships
Beneficiary "1" --> "1" SkillProfile : has
Beneficiary "1" --> "0..*" Document : uploads
Beneficiary "1" --> "0..*" Recommendation : receives
Beneficiary "1" --> "0..*" Application : submits
Beneficiary "1" --> "0..*" SavedOpportunity : saves
Beneficiary "1" --> "1" Location : located at
%% Organization relationships
Organization "1" --> "0..*" EconomicOpportunity : publishes
Organization "1" --> "0..1" Location : based at
%% Opportunity relationships
EconomicOpportunity <|-- JobOpportunity : extends
EconomicOpportunity <|-- TrainingProgram : extends
EconomicOpportunity <|-- AGRInitiative : extends
EconomicOpportunity <|-- Financing : extends
EconomicOpportunity "1" --> "1" Location : located at
EconomicOpportunity "1" --> "0..*" Recommendation : generates
EconomicOpportunity "1" --> "0..*" Application : has
EconomicOpportunity "1" --> "0..*" SavedOpportunity : saved in
%% Recommendation relationships
Recommendation "0..*" --> "1" AIModel : generated by
Recommendation "1" --> "0..*" Alert : triggers
%% Analytics relationships
AnalyticsEvent --> AIModel : feeds
%% Enumerations
class UserStatus {
<<enumeration>>
PENDING_VERIFICATION
ACTIVE
SUSPENDED
INACTIVE
}
class BeneficiaryCategory {
<<enumeration>>
RETURNING_MIGRANT
IDP
FORMER_ARMED_GROUP_MEMBER
AFFECTED_COMMUNITY
CROSS_BORDER_TRADER
DIASPORA_MEMBER
}
class OpportunityType {
<<enumeration>>
EMPLOYMENT
TRAINING
ENTREPRENEURSHIP
GRANT
MICROCREDIT
}
class OpportunityStatus {
<<enumeration>>
ACTIVE
EXPIRED
FILLED
CANCELLED
}
class ApplicationStatus {
<<enumeration>>
EXTERNAL_REDIRECT
SUBMITTED
UNDER_REVIEW
ACCEPTED
REJECTED
WITHDRAWN
}
class NotificationType {
<<enumeration>>
NEW_RECOMMENDATION
OPPORTUNITY_DEADLINE
APPLICATION_UPDATE
SYSTEM_ANNOUNCEMENT
}
Figure 3. Class diagram — main entities and relationships.
Data Flow
A document or opportunity travels through the platform as follows:
Document upload (PDF, DOCX, PPTX, TXT).
Text extraction (OCR via GLM-4.5V where needed).
Security classification (GREEN / YELLOW / RED).
Intelligent chunking (maximum 350 tokens per chunk).
Vectorization with Ollama
bge-m3(1024 dimensions).Indexing in Qdrant.
Vector search against the user query / profile.
Recommendation generation with Gemini.
Persistence to PostgreSQL.
JSON response returned to the client.
flowchart LR
subgraph Indexing["INDEXING PIPELINE"]
direction TB
U["Document upload<br/>(PDF/DOCX/PPTX/TXT)"] --> X["Text extraction<br/>(OCR if needed)"]
X --> C["Security classification<br/>(GREEN/YELLOW/RED)"]
C --> CH["Chunking<br/>(max 350 tokens)"]
CH --> V["Vectorization<br/>(Ollama bge-m3, 1024D)"]
V --> IDX["Index in Qdrant"]
end
subgraph Recommend["RECOMMENDATION PIPELINE"]
direction TB
Q["User profile / query"] --> S["Vector search<br/>(Qdrant)"]
S --> G["Recommendation generation<br/>(Gemini)"]
G --> P["Persist to PostgreSQL"]
P --> R["JSON response to client"]
end
IDX -.indexed opportunities.-> S
classDef idx fill:#E8F5E9,stroke:#2E7D32,stroke-width:2px,color:#000
classDef rec fill:#FFF3E0,stroke:#F57C00,stroke-width:2px,color:#000
class U,X,C,CH,V,IDX idx
class Q,S,G,P,R rec
Figure 4. Data flow — indexing pipeline and recommendation pipeline.
Recommendation Sequence
The end-to-end sequence from beneficiary registration to external application — covering profile creation, recommendation generation, and redirection to a partner site — is shown below.
%% IOM Digital Tool - Sequence Diagram: AI-Powered Recommendation and External Redirection
%% Complete flow from profile creation to external application - Official IOM Terminology
sequenceDiagram
participant B as Beneficiary
participant W as Web/Mobile Interface
participant API as Backend API
participant DB as PostgreSQL DB
participant AIEngine as AI-Powered<br/>Recommendation Engine
participant VDB as Qdrant Vector DB
participant ETL as ETL Pipeline
participant EXT as External Partner Site
participant SMS as SMS Service
participant NOTIF as Notification Service
Note over B,NOTIF: PHASE 1: Profile Creation and Completion
B->>W: Register on Digital Tool
W->>API: POST /api/auth/register
API->>DB: INSERT INTO users
DB-->>API: user_id
API->>NOTIF: Send verification email
NOTIF-->>B: Verification email
B->>W: Click verification link
W->>API: GET /api/auth/verify/{token}
API->>DB: UPDATE users SET status='ACTIVE'
B->>W: Complete beneficiary profile
W->>API: PUT /api/beneficiaries/profile
API->>DB: INSERT INTO beneficiaries
API->>DB: INSERT INTO skill_profiles
DB-->>API: profile_completion_percentage
Note over B,NOTIF: PHASE 2: External Opportunities Aggregation
rect rgb(220, 237, 200)
Note over ETL,EXT: ETL Process (Background - Scheduled)
ETL->>EXT: Scrape/API call to job portals
EXT-->>ETL: Employment opportunities data
ETL->>EXT: Fetch MINEFOP trainings
EXT-->>ETL: Training data
ETL->>EXT: Fetch FAO/UNDP AGR programs
EXT-->>ETL: AGR data
ETL->>DB: UPSERT INTO economic_opportunities
ETL->>DB: Log in data_sources
end
Note over B,NOTIF: PHASE 3: AI-Powered Recommendation Generation
B->>W: View recommendations
W->>API: GET /api/recommendations
API->>DB: SELECT FROM beneficiaries WHERE user_id=?
DB-->>API: Complete beneficiary profile
API->>AIEngine: generateRecommendations(beneficiary_profile)
rect rgb(255, 245, 220)
Note over AIEngine,VDB: AI-Powered Recommendation Engine
AIEngine->>VDB: Vectorize beneficiary profile
VDB-->>AIEngine: profile_vector
AIEngine->>DB: SELECT FROM economic_opportunities WHERE status='ACTIVE'
DB-->>AIEngine: Active opportunities
AIEngine->>VDB: Vectorize opportunities
VDB-->>AIEngine: opportunity_vectors
AIEngine->>VDB: Semantic similarity search
VDB-->>AIEngine: Top 50 matches (cosine similarity)
AIEngine->>AIEngine: Calculate multi-criteria scores
Note over AIEngine: - Skills 30%<br/>- Location 20%<br/>- Salary/Budget 15%<br/>- Opportunity Type 15%<br/>- Experience 10%<br/>- Inclusion 10%
AIEngine->>AIEngine: Apply contextual adjustments
AIEngine->>AIEngine: Filter and final ranking
AIEngine-->>API: Top 20 recommendations with scores
end
API->>DB: INSERT INTO recommendations (batch)
API->>DB: INSERT INTO analytics_events
API-->>W: Recommendations list with match_score
W-->>B: Display personalized recommendations
Note over B,NOTIF: PHASE 4: View Details and External Redirection
B->>W: Click on opportunity (Match: 95%)
W->>API: GET /api/opportunities/{opportunity_id}
API->>DB: SELECT FROM economic_opportunities
DB-->>API: Opportunity details + external_url
API->>DB: SELECT FROM recommendations WHERE beneficiary_id=? AND opportunity_id=?
DB-->>API: Match score and reasoning
API-->>W: Complete details + match explanation
W-->>B: Display opportunity detail page
B->>W: Click "APPLY" / "GO TO EXTERNAL SITE"
W->>API: POST /api/opportunities/{opportunity_id}/redirect
rect rgb(255, 235, 235)
Note over API,SMS: Redirection Processing
API->>DB: UPDATE recommendations SET clicked=true, viewed_at=NOW()
API->>DB: INSERT INTO analytics_events (EXTERNAL_REDIRECT)
opt User checked "Track this application"
API->>DB: INSERT INTO applications (status='EXTERNAL_REDIRECT')
end
opt User requested SMS link
API->>SMS: Send external_url via SMS
SMS-->>B: SMS with link
end
API->>DB: INSERT INTO activity_logs
end
API-->>W: external_url + confirmation
W->>W: window.open(external_url, '_blank')
W-->>B: New tab to partner site
B->>EXT: Visit external site
EXT-->>B: External application form
Note over B,NOTIF: PHASE 5: Feedback and Learning
rect rgb(230, 240, 255)
Note over B,W: Post-Redirection Modal (after 30s)
W-->>B: "Were you able to apply?"
B->>W: "Yes, application submitted"
W->>API: POST /api/recommendations/{rec_id}/feedback
API->>DB: UPDATE recommendations SET feedback_rating=5, applied=true
opt User wants to track
API->>DB: UPDATE applications SET status='SUBMITTED'
end
API->>DB: INSERT INTO analytics_events (RECOMMENDATION_FEEDBACK)
API->>AIEngine: Send feedback for machine learning
AIEngine->>AIEngine: Adjust model (batch learning)
end
Note over B,NOTIF: PHASE 6: Automatic Personalized Alerts
rect rgb(245, 245, 220)
Note over AIEngine,NOTIF: Alert System (Background)
AIEngine->>DB: SELECT new opportunities (last 24h)
DB-->>AIEngine: New opportunities
AIEngine->>DB: SELECT FROM notification_preferences WHERE enabled=true
DB-->>AIEngine: User preferences
AIEngine->>AIEngine: Generate recommendations for each user
AIEngine->>DB: SELECT FROM recommendations WHERE match_score >= user.min_match_score
loop For each user with new recommendations
AIEngine->>DB: INSERT INTO alerts
AIEngine->>NOTIF: Trigger notification
alt Email configured
NOTIF->>B: Email "3 new opportunities for you"
end
alt SMS configured AND urgent opportunity
NOTIF->>SMS: Send SMS alert
SMS->>B: SMS alert
end
alt Push notification
NOTIF->>B: In-app notification
end
end
end
Note over B,NOTIF: PHASE 7: Application Tracking
B->>W: View "My Applications"
W->>API: GET /api/applications
API->>DB: SELECT FROM applications WHERE beneficiary_id=?
DB-->>API: Applications list
API-->>W: Applications with statuses
W-->>B: Tracking dashboard
B->>W: Manually update status
W->>API: PUT /api/applications/{app_id}
API->>DB: UPDATE applications SET status='INTERVIEW_SCHEDULED'
API->>DB: INSERT INTO activity_logs
API->>NOTIF: Confirmation notification
NOTIF-->>B: "Status updated successfully"
Figure 5. Sequence diagram — AI-powered recommendation and external redirection.
The participants are the Beneficiary, the Web/Mobile interface, the Backend API, PostgreSQL, the AI-Powered Recommendation Engine, the Qdrant Vector DB, the ETL pipeline, external partner sites, and the SMS / notification services.
Application Lifecycle
An external application moves through a well-defined state machine, from
DRAFT to terminal states such as SUBMITTED, UNDER_REVIEW,
EXPIRED, WITHDRAWN, ABANDONED, or DELETED.
%% IOM Digital Tool - State Diagram: Application Status Lifecycle
%% Complete lifecycle of an external application - Official IOM Terminology
stateDiagram-v2
[*] --> DRAFT : User saves draft
DRAFT --> DRAFT : Edit draft
DRAFT --> EXTERNAL_REDIRECT : Submit external application
DRAFT --> DELETED : Delete draft
DELETED --> [*]
EXTERNAL_REDIRECT --> EXTERNAL_REDIRECT : Application attempt
EXTERNAL_REDIRECT --> SUBMITTED : Confirm application submitted
EXTERNAL_REDIRECT --> ABANDONED : User abandons
EXTERNAL_REDIRECT --> DELETED : Remove from tracking
ABANDONED --> [*]
SUBMITTED --> UNDER_REVIEW : Employer reviews application
SUBMITTED --> EXPIRED : Deadline passed without response
SUBMITTED --> WITHDRAWN : Candidate withdraws
SUBMITTED --> DELETED : Remove from tracking
UNDER_REVIEW --> UNDER_REVIEW : Review in progress
UNDER_REVIEW --> SHORTLISTED : Pre-selected
UNDER_REVIEW --> REJECTED : Not selected
UNDER_REVIEW --> EXPIRED : Too long without response
UNDER_REVIEW --> WITHDRAWN : Candidate withdraws
SHORTLISTED --> INTERVIEW_SCHEDULED : Interview scheduled
SHORTLISTED --> TEST_SCHEDULED : Practical test scheduled
SHORTLISTED --> REJECTED : Finally not selected
SHORTLISTED --> WITHDRAWN : Candidate withdraws
INTERVIEW_SCHEDULED --> INTERVIEW_COMPLETED : Interview completed
INTERVIEW_SCHEDULED --> INTERVIEW_MISSED : Missed interview
INTERVIEW_SCHEDULED --> WITHDRAWN : Candidate cancels
INTERVIEW_MISSED --> REJECTED : Opportunity lost
TEST_SCHEDULED --> TEST_COMPLETED : Test completed
TEST_SCHEDULED --> TEST_MISSED : Missed test
TEST_SCHEDULED --> WITHDRAWN : Candidate cancels
TEST_MISSED --> REJECTED : Opportunity lost
INTERVIEW_COMPLETED --> UNDER_REVIEW : Deliberation
TEST_COMPLETED --> UNDER_REVIEW : Deliberation
UNDER_REVIEW --> ACCEPTED : Application accepted
ACCEPTED --> OFFER_MADE : Offer made
OFFER_MADE --> OFFER_ACCEPTED : Candidate accepts offer
OFFER_MADE --> OFFER_DECLINED : Candidate declines offer
OFFER_MADE --> OFFER_EXPIRED : Response deadline exceeded
OFFER_DECLINED --> REJECTED
OFFER_EXPIRED --> REJECTED
OFFER_ACCEPTED --> ONBOARDING : Onboarding process
ONBOARDING --> HIRED : Hired / Enrolled
ONBOARDING --> WITHDRAWN : Candidate withdraws
HIRED --> [*] : Success
REJECTED --> [*] : Failure
EXPIRED --> [*] : Failure
WITHDRAWN --> [*] : Abandoned
note right of DRAFT
INITIAL STATE
Draft application
Not submitted
end note
note right of EXTERNAL_REDIRECT
CRITICAL TRANSITION
Redirect to external site
Tracking starts
end note
note right of SUBMITTED
APPLICATION CONFIRMED
Awaiting review
Typical delay: 3-7 days
end note
note right of UNDER_REVIEW
EMPLOYER REVIEW
Can return to this state
after interview/test
end note
note right of SHORTLISTED
POSITIVE PRE-SELECTION
Next step:
interview or test
end note
note right of ACCEPTED
POSITIVE DECISION
Offer incoming
end note
note right of HIRED
FINAL SUCCESS STATE
Goal achieved
Positive statistics
end note
note right of REJECTED
FINAL FAILURE STATE
Opportunity lost
Feedback possible
end note
note right of WITHDRAWN
FINAL ABANDONED STATE
Candidate initiative
end note
Figure 6. State diagram — application status lifecycle.