Projects

Eodisalji — AI Real Estate Brokerage Platform

2026.01 - 2026.07

EodisaljiContract
React
TypeScript
FastAPI
PostgreSQL
Qdrant
Redis
SQLAlchemy
Socket.io
Docker
The real screens, running on mock data — switch between tenant and broker, and between signed-out and signed-in on the tenant side

The problem

Hunting for an apartment in Korea mostly means seeing the same room over and over. Every agency posts the same unit, paid placements float to the top, and you call ten offices before one condition-matching listing turns up. All the labor sits with the person searching.

Eodisalji inverts it: the tenant posts conditions, and brokers go find matches. That inversion isn't a few screens — it's a system where four roles (tenant, landlord, broker, admin) each watch the same transaction from their own vantage point.

Architecture

The backend is hexagonal (ports & adapters). Twenty-seven domains each own their entities / ports / use_cases, and every piece of technology lives behind an adapter. Importing sqlalchemy or httpx inside domain code fails CI (import-linter).

domains/{properties, contracts, matching, messaging, ...27 total} ├── entities/ pure domain models — zero external deps ├── ports/ ABC interfaces └── use_cases/ business logic, depends only on ports adapters/ SQLAlchemy · Qdrant · Redis · S3 · OAuth · Socket.io api/ · workers/ composition root — the only place wiring happens

Failures are values, not exceptions: a Result[T, E] monad carries them. In flows where partial failure is expensive — payments, contracts — "can this fail?" belongs in the type.

The frontend runs one SPA as four apps through role-based subdomain routing. eodisalji.com serves tenants, agent.eodisalji.com serves the broker console, and the same codebase resolves viewport × role into three layouts (mobile / desktop-consumer / desktop-dashboard). A tenant on a desktop gets the mobile frame inside a branded backdrop; a broker gets a full-width dashboard.

What I built

Conditions gathered through conversation. A mascot named Yeolmu asks about your life — where you commute, whether you have a pet, what you can spend — and the conditions extracted from that conversation harden into a request. The property-search bot and the lifestyle bot are separate hexagons joined by a single handoff gate; letting their conversational state mix means asking the user the same question twice.

One transaction, four vantage points. A tenant's request lands in the broker's inbox, the broker's offers stack up in the tenant's list, and once they settle a viewing time in chat, the deal stage (consult → scheduling → confirmed → contract → done) moves on both screens at once. The backend event handler owns those transitions and pushes them over Socket.io.

Connections to the outside world. Kakao Maps, Daum postcode, the national building registry (data.go.kr), public transit (TAGO), school data, e-signatures (ModuSign). Every external service got a port first and an adapter second — listing registration had to survive the building-registry API going down.

Operations tooling. LLM usage and cost tracking, AI conversation log auditing, chat room monitoring, settlements and payments, broker verification review. AI features become an unmanageable cost the moment operators can't see what's happening.

About this demo

The live service needs a login and carries real data, so it can't be embedded here directly. Instead I took the production frontend as-is and swapped only the network layer for mocks.

The interception happens at fetch, so components, hooks, and state management run on the original code. Loading skeletons, optimistic updates, and error toasts all take the same paths they take in production. Only the data is fiction — set in Yuseong-gu, Daejeon.

Hit Launch demo above to move between the visitor, tenant, and broker views. The request you file as a tenant is the one sitting in the broker's inbox, and the three offers the broker sent show up in the tenant's list in exactly the same states.

GitHub