DEV Community

Cover image for Before AI Values a Property, Normalize the Request

Before AI Values a Property, Normalize the Request

A property valuation workflow rarely receives the neat JSON object we wish it did.

One person fills out a website form.

Another sends a WhatsApp message.

Someone pastes a property listing URL.

Someone else uploads a document and expects the system to understand the useful details inside it.

They may all be asking for the same outcome:

What is this property likely worth?

But from the backend’s point of view, those are four very different requests.

If every one of them goes directly into the valuation layer, the AI is being asked to do too many jobs at once.

It has to understand the input, decide which details matter, notice what is missing, and then produce a valuation.

A cleaner architecture puts another boundary in front of that decision.

First, define one internal request

The public interface can stay flexible.

The backend should not.

A useful internal request might look something like this:

type PropertyRequest = {
  intent?: "sale" | "rent";
  propertyName?: string;
  unitNumber?: string;
  bedrooms?: number;
  areaSqFt?: number;
  listingUrl?: string;
  documentRefs?: string[];

  requester?: {
    name?: string;
    phone?: string;
  };
};
Enter fullscreen mode Exit fullscreen mode

The user never needs to see this structure.

They can still type naturally, upload a file, paste a link, or continue a WhatsApp conversation.

The job of the intake layer is to turn those different inputs into one shape the rest of the application understands.

Different inputs need different adapters

A website form is already structured.

A WhatsApp message is not.

A listing URL may need to be parsed.

An uploaded document may contain fields that have to be extracted before the workflow can use them.

So the beginning of the system can stay source-specific:

Website form ────────┐
                     │
WhatsApp messages ───┤
                     │
Listing URL ─────────┼──> Normalized property request
                     │
Uploaded document ───┘
Enter fullscreen mode Exit fullscreen mode

Each adapter understands its own input.

Everything after that should work with the same request model.

This keeps channel-specific complexity at the edge instead of spreading it throughout the whole workflow.

Extraction does not mean the request is ready

Suppose a document parser finds:

{
  "propertyName": "Marina Residence",
  "unitNumber": "1204"
}
Enter fullscreen mode Exit fullscreen mode

That extraction may be completely correct.

The valuation still may not be ready to run.

Perhaps the workflow also needs the property area.

Perhaps it needs the transaction intent.

Perhaps the property has to be matched to a known record before comparable evidence can be selected.

This is why extraction and validation should be separate steps.

Extraction asks:

What information did we find?

Validation asks:

Do we have enough reliable information to continue?

Those are different questions.

Missing information should become an explicit state

One of the easiest ways to make an AI workflow look smooth is to let the model infer whatever is missing.

That can also be one of the fastest ways to make the result difficult to trust.

If a missing field can materially affect the valuation, the application should know that the field is missing.

For example:

type ValidationResult =
  | {
      ready: true;
      request: PropertyRequest;
    }
  | {
      ready: false;
      request: Partial<PropertyRequest>;
      missing: string[];
    };
Enter fullscreen mode Exit fullscreen mode

Now the workflow has a deliberate branch:

Normalize request
       ↓
Validate required fields
       ↓
Enough information?
   ↙              ↘
 No                Yes
 ↓                  ↓
Ask for the         Continue to
missing detail      valuation
Enter fullscreen mode Exit fullscreen mode

The model does not silently decide that an unknown value is probably safe to invent.

The application decides whether the request is ready.

Conversational channels make this even more important

WhatsApp requests do not necessarily arrive as one complete message.

A conversation might look like this:

User:
Can you value a unit in Marina Residence?

User:
It is a 2 bed, around 1,300 sq ft.

User:
Here is the listing link.
Enter fullscreen mode Exit fullscreen mode

Those are not three separate valuation requests.

They are three pieces of the same request.

The workflow therefore needs an inquiry record that can accumulate context across messages.

Conceptually:

const inquiry = await inquiryStore.findOpenByPhone(phone);

const updatedInquiry = mergeIncomingMessage(
  inquiry,
  incomingMessage
);

const normalized = await normalizeInquiry(updatedInquiry);

const validation = validatePropertyRequest(normalized);
Enter fullscreen mode Exit fullscreen mode

Now the conversation history belongs to the workflow.

The valuation layer receives the accumulated request instead of whichever message happened to arrive last.

URLs and documents should enrich the same request

A listing URL should not create an entirely separate valuation path.

Neither should a document.

Both can enrich the same canonical request.

For a listing:

Listing URL
    ↓
Extract useful property details
    ↓
Merge with information already known
    ↓
Validate
Enter fullscreen mode Exit fullscreen mode

For a document:

Uploaded document
    ↓
Extract useful property details
    ↓
Merge with information already known
    ↓
Validate
Enter fullscreen mode Exit fullscreen mode

The downstream valuation process no longer needs to care whether a field came from a form, a URL, WhatsApp, or a document.

It receives one validated request.

Preserve where important information came from

Normalizing data does not mean throwing away its origin.

In workflows where evidence matters, keeping provenance can be useful.

A field can carry both its value and its source:

type FieldValue<T> = {
  value: T;
  source:
    | "user_input"
    | "whatsapp"
    | "listing_url"
    | "document";
};
Enter fullscreen mode Exit fullscreen mode

That gives the system a clearer answer when someone later asks:

Where did we get this property detail from?

It can also help when two sources disagree.

A user-entered area and an extracted document area should not necessarily overwrite each other silently.

The workflow can surface the conflict instead.

Only then should valuation begin

Once the request is normalized and validated, the AI and valuation layers can work with something predictable.

The full path becomes:

Messy customer input
        ↓
Channel-specific extraction
        ↓
Normalized property request
        ↓
Required-field validation
        ↓
Property matching
        ↓
Comparable evidence
        ↓
Valuation
        ↓
Structured result
Enter fullscreen mode Exit fullscreen mode

That boundary makes the AI layer much easier to reason about.

It no longer has to reconstruct an incomplete request while also producing the business result.

The result should be structured too

A useful valuation response is rarely just one number.

The product may need:

  • the estimate
  • a range
  • comparable evidence
  • the property details used
  • warnings or uncertainty
  • generated explanation
  • timestamps
  • report status

A structured result lets the same workflow support a preview, a complete report, delivery, audit history, and internal follow-up without rebuilding the valuation from scratch.

A recent workflow where we used this pattern

We recently worked on an AI Property Valuation & Lead Workflow System where requests could enter through a website, free text, listing links, uploaded documents, and WhatsApp.

The workflow had to make those different inputs usable before valuation, handle missing details instead of quietly guessing them, connect the request to comparable-backed valuation, and carry the result forward into reports and operational follow-up.

The public project breakdown is here:

Related work:
https://ascentinnovate.com/work/ai-property-valuation-lead-workflow-system

Flexible input does not require a flexible decision boundary

Users should be able to interact naturally.

They should not have to learn the shape of your database before they can ask for a valuation.

Let them write a message.

Let them paste the listing.

Let them upload the document.

Then normalize what arrived, check what is missing, and only move into the consequential part of the workflow when the request is ready.

Flexible input is good product design.

Letting uncertain input flow directly into the decision layer is not.

Top comments (0)