The Done Platform is a full-featured real estate investment platform targeting the Egyptian and international markets. I served as the primary backend developer, owning the API from day one, designing the architecture, setting the code conventions, and building out every major system described below.
The backend powers three distinct client applications:
- Public Website — property discovery, search, bookings, AI chatbot
- Admin & Sales Dashboard — full CMS, analytics, lead management, RBAC access control
- Mobile App — with push notifications and personalized AI-driven recommendations
Engineering Highlights:
This was one of the biggest projects I've yet to work on, It took a team of 7 engineers a period of 3 months to get it to a pre-launch state. Here I will mention some of my favorite pieces of this platform that I had worked on alongside the Rockaidev team.
1. Event-Logging Architecture
Every meaningful action in the system — whether a user views a unit, a dashboard admin creates a project, or a booking status changes — is recorded as a structured UserEvent document in the Database.
The UserEventType enum covers 90+ distinct event types across 12 categories:
| Category | Sample Events |
|---|---|
| Authentication | user_login, oauth_completed, otp_verified |
| Property Discovery | unit_viewed, unit_masterplan_viewed, zone_viewed |
| Engagement | unit_favorite, unit_compared, payment_plan_viewed |
| CMS Operations | project_created, article_updated, zone_deleted |
| Transactions | booking_created, sale_request_updated, contact_request_resolved |
Each event carries a typed metadata object:
export const eventMetadata = z.object({
unitId: z.string().optional(),
projectId: z.string().optional(),
searchFilter: normalizedUnitSearchFilter.optional(),
statusFrom: z.string().optional(),
statusTo: z.string().optional(),
// ...
});This single collection then powers three downstream systems:
-
Dashboard Analytics — aggregated into charts and KPI metrics
-
User Personalization — behavioral signals fed into the AI chatbot engine
-
Activity Feed — surfaced in the dashboard's real-time activity log.
Tracking values like
statusFromandstatusToallows us to keep a complete version history of the actions of any user, Just like Git does !
2. Behavioral AI Preference Engine
The most algorithmically interesting piece of the codebase. Before calling the AI chatbot, the system computes five real-time preference profiles from the user's event history:
- Budget — infers spending range from both explicit search filters and implicit unit interactions
- Location — zones the user shows repeated interest in
- Unit Type — apartment, villa, duplex, etc.
- Amenities — derived from the amenities of units the user engaged with
- Unit Affinity — top candidate units the user is closest to booking
Each preference function uses the same core time-decay + spacing-boost scoring algorithm:
const DAILY_TIME_DECAY = 0.98; // Score halves every ~35 days
const calculateSpacingBoost = (daysSinceLast: number | null): number => {
if (daysSinceLast === null) return 1.0; // First interaction: neutral
if (daysSinceLast < 1) return 0.5; // Spam/refresh: penalize
if (daysSinceLast <= 3) return 0.8; // Recent repeat: mild penalty
if (daysSinceLast <= 7) return 1.0; // Neutral
if (daysSinceLast <= 21) return 1.4; // Sustained interest: reward
return 1.6; // Long-gap return: max reward
};This design means a user who bookmarks a unit, comes back two weeks later, then books it will score significantly higher than someone who refreshes the same listing 20 times in one session. The system distinguishes intent from spam.
Budget scoring is particularly sophisticated — it fuses two signals with configurable weights:
recommendedBudget =
avgInteractionPrice * 0.7 + // Implicit: what prices did they engage with?
avgSearchPrice * 0.3; // Explicit: what did they filter for?Each tracked stat also comes with a confidence percentage (capped at 100%) computed based on the total amount of "interactions" a user made. A fresh user with 5 events gets a 5% budget confidence; a power user with 300+ interactions gets 100%. The AI chatbot receives both the recommendation and the confidence score to calibrate how assertively it should make suggestions.
Here are some sample scenarios showcasing how the chatbot will respond to a user based on his aggregated preferences:
Scenario 1 — Low Confidence (New User)
I only have limited information so far, but you may be interested in apartments in New Cairo within the 2–3 million EGP range. As you continue browsing, I can refine my recommendations.
Scenario 2 — Medium Confidence (Emerging Patterns)
Your recent activity suggests a preference for villas in Sheikh Zayed within the 4–5 million EGP range. I'll prioritize similar properties while still keeping some variety in the results.
Scenario 3 — High Confidence (Established Preferences)
Based on your activity, you consistently prefer duplex units in New Cairo within the 8–10 million EGP range. Unit X is currently my strongest recommendation, as it closely matches your established preferences.
Collaborating with my AI Engineer teammate & friend Moamen Osama on this pipeline was a such an experience, seeing the chatbot's accuracy improve over time was incredibly rewarding.
3. AI Scoring Pipeline
Every Zone, Project, and Developer in the system has an aiScore sub-document populated by a batch scoring engine that runs on a cron schedule.
The scoring algorithm is a multi-pass pipeline:
Pass 1 — Unit Aggregation (handles 19K+ units efficiently)
A single MongoDB $aggregate groups all units by project, computing unit counts, price distributions, price-per-sqm (PPSQM), and supply ratios (% of units sold vs total units).
Pass 2 — Zone Scoring (weighted formula)
zoneScore = (0.4 × pricePower) + (0.3 × marketActivity) + (0.2 × amenityScore) + (0.1 × supplyRatio)Where pricePower and marketActivity are normalized against the global max values across all zones.
Pass 3 — Project Scoring (inherits zone context)
projectScore = (0.3 × unitStrength) + (0.25 × amenityScore) + (0.2 × pricePositionScore) + (0.15 × zoneScore) + (0.1 × supplyHealth)These formulas were created by my fellow AI engineer teammate Seif Khaled, and gathered via web-scrapping bots triggered periodically or on-demand via the admin dashboard.
Pass 4 — Developer Scoring
Aggregates weighted project scores across all a developer's portfolio, normalized against the highest performing developer.
Pass 5 — Bulk Writes
All scores are flushed with a single bulkWrite per collection — zero N+1 queries.
These scores are then consumed by the AI chatbot, The Investment analysis system & the unit comparison system.
4. VIP Tier System
A loyalty-like scoring system that automatically identifies high-value users and triggers VIP invitations via email + push notification.
Scoring formula:
score += Math.pow(completedBookings, 1.5) × 50 // Ownership (exponential reward)
score += budget_percentile_points // Top 5% = 75pts, Top 20% = 15pts
score += completedBookings × 30 // Site visit completions
score += min(favorites × 2 + savedUnits × 2, 20) // Activity (capped)Users whose budget profile falls below the top 20th percentile of published unit prices are immediately disqualified, regardless of other scores. This ensures only the Elite users are considered eligible for VIP status.
Users in the higher percentile of the budget range are awarded more points. High points are awarded for booking a site visit to a unit and actually attending, but the highest way to gain points is to actually be an owner of unit purchased through the DONE platform.
5. Role-Based Access Control (RBAC)
The dashboard supports 6 distinct roles, each with a fine-grained permission set:
| Role | Key Capabilities |
|---|---|
SuperAdmin | All permissions |
Admin | Full CMS + analytics, no user deletion, no settings |
Manager | Same as Admin minus user management |
Developer | Own projects/units only (scoped write access) |
Sales | Read-only on all CMS content |
DataEntry | Read/write CMS, no delete |
6. FCM Push Notification Infrastructure
Firebase Cloud Messaging integration with multi-device and multi-role targeting:
- Per-user delivery: Finds all registered device tokens for a user, deduplicates them, and fans out in parallel
- Role-based broadcasts:
notifyRoles(['admin', 'manager'], payload)— targets all devices belonging to users of specified roles - Favoriter notifications:
notifyFavoriters(unitId, payload)— automatically notifies all users who saved a specific unit (used when price or availability changes) - Bilingual content: FCM payload stores both
title.enandtitle.ar; delivery language is determined by the stored device language preference - automatic expired token removal: Invalid/unregistered FCM tokens are automatically deleted from the database on first failure