maeandrew / novaposhta-address-resolver
Framework-free Nova Poshta address resolution core
Package info
github.com/maeandrew/novaposhta-address-resolver
pkg:composer/maeandrew/novaposhta-address-resolver
Requires
- php: >=8.2
- ext-mbstring: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^10.5 || ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Standalone PHP tooling for turning a free-form Nova Poshta address into a validated settlement and warehouse selection.
The project is intentionally split into a framework-free resolver core and optional adapters. A user may connect a ready Nova Poshta SDK or a custom HTTP client, then add an AI provider without coupling the core to a vendor SDK.
Unofficial community project. It is not affiliated with Nova Poshta.
What it solves
Real order messages often contain variants such as:
Київ, відділення номер 285
м. Київ НП 285
Львів поштомат 12345
The resolver parses and normalizes the text, queries a location provider, scores candidates, and returns a result that is safe to persist:
resolved exact settlement and warehouse selected
ambiguous more than one plausible candidate; manual choice required
not_found no candidate matched the requested address
provider_error external data source failed or returned invalid data
invalid_input the input cannot be meaningfully parsed
The resolver never writes to an order or silently chooses a branch when the evidence is insufficient.
Installation
The framework-free core is installed with Composer:
composer require maeandrew/novaposhta-address-resolver
The core requires PHP 8.2 or newer and ext-mbstring. It has no Laravel,
HTTP-client, Nova Poshta SDK, or AI SDK dependency.
Core usage
use MaeAndrew\NovaPoshtaAddressResolver\AddressResolver; use MaeAndrew\NovaPoshtaAddressResolver\DTO\AddressInput; $resolver = new AddressResolver($locationProvider); $result = $resolver->resolve( AddressInput::fromText('Київ, відділення №285') ); if ($result->isResolved()) { $settlementRef = $result->settlement?->ref; $warehouseRef = $result->warehouse?->ref; } elseif ($result->needsReview()) { foreach ($result->candidates as $candidate) { // Show candidates to a person and persist only after an explicit choice. } } $safePayload = $result->toArray();
toArray() returns the status, normalized records, confidence, candidates,
parsed fields, diagnostics, and provider name. Raw provider payloads are omitted
unless toArray(includeRawPayload: true) is explicitly requested.
Provider extension
The application supplies the source of truth by implementing three small methods. A provider translates its SDK, HTTP client, or local fixture data into the core DTOs:
use MaeAndrew\NovaPoshtaAddressResolver\Contracts\LocationProvider; use MaeAndrew\NovaPoshtaAddressResolver\DTO\ProviderHealth; use MaeAndrew\NovaPoshtaAddressResolver\DTO\ProviderResult; use MaeAndrew\NovaPoshtaAddressResolver\DTO\Settlement; use MaeAndrew\NovaPoshtaAddressResolver\DTO\SettlementQuery; use MaeAndrew\NovaPoshtaAddressResolver\DTO\Warehouse; use MaeAndrew\NovaPoshtaAddressResolver\DTO\WarehouseQuery; final class MyLocationProvider implements LocationProvider { public function searchSettlements(SettlementQuery $query): ProviderResult { // Query the chosen source and return list<Settlement>. return new ProviderResult([]); } public function searchWarehouses( Settlement $settlement, WarehouseQuery $query, ): ProviderResult { // Query only warehouses belonging to $settlement->ref. return new ProviderResult([]); } public function healthCheck(): ProviderHealth { return ProviderHealth::healthy('my-provider'); } }
Provider failures should throw ProviderException or another clear exception;
the resolver exposes them as provider_error instead of treating an outage as
not_found. The provider must return normalized Settlement and Warehouse
objects. It must not trust a reference supplied by an AI or by an unvalidated
caller.
The built-in parser recognizes Ukrainian and common Russian forms such as м.,
відд., відділення, поштомат, пункт, №, НП, and spoken warehouse
numbers such as двісті вісімдесят п’ять. Matching is configurable:
use MaeAndrew\NovaPoshtaAddressResolver\DTO\ResolutionPolicy; use MaeAndrew\NovaPoshtaAddressResolver\Matching\StrictMatchingStrategy; $resolver = new AddressResolver( $locationProvider, matchingStrategy: new StrictMatchingStrategy(), policy: new ResolutionPolicy( autoResolveThreshold: 0.90, ambiguityMargin: 0.08, ), );
BalancedMatchingStrategy is the default. StrictMatchingStrategy uses exact
references, names, types, numbers, and address fields. SuggestMatchingStrategy
returns ranked candidates while keeping the result reviewable.
AI is recommended for production input
For production free-form messages, AI should be treated as the primary quality layer. It handles spelling mistakes, mixed languages, omitted labels, and unusual formats that deterministic parsing cannot cover as well. The deterministic pipeline remains a safe degraded mode for outages, privacy- restricted inputs, offline jobs, and tests, so the core package does not force an AI SDK or API key.
The core AI contracts and a fake provider are included in the core package. An AI suggestion may only rank provider candidates; it can never create or persist a settlement or warehouse reference. Configure a fallback chain when more than one provider is available.
The optional OpenAI adapter uses the Responses API and structured JSON output:
# after publishing the adapter package
composer require maeandrew/novaposhta-address-resolver-openai
During monorepo development, install its local dependencies with
ddev exec bash scripts/install-openai.sh. The adapter is kept in a separate
package so the core's Composer install remains small.
It accepts PSR HTTP client and factory implementations from the host
application, so the core and adapter remain independent of a particular HTTP
client. The adapter is wired through StructuredAddressAiInterpreter:
use MaeAndrew\NovaPoshtaAddressResolver\AI\StructuredAddressAiInterpreter; use MaeAndrew\NovaPoshtaAddressResolver\OpenAI\OpenAiStructuredAiProvider; $structuredProvider = new OpenAiStructuredAiProvider( $httpClient, $requestFactory, $streamFactory, $_ENV['OPENAI_API_KEY'], ); $resolver = new AddressResolver( $locationProvider, aiInterpreter: new StructuredAddressAiInterpreter($structuredProvider), );
The adapter is optional and its tests use a fake HTTP client; CI never calls an AI endpoint.
Optional Laravel bridge
Laravel integration is a separate package and does not add framework classes to the core:
composer require maeandrew/novaposhta-address-resolver-laravel
It provides service-container bindings, configurable cache, resolution events,
a queue job, and novaposhta:resolve. The host application binds its own
LocationProvider and decides how resolved references are persisted. See the
Laravel integration guide.
The bridge source is published in its own GitHub repository and is available on Packagist.
During monorepo development, use ddev exec bash scripts/install-laravel.sh to
install the bridge against the local core package.
Security and limitations
- The core never writes to orders, customers, databases, or caller-owned data.
- No live API calls are made by the test suite or CI; tests use synthetic
fixture-*records. - Raw input and raw provider payloads are not included in result diagnostics by default. Applications should redact personal data before optional AI use.
- Fuzzy matching is a suggestion mechanism. A result stays
ambiguouswhen the top candidates are too close or the configured threshold is not met. - Provider data freshness, authentication, retries, rate limits, and caching remain responsibilities of the adapter or host application.
Development
For consistent local development, this repository includes a DDEV configuration with PHP and Composer:
ddev start ddev composer install ddev composer quality
The quality command runs PHP CS Fixer, PHPStan, and the complete PHPUnit suite. CI runs the same checks on PHP 8.2 and 8.3.
The optional Laravel bridge has its own local dependency setup and quality suite:
ddev exec bash scripts/install-laravel.sh
ddev composer quality --working-dir=packages/laravel
Documentation
AGENTS.md— handoff rules for implementation agents.docs/IMPLEMENTATION_PLAN.md— detailed scope, contracts, algorithm, milestones, and acceptance criteria.docs/AI_PROVIDERS.md— provider abstraction, adapters, fallback, and privacy.docs/ARCHITECTURE.md— core, provider, AI, and Laravel boundaries.docs/LARAVEL.md— optional Laravel bridge, queue, events, cache, and mapping.examples/— synthetic provider, AI, and address fixtures; no customer data.
License
Use MIT unless the project owner selects another permissive license before the first public release.