我建立 Yaki,因為地址看似正確,仍可能有誤
I Built Yaki Because an Address Can Look Right and Still Be Wrong
作者以 Geoapify Forward Geocoding API 建立地址審核工具 Yaki,指出地理編碼信心分數即使滿分,也未必能判定對方所指的地點。用戶可比較候選地點、選擇並確認目標地址,亦可透過 CSV 批次審核;未完成審核的地址不會列入 Ready 匯出。
How I used Geoapify to build an address-review tool, why perfect geocoding matches can still be ambiguous, and what I learned along the way.
Imagine someone gives you an address.
You enter it into a geocoding service, and a result comes back with a perfect confidence score.
You'd probably assume everything is fine.
But here's something I found interesting while building Yaki: a perfect match doesn't necessarily mean you've found the place someone intended.
Take Springfield, for example.
In one of my recorded experiments with Geoapify, searching for Springfield within the United States returned five cities:
- Springfield, Illinois
- Springfield, Massachusetts
- Springfield, Missouri
- Springfield, Ohio
- Springfield, Oregon
Every result had a confidence score of 1.
Five convincing results. One location to identify.
And no way to determine which Springfield the sender meant from that information alone.
That experiment captured the problem I wanted Yaki to address.
Meet Yaki
Yaki is an address-review application built with Geoapify's Forward Geocoding API.
I designed it around a simple question:
What if finding an address wasn't the end of the process, but the beginning of a more careful review?
Instead of automatically accepting the first geocoding result, Yaki lets someone examine possible locations, choose the intended one, and explicitly confirm their decision.
It also supports CSV files, so multiple addresses can go through the same review process.
The goal isn't to replace human judgment.
It's to give people better information before they make a decision.
Try Yaki: https://yaki-seven.vercel.app/
Source code: https://github.com/YakiUdoph/Yaki
Watch Yaki in action
I put together a cinematic walkthrough to explain the problem, demonstrate the application, and show how Geoapify fits into the experience.
Below, I'll walk through the technical decisions behind it.
1. The problem I wanted to solve
Geocoding is incredibly useful.
You provide an address or place name, and a service returns geographic information such as coordinates and formatted addresses.
But there's an important distinction between two questions:
Question one: Which locations match this address?
Question two: Which of those locations did the person actually intend?
A geocoding service can help answer the first.
The second may require additional information or human judgment.
For example, an address could match several places with similar names. A venue and a business inside that venue might share the same street address. Or a result might have strong matching confidence without providing enough information about an entrance or delivery access.
I didn't want Yaki to confuse those situations with certainty.
That's why the application treats geocoding results as evidence to review rather than automatic permission to dispatch.
2. How Yaki works
The workflow is deliberately straightforward.
First, the person using Yaki selects a destination country.
They can then enter a single address or upload a CSV containing multiple addresses.
Yaki sends the address and selected country to its server-side geocoding endpoint.
Geoapify returns possible matches, and Yaki presents the relevant information for review.
The person can compare candidates, select the intended location, and explicitly confirm it.
Only then can an eligible address move into the Ready export.
Addresses that haven't completed the review remain unresolved.
Yaki uses four main outcomes:
| Status | Meaning |
|---|---|
| Needs review | Possible matches exist, but the review isn't complete. |
| Ready | An eligible result has been selected and explicitly confirmed. |
| No match | The geocoding request succeeded but returned no candidates. |
| Check failed | The address check couldn't be completed successfully. |
That last distinction matters.
If a network request fails, it doesn't mean the address itself is invalid.
Yaki keeps those outcomes separate.
And importantly, Ready means the review workflow was completed. It does not guarantee that a delivery can reach the location.
3. Integrating Geoapify
Geoapify provides the geocoding functionality behind Yaki.
The production application uses its Forward Geocoding API.
Here's the simplified architecture:
User enters an address
|
v
Yaki browser interface
|
| POST /api/geocode
| Address + selected country
v
Yaki server-side function
|
| Validates the request
| Uses a protected API key
v
Geoapify Forward Geocoding API
|
v
Possible address matches
|
v
Yaki review interface
|
| Human selection
| Explicit confirmation
v
Ready or Unresolved CSV
One decision I made was to keep the Geoapify API key on the server.
The browser doesn't call Geoapify directly using an exposed credential.
Instead, it sends requests to Yaki's own endpoint.
The server validates the input and communicates with Geoapify.
A look at the actual implementation
In src/geoapify.ts, Yaki constructs the Forward Geocoding request.
Here's an excerpt from the implementation:
const url = new URL(
`${options.baseUrl || "https://api.geoapify.com/v1"}/geocode/search`
);
url.searchParams.set("text", normalizedQuery);
url.searchParams.set("format", "json");
url.searchParams.set("limit", String(limit));
url.searchParams.set("lang", options.lang || "en");
url.searchParams.set(
"bias",
`countrycode:${options.countryBias || "none"}`
);
const countryCode = normalizeCountryCode(options.countryCode);
if (countryCode) {
url.searchParams.set("countrycodes", countryCode);
}
The request uses the API key in a header:
{
method: "GET",
headers: {
"x-api-key": apiKey,
accept: "application/json"
},
signal: options.signal
}
The important detail here isn't just that the request works.
It's that the selected country becomes an explicit restriction in the geocoding request.
Yaki doesn't silently assume a country based on the first result.
And the API key doesn't need to appear in the browser's JavaScript or request URL.
The production endpoint also checks request methods, input shape, body size, and cross-site request context.
These are useful safeguards, although they don't make the application immune to every possible security or abuse scenario.
4. The Springfield experiment
This was one of the most useful parts of developing Yaki.
I wanted to understand how ambiguous place names behaved across Geoapify's direct API and MCP interface.
The first experiment exposed a problem with the comparison itself.
The two paths weren't operating with equivalent geographic context.
That meant their different results couldn't fairly establish which approach was more accurate.
The correction was to make the country restriction explicit.
For the direct API:
countrycodes=us
For the MCP experiment:
{
"country_codes": ["us"]
}
With equivalent country restrictions, the recorded experiment returned the same five Springfield candidates in the same order.
And here's the interesting part.
Every candidate had a confidence score of 1.
Yet the result still didn't establish which city was intended.
So Yaki's policy kept the address under review.
That taught me something important:
A high-confidence match is not the same thing as a high-confidence decision.
The geocoding service can return strong evidence.
But the person reviewing the address may still need to supply the missing context.
The saved experiment is available here:
https://github.com/YakiUdoph/Yaki/blob/main/artifacts/country-policy-live-results.json
One clarification: those five candidates belong to that recorded experiment. Geoapify's results can change, and a later production test returned ten candidates for the same general query.
I wouldn't treat either number as permanent.
5. Why selecting a result isn't enough
This was another important design decision.
Imagine someone searches for an address and receives several possible locations.
They select one.
Should the application immediately mark it Ready?
I decided it shouldn't.
Selection and confirmation are separate actions in Yaki.
The person first chooses a candidate.
Then they explicitly confirm that choice.
The review model enforces this.
Here's an excerpt from public/review-model.js:
export function confirmReady(
record,
explicitlyConfirmed,
timestamp
) {
const blocked = reviewBlockMessages(record);
if (blocked.length > 0) {
throw new Error(blocked[0]);
}
if (explicitlyConfirmed !== true) {
throw new Error("Human confirmation is required.");
}
const candidate = record.candidates.find(
(item) => item.place_id === record.selectedPlaceId
);
if (!candidate) {
throw new Error(
"Select a candidate before marking this address ready."
);
}
return {
...record,
status: REVIEW_STATUSES.ready,
history: [
...record.history,
event(
"Marked ready after human confirmation",
timestamp,
candidate.formatted || candidate.place_id
)
]
};
}
This also checks whether the server's policy permits confirmation.
That's important because an address requiring clarification shouldn't become Ready simply because someone selected a result and checked a box.
Changing the selected candidate also revokes the previous confirmation.
The new selection must be reviewed again.
It's a small interaction, but it prevents the interface from silently carrying an old decision over to a different location.
6. Supporting CSV files
I also wanted Yaki to work with more than one address at a time.
A delivery coordinator might have a spreadsheet containing several destinations.
Opening a separate geocoding tool for every row would be inconvenient.
So Yaki supports CSV import.
The person uploads a file, previews its contents, chooses the address column, and reviews the addresses through the same workflow.
The current implementation processes rows sequentially.
It also places limits on imported files:
- Maximum file size: 1 MB
- Maximum data rows: 500
- Maximum columns: 50
- Maximum field length: 4,096 characters
These limits help keep the current browser-based workflow manageable.
Yaki produces separate exports for Ready and unresolved addresses.
That distinction means an unfinished review doesn't quietly disappear into the same output as a confirmed selection.
The export implementation also protects spreadsheet cells against common formula-injection prefixes.
For example:
const FORMULA_PREFIX = /^[\t\r ]*[=+\-@]/u;
export function protectSpreadsheetCell(value) {
const text =
value === undefined || value === null
? ""
: String(value);
return FORMULA_PREFIX.test(text)
? `'${text}`
: text;
}
This is a small but practical security consideration when exporting data that originated from user input.
Why not use Geoapify's Batch API?
Geoapify offers batch geocoding capabilities.
I evaluated those options, but Yaki's current CSV workflow uses sequential requests.
The reason is the product's focus on interactive review.
I wanted to preserve multiple candidates for each address and make the decision process easy to inspect.
That doesn't mean sequential processing is the best approach for every application.
A future version designed for much larger datasets would need a different architecture, including resumable processing and more durable storage.
7. Exploring Geoapify MCP
Alongside the production integration, the repository documents an experiment using Geoapify's MCP interface.
MCP provides a way for compatible tools and assistants to discover and call supported capabilities.
In the recorded experiment, the MCP endpoint exposed tools including:
geocode_addressgeocode_structured_addressreverse_geocode_coordinatessearch_placescalculate_route
The experiment used tool discovery and a geocoding call to compare behavior with the direct API.
An important lesson was that a successful HTTP response doesn't necessarily mean an MCP operation succeeded.
The client needed to distinguish between HTTP failures, JSON-RPC errors, and tool-level errors.
Those distinctions are handled in Yaki's MCP client code.
Why doesn't Yaki use MCP in production?
Because the direct Forward Geocoding API was a better fit for the application's current workflow.
It provided the structured evidence needed for address review without introducing an additional tool-calling layer.
The MCP experiment was useful for understanding the available capabilities and comparing results.
But it didn't provide independent proof that an address was deliverable.
The shipped application uses direct geocoding. MCP was a separate development experiment.
I think that distinction is worth making because exploring a technology doesn't automatically mean it belongs in the final product.
8. Building with AI: what needed human judgment
AI-assisted development was part of the engineering workflow documented around Yaki.
The repository preserves technical plans, API-contract work, tests, experiments, implementation changes, and corrections.
It doesn't preserve a complete transcript of every historical coding-assistant prompt, so I won't pretend to reproduce those conversations word for word.
What the development evidence does show is why AI-generated or AI-assisted code still needs careful review.
One example involved Yaki's browser confirmation logic.
An earlier version allowed the local selection and confirmation state to move ahead without fully enforcing the server's clarification policy.
That meant the browser could potentially treat a blocked record as ready.
The correction made the browser enforce the policy when displaying confirmation controls, changing review state, and exporting records.
Another issue involved the Vercel function exports.
The local server worked differently from the production serverless environment, and the function exports needed correction to match the platform's expected interface.
There was also a smaller but important UI issue.
After an address became Ready, some informational text could still say that review was required.
The underlying status and the explanatory text weren't updating together.
That was corrected so the messages reflect the current review state.
These weren't features to hide.
They were useful reminders that building software involves more than getting code to compile.
A working API call doesn't prove the entire workflow is correct.
A successful local test doesn't prove a deployment will behave identically.
And a correct status badge doesn't help much if the surrounding text tells the user something contradictory.
A reproducible AI-assisted workflow
For someone building a similar application with a coding assistant, I'd recommend a process like this:
- Start with the official API specification and existing application requirements.
- Ask the assistant to identify assumptions before writing code.
- Test request formatting, response handling, and errors without consuming live API requests.
- Run a small number of authorized live checks using public addresses.
- Record what actually happened.
- Review every decision that changes an address from unresolved to Ready.
The important part is not simply asking an assistant to build an application.
It's being able to explain and verify what the resulting application actually does.
9. Testing Yaki with real locations
The repository contains saved production verification evidence.
One recorded production session used Springfield, United States.
Yaki returned multiple candidates, but the address remained under review.
Selecting Springfield, Illinois still didn't immediately enable Ready export.
Only after explicit confirmation did the row become Ready.
A later production verification exercise also used public landmarks in four countries:
| Country | Address used |
|---|---|
| Nigeria | National Theatre, Lagos |
| United Kingdom | Buckingham Palace, London |
| Ghana | Independence Square, Accra |
| Canada | CN Tower, Toronto |
The saved evidence records successful selection, confirmation, and Ready-export workflows for those specific checks.
These tests are useful because they show how the application behaved with real provider responses.
But they don't prove that every address in those countries will work correctly.
They also don't establish postal deliverability or guarantee that future Geoapify results will remain unchanged.
The evidence is available in the repository:
I think documenting the boundaries of a test is just as important as documenting that it passed.
10. How to run Yaki yourself
Yaki's source code is public.
You'll need:
- Node.js 22.18 or later
- A Geoapify API key authorized for Forward Geocoding
- Git
The following commands use Windows PowerShell.
Clone the repository:
git clone https://github.com/YakiUdoph/Yaki.git
Set-Location Yaki
Install the dependencies:
npm.cmd ci
Create a local environment file:
Copy-Item .env.example .env
Open .env and configure your own API key:
GEOAPIFY_API_KEY=your_own_api_key
YAKI_PORT=4173
Don't commit your real API key to GitHub.
Start the application:
npm.cmd start
Then open:
http://127.0.0.1:4173
Try the Springfield example
Select the United States as your destination country.
Enter:
Springfield
Run the address check.
Examine the candidates returned by Geoapify.
Choose the intended location, explicitly confirm it, and observe how Yaki changes the review state.
You can also try the sample CSV included in the repository:
examples/addresses-us.csv
Running tests
The repository includes TypeScript checks, unit tests, browser tests, and tests for the Vercel function contracts.
For example:
npm.cmd run typecheck
npm.cmd run test:vercel
npm.cmd run test:browser
The browser tests use mocked responses, so passing them doesn't establish that Geoapify's live service is available.
Live tests are separate and may consume API quota.
The repository README explains the available commands and their requirements.
11. What Yaki doesn't do
I think it's important to be clear about the application's limitations.
Yaki doesn't verify whether a building entrance is accessible.
It doesn't guarantee that a courier can complete a delivery.
It doesn't prove that a postal address is valid simply because Geoapify returned a matching location.
And it doesn't automatically know which candidate a sender intended.
The current application also doesn't have user accounts, a database, or persistent review history.
Review information stays in browser memory during the session.
CSV files are parsed locally, while individual address checks are sent through Yaki's server endpoint to Geoapify.
That means Geoapify necessarily receives the address being checked.
Yaki is currently a prototype and shouldn't be treated as approved for real customer address processing without further privacy, security, and operational work.
There are improvements I'd like to explore, including more durable review records, authentication, stronger distributed rate limiting, accessibility testing, and support for larger address imports.
But those are future possibilities, not features I'm claiming exist today.
12. What I learned
Building Yaki changed how I think about the relationship between data and decisions.
At first glance, geocoding seems straightforward.
You provide an address.
You get a location.
But the interesting questions begin when the result is ambiguous.
What should the application do when several places look correct?
What happens when the provider can't complete a request?
Should a high confidence score automatically approve an address?
And how do you make those decisions understandable to someone who isn't a developer?
Working through those questions helped shape Yaki more than adding another feature would have.
Geoapify's structured location results made it possible to build the review experience.
Its country restrictions helped make ambiguous searches more controlled.
And exploring the MCP interface gave me a better understanding of the differences between direct API integration and tool-based geospatial workflows.
There are also areas where clearer documentation could help developers.
For example, I'd welcome more explicit guidance about how implicit geographic bias differs between direct geocoding and MCP, and more examples showing how MCP clients should distinguish transport success from tool-level failure.
Those details matter when developers are trying to build reliable applications.
13. One last thought
I don't think useful software always needs to make decisions for people.
Sometimes, it needs to make the information behind those decisions easier to understand.
That's the thinking behind Yaki.
It doesn't promise to eliminate every address mistake.
It simply creates a place to look again, compare the available evidence, and make a more deliberate choice.
Because finding an address is one thing.
Being confident you've found the place someone intended is another.
That's Yaki. The last check before dispatch.
Explore Yaki
Live application: https://yaki-seven.vercel.app/
GitHub repository: https://github.com/YakiUdoph/Yaki
Cinematic walkthrough: https://youtu.be/s4neNKEMbXc?si=6He4LuMGfAA4x1Ge
Original technical documentation: https://github.com/YakiUdoph/Yaki/blob/main/docs/geoapify-batch2-tutorial.md
Built using Geoapify's Forward Geocoding API as part of the Geoapify Open Geospatial Program.
來源:DEV Community · dev.to