{"slug": "how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus", "title": "How We Built an AI-Native Real Estate Platform for Northern Cyprus", "summary": "Evlek, an AI-native property platform for Northern Cyprus, was built with a strict separation between language understanding and data truth to prevent AI hallucinations in real estate. The system uses a language model only for intent extraction, converting natural-language queries into structured search objects, while all property data comes from deterministic database retrieval and validation.", "body_md": "While building Evlek, an AI-native property platform for Northern Cyprus, we encountered a problem that appears in almost every AI search product:\n\nA language model can understand what the user wants, but it should not be allowed to invent the facts used in the answer.\n\nThis becomes especially important in real estate.\n\nA fabricated product recommendation may be inconvenient. A fabricated property price, title deed type, availability status, or location can influence a high-value decision.\n\nWe therefore designed our property-search architecture around a strict separation:\n\nThe model never becomes the source of truth.\n\nThis article explains why we chose that architecture, how a natural-language property query becomes a structured search, and how we preserve uncertainty when the underlying data is incomplete.\n\nMost property platforms are built around filters:\n\nFilters are effective when users already understand the market and know exactly what they want.\n\nMany users do not begin with a filter configuration. They begin with a sentence:\n\nFind me a modern two-bedroom apartment in Kyrenia below £180,000, close to the sea and suitable for investment.\n\nThis request contains both explicit and implicit information.\n\nThe explicit constraints include:\n\nThe softer preferences include:\n\nA language model can interpret those signals well. The danger begins when it is also expected to provide the actual listings from memory or from an unverified text context.\n\nA fluent answer is not necessarily a factual answer.\n\nWe use the model for language understanding, not property truth.\n\nThe simplified request flow looks like this:\n\n```\ntext\nNatural-language request\n          │\n          ▼\nIntent extraction\n          │\n          ▼\nSchema validation\n          │\n          ▼\nDatabase retrieval\n          │\n          ▼\nDeterministic filtering\n          │\n          ▼\nRelevance ranking\n          │\n          ▼\nGrounded explanation\n\nEvery property presented to the user must originate from a real record returned by the application.\n\nThe language model may explain why that property is relevant, but it cannot create a new listing, alter its price, add an unconfirmed amenity, or infer a legal field that is missing.\n\nConverting language into structured intent\n\nThe first step is converting an open-ended sentence into a predictable search object.\n\nA simplified TypeScript representation might look like this:\n\ntype PropertySearchIntent = {\n  transaction?: \"sale\" | \"rent\" | \"daily\";\n  propertyTypes?: Array<\n    \"apartment\" | \"villa\" | \"house\" | \"land\" | \"commercial\"\n  >;\n  city?: string;\n  districts?: string[];\n  bedrooms?: number;\n  minimumPriceGbp?: number;\n  maximumPriceGbp?: number;\n  features?: string[];\n  preferences?: {\n    nearSea?: boolean;\n    newBuild?: boolean;\n    investmentFocused?: boolean;\n    furnished?: boolean;\n  };\n};\n\nThe model is asked to populate this structure rather than produce a list of properties.\n\nFor the example request, the result could resemble:\n\n{\n  \"transaction\": \"sale\",\n  \"propertyTypes\": [\"apartment\"],\n  \"city\": \"Kyrenia\",\n  \"bedrooms\": 2,\n  \"maximumPriceGbp\": 180000,\n  \"features\": [],\n  \"preferences\": {\n    \"nearSea\": true,\n    \"newBuild\": true,\n    \"investmentFocused\": true\n  }\n}\n\nThis object is not trusted automatically.\n\nIt still passes through application validation.\n\nValidation must happen outside the model\n\nA model can return malformed, contradictory, or unsupported values.\n\nExamples include:\n\nA negative maximum price\nA city that does not exist in the platform\nUnsupported transaction types\nBedroom values represented as text\nA district assigned to the wrong city\nA currency that the search service does not support\n\nThe application must normalise and validate these fields before any query is executed.\n\nA simplified example:\n\nfunction validateSearchIntent(\n  input: PropertySearchIntent\n): PropertySearchIntent {\n  const output: PropertySearchIntent = {};\n\n  if (\n    input.transaction === \"sale\" ||\n    input.transaction === \"rent\" ||\n    input.transaction === \"daily\"\n  ) {\n    output.transaction = input.transaction;\n  }\n\n  if (\n    typeof input.maximumPriceGbp === \"number\" &&\n    input.maximumPriceGbp > 0\n  ) {\n    output.maximumPriceGbp = input.maximumPriceGbp;\n  }\n\n  if (\n    typeof input.bedrooms === \"number\" &&\n    Number.isInteger(input.bedrooms) &&\n    input.bedrooms > 0\n  ) {\n    output.bedrooms = input.bedrooms;\n  }\n\n  return output;\n}\n\nThe real implementation also needs controlled vocabularies, location resolution, aliases, multilingual values, and relationships between regions.\n\nThe important principle is that the application—not the language model—decides which values are valid.\n\nHard constraints and soft preferences are different\n\nNot every part of a user request should become a strict database filter.\n\nConsider:\n\nI want a sea-view apartment in Kyrenia below £180,000.\n\nThe budget may be a hard constraint. The sea view may be a strong preference.\n\nIf we require every result to contain a confirmed sea_view = true field, the search may return nothing—even when relevant listings mention sea proximity in another structured attribute.\n\nWe therefore divide the request into different signal types.\n\nHard constraints\n\nThese normally exclude a listing when they are not satisfied:\n\nTransaction type\nMaximum price\nRequired bedroom count\nExplicit property type\nRequired city or district\nStrong preferences\n\nThese influence ranking but may not exclude every result:\n\nSea view\nNew development\nFurnished\nWalking distance to amenities\nInvestment potential\nContextual preferences\n\nThese may require further interpretation:\n\nGood for a family\nSuitable for students\nQuiet area\nStrong rental potential\nModern design\n\nThis distinction gives the system room to return useful alternatives without pretending that every result is an exact match.\n\nRetrieval comes before explanation\n\nAfter validation, the application queries the property database.\n\nThe retrieval layer should return structured records such as:\n\ntype PropertyRecord = {\n  id: string;\n  title: string;\n  transaction: \"sale\" | \"rent\" | \"daily\";\n  propertyType: string;\n  city: string;\n  district?: string;\n  bedrooms?: number;\n  priceGbp?: number;\n  features: string[];\n  titleDeedType?: string;\n  publicationStatus: \"active\" | \"inactive\";\n  publishedAt: string;\n  updatedAt: string;\n};\n\nThe language model does not create or modify these records.\n\nA result can only be shown if:\n\nIt exists in the database\nIt is available to the current user\nIts publication status permits display\nIt satisfies the validated hard constraints\nIt passes any additional business rules\n\nOnly after this retrieval step does the model receive the approved result set.\n\nPreventing fabricated attributes\n\nSuppose a listing contains:\n\n{\n  \"bedrooms\": 2,\n  \"priceGbp\": 175000,\n  \"features\": [\"shared_pool\", \"balcony\"],\n  \"titleDeedType\": null\n}\n\nThe model must not transform this into:\n\nThis two-bedroom apartment includes a shared pool, balcony, and Turkish title deed.\n\nThe title deed field is unknown.\n\nThe grounded explanation should instead say:\n\nThis listing matches the requested bedroom count and budget. It includes a shared pool and balcony. The title deed type is not specified in the available listing data.\n\nThis is less polished than inventing a complete answer.\n\nIt is also the correct answer.\n\nWe found that explicit handling of missing information is one of the most important features of trustworthy AI search.\n\nRepresenting uncertainty in the response\n\nMissing data and negative data are not the same.\n\nThese statements have different meanings:\n\nThe property does not have a pool.\nThe listing does not mention a pool.\nPool information has not been confirmed.\n\nA useful property model should distinguish among them.\n\nFor example:\n\ntype AttributeStatus<T> =\n  | { status: \"confirmed\"; value: T }\n  | { status: \"not_available\" }\n  | { status: \"not_specified\" }\n  | { status: \"requires_verification\" };\n\nThis allows the interface and explanation layer to preserve uncertainty instead of converting every empty field into a confident conclusion.\n\nFor high-value marketplaces, uncertainty is part of the product experience.\n\nIt should not be hidden.\n\nExplainable ranking\n\nA search may return several valid properties. The user still needs to understand why one result appears above another.\n\nWe calculate relevance from multiple signals, such as:\n\nHard-constraint compliance\nBudget proximity\nLocation relevance\nFeature overlap\nListing freshness\nData completeness\nConfirmation status\nPreference matching\n\nA simplified scoring function could look like this:\n\ntype RankingSignals = {\n  hardConstraintMatch: number;\n  locationMatch: number;\n  budgetMatch: number;\n  featureMatch: number;\n  freshness: number;\n  dataCompleteness: number;\n};\n\nfunction calculateScore(signals: RankingSignals): number {\n  return (\n    signals.hardConstraintMatch * 0.35 +\n    signals.locationMatch * 0.2 +\n    signals.budgetMatch * 0.15 +\n    signals.featureMatch * 0.15 +\n    signals.freshness * 0.1 +\n    signals.dataCompleteness * 0.05\n  );\n}\n\nThe exact weights depend on the product and the request.\n\nThe more important decision is to preserve the signals that produced the ranking.\n\nThis makes it possible to explain a result:\n\nWithin your stated budget\nMatches the requested property type\nMatches the requested bedroom count\nLocated in the preferred city\nRecently updated\nSea-view information is not confirmed\n\nAn unexplained AI score may look impressive, but an understandable match explanation is more useful.\n\nMultilingual search without duplicating the truth\n\nEvlek is designed for users across multiple languages.\n\nThe same concept may appear differently across Turkish, English, German, Russian, and Arabic.\n\nFor example:\n\nCanonical value: transaction_type = sale\n\nEnglish: For sale\nTurkish: Satılık\nGerman: Zu verkaufen\nRussian: Продажа\nArabic: للبيع\n\nWe try to keep canonical property facts independent from the display language.\n\nThe language layer can translate or interpret the request, but the search ultimately operates on canonical values.\n\nThis avoids a serious data problem: five translated versions gradually becoming five different descriptions of the same property.\n\nThe same principle applies to:\n\nProperty types\nRoom counts\nAmenities\nTransaction types\nLocation aliases\nLegal and market terminology\n\nTranslation belongs in the presentation and interpretation layers. Property truth belongs in the canonical data model.\n\nSource dates matter\n\nProperty information changes.\n\nListings become inactive. Prices are updated. Market statistics belong to a specific period. A statement that was accurate three months ago may no longer be current.\n\nWhenever possible, the system should preserve:\n\nPublication date\nLast update date\nPrice-change date\nMarket-data reference period\nSource information\nReview or confirmation status\n\nThis allows the assistant to say:\n\nThe listing was last updated on 15 July 2026.\n\nor:\n\nThe market figure refers to the second quarter of 2026.\n\nWithout source dates, AI-generated explanations can make historical information appear current.\n\nFor real estate research, data freshness is not metadata. It is part of the answer.\n\nExternal AI access should use the same rules\n\nWe also provide selected read-only access to structured property capabilities through the Model Context Protocol.\n\nThe objective is not to make a separate AI product.\n\nThe objective is to allow compatible clients to use controlled tools rather than scraping pages or depending on model memory.\n\nThe same principles apply:\n\nInputs are validated\nTools have defined scopes\nResults originate from structured records\nPersonal contact information is not exposed unnecessarily\nMissing fields remain missing\nThe client cannot create listings through a read-only search tool\n\nA simplified tool boundary looks like this:\n\nExternal AI client\n        │\n        ▼\nDefined MCP tool\n        │\n        ▼\nInput validation\n        │\n        ▼\nAuthorised read-only query\n        │\n        ▼\nStructured property response\n\nMCP is an access layer over the same property system.\n\nIt does not replace the underlying validation, permissions, or data-quality requirements.\n\nWhat about virtual staging and marketing agents?\n\nNatural-language search is only one part of the wider platform.\n\nWe are also working on workflows for virtual staging and AI-assisted property marketing.\n\nThose systems follow the same grounding principle.\n\nA marketing workflow should receive confirmed property facts instead of an unstructured prompt.\n\nFor example:\n\n{\n  \"propertyType\": \"apartment\",\n  \"city\": \"Kyrenia\",\n  \"bedrooms\": 2,\n  \"priceGbp\": 175000,\n  \"confirmedFeatures\": [\n    \"shared_pool\",\n    \"balcony\"\n  ],\n  \"unconfirmedFields\": [\n    \"title_deed_type\"\n  ],\n  \"restrictions\": [\n    \"Do not claim guaranteed investment returns\",\n    \"Do not invent legal status\",\n    \"Do not describe unconfirmed amenities\"\n  ]\n}\n\nThe generated description may change in tone or language, but the factual boundary should remain fixed.\n\nVirtual staging needs a similar boundary.\n\nA staged image should help a buyer visualise a space without pretending that the furniture exists or altering permanent architectural facts.\n\nThat requires:\n\nKeeping the original image available\nLabelling the staged version\nReviewing generated outputs\nAvoiding misleading structural changes\nRejecting unsuitable images\n\nGenerative features need product rules, not only model prompts.\n\nLessons from building the system\n1. Better prompts do not replace structured data\n\nPrompt engineering can improve model behaviour, but it cannot reliably repair missing, inconsistent, or unverified property records.\n\n2. The model should interpret, not author, marketplace facts\n\nThe model is useful for understanding intent and explaining results. The application remains responsible for truth.\n\n3. Missing information must remain visible\n\nA blank field should not become a confident claim.\n\n4. Retrieval and ranking are separate problems\n\nFinding valid results is not the same as ordering them intelligently.\n\n5. AI features inherit the quality of the underlying inventory\n\nAn advanced search interface cannot compensate for outdated or incomplete listings.\n\n6. High-value decisions require stronger guardrails\n\nA property-search assistant should behave differently from a casual content generator.\n\nAccuracy, provenance, uncertainty, and review are product requirements.\n\nFinal architecture\n\nThe architecture can be summarised as:\n\nUser request\n    │\n    ▼\nLanguage-model intent extraction\n    │\n    ▼\nApplication validation and normalisation\n    │\n    ▼\nDatabase retrieval\n    │\n    ▼\nDeterministic filtering\n    │\n    ▼\nExplainable ranking\n    │\n    ▼\nGrounded response generation\n    │\n    ▼\nUser-visible sources, dates and uncertainty\n\nThe language model is an important component.\n\nIt is not the database, the permission system, the legal authority, or the source of property facts.\n\nFinal thought\n\nThe most important design decision in our AI property search was not which model to use.\n\nIt was deciding what the model was not allowed to do.\n\nBy separating language interpretation from property truth, we can create a more natural search experience without allowing fluent generated text to override real marketplace data.\n\nThat principle applies beyond real estate.\n\nAny AI product working with financial, medical, legal, marketplace, or other high-value information should ask the same question:\n\nWhich parts of the answer may be generated, and which parts must always come from a verified system of record?\n\nDisclosure: AI was used to help refine the English, structure the article, and create the cover artwork. I reviewed the technical claims and final content based on our actual work building Evlek.\n```\n\n", "url": "https://wpnews.pro/news/how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus", "canonical_source": "https://dev.to/onur_dokuzolu_ae3db85aa9/how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus-31h0", "published_at": "2026-07-21 17:56:32+00:00", "updated_at": "2026-07-21 18:22:34.751308+00:00", "lang": "en", "topics": ["artificial-intelligence", "natural-language-processing", "ai-products", "developer-tools"], "entities": ["Evlek"], "alternates": {"html": "https://wpnews.pro/news/how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus", "markdown": "https://wpnews.pro/news/how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus.md", "text": "https://wpnews.pro/news/how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus.txt", "jsonld": "https://wpnews.pro/news/how-we-built-an-ai-native-real-estate-platform-for-northern-cyprus.jsonld"}}