postcode

package module
v0.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 33 Imported by: 1

README

Nigerian Postcode Go SDK (postcode)

A high-performance, zero-allocation Go client library for Nigeria's National Digital Alphanumeric Postcode system (docs.postcode.gov.ng).

Go Reference Go Version

Features

  • Zero-Allocation Parsing: postcode.Parse() parses and validates 11-character postcodes with 0 heap escapes (~28ns/op).
  • Safe & Idiomatic Go: Fully memory-safe accessors, Go range-over-func iterators (postcode.ParseSeq), standard errors (errors.As), and functional options.
  • Protocol Conformance: Built to match the official NIPOST OpenAPI spec (GET /v1/lookup, GET /v1/search/autocomplete, GET /v1/search/nearby, GET /v1/search/reverse, and POST /v1/assembly/assemble).
  • Resilient Transport: Production-tuned HTTP transport with exponential backoff, full jitter, Retry-After header support, and safe connection draining.
  • Pluggable Observability: Zero external dependencies in the core module, with an optional OpenTelemetry module (github.com/abcubed3/postcode/otelpostcode).
  • AI & Agent Native: Built-in Model Context Protocol (MCP) server (postcode mcp), standard LLM function calling schemas (Gemini, OpenAI, Anthropic), and actionable diagnostic self-correction (postcode.Diagnose).
  • Shareable & Printable QR Codes: Built-in, dependency-free ISO/IEC 18004 QR encoder (postcode.GenerateQR) rendering SVG, PNG, Base64 Data URIs, terminal Unicode, and print-ready NIPOST postcode labels.
  • Web Client Component: Embeddable <nipost-address-input> custom element (@abcubed3/nipost-address-input) with smart freeform address parsing, 774 LGA centroids, self-healing diagnostics, and form participation powered by @abcubed3/postcode-wasm.

Installation

Requires Go 1.27+ (for Go SDK).

Go Library (SDK)

Add the core library to your Go project:

go get github.com/abcubed3/postcode
Command-Line Interface (postcode)

Install the postcode CLI binary directly to your $GOPATH/bin:

go install github.com/abcubed3/postcode/cmd/postcode@latest

Or download pre-compiled binaries for Linux, macOS, and Windows from GitHub Releases.

Verify installation:

postcode version
Web Client Component (@abcubed3/nipost-address-input)

Install the embeddable Web Component via npm:

npm install @abcubed3/nipost-address-input

Or load directly from a CDN without an installation step:

<script type="module" src="https://unpkg.com/@abcubed3/nipost-address-input"></script>

Quickstart

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "time"

    "github.com/abcubed3/postcode"
)

func main() {
    ctx := context.Background()

    // 1. High-throughput client with functional options
    client, err := postcode.NewClient()
    if err != nil {
        log.Fatal(err)
    }

    // 2. Graded postcode lookup (Level 1 is free, Level 2-3 require commercial credits)
    res, err := client.Lookup(ctx, "EK 01 A03 FK 01", postcode.Level1)
    if err != nil {
        var apiErr *postcode.APIError
        if errors.As(err, &apiErr) && apiErr.IsInsufficientCredits() {
            log.Fatalf("Top up required: %v", apiErr)
        }
        log.Fatalf("Lookup failed: %v", err)
    }
    fmt.Printf("Postcode: %s, Valid: %t\n", res.Postcode, res.Valid)

    // 3. Segment-aware autocomplete
    suggs, err := client.Autocomplete(ctx, "EK 01 A")
    if err != nil {
        log.Fatalf("Autocomplete failed: %v", err)
    }
    fmt.Printf("Active segment: %s, Suggestions: %+v\n", suggs.Segment, suggs.Suggestions)

    // 4. Generate Google Maps URL & Coordinates from 11-digit postcode string
    gmapsURL, err := postcode.GoogleMapsURL("EK-01-A03-FK-01")
    if err != nil {
        log.Fatalf("Mapping failed: %v", err)
    }
    lat, lng, _ := postcode.Coordinates("EK-01-A03-FK-01")
    fmt.Printf("Google Maps URL: %s\n", gmapsURL)
    fmt.Printf("Coordinates: %.6f, %.6f\n", lat, lng)

    // 5. Batch streaming parse 
    records := []string{"EK 01 A03 FK 01", "INVALID_CODE", "FC 02 A09 DB 09"}
    for p, err := range postcode.ParseSeq(records) {
        if err != nil {
            fmt.Printf("Skipping invalid entry: %v\n", err)
            continue
        }
        fmt.Printf("Parsed: %s (State: %s, LGA: %s, Unit: %s)\n",
            p.Formatted(), p.State(), p.LGA(), p.BuildingUnit())
    }

    // 6. Generate shareable & printable QR code (pure Go, zero CGO/dependencies)
    qr, err := postcode.GenerateQR("EK-01-A03-FK-01", postcode.QROptions{
        Payload: postcode.QRPayloadPostcode,
        Level:   postcode.QRLevelM,
    })
    if err == nil {
        _ = qr.SaveSVG("ekiti-postcode.svg")
        fmt.Println(qr.Terminal())
    }
}

API Surface

Method / Function Endpoint / Scope Description
postcode.GoogleMapsURL(code) Offline / Local Generates direct universal Google Maps URL for an 11-digit postcode.
postcode.Coordinates(code) Offline / Local Returns (latitude, longitude) for an 11-digit postcode string.
postcode.ResolveLocation(code) Offline / Local Resolves Location struct (lat/long, precision, Google Maps/Apple/OSM URLs).
postcode.GenerateQR(code, opts...) Offline / Local Generates shareable & printable ISO/IEC 18004 QR codes (SVG, PNG, Label SVG, HTML, Terminal).
p.QR(opts...) Method on Postcode Converts a parsed Postcode into a QRCode with SVG, PNG, Data URI, and terminal outputs.
p.GoogleMapsURL() Method on Postcode Zero-allocation / sub-microsecond Google Maps URL generation.
client.ResolveLocation(ctx, code) Gateway + Geocoding Multi-stage pipeline: NIPOST Level 1–3 + Google Maps/Nominatim + auto disk caching.
client.GenerateQR(ctx, code, opts...) Gateway + Local QR Enriches address metadata online and renders a printable/shareable QR code.
client.Lookup(ctx, code, level) GET /v1/lookup Graded postcode lookup (Levels 1–3 cumulative per official spec).
client.Autocomplete(ctx, q) GET /v1/search/autocomplete Segment-aware suggestions for partial input.
client.Nearby(ctx, params) GET /v1/search/nearby Units within a radius (default 300m) of coordinates or reference postcode.
client.NearbyPostcode(ctx, code, radiusM) Convenience Helper Nearby search surrounding a reference postcode's centroid.
client.Reverse(ctx, params) GET /v1/search/reverse Snaps coordinate to nearest active unit (default 25m).
client.ReverseCoordinates(ctx, lat, lng, maxDist) Convenience Helper Snaps lat/lng directly to nearest active unit.
client.Assemble(ctx, segs) POST /v1/assembly/assemble Assembles 5 segments into canonical format.
client.Disassemble(ctx, code) GET /v1/assembly/disassemble Decomposes code into its 5 administrative segments.

Shareable & Printable QR Codes

Every validated Nigerian digital postcode can be converted into a high-density, printable, or shareable ISO/IEC 18004 QR code with zero external Go dependencies and zero CGO.

1. Multi-Format Rendering & File Helpers
qr, err := postcode.GenerateQR("LA-11-W06-TC-10", postcode.QROptions{
    Payload:         postcode.QRPayloadGoogleMaps, // Encodes direct navigation link
    Size:            300,                          // Pixel/SVG dimension
    ForegroundColor: "#0B6623",                    // NIPOST Nigerian Postal Green
    BackgroundColor: "#FFFFFF",
})
if err != nil {
    log.Fatal(err)
}

// In-memory renderers
svgData     := qr.SVG()                                  // Scalable Vector Graphics string
pngBytes, _ := qr.PNG()                                  // Compressed PNG image bytes
htmlPage    := qr.PrintableHTML()                        // Self-contained, print-styled HTML badge
termView    := qr.Terminal()                             // Unicode half-block characters for stdout
dataURI, _  := qr.DataURI()                              // data:image/png;base64,...

// Direct file export helpers
_ = qr.SaveSVG("lagos-postcode.svg")
_ = qr.SavePNG("lagos-postcode.png")
_ = qr.SaveHTML("lagos-label.html")
2. Print-Ready NIPOST Delivery Badges & Labels

Generate professional delivery labels with the official NIPOST green badge header, human-readable postcode segments, and physical locality description for package shipping and building signage:

qr, _ := postcode.GenerateQR("EK-01-A03-FK-01", postcode.QROptions{
    IncludeLabel: true,
    Title:        "NIPOST DIGITAL POSTCODE",
    Subtitle:     "NTA Road, Back of Fabian Hotel, Ado Ekiti",
})

// Generates an SVG shipping label with clear typography and quiet zones
labelSVG := qr.LabelSVG()
_ = qr.SaveSVG("ekiti-delivery-badge.svg")
3. Payload Options
Payload Option Value / Format Example Output / URL
postcode.QRPayloadPostcode (Default) Hyphenated 11-char EK-01-A03-FK-01
postcode.QRPayloadCompact Unspaced 11-char EK01A03FK01
postcode.QRPayloadSpaced 5 spaced segments EK 01 A03 FK 01
postcode.QRPayloadGoogleMaps Web Maps search URL https://maps.google.com/?q=7.621100,5.221500
postcode.QRPayloadDirections Direct navigation URL https://www.google.com/maps/dir/?api=1&destination=7.621100,5.221500
postcode.QRPayloadAppleMaps Apple Maps URL https://maps.apple.com/?q=...
postcode.QRPayloadOSM OpenStreetMap link https://www.openstreetmap.org/?mlat=...
postcode.QRPayloadGeoURI RFC 5870 geo: URI geo:7.621100,5.221500
postcode.QRPayloadJSON Machine-readable JSON {"postcode":"EK-01-A03-FK-01","state":"EK",...}
postcode.QRPayloadText Rich address text "EK-01-A03-FK-01 (Ekiti, Ado Ekiti)"
4. CLI QR Generation
# Print QR code directly in the terminal
postcode qr EK-01-A03-FK-01

# Save SVG and PNG
postcode qr LA-11-W06-TC-10 --render svg --out ikeja.svg
postcode qr LA-11-W06-TC-10 --render png --out ikeja.png

# Generate a printable shipping label badge and preview in browser
postcode qr EK-01-A03-FK-01 --label --out label.svg --open

# Encode direct turn-by-turn navigation for delivery couriers
postcode qr FC-03-B06-AG-12 --payload directions --out abuja-nav.svg

Web Client Component (<nipost-address-input>)

For web applications, checkout flows, and logistics portals, the repository provides an official embeddable Web Component (packages/address-input) published on npm as @abcubed3/nipost-address-input:

  • Framework-Agnostic Web Component: Runs natively in plain HTML, React, Next.js, Vue, Angular, Svelte, or server-rendered templates (Django, Laravel, Go html/template).
  • Hybrid Dual-Mode UX:
    • Smart Quick-Paste / Search: Users can paste freeform address strings (e.g. "Plot 14 Admiralty Way, Lekki Phase 1, Opposite Domino's Pizza, Lagos"). The disambiguation engine isolates the State, LGA, Locality, Landmark, Street, and canonical 11-character NIPOST Postcode.
    • Expandable Structured Breakdown: Toggle to cascading dropdowns for 37 States + FCT and all 774 Nigerian LGAs.
  • Form-Associated Custom Element: Automatically registers with native HTML <form> submissions, submitting structured sub-keys (shipping_address[state], shipping_address[lga], shipping_address[postcode], shipping_address[latitude], etc.) with zero custom JavaScript serializers.
  • Self-Healing Diagnostics: Visual 5-segment chips (SS-LL-DDD-AA-UU) with instant tips for legacy 6-digit codes or illegal units (00).
  • Edge WASM Speed: Zero external network requests needed for validation or LGA extraction — powered entirely by @abcubed3/postcode-wasm.
  • Theming & Design Tokens: Preset themes (light, dark, minimal, unstyled), CSS custom properties (--nipost-primary, etc.), and Shadow DOM ::part() styling.
HTML Form Integration
<!-- 1. Drop script in <head> or before </body> -->
<script type="module" src="https://unpkg.com/@abcubed3/nipost-address-input"></script>

<!-- 2. Embed inside any standard HTML form -->
<form action="/checkout" method="POST">
  <nipost-address-input
    name="shipping_address"
    placeholder="Enter street, landmark, or 11-digit NIPOST code..."
    theme="light"
    required
  ></nipost-address-input>

  <button type="submit">Place Order</button>
</form>

<script>
  const widget = document.querySelector('nipost-address-input');

  // Keystroke-level draft updates
  widget.addEventListener('nipost-change', (e) => {
    console.log('Address draft:', e.detail.address);
  });

  // Canonical NIPOST postcode verified
  widget.addEventListener('nipost-resolve', (e) => {
    console.log('Verified Postcode:', e.detail.postcode);
    console.log('Centroid Coords:', e.detail.address.latitude, e.detail.address.longitude);
  });
</script>

💻 Runnable Example: Check out examples/address_input_web/ for a ready-to-run interactive checkout demo with live telemetry and FormData inspection.

Command-Line Interface (postcode)

The repository includes a production-grade CLI binary for terminal and pipeline workflows:

# Install the CLI
go install github.com/abcubed3/postcode/cmd/postcode@latest
Key CLI Capabilities
  • Offline Validation: postcode validate "EK 01 A03 FK 01" (grammar, format, state codes, exit codes).
  • Diagnostic Self-Correction: postcode diagnose "ZZ 00 A03 FK 00" (segment errors, state typo suggestions, agent tips).
  • Segment Parsing: postcode parse "LA 11 W06 TC 10" -o json (extracts State, LGA, District, Area, Unit, Zone).
  • Format Normalization: cat dirty.txt | postcode format --style canonical (Unix pipe friendly).
  • Geocoding & Maps: postcode map EK-01-A03-FK-01 (generates Google Maps, Apple Maps, OSM URLs).
  • Shareable & Printable QR Codes: postcode qr EK-01-A03-FK-01 --label --out label.svg (renders terminal Unicode, SVG, PNG, Data URI, or printable NIPOST badges).
  • Batch CSV Processing: postcode batch --input orders.csv --output enriched.csv --column postcode (processes >350k rows/sec offline).
  • Gateway Operations: postcode lookup, postcode autocomplete, postcode nearby, postcode reverse, and postcode status.
  • Model Context Protocol: postcode mcp (exposes stdio MCP server for Claude Desktop, Cursor, Antigravity IDE).
  • Embedded Simulator: postcode serve --port 2340 (runs local mock NIPOST gateway directly, with optional --data flag).

📖 Full User Guide: For complete documentation, command options, and real-world recipes, see the CLI User Guide & Reference.

Live Server Simulator

For local development and automated CI testing without incurring API fees or requiring live network access, the toolkit includes an in-memory mock server pre-loaded with all official test postcodes from docs.postcode.gov.ng:

1. Run as a Standalone Server

Start the simulator directly via the CLI:

# Start on localhost:2340 (or specify --port 2340)
postcode serve --port 2340

# Or run directly from source without installation:
go run ./cmd/postcode serve --port 2340

Test with curl:

# L1 Public validity lookup (Free, no auth)
curl "http://localhost:2340/v1/lookup?code=EK-01-A03-FK-01&level=1"

# L3 Commercial lookup (Requires X-API-Key header)
curl "http://localhost:2340/v1/lookup?code=EK-01-A03-FK-01&level=3" \
  -H "X-API-Key: nipost_live_test"
2. Embed Directly in Go Unit & Integration Tests
import "github.com/abcubed3/postcode/simulator"

func TestMyService(t *testing.T) {
    srv := simulator.NewServer() // Starts httptest.Server
    defer srv.Close()

    client, err := postcode.NewClient(
        postcode.WithBaseURL(srv.URL),
        postcode.WithAPIKey("test_key"),
    )
    // Run your application tests against srv.URL
}
3. Preloaded Test Postcodes
Postcode State Locality / Address
EK-01-A03-FK-01 Ekiti NTA Road, Back of Fabian Hotel, Ado Ekiti
AK-11-I61-ZF-12 Akwa Ibom 12 Oron Road, Uyo
AK-11-H40-WD-11 Akwa Ibom 11 Wellington Bassey Way, Uyo
BA-02-M67-BL-69 Bauchi 69 Bank Road, GRA, Bauchi
BA-02-E99-NE-30 Bauchi 30 Ahmadu Bello Way, Bauchi
EB-13-G95-FR-90 Ebonyi 90 Ogoja Road, Abakaliki
EB-13-I97-AB-30 Ebonyi 30 Water Works Road, Abakaliki
EN-05-V19-CD-22 Enugu 22 Chime Avenue, New Haven, Enugu
EN-05-V19-FT-20 Enugu 20 Ogui Road, Enugu
FC-03-B06-AG-12 FCT 12 Shehu Shagari Way, Garki, Abuja
FC-02-B19-RT-30 FCT 30 Gado Nasko Way, Phase 4, Kubwa, Abuja
JI-24-O18-JP-23 Jigawa 23 Sani Abacha Way, Dutse
JI-24-N11-VM-58 Jigawa 58 Kano-Dutse Expressway, Dutse
KN-31-F82-WJ-80 Kano 80 Badu Road, Bompai, Kano
KN-31-D78-IQ-38 Kano 38 Ibrahim Taiwo Road, Kano
LA-11-W06-TC-10 Lagos 10 Obafemi Awolowo Way, Ikeja, Lagos
LA-11-U34-ZR-63 Lagos 63 Isaac John Street, GRA Ikeja, Lagos
NI-09-J67-QC-65 Niger 65 Bosso Road, Minna
NI-09-A75-DA-10 Niger 10 Paida Road, Minna
OG-14-T18-BN-16 Ogun 16 Lalubu Street, Oke-Ilewo, Abeokuta
OG-14-M82-QA-09 Ogun 9 Quarry Road, Abeokuta

Rate Limiting & Resilient Retries

As specified in the NIPOST Authentication Documentation, all authenticated gateway requests are rate-limited per key. The gateway returns quota headers on every response:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597

When quota is exceeded, the gateway returns 429 Too Many Requests with a cooldown duration.

Intelligent 429 Handling
  • Automatic Cooldown Synchronization: On receiving HTTP 429, the transport extracts cooldown intervals from Retry-After (integer seconds or RFC1123 HTTP dates) and X-RateLimit-Reset, adding randomized jitter to avoid thundering herds when the window resets.
  • Fail-Fast Defense (MaxRateLimitDelay): If the gateway requests a cooldown longer than the configured threshold (default: 10s), the client aborts immediately, preventing goroutine hangs and wasted retries.
  • Live Quota Introspection: Call client.RateLimit() at any time to inspect Remaining, Limit, and ResetAt in a thread-safe manner without making extra network calls.
  • Rich Error Context: apiErr.IsRateLimit() returns true on 429 errors, exposing apiErr.RetryAfter and apiErr.RateLimit.
// 1. Fine-tune rate limit retries
client, err := postcode.NewClient(
    postcode.WithAPIKey("nipost_live_..."),
    postcode.WithRateLimitRetry(true, 5*time.Second), // Max wait threshold: 5s
)

// 2. Inspect rate limit quota
if rl := client.RateLimit(); rl != nil {
    fmt.Printf("Quota remaining: %d/%d (resets at %v)\n", rl.Remaining, rl.Limit, rl.ResetAt)
}

// 3. Structured error handling
res, err := client.Lookup(ctx, "EK-01-A03-FK-01", postcode.Level2)
if err != nil {
    var apiErr *postcode.APIError
    if errors.As(err, &apiErr) && apiErr.IsRateLimit() {
        log.Printf("Rate limit exceeded. Try again in %v", apiErr.RetryAfter)
    }
}

OpenTelemetry Integration

Install the telemetry adapter submodule:

go get github.com/abcubed3/postcode/otelpostcode

Hook it into your client:

package main

import (
    "context"
    "log"

    "github.com/abcubed3/postcode"
    "github.com/abcubed3/postcode/otelpostcode"
)

func main() {
    // Initialize tracing and metrics from global OTel providers
    tel, err := otelpostcode.NewTelemetry()
    if err != nil {
        log.Fatal(err)
    }

    client, err := postcode.NewClient(
        postcode.WithAPIKey("YOUR_API_KEY"),
        postcode.WithTelemetry(tel),
    )
    if err != nil {
        log.Fatal(err)
    }

    _, _ = client.Lookup(context.Background(), "EK 01 A03 FK 01", postcode.Level2)
}

AI Agents & Model Context Protocol (MCP)

The postcode toolkit is engineered as a first-class geocoding foundation for AI models and autonomous agents.

1. Model Context Protocol (MCP) Server

Connect the toolkit directly to Claude Desktop, Cursor, Antigravity IDE, or custom agent runners using the standard MCP protocol:

# Start MCP server via CLI
postcode mcp

Claude Desktop Configuration (claude_desktop_config.json):

{
  "mcpServers": {
    "postcode": {
      "command": "postcode",
      "args": ["mcp"],
      "env": {
        "POSTCODE_API_KEY": "YOUR_NIPOST_API_KEY"
      }
    }
  }
}
2. Native LLM Function Calling (postcode.DefaultAgentTools)

Export standard JSON-Schema Draft-07 tool declarations compatible with Google Gemini, OpenAI, and Anthropic (covering validation, diagnostics, coordinates, reverse geocoding, nearby radius search, autocomplete, graded lookups, states and LGA reference catalogs, and segment-level assembly and disassembly):

// 1. Get standard tool definitions
tools := postcode.DefaultAgentTools()
for _, t := range tools {
    geminiDecl := t.GeminiFunctionDeclaration() // Google Gemini
    openAIFunc := t.OpenAITool()                 // OpenAI / Mistral / Ollama
    anthropicTool := t.AnthropicTool()           // Anthropic Claude
}

// 2. Dispatch tool calls seamlessly
dispatcher := postcode.NewAgentDispatcher(client)
jsonResult, err := dispatcher.DispatchString(ctx, toolName, argsJSON)
3. Diagnostic Self-Correction (postcode.Diagnose)

Traditional validators return opaque errors that cause agents to hallucinate. postcode.Diagnose() generates structured, actionable guidance with typo suggestions that agents can use to self-correct in subsequent reasoning steps:

report := postcode.Diagnose("ZZ 00 A03 FK 00")
// report.Diagnoses -> Segment-by-segment errors with closest valid state suggestions
// report.ActionableTip -> "Fix State: consider ZA (Zamfara); Fix LGA: 00 invalid (01-99)..."
// report.FormatScore -> 40.0 / 100.0
4. Autonomous Agent Budget & Quota Guardrails

Autonomous agents in reasoning loops can quickly burn API credits or encounter rate limits. Configure safety ceilings and automated fallback behaviors:

client, err := postcode.NewClient(
    postcode.WithAPIKey("YOUR_KEY"),
    postcode.WithAgentGuard(postcode.AgentGuardConfig{
        MaxCommercialCallsPerRun: 10,   // Ceiling on paid Level 2/3 lookups per session
        MaxTotalCallsPerRun:      50,   // Absolute ceiling on all calls
        AutoDowngradeToLevel1:    true, // Downgrade to free Level 1 validation if budget depleted
        AutoFallbackToOffline:    true, // Fall back to offline geocoding on 429/402 errors
    }),
)

metrics := client.AgentMetrics()
fmt.Printf("Total: %d, Commercial: %d, Downgraded: %d, Fallbacks: %d\n",
    metrics.TotalCalls, metrics.CommercialCalls, metrics.DowngradedCalls, metrics.OfflineFallbacks)
5. Tool Call Caching & Idempotency

Prevent redundant network calls and token spend across agent retry loops:

// Enable built-in thread-safe memory cache (1000 items, 30m TTL)
client, err := postcode.NewClient(
    postcode.WithDefaultCache(),
)
6. WebAssembly (WASM) Edge Engine

Run the entire offline validation, diagnostics, and geocoding engine inside Node.js, Deno, Bun, Cloudflare Workers, or web browsers with zero network latency (also published as @abcubed3/postcode-wasm, powering the <nipost-address-input> frontend component):

# Build WASM binary
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o wasm/postcode.wasm ./wasm
const { initPostcode } = require('./wasm/index.js');

const postcode = await initPostcode();
const result = postcode.validate("EK 01 A03 FK 01");
const loc = postcode.resolveLocation("EK 01 A03 FK 01");
7. Synthetic Address Generator & Eval Harness (postcode eval)

Benchmark AI agents, NER extractors, and LLMs against realistic Nigerian addresses with noisy landmarks, informal local descriptions, and typos:

# Benchmark baseline accuracy on 100 synthetic noisy addresses
postcode eval --samples 100 --noise 0.3

# Export synthetic dataset for Promptfoo, LangSmith, or Braintrust
postcode eval --samples 500 --export addresses.json
// Programmatic generation & evaluation
dataset := postcode.GenerateSyntheticDataset(postcode.GeneratorOptions{
    Count:     100,
    NoiseRate: 0.3,
})
result := postcode.EvaluateAgent(dataset, myAgentExtractor)
fmt.Printf("State Accuracy: %.2f%%, Postcode Accuracy: %.2f%%\n",
    result.StateAccuracy, result.PostcodeAccuracy)
8. Google Antigravity Plugin & Universal Agent Skill Bundle

This repository is packaged as an official Google Antigravity Plugin (plugin.json, mcp_config.json, rules/, skills/) adhering to the Antigravity Plugin Guidelines:

├── plugin.json                       # Official Antigravity manifest ($schema v1)
├── mcp_config.json                   # Auto-discovered MCP server definition
├── rules/
│   └── nipost-postcode.md            # Active rules for Nigerian postcode operations
└── skills/
    └── nipost-postcode/
        ├── SKILL.md                  # Domain workflow & self-healing procedures
        ├── references/               # Deep-dive manuals (grammar, levels, recovery)
        └── examples/                 # Runnable SDK, CLI, & MCP interaction patterns
Installing the Plugin

You can install and activate the plugin across all Antigravity surfaces:

1. Via Antigravity CLI Shell (agy plugin):

# Install directly from the GitHub repository URL
agy plugin install https://github.com/abcubed3/postcode

# Or install from a local cloned directory
agy plugin install ./

# Verify installation
agy plugin list

2. Inside an Interactive Session (/plugin): Run /plugin in the chat prompt to open the Plugins Manager, or install directly via inline command:

/plugin install https://github.com/abcubed3/postcode

3. Workspace-Level (Antigravity IDE & 2.0): To enable for a specific repository, add the plugin to your workspace's .agents/plugins/ directory:

mkdir -p .agents/plugins
git clone https://github.com/abcubed3/postcode.git .agents/plugins/nipost-postcode

4. Global Installation (All Projects & Workspaces): To enable across all projects on your workstation, clone into your global plugins directory:

mkdir -p ~/.gemini/config/plugins
git clone https://github.com/abcubed3/postcode.git ~/.gemini/config/plugins/nipost-postcode
What the Plugin Provides
  • Auto-Configured MCP Server: Automatically connects your agent to postcode mcp using mcp_config.json.
  • Cost-Aware Escalation: Enforces offline-first validation $\to$ Level 1 (Free) $\to$ Levels 2-3 (Commercial credits only when explicitly demanded).
  • Informal Address Disambiguation: 5-step SOP for extracting and synthesizing postcodes from descriptive landmarks and cross-streets.
  • Diagnostic Self-Healing: Automatically repairs typos and invalid segments via postcode.Diagnose.
  • Reference Manuals: Grammar & States, Lookup Levels, Address Disambiguation, and Error Recovery.

Examples & Demos

Ready-to-run examples and reference integrations in the examples/ directory:

Example Domain / Focus Description Execution Command
examples/address_input_web/ Web Client Component Embedding <nipost-address-input> in an HTML checkout form with real-time telemetry and FormData submission. open ./examples/address_input_web/index.html
examples/main.go Go SDK Tour End-to-end tour of local parsing, simulator, graded lookups, autocomplete, and QR generation. go run ./examples/main.go
examples/ai_agent_guardrails/ AI Agent Safety Logistics dispatcher agent with call ceilings, caching, and auto-downgrade. go run ./examples/ai_agent_guardrails/main.go
examples/ai_synthetic_eval/ AI Benchmarking Synthetic address generator and evaluation harness for LLMs and NER extractors. go run ./examples/ai_synthetic_eval/main.go
examples/wasm_node/ Edge / WebAssembly Running the zero-dependency compiled WebAssembly binary inside Node.js. node ./examples/wasm_node/demo.js

Benchmarks

Microbenchmarks measured on darwin/arm64 (Apple M2 Max) using go test -bench=. -benchmem:

BenchmarkParse-12             42424563        28.25 ns/op          0 B/op        0 allocs/op
BenchmarkFormatted-12        195061753         6.15 ns/op          0 B/op        0 allocs/op
BenchmarkResolveLocation-12   10245876       115.70 ns/op         32 B/op        4 allocs/op
BenchmarkGoogleMapsURL-12      3721462       325.20 ns/op        128 B/op        7 allocs/op
  • postcode.Parse: Fully validates, normalizes, and extracts segments with 0 B/op and 0 allocs/op in ~28 ns/op.
  • postcode.Formatted: Formats canonical hyphenated postcodes (AA-99-H77-BB-55) with 0 B/op and 0 allocs/op in ~6 ns/op.
  • postcode.ResolveLocation: Resolves building and administrative coordinates offline in ~115 ns/op.
  • postcode.GoogleMapsURL: Formats and generates universal Google Maps search URLs in ~325 ns/op.

Contributing

Contributions, bug reports, and optimizations are welcome! Please read the Contributing Guide for details on repository architecture, zero-allocation requirements, development with Taskfile, and pull request guidelines.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	// MCPProtocolVersionLatest is the modern stateless protocol revision (SEP-2575).
	MCPProtocolVersionLatest = "2026-07-28"
	// MCPProtocolVersion2025 is the intermediate 2025 specification revision.
	MCPProtocolVersion2025 = "2025-11-25"
	// MCPProtocolVersionLegacy is the original initialization-based protocol revision.
	MCPProtocolVersionLegacy = "2024-11-05"

	MCPServerName    = "postcode-mcp"
	MCPServerVersion = "1.0.0"
)

MCP Protocol Revisions & Server Metadata per MCP 2026-07-28 Specification

View Source
const (
	DefaultBaseURL = "https://api.postcode.gov.ng"
)

Variables

View Source
var (
	ErrInvalidLength = errors.New("postcode: invalid length, must be exactly 11 alphanumeric characters")
	ErrInvalidFormat = errors.New("postcode: invalid format structure")
	ErrTemporaryCode = errors.New("postcode: temporary NIPOST code (TP-...) pending survey; resolve via gateway lookup")
)
View Source
var ErrAgentBudgetExceeded = errors.New("postcode: agent call budget exceeded for session")

ErrAgentBudgetExceeded is returned when an autonomous agent reaches its configured call ceiling.

View Source
var ErrUnknownState = errors.New("postcode: unknown nigerian state code")

ErrUnknownState indicates that a postcode has a state prefix not recognized in the Nigerian state registry.

MCPSupportedVersions lists all supported protocol revisions in order of preference.

View Source
var NigerianStates = map[string]StateRecord{
	"AB": {Code: "AB", Name: "Abia", Capital: "Umuahia", Latitude: 5.5320, Longitude: 7.4860, Zone: "SOUTH EAST"},
	"AD": {Code: "AD", Name: "Adamawa", Capital: "Yola", Latitude: 9.2094, Longitude: 12.4818, Zone: "NORTH EAST"},
	"AK": {Code: "AK", Name: "Akwa Ibom", Capital: "Uyo", Latitude: 5.0377, Longitude: 7.9128, Zone: "SOUTH SOUTH"},
	"AN": {Code: "AN", Name: "Anambra", Capital: "Awka", Latitude: 6.2209, Longitude: 7.0670, Zone: "SOUTH EAST"},
	"BA": {Code: "BA", Name: "Bauchi", Capital: "Bauchi", Latitude: 10.3158, Longitude: 9.8442, Zone: "NORTH EAST"},
	"BY": {Code: "BY", Name: "Bayelsa", Capital: "Yenagoa", Latitude: 4.9267, Longitude: 6.2676, Zone: "SOUTH SOUTH"},
	"BN": {Code: "BN", Name: "Benue", Capital: "Makurdi", Latitude: 7.7304, Longitude: 8.5211, Zone: "NORTH CENTRAL"},
	"BO": {Code: "BO", Name: "Borno", Capital: "Maiduguri", Latitude: 11.8333, Longitude: 13.1500, Zone: "NORTH EAST"},
	"CR": {Code: "CR", Name: "Cross River", Capital: "Calabar", Latitude: 4.9757, Longitude: 8.3417, Zone: "SOUTH SOUTH"},
	"DE": {Code: "DE", Name: "Delta", Capital: "Asaba", Latitude: 6.1984, Longitude: 6.7329, Zone: "SOUTH SOUTH"},
	"EB": {Code: "EB", Name: "Ebonyi", Capital: "Abakaliki", Latitude: 6.3249, Longitude: 8.1137, Zone: "SOUTH EAST"},
	"ED": {Code: "ED", Name: "Edo", Capital: "Benin City", Latitude: 6.3350, Longitude: 5.6037, Zone: "SOUTH SOUTH"},
	"EK": {Code: "EK", Name: "Ekiti", Capital: "Ado Ekiti", Latitude: 7.6211, Longitude: 5.2215, Zone: "SOUTH WEST"},
	"EN": {Code: "EN", Name: "Enugu", Capital: "Enugu", Latitude: 6.4584, Longitude: 7.5464, Zone: "SOUTH EAST"},
	"FC": {Code: "FC", Name: "Federal Capital Territory", Capital: "Abuja", Latitude: 9.0579, Longitude: 7.4951, Zone: "NORTH CENTRAL"},
	"GO": {Code: "GO", Name: "Gombe", Capital: "Gombe", Latitude: 10.2897, Longitude: 11.1711, Zone: "NORTH EAST"},
	"IM": {Code: "IM", Name: "Imo", Capital: "Owerri", Latitude: 5.4858, Longitude: 7.0355, Zone: "SOUTH EAST"},
	"JI": {Code: "JI", Name: "Jigawa", Capital: "Dutse", Latitude: 11.7594, Longitude: 9.3389, Zone: "NORTH WEST"},
	"KD": {Code: "KD", Name: "Kaduna", Capital: "Kaduna", Latitude: 10.5105, Longitude: 7.4165, Zone: "NORTH WEST"},
	"KN": {Code: "KN", Name: "Kano", Capital: "Kano", Latitude: 12.0022, Longitude: 8.5920, Zone: "NORTH WEST"},
	"KT": {Code: "KT", Name: "Katsina", Capital: "Katsina", Latitude: 12.9855, Longitude: 7.6171, Zone: "NORTH WEST"},
	"KE": {Code: "KE", Name: "Kebbi", Capital: "Birnin Kebbi", Latitude: 12.4504, Longitude: 4.1999, Zone: "NORTH WEST"},
	"KO": {Code: "KO", Name: "Kogi", Capital: "Lokoja", Latitude: 7.8023, Longitude: 6.7333, Zone: "NORTH CENTRAL"},
	"KW": {Code: "KW", Name: "Kwara", Capital: "Ilorin", Latitude: 8.4966, Longitude: 4.5421, Zone: "NORTH CENTRAL"},
	"LA": {Code: "LA", Name: "Lagos", Capital: "Ikeja", Latitude: 6.6018, Longitude: 3.3515, Zone: "SOUTH WEST"},
	"NA": {Code: "NA", Name: "Nasarawa", Capital: "Lafia", Latitude: 8.4932, Longitude: 8.5153, Zone: "NORTH CENTRAL"},
	"NI": {Code: "NI", Name: "Niger", Capital: "Minna", Latitude: 9.6139, Longitude: 6.5569, Zone: "NORTH CENTRAL"},
	"OG": {Code: "OG", Name: "Ogun", Capital: "Abeokuta", Latitude: 7.1475, Longitude: 3.3619, Zone: "SOUTH WEST"},
	"ON": {Code: "ON", Name: "Ondo", Capital: "Akure", Latitude: 7.2571, Longitude: 5.2058, Zone: "SOUTH WEST"},
	"OS": {Code: "OS", Name: "Osun", Capital: "Osogbo", Latitude: 7.7827, Longitude: 4.5418, Zone: "SOUTH WEST"},
	"OY": {Code: "OY", Name: "Oyo", Capital: "Ibadan", Latitude: 7.3775, Longitude: 3.9470, Zone: "SOUTH WEST"},
	"PL": {Code: "PL", Name: "Plateau", Capital: "Jos", Latitude: 9.9271, Longitude: 8.8921, Zone: "NORTH CENTRAL"},
	"RI": {Code: "RI", Name: "Rivers", Capital: "Port Harcourt", Latitude: 4.8156, Longitude: 7.0498, Zone: "SOUTH SOUTH"},
	"SO": {Code: "SO", Name: "Sokoto", Capital: "Sokoto", Latitude: 13.0609, Longitude: 5.2476, Zone: "NORTH WEST"},
	"TA": {Code: "TA", Name: "Taraba", Capital: "Jalingo", Latitude: 8.8937, Longitude: 11.3600, Zone: "NORTH EAST"},
	"YO": {Code: "YO", Name: "Yobe", Capital: "Damaturu", Latitude: 11.7470, Longitude: 11.9608, Zone: "NORTH EAST"},
	"ZA": {Code: "ZA", Name: "Zamfara", Capital: "Gusau", Latitude: 12.1702, Longitude: 6.6592, Zone: "NORTH WEST"},
}

NigerianStates contains geographic centroids and administrative capitals for all 36 States and FCT.

Functions

func BaselineExtract added in v0.1.4

func BaselineExtract(raw string) (extractedState string, extractedPostcode string)

BaselineExtract provides an offline heuristic extractor for Nigerian addresses. It searches raw unstructured text for state names/codes and postcode patterns using the 5-stage Nigerian address disambiguation pipeline.

func BatchParseSeq

func BatchParseSeq(inputs []string) iter.Seq2[int, Result[Postcode]]

BatchParseSeq yields an iter.Seq2 iterator with slice index and Result wrapper.

func ClearDiskCache added in v0.1.9

func ClearDiskCache() error

ClearDiskCache removes all custom in-memory buildings and deletes the persistent cache file.

func Coordinates added in v0.1.1

func Coordinates(raw string) (lat, lng float64, err error)

Coordinates parses any valid 11-character Nigerian postcode string and returns its latitude and longitude coordinates.

func DefaultCachePath added in v0.1.9

func DefaultCachePath() string

DefaultCachePath returns the standard user-level cache file path: ~/.postcode/cache.json

func DistanceMeters added in v0.2.2

func DistanceMeters(codeA, codeB string) (float64, error)

DistanceMeters parses two Nigerian postcode strings and returns the Haversine distance in meters between their resolved geographic coordinates.

func ExportBuildingsJSON added in v0.1.9

func ExportBuildingsJSON(w io.Writer) error

ExportBuildingsJSON writes all cached custom building records to a JSON writer.

func FormatTemporary added in v0.2.4

func FormatTemporary(raw string) string

FormatTemporary normalizes a temporary code to an uppercase canonical representation prefixed with "TP-". For example, "tp_01_a01_01" becomes "TP-01-A01-01", and "tp-123456" becomes "TP-123456".

func GetCachePath added in v0.1.9

func GetCachePath() string

GetCachePath returns the active cache file path.

func GetCacheStats added in v0.1.9

func GetCacheStats() (int, string)

GetCacheStats returns the number of custom cached building records and the file location.

func GoogleMapsURL added in v0.1.1

func GoogleMapsURL(raw string) (string, error)

GoogleMapsURL parses any valid 11-character Nigerian postcode string and directly generates its Google Maps location URL.

func IsTemporary added in v0.2.4

func IsTemporary(raw string) bool

IsTemporary reports whether raw is a NIPOST temporary building code (e.g. "TP-...", "TP_..."). Temporary codes are minted by NIPOST widgets for unsurveyed buildings and resolve to official 11-character postcodes once field survey indexing is completed.

func IsTemporaryCode added in v0.2.4

func IsTemporaryCode(raw string) bool

IsTemporaryCode is an alias for IsTemporary.

func IsValid added in v0.1.9

func IsValid(raw string) bool

IsValid reports whether raw is a valid Nigerian postcode with a recognized state code.

func LoadBuildingsCSV added in v0.1.9

func LoadBuildingsCSV(r io.Reader) (int, error)

LoadBuildingsCSV reads building records from a CSV reader. Expected header format: postcode,latitude,longitude,address,state_code,state_name,lga_code,lga_name,zone

func LoadBuildingsJSON added in v0.1.9

func LoadBuildingsJSON(r io.Reader) (int, error)

LoadBuildingsJSON reads building records from a JSON array or object reader and stores them in cache.

func LoadDiskCache added in v0.1.9

func LoadDiskCache() error

LoadDiskCache ensures disk cache entries from ~/.postcode/cache.json (or custom path) are loaded into the offline building registry.

func LookupLegacyPrefix added in v0.2.2

func LookupLegacyPrefix(sixDigit string) (prefix string, label string, ok bool)

LookupLegacyPrefix maps a 6-digit legacy Nigerian postal code (e.g. "100001", "900001") to its corresponding modern NIPOST State-LGA prefix (e.g. "LA-11", "FC-03") and description.

func NewHandlerTransport added in v0.2.2

func NewHandlerTransport(h http.Handler) http.RoundTripper

NewHandlerTransport wraps an http.Handler as an in-memory http.RoundTripper. Ideal for deterministic, zero-network unit testing and sandboxed agent execution.

func ParseSeq

func ParseSeq(inputs []string) iter.Seq2[Postcode, error]

ParseSeq streams parsed postcodes as (Postcode, error) pairs using Go iterators.

func QRPNG added in v0.2.3

func QRPNG(raw string, opts ...QROptions) ([]byte, error)

QRPNG validates a Nigerian postcode string and returns its PNG QR code image bytes.

func QRSVG added in v0.2.3

func QRSVG(raw string, opts ...QROptions) (string, error)

QRSVG validates a Nigerian postcode string and returns its SVG QR code representation.

func QRTerminal added in v0.2.3

func QRTerminal(raw string, opts ...QROptions) (string, error)

QRTerminal validates a Nigerian postcode string and returns a compact Unicode terminal QR code.

func RegisterKnownBuilding added in v0.1.1

func RegisterKnownBuilding(rec BuildingRecord)

RegisterKnownBuilding allows applications to register custom building units into the offline geocoding cache.

func SaveDiskCache added in v0.1.9

func SaveDiskCache() error

SaveDiskCache persists all registered custom building records to the cache file.

func SetCachePath added in v0.1.9

func SetCachePath(path string) error

SetCachePath overrides the default cache file path and loads existing entries.

func Validate added in v0.1.9

func Validate(raw string) error

Validate checks that raw is a grammatically valid Nigerian postcode with a recognized Nigerian state or FCT code.

Types

type APIError

type APIError struct {
	StatusCode int            `json:"-"`
	Code       string         `json:"code"`
	Message    string         `json:"message"`
	RateLimit  *RateLimitInfo `json:"-"`
	RetryAfter time.Duration  `json:"-"`
}

APIError represents an error returned by the NIPOST Postcode API gateway.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) IsInsufficientCredits

func (e *APIError) IsInsufficientCredits() bool

func (*APIError) IsNotFound

func (e *APIError) IsNotFound() bool

func (*APIError) IsRateLimit

func (e *APIError) IsRateLimit() bool

func (*APIError) IsUnauthorized

func (e *APIError) IsUnauthorized() bool

func (*APIError) UnmarshalJSON

func (e *APIError) UnmarshalJSON(data []byte) error

UnmarshalJSON handles both the official nested envelope {"error": {"code": ..., "message": ...}} and flat payloads {"code": ..., "message": ...}.

type AddressExtraction added in v0.2.2

type AddressExtraction struct {
	Raw             string  `json:"raw"`
	Postcode        string  `json:"postcode,omitempty"`         // Canonical hyphenated 11-char postcode (if found/repaired)
	PostcodeValid   bool    `json:"postcode_valid"`             // True if Postcode is a valid 11-char NIPOST code
	LegacyPostcode  string  `json:"legacy_postcode,omitempty"`  // Deprecated 6-digit numeric postal code (if found)
	SuggestedPrefix string  `json:"suggested_prefix,omitempty"` // Resolved State or State-LGA prefix (e.g. "LA-08", "FC-03")
	StateCode       string  `json:"state_code,omitempty"`       // 2-letter NIPOST state code (e.g. "LA", "FC")
	StateName       string  `json:"state_name,omitempty"`       // Full state name (e.g. "Lagos", "Federal Capital Territory")
	LGACode         string  `json:"lga_code,omitempty"`         // 2-digit LGA code (e.g. "08", "11")
	LGAName         string  `json:"lga_name,omitempty"`         // Full LGA name (e.g. "Eti-Osa", "Ikeja")
	Locality        string  `json:"locality,omitempty"`         // Neighborhood, district, or estate (e.g. "Lekki Phase 1", "Wuse II")
	Street          string  `json:"street,omitempty"`           // Street or house number component
	Landmark        string  `json:"landmark,omitempty"`         // Landmark anchor (e.g. "Beside First Bank", "Opposite Central Mosque")
	Latitude        float64 `json:"latitude,omitempty"`         // Best-available resolved latitude
	Longitude       float64 `json:"longitude,omitempty"`        // Best-available resolved longitude
	Confidence      string  `json:"confidence"`                 // "high", "medium", or "low"
}

AddressExtraction holds the structured entities extracted from an unstructured Nigerian address using the 5-stage NIPOST address disambiguation pipeline.

func ExtractAddress added in v0.2.2

func ExtractAddress(raw string) AddressExtraction

ExtractAddress runs the 5-stage Nigerian address disambiguation pipeline on an unstructured address string, extracting postcodes (modern or legacy 6-digit), administrative state/LGA, neighborhood locality, street, landmark anchors, and resolved coordinates.

type AdministrativeAddress

type AdministrativeAddress struct {
	State        string `json:"state,omitempty"`
	StateName    string `json:"state_name,omitempty"`
	LGA          string `json:"lga,omitempty"`
	LGAName      string `json:"lga_name,omitempty"`
	District     string `json:"district,omitempty"`
	DistrictName string `json:"district_name,omitempty"`
	Area         string `json:"area,omitempty"`
	AreaName     string `json:"area_name,omitempty"`
	Unit         string `json:"unit,omitempty"`
	Zone         string `json:"zone,omitempty"`
	LocalityName string `json:"locality_name,omitempty"`
}

AdministrativeAddress details administrative boundaries up to area level (L2+).

type AgentDispatcher added in v0.1.4

type AgentDispatcher struct {
	// contains filtered or unexported fields
}

AgentDispatcher handles incoming tool executions from AI agent frameworks.

func NewAgentDispatcher added in v0.1.4

func NewAgentDispatcher(client *Client) *AgentDispatcher

NewAgentDispatcher creates a dispatcher. If client is nil, the dispatcher operates in offline mode, servicing local validation, diagnostics, state listing, and reference geocoding.

func (*AgentDispatcher) Dispatch added in v0.1.4

func (d *AgentDispatcher) Dispatch(ctx context.Context, name string, argsJSON []byte) (any, error)

Dispatch executes the specified tool by name with unparsed JSON arguments.

func (*AgentDispatcher) DispatchString added in v0.1.4

func (d *AgentDispatcher) DispatchString(ctx context.Context, name string, argsJSON []byte) (string, error)

DispatchString executes the tool and returns the result serialized as a JSON string, safe for direct injection into LLM response messages.

func (*AgentDispatcher) Tools added in v0.1.4

func (d *AgentDispatcher) Tools() []ToolDef

Tools returns the list of available tools.

type AgentGuardConfig added in v0.1.4

type AgentGuardConfig struct {
	// MaxCommercialCallsPerRun limits the number of paid/commercial calls (Level 2/3) in a session.
	// 0 means unlimited.
	MaxCommercialCallsPerRun int

	// MaxTotalCallsPerRun limits the total number of all gateway calls in a session.
	// 0 means unlimited.
	MaxTotalCallsPerRun int

	// AutoDowngradeToLevel1 automatically downgrades Level 2/3 commercial requests to
	// free Level 1 validity checks if commercial limits are reached or insufficient credits occur.
	AutoDowngradeToLevel1 bool

	// AutoFallbackToOffline automatically falls back to offline reference geocoding
	// if gateway calls fail with 429 (rate limit) or 402 (insufficient credits).
	AutoFallbackToOffline bool
}

AgentGuardConfig configures autonomous guardrails for AI agents to prevent runaway costs and infinite loops during tool-calling execution.

type AgentGuardMetrics added in v0.1.4

type AgentGuardMetrics struct {
	TotalCalls       int64 `json:"total_calls"`
	CommercialCalls  int64 `json:"commercial_calls"`
	DowngradedCalls  int64 `json:"downgraded_calls"`
	OfflineFallbacks int64 `json:"offline_fallbacks"`
}

AgentGuardMetrics reports live telemetry for calls executed under agent guardrails.

type AssembledPostcode

type AssembledPostcode struct {
	Postcode string `json:"postcode"` // Canonical: "EK-01-A03-FK-01"
	Display  string `json:"display"`  // Spaced: "EK 01 A03 FK 01"
	Compact  string `json:"compact"`  // Compact: "EK01A03FK01"
}

AssembledPostcode represents the output of POST /v1/assembly/assemble.

func AssembleSegments added in v0.1.9

func AssembleSegments(segs Segments) (AssembledPostcode, error)

AssembleSegments validates and combines segments into an AssembledPostcode locally without network calls.

type AutocompleteResponse

type AutocompleteResponse struct {
	Segment     string                   `json:"segment"`
	Suggestions []AutocompleteSuggestion `json:"suggestions"`
}

AutocompleteResponse contains suggestions for the currently active segment.

type AutocompleteSuggestion

type AutocompleteSuggestion struct {
	Code  string `json:"code"`
	Label string `json:"label"`
}

AutocompleteSuggestion represents an individual suggestion returned by autocomplete.

type BuildingRecord added in v0.1.1

type BuildingRecord struct {
	Postcode  string // Canonical (e.g. "EK-01-A03-FK-01") or Compact ("EK01A03FK01")
	Latitude  float64
	Longitude float64
	Address   string
	StateCode string
	StateName string
	LGACode   string
	LGAName   string
	Zone      string
}

BuildingRecord holds high-precision point geometry for a specific building unit.

func AllBuildingRecords added in v0.1.9

func AllBuildingRecords() []BuildingRecord

AllBuildingRecords returns all known building benchmark records and loaded custom cached buildings.

type Cache added in v0.1.4

type Cache interface {
	Get(key string) (any, bool)
	Set(key string, val any, ttl time.Duration)
	Delete(key string)
	Clear()
	Len() int
}

Cache defines the interface for caching postcode gateway lookups and resolutions. Enables idempotency and prevents redundant paid API calls during AI agent loops.

type ChainGeocoder added in v0.1.9

type ChainGeocoder struct {
	Geocoders []Geocoder
}

ChainGeocoder attempts each provided Geocoder in sequence until one succeeds.

func NewChainGeocoder added in v0.1.9

func NewChainGeocoder(geocoders ...Geocoder) *ChainGeocoder

NewChainGeocoder creates a composite geocoder.

func (*ChainGeocoder) Geocode added in v0.1.9

func (c *ChainGeocoder) Geocode(ctx context.Context, query string) (float64, float64, error)

Geocode executes the geocoder chain in priority order.

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(opts ...ClientOption) (*Client, error)

NewClient instantiates a production-tuned HTTP client.

func (*Client) AgentMetrics added in v0.1.4

func (c *Client) AgentMetrics() AgentGuardMetrics

AgentMetrics returns live telemetry metrics for calls executed under agent guardrails.

func (*Client) Assemble

func (c *Client) Assemble(ctx context.Context, segs Segments) (*AssembledPostcode, error)

Assemble builds canonical, display, and compact postcodes from individual segments. Numeric segments are zero-filled (e.g. "1" -> "01") and alpha segments are converted to uppercase.

func (*Client) Autocomplete

func (c *Client) Autocomplete(ctx context.Context, query string) (*AutocompleteResponse, error)

Autocomplete retrieves segment-aware suggestions for a partial postcode input (e.g. "EK 01 A").

func (*Client) Disassemble

func (c *Client) Disassemble(ctx context.Context, code string) (*Segments, error)

Disassemble decomposes a postcode string into its 5 structural segments.

func (*Client) GenerateQR added in v0.2.3

func (c *Client) GenerateQR(ctx context.Context, code string, opts ...QROptions) (*QRCode, error)

GenerateQR resolves the postcode location via the NIPOST gateway (falling back to offline reference data) and generates a QRCode enriched with any available street address and point coordinates.

func (*Client) Health added in v0.1.9

func (c *Client) Health(ctx context.Context) error

Health checks the operational status of the gateway via GET /healthz. Returns nil if the gateway responds with HTTP 200 OK.

func (*Client) Lookup

func (c *Client) Lookup(ctx context.Context, code string, level LookupLevel) (*LookupResponse, error)

Lookup queries the gateway for detailed attributes of a postcode. Level defaults to Level1 (free) if 0 or unspecified.

func (*Client) Nearby

func (c *Client) Nearby(ctx context.Context, params NearbyParams) (*NearbyResponse, error)

Nearby searches for active postcode units within a radius (default 300m) of a coordinate or reference postcode.

func (*Client) NearbyPostcode added in v0.1.9

func (c *Client) NearbyPostcode(ctx context.Context, code string, radiusM float64) (*NearbyResponse, error)

NearbyPostcode searches for active units within a radius of a reference postcode.

func (*Client) RateLimit

func (c *Client) RateLimit() *RateLimitInfo

RateLimit returns the most recent rate limit quotas reported by the NIPOST gateway, or nil if no request has yet returned rate limit headers.

func (*Client) ReferenceAreas added in v0.1.9

func (c *Client) ReferenceAreas(ctx context.Context, state, lga, district string) ([]NamedCode, error)

ReferenceAreas queries all postal area codes under a state, LGA, and district (GET /v1/reference/areas?state=...&lga=...&district=...). Areas have no human name (code only). Free, no credits consumed.

func (*Client) ReferenceDistricts added in v0.1.9

func (c *Client) ReferenceDistricts(ctx context.Context, state, lga string) ([]NamedCode, error)

ReferenceDistricts queries all postal district codes under a state and LGA (GET /v1/reference/districts?state=...&lga=...). Districts have no human name (code only). Free, no credits consumed.

func (*Client) ReferenceLGAs added in v0.1.9

func (c *Client) ReferenceLGAs(ctx context.Context, state string) ([]NamedCode, error)

ReferenceLGAs queries all Local Government Areas for a given state code (GET /v1/reference/lgas?state=...). This endpoint is free and consumes no credits.

func (*Client) ReferenceStates added in v0.1.9

func (c *Client) ReferenceStates(ctx context.Context) ([]NamedCode, error)

ReferenceStates queries the official precomputed catalog for all 37 Nigerian states (GET /v1/reference/states). This endpoint is free and consumes no credits.

func (*Client) ResolveLocation added in v0.1.1

func (c *Client) ResolveLocation(ctx context.Context, code string) (*Location, error)

ResolveLocation resolves rich geographic metadata for a postcode. It enriches the location via the NIPOST gateway at the highest available commercial tier (Level 3 down to Level 1) if configured, adopting any explicit point geometry returned by the gateway (e.g. enterprise accounts or simulator). If point geometry is not present, it leverages the SDK's high-precision offline reference geocoding engine (building benchmarks -> LGA centroid -> State centroid) to guarantee reliable coordinates and mapping URLs.

func (*Client) Reverse

func (c *Client) Reverse(ctx context.Context, params ReverseParams) (*ReverseResponse, error)

Reverse resolves a geographic coordinate to the nearest active postcode unit.

func (*Client) ReverseCoordinates added in v0.1.9

func (c *Client) ReverseCoordinates(ctx context.Context, lat, lng, maxDistanceM float64) (*ReverseResponse, error)

ReverseCoordinates resolves geographic coordinates directly to the nearest active postcode unit.

type ClientOption

type ClientOption func(*Client)

func WithAPIKey

func WithAPIKey(key string) ClientOption

WithAPIKey sets the NIPOST API key sent in the X-API-Key header. An apiKey is optional for public endpoints (L1 lookup, search, assembly).

func WithAgentGuard added in v0.1.4

func WithAgentGuard(cfg AgentGuardConfig) ClientOption

WithAgentGuard configures budget and safety ceilings for autonomous agents.

func WithBaseTransport

func WithBaseTransport(rt http.RoundTripper) ClientOption

WithBaseTransport sets the underlying round tripper underneath the retry middleware.

func WithBaseURL

func WithBaseURL(customURL string) ClientOption

WithBaseURL overrides the default gateway URL (e.g. for testing with httptest.Server).

func WithCache added in v0.1.4

func WithCache(cache Cache) ClientOption

WithCache configures a custom cache implementation for the client.

func WithDefaultCache added in v0.1.4

func WithDefaultCache() ClientOption

WithDefaultCache configures a standard in-memory TTL cache (1000 items, 30m TTL).

func WithGeocoder added in v0.1.9

func WithGeocoder(g Geocoder) ClientOption

WithGeocoder configures an external geocoder for resolving textual address locations into coordinates.

func WithGoogleMapsKey added in v0.1.9

func WithGoogleMapsKey(key string) ClientOption

WithGoogleMapsKey configures Google Maps Geocoding API key for the client's geocoding chain.

func WithHandler added in v0.2.2

func WithHandler(h http.Handler) ClientOption

WithHandler configures the client to execute HTTP requests directly in-memory against h (such as simulator.NewHandler()) without opening TCP sockets, while preserving retry and telemetry middleware.

func WithRateLimitRetry

func WithRateLimitRetry(enabled bool, maxDelay time.Duration) ClientOption

WithRateLimitRetry configures automatic retry on 429 Too Many Requests responses. If enabled is false, 429 responses will not be retried. maxDelay sets the maximum duration the client is willing to sleep for a rate limit cooldown.

func WithRetryConfig

func WithRetryConfig(cfg RetryConfig) ClientOption

WithRetryConfig configures backoff, jitter, and retry limits.

func WithTelemetry

func WithTelemetry(t Telemetry) ClientOption

WithTelemetry registers custom observability hooks.

func WithTimeout

func WithTimeout(d time.Duration) ClientOption

WithTimeout sets the overall client request timeout.

func WithTransport

func WithTransport(rt http.RoundTripper) ClientOption

WithTransport explicitly overrides the entire round tripper stack (bypassing retries).

type DiagnosticReport added in v0.1.4

type DiagnosticReport struct {
	Input         string             `json:"input"`
	Valid         bool               `json:"valid"`
	IsTemporary   bool               `json:"is_temporary,omitempty"`  // True if input is a temporary NIPOST building code (TP-...)
	Normalized    string             `json:"normalized,omitempty"`    // Hyphenated canonical form if valid or recoverable
	SuggestedFix  string             `json:"suggested_fix,omitempty"` // Auto-repaired canonical postcode when deterministically recoverable
	CleanLength   int                `json:"clean_length"`            // Number of alphanumeric characters found
	Diagnoses     []SegmentDiagnosis `json:"diagnoses,omitempty"`
	ActionableTip string             `json:"actionable_tip,omitempty"` // Summary recommendation for the caller/agent
	FormatScore   float64            `json:"format_score"`             // Structural conformance score (0.0 to 100.0)
}

DiagnosticReport provides deep structural and administrative analysis of a postcode string. Designed for humans and autonomous AI agents to self-correct invalid inputs.

func Diagnose added in v0.1.4

func Diagnose(raw string) DiagnosticReport

Diagnose inspects a candidate postcode string, performing exhaustive grammar, structural, and administrative checks. Returns an actionable DiagnosticReport that AI agents can use to understand the error and formulate corrections.

type EvalResult added in v0.1.4

type EvalResult struct {
	TotalSamples     int     `json:"total_samples"`
	StateAccuracy    float64 `json:"state_accuracy"`
	PostcodeAccuracy float64 `json:"postcode_accuracy"`
	ValidRate        float64 `json:"valid_rate"`
	AvgFormatScore   float64 `json:"avg_format_score"`
	DurationMs       int64   `json:"duration_ms"`
}

EvalResult contains accuracy and performance scores from evaluating an agent on synthetic data.

func EvaluateAgent added in v0.1.4

func EvaluateAgent(dataset []SyntheticAddress, extractor func(raw string) (string, string)) EvalResult

EvaluateAgent runs an extraction function against a synthetic dataset and tallies accuracy metrics.

type GeneratorOptions added in v0.1.4

type GeneratorOptions struct {
	Count     int     `json:"count"`
	Seed      uint64  `json:"seed,omitempty"`
	NoiseRate float64 `json:"noise_rate,omitempty"` // 0.0 to 1.0 (default 0.3)
}

GeneratorOptions configures the synthetic Nigerian address generator.

type Geocoder added in v0.1.9

type Geocoder interface {
	Geocode(ctx context.Context, query string) (lat, lng float64, err error)
}

Geocoder resolves a geographic textual address or landmark query to latitude and longitude coordinates.

func DefaultGeocoder added in v0.1.9

func DefaultGeocoder(googleMapsKey string, httpClient *http.Client) Geocoder

DefaultGeocoder returns a multi-tier geocoder incorporating Google Maps v4 (if key provided) and OSM Nominatim.

type GoogleMapsGeocoder added in v0.1.9

type GoogleMapsGeocoder struct {
	APIKey     string
	BaseURL    string // Defaults to "https://geocode.googleapis.com"
	HTTPClient *http.Client
}

GoogleMapsGeocoder resolves coordinates using the modern Google Maps Geocoding API v4 (https://geocode.googleapis.com/v4/geocode/address), with automatic fallback to v3.

func NewGoogleMapsGeocoder added in v0.1.9

func NewGoogleMapsGeocoder(apiKey string, client *http.Client) *GoogleMapsGeocoder

NewGoogleMapsGeocoder creates a Geocoder backed by Google Maps Geocoding API v4.

func (*GoogleMapsGeocoder) Geocode added in v0.1.9

func (g *GoogleMapsGeocoder) Geocode(ctx context.Context, query string) (float64, float64, error)

Geocode queries the Google Maps Geocoding API v4 for the provided Nigerian address query.

type LGARecord added in v0.1.1

type LGARecord struct {
	StateCode string
	LGACode   string
	Name      string
	Latitude  float64
	Longitude float64
}

LGARecord holds reference coordinates and names for an LGA within a state.

func LookupLGA added in v0.2.2

func LookupLGA(state, query string) (LGARecord, bool)

LookupLGA resolves an LGA record within a state by its 2-digit code (e.g. "11", "01", "1") or name (e.g. "Ikeja", "Eti-Osa", "Eti Osa", "AMAC"). If state is empty, searches across all states.

func NearestLGA added in v0.2.5

func NearestLGA(lat, lng float64) (LGARecord, float64, bool)

NearestLGA finds the single closest Nigerian LGA centroid to the given (lat, lng) coordinates.

type Location added in v0.1.1

type Location struct {
	Postcode  string    `json:"postcode"`           // Canonical hyphenated: "EK-01-A03-FK-01"
	Compact   string    `json:"compact"`            // Compact 11-char: "EK01A03FK01"
	Latitude  float64   `json:"latitude"`           // Geographic latitude (-90 to +90)
	Longitude float64   `json:"longitude"`          // Geographic longitude (-180 to +180)
	Address   string    `json:"address,omitempty"`  // Known street/building address if available
	StateCode string    `json:"state_code"`         // 2-letter state code (e.g. "EK")
	StateName string    `json:"state_name"`         // Full state name (e.g. "Ekiti")
	LGACode   string    `json:"lga_code,omitempty"` // 2-digit LGA code (e.g. "01")
	LGAName   string    `json:"lga_name,omitempty"` // Full LGA name (e.g. "Ado Ekiti")
	Zone      string    `json:"zone,omitempty"`     // Geopolitical zone (e.g. "SOUTH WEST")
	Precision Precision `json:"precision"`          // Coordinate precision tier (placed at end to minimize struct alignment padding)
}

Location encapsulates geographic coordinates, address descriptors, and mapping URLs.

func ResolveLocation added in v0.1.1

func ResolveLocation(raw string) (Location, error)

ResolveLocation parses any valid 11-character Nigerian postcode string (compact, hyphenated, or spaced) and returns its resolved geographic Location. Uses local administrative reference data without requiring network calls.

func (Location) AppleMapsURL added in v0.1.1

func (l Location) AppleMapsURL() string

AppleMapsURL returns an Apple Maps URL for iOS/macOS devices. Format: https://maps.apple.com/?ll=lat,lng&q=...

func (Location) Coordinates added in v0.1.1

func (l Location) Coordinates() (lat, lng float64)

Coordinates returns the latitude and longitude as a float64 pair.

func (Location) DistanceTo added in v0.2.2

func (l Location) DistanceTo(other Location) float64

DistanceTo returns the Haversine distance in meters between this location and other.

func (Location) GeoURI added in v0.2.3

func (l Location) GeoURI() string

GeoURI returns an RFC 5870 geo: URI for this location (e.g. "geo:7.621100,5.221500?q=EK-01-A03-FK-01").

func (Location) GoogleMapsDirectionsURL added in v0.1.1

func (l Location) GoogleMapsDirectionsURL() string

GoogleMapsDirectionsURL returns a Google Maps navigation/directions URL to this location. Format: https://www.google.com/maps/dir/?api=1&destination=lat,lng

func (Location) GoogleMapsURL added in v0.1.1

func (l Location) GoogleMapsURL() string

GoogleMapsURL returns a standard, universal Google Maps search URL with exact coordinates when Precision is Building, or a rich search query for coarse (LGA/State) locations. Format: https://www.google.com/maps/search/?api=1&query=...

func (Location) IsZero added in v0.1.1

func (l Location) IsZero() bool

IsZero reports whether the location represents a zero/uninitialized value.

func (Location) OpenStreetMapURL added in v0.1.1

func (l Location) OpenStreetMapURL() string

OpenStreetMapURL returns an OpenStreetMap pin URL.

func (Location) PointGeometry added in v0.2.2

func (l Location) PointGeometry() PointGeometry

PointGeometry returns a GeoJSON PointGeometry ([longitude, latitude]) for this location.

func (Location) QR added in v0.2.3

func (l Location) QR(opts ...QROptions) (*QRCode, error)

QR generates a QRCode from a resolved Location, preserving any enriched address and coordinate data.

func (Location) SearchQuery added in v0.1.1

func (l Location) SearchQuery() string

SearchQuery produces a descriptive location query string suitable for search engines (e.g. "Ado Ekiti, Ekiti State, Nigeria" or "Abuja Municipal, Federal Capital Territory, Nigeria").

type LookupLevel

type LookupLevel int

LookupLevel specifies the depth of attributes returned by a lookup request. Per official NIPOST documentation (docs.postcode.gov.ng/concepts/lookup-levels): Lookup responses are graded into 3 cumulative levels (L1–L3):

  • Level 1 (Free, Public): Validity status (valid: true/false) and canonical postcode.
  • Level 2 (Commercial): Administrative address (state, LGA, district, area) and street address.
  • Level 3 (Commercial): Full building use status (residential, commercial, mixed, etc.) and all lower levels.

Standard commercial API keys are strictly capped at Level 3 (LevelMax). Attempting to request a level higher than your organization's granted quota yields a 403 Forbidden ("key lacks scope or level").

Note on Level 4 & Level 5: In raw backend OpenAPI schemas (docs.postcode.gov.ng/api-reference/graded-postcode-lookup-levels-1–5-cumulative), levels 4 and 5 represent unreleased or restricted government tiers:

  • Level 4: other_building_info (unreleased building metadata)
  • Level 5: point_geometry (restricted government GIS coordinates)

Standard commercial keys do NOT have access to L4/L5. The SDK defines Level4 and Level5 for backward compatibility and private enterprise mock environments.

const (
	LevelDefault LookupLevel = 0 // Default to Level 1
	Level1       LookupLevel = 1 // Free: Validity check and canonical postcode
	Level2       LookupLevel = 2 // Commercial: Administrative boundaries and street address
	Level3       LookupLevel = 3 // Commercial: Building use status and unit metadata (Official Maximum)
	LevelMax     LookupLevel = 3 // Standard commercial maximum level (L3)

	// Restricted / Internal tiers (not accessible with standard API keys):
	Level4 LookupLevel = 4 // Internal/Enterprise: Additional building info
	Level5 LookupLevel = 5 // Restricted: High-precision point geometry (requires special government grant)
)

type LookupResponse

type LookupResponse struct {
	Postcode              string                 `json:"postcode"`
	Valid                 bool                   `json:"valid"`
	Temporary             bool                   `json:"temporary,omitempty"`
	AdministrativeAddress *AdministrativeAddress `json:"administrative_address,omitempty"`
	RecentHouseAddress    *RecentHouseAddress    `json:"recent_house_address,omitempty"`
	BuildingUseStatus     string                 `json:"building_use_status,omitempty"`
	OtherBuildingInfo     map[string]any         `json:"other_building_info,omitempty"`
	PointGeometry         *PointGeometry         `json:"point_geometry,omitempty"`
}

LookupResponse represents the graded attributes returned by GET /v1/lookup.

type MCPOption added in v0.1.4

type MCPOption func(*MCPServer)

MCPOption configures an MCPServer instance.

func WithMCPLogger added in v0.1.4

func WithMCPLogger(logger *log.Logger) MCPOption

WithMCPLogger specifies a logger for internal MCP diagnostics (must write to stderr).

type MCPServer added in v0.1.4

type MCPServer struct {
	// contains filtered or unexported fields
}

MCPServer implements a dual-era Model Context Protocol (MCP) server conforming to the MCP 2026-07-28 specification, with backward compatibility for legacy clients. Operates statelessly over stdio or any streaming io.ReadWriter.

func NewMCPServer added in v0.1.4

func NewMCPServer(client *Client, opts ...MCPOption) *MCPServer

NewMCPServer creates a ready-to-run MCP server. If client is nil, the server operates in offline mode, servicing local validation, diagnostics, and reference geocoding.

func (*MCPServer) Serve added in v0.1.4

func (s *MCPServer) Serve(in io.Reader, out io.Writer) error

Serve reads JSON-RPC 2.0 messages from in, executes corresponding MCP methods, and writes newline-delimited JSON-RPC responses to out.

type MemoryCache added in v0.1.4

type MemoryCache struct {
	// contains filtered or unexported fields
}

MemoryCache provides a thread-safe, in-memory TTL cache with size bounding.

func NewMemoryCache added in v0.1.4

func NewMemoryCache(maxEntries int, defaultTTL time.Duration) *MemoryCache

NewMemoryCache creates a memory cache. If maxEntries <= 0, defaults to 1000 entries. If defaultTTL <= 0, defaults to 30 minutes.

func (*MemoryCache) Clear added in v0.1.4

func (c *MemoryCache) Clear()

Clear flushes all entries from the cache.

func (*MemoryCache) Delete added in v0.1.4

func (c *MemoryCache) Delete(key string)

Delete removes a specific key from the cache.

func (*MemoryCache) Get added in v0.1.4

func (c *MemoryCache) Get(key string) (any, bool)

Get retrieves an item from the cache if present and unexpired.

func (*MemoryCache) Len added in v0.1.4

func (c *MemoryCache) Len() int

Len returns the current count of items in the cache.

func (*MemoryCache) Set added in v0.1.4

func (c *MemoryCache) Set(key string, val any, ttl time.Duration)

Set stores a value in the cache with an expiration duration. If ttl <= 0, the cache's defaultTTL is used.

type MetricsRecorder

type MetricsRecorder interface {
	RecordDuration(ctx context.Context, method, endpoint string, statusCode int, duration time.Duration)
	AddInflight(ctx context.Context, delta int64)
}

MetricsRecorder defines standard metrics contracts for client operations.

type NamedCode added in v0.1.9

type NamedCode struct {
	Code string `json:"code"`
	Name string `json:"name,omitempty"`
}

NamedCode represents a geographic administrative unit code with an optional human name. For states and LGAs, Name is populated. For districts and areas, Name is omitted or empty.

func StateLGAs added in v0.1.9

func StateLGAs(state string) []NamedCode

StateLGAs returns all reference LGAs for a given state code from the built-in reference dataset.

type NearbyParams

type NearbyParams struct {
	Postcode  string  `json:"postcode,omitempty"` // Optional reference postcode (auto-resolves centroid if coordinates are 0)
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
	RadiusM   float64 `json:"radius_m"` // Search radius in meters (default 300, max 300)
}

NearbyParams configures location search around a geographic coordinate or reference postcode.

type NearbyResponse

type NearbyResponse struct {
	Results []NearbyUnit `json:"results"`
}

NearbyResponse contains location units within the requested radius.

func (*NearbyResponse) UnmarshalJSON added in v0.1.9

func (r *NearbyResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON supports both a direct slice of units `[...]` (returned by live NIPOST Gateway) and an object payload `{"results": [...]}` (returned by simulators/proxies).

type NearbyUnit

type NearbyUnit struct {
	Postcode     string  `json:"postcode"`
	Display      string  `json:"display"`
	DistanceM    float64 `json:"distance_m"`
	Confidence   string  `json:"confidence"`
	StateName    string  `json:"state_name,omitempty"`
	LGAName      string  `json:"lga_name,omitempty"`
	LocalityName string  `json:"locality_name,omitempty"`
	Address      string  `json:"address,omitempty"`
}

NearbyUnit represents a building unit found near a coordinate.

func SearchNearbyBuildingsOffline added in v0.1.9

func SearchNearbyBuildingsOffline(lat, lng, radiusM float64) []NearbyUnit

SearchNearbyBuildingsOffline searches local embedded and cached buildings within radiusM meters of (lat, lng).

type NominatimGeocoder added in v0.1.9

type NominatimGeocoder struct {
	UserAgent  string
	BaseURL    string // Defaults to "https://nominatim.openstreetmap.org"
	HTTPClient *http.Client
}

NominatimGeocoder resolves coordinates using OpenStreetMap Nominatim with country restricted to Nigeria.

func NewNominatimGeocoder added in v0.1.9

func NewNominatimGeocoder(userAgent string, client *http.Client) *NominatimGeocoder

NewNominatimGeocoder creates a Geocoder backed by OpenStreetMap Nominatim.

func (*NominatimGeocoder) Geocode added in v0.1.9

func (n *NominatimGeocoder) Geocode(ctx context.Context, query string) (float64, float64, error)

Geocode queries the OSM Nominatim API for the address query in Nigeria.

type PointGeometry

type PointGeometry struct {
	Type        string    `json:"type,omitempty"`
	Coordinates []float64 `json:"coordinates,omitempty"` // [lng, lat]
}

PointGeometry represents GeoJSON point coordinates (L5).

type Postcode

type Postcode struct {
	// contains filtered or unexported fields
}

Postcode represents an immutable, validated 11-byte Nigerian postcode. Backed by a fixed byte array to ensure zero heap escapes.

func MustParse added in v0.2.2

func MustParse(raw string) Postcode

MustParse is like Parse but panics if raw cannot be parsed as a valid 11-character postcode. Intended for package-level test fixtures and static initialization.

func Parse

func Parse(raw string) (Postcode, error)

Parse extracts and validates segments with 0 heap allocations.

func Repair added in v0.2.2

func Repair(raw string) (Postcode, error)

Repair attempts to deterministically self-heal a postcode string, returning a ValidationError if unrecoverable.

func TryRepair added in v0.2.2

func TryRepair(raw string) (Postcode, bool)

TryRepair attempts to deterministically self-heal a malformed or noisy Nigerian postcode string. It handles:

  • Single-digit unpadded LGA or BuildingUnit segments in delimited strings (e.g. "EK-1-A03-FK-1" -> "EK-01-A03-FK-01")
  • Zero-floor violations ("00" -> "01" in LGA or BuildingUnit)
  • Common state code aliases ("BE" -> "BN" for Benue)
  • OCR lookalikes in digit/alpha segments ('O' <-> '0', 'I'/'L' <-> '1', 'S' <-> '5', 'B' <-> '8')
  • Surrounding noise or punctuation (e.g. "(LA-11-W06-TC-10)")

Returns the repaired Postcode and true only if the resulting candidate passes full Validate().

func (Postcode) AppendFormatted added in v0.2.2

func (p Postcode) AppendFormatted(b []byte) []byte

AppendFormatted appends the canonical hyphenated representation (e.g. "EK-01-A03-FK-01") to b with 0 heap allocations when b has sufficient capacity (15 bytes).

func (Postcode) AppendText

func (p Postcode) AppendText(b []byte) ([]byte, error)

AppendText implements encoding.TextAppender for zero-alloc formatting. Outputs the spaced format: "AA 99 H77 BB 55".

func (Postcode) Area

func (p Postcode) Area() string

func (Postcode) AreaPrefix added in v0.2.3

func (p Postcode) AreaPrefix() string

AreaPrefix returns the 4-segment Area-level prefix (e.g. "EK-01-A03-FK") without the building unit.

func (Postcode) BuildingUnit

func (p Postcode) BuildingUnit() string

func (Postcode) Compact added in v0.1.4

func (p Postcode) Compact() string

Compact returns the compact 11-character representation without delimiters (e.g. "EK01A03FK01"). Alias for Raw().

func (Postcode) Compare added in v0.2.2

func (p Postcode) Compare(other Postcode) int

Compare returns an integer comparing two postcodes lexicographically: 0 if p == other, -1 if p < other, and +1 if p > other. Suitable for use with slices.SortFunc.

func (Postcode) Coordinates added in v0.1.1

func (p Postcode) Coordinates() (lat, lng float64)

Coordinates returns the latitude and longitude coordinates for this postcode.

func (Postcode) Disassemble added in v0.2.0

func (p Postcode) Disassemble() Segments

Disassemble returns the 5 constituent administrative segments of the postcode. Alias for Segments().

func (Postcode) DistanceTo added in v0.2.2

func (p Postcode) DistanceTo(other Postcode) float64

DistanceTo returns the Haversine distance in meters between this postcode and other.

func (Postcode) District

func (p Postcode) District() string

func (Postcode) Equal added in v0.2.2

func (p Postcode) Equal(other Postcode) bool

Equal reports whether p and other represent the same 11-character postcode.

func (Postcode) Formatted

func (p Postcode) Formatted() string

Formatted returns the canonical hyphenated representation (e.g. "EK-01-A03-FK-01").

func (Postcode) GoogleMapsURL added in v0.1.1

func (p Postcode) GoogleMapsURL() string

GoogleMapsURL returns a direct Google Maps search URL with exact coordinates.

func (Postcode) IsZero

func (p Postcode) IsZero() bool

func (Postcode) LGA

func (p Postcode) LGA() string

func (Postcode) Location added in v0.1.1

func (p Postcode) Location() Location

Location returns the resolved geographic location and mapping URLs for this postcode. Resolves offline via built-in administrative and building registries without network calls.

func (Postcode) MarshalText

func (p Postcode) MarshalText() ([]byte, error)

func (Postcode) Masked added in v0.2.3

func (p Postcode) Masked() string

Masked returns the NDPA data-minimized representation of the postcode (e.g. "EK-01-A03-FK-**"), preserving State, LGA, District, and Area for routing and analytics while masking the exact building unit ("UU") to protect residential privacy under the Nigeria Data Protection Act (NDPA).

func (Postcode) QR added in v0.2.3

func (p Postcode) QR(opts ...QROptions) (*QRCode, error)

QR generates a QRCode for this Postcode. Returns an error if the postcode is zero or has an unrecognized Nigerian state code.

func (Postcode) QRPNG added in v0.2.3

func (p Postcode) QRPNG(opts ...QROptions) ([]byte, error)

QRPNG returns PNG bytes of the QR code for this Postcode.

func (Postcode) QRSVG added in v0.2.3

func (p Postcode) QRSVG(opts ...QROptions) (string, error)

QRSVG returns an SVG string of the QR code for this Postcode.

func (Postcode) QRTerminal added in v0.2.3

func (p Postcode) QRTerminal(opts ...QROptions) (string, error)

QRTerminal returns a compact Unicode terminal representation of the QR code for this Postcode.

func (Postcode) Raw

func (p Postcode) Raw() string

Raw returns the compact 11-character representation (e.g. "EK01A03FK01").

func (*Postcode) Scan added in v0.2.2

func (p *Postcode) Scan(src any) error

Scan implements sql.Scanner, accepting string, []byte, or nil database values.

func (Postcode) Segments added in v0.2.0

func (p Postcode) Segments() Segments

Segments returns the 5 constituent administrative segments of the postcode.

func (Postcode) Slug added in v0.2.2

func (p Postcode) Slug() string

Slug returns the URL-friendly lowercase hyphenated representation (e.g. "ek-01-a03-fk-01").

func (Postcode) State

func (p Postcode) State() string

Safe segment accessors.

func (Postcode) String

func (p Postcode) String() string

String returns the spaced format (e.g. "EK 01 A03 FK 01").

func (*Postcode) UnmarshalText

func (p *Postcode) UnmarshalText(text []byte) error

func (Postcode) Value added in v0.2.2

func (p Postcode) Value() (driver.Value, error)

Value implements driver.Valuer, storing the canonical hyphenated postcode (or nil if zero).

type Precision added in v0.1.1

type Precision uint8

Precision indicates the resolution accuracy of the resolved coordinates.

const (
	// PrecisionBuilding indicates exact building-level point geometry.
	PrecisionBuilding Precision = iota
	// PrecisionArea indicates area or street axis level resolution.
	PrecisionArea
	// PrecisionDistrict indicates district-level centroid resolution.
	PrecisionDistrict
	// PrecisionLGA indicates Local Government Area centroid resolution.
	PrecisionLGA
	// PrecisionState indicates State-level centroid resolution.
	PrecisionState
)

func (Precision) MarshalJSON added in v0.1.1

func (p Precision) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (Precision) MarshalText added in v0.1.1

func (p Precision) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (Precision) String added in v0.1.1

func (p Precision) String() string

String returns the human-readable description of the precision level.

func (*Precision) UnmarshalJSON added in v0.1.1

func (p *Precision) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler to accept both string ("building") and numeric (0) forms.

func (*Precision) UnmarshalText added in v0.1.1

func (p *Precision) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type QRCode added in v0.2.3

type QRCode struct {
	Postcode        string                 `json:"postcode"`
	Compact         string                 `json:"compact"`
	Spaced          string                 `json:"spaced"`
	StateCode       string                 `json:"state_code"`
	StateName       string                 `json:"state_name"`
	LGACode         string                 `json:"lga_code,omitempty"`
	LGAName         string                 `json:"lga_name,omitempty"`
	Zone            string                 `json:"zone,omitempty"`
	Address         string                 `json:"address,omitempty"`
	Latitude        float64                `json:"latitude"`
	Longitude       float64                `json:"longitude"`
	GoogleMapsURL   string                 `json:"google_maps_url"`
	Payload         string                 `json:"payload"`
	PayloadType     QRPayloadType          `json:"payload_type"`
	Level           QRErrorCorrectionLevel `json:"level"`
	Version         int                    `json:"version"`
	Dimension       int                    `json:"dimension"`
	QuietZone       int                    `json:"quiet_zone"`
	Size            int                    `json:"size"`
	ForegroundColor string                 `json:"foreground_color"`
	BackgroundColor string                 `json:"background_color"`
	IncludeLabel    bool                   `json:"include_label"`
	Title           string                 `json:"title,omitempty"`
	Subtitle        string                 `json:"subtitle,omitempty"`
	InvertASCII     bool                   `json:"invert_ascii,omitempty"`
	Modules         [][]bool               `json:"-"`
}

QRCode represents a generated, standards-compliant (ISO/IEC 18004) QR code for a Nigerian postcode.

func GenerateQR added in v0.2.3

func GenerateQR(raw string, opts ...QROptions) (*QRCode, error)

GenerateQR validates a Nigerian postcode string and generates a QRCode with the given options.

func (*QRCode) ASCII added in v0.2.3

func (q *QRCode) ASCII() string

ASCII renders the QR code using pure 2-character ASCII/Unicode blocks ("██" and " ").

func (*QRCode) DataURI added in v0.2.3

func (q *QRCode) DataURI() (string, error)

DataURI returns a base64-encoded PNG data URI ("data:image/png;base64,...") suitable for HTML <img> src attributes.

func (*QRCode) Image added in v0.2.3

func (q *QRCode) Image() image.Image

Image renders the QRCode as a standard Go image.Image. When IncludeLabel is true, a footer banner with the formatted postcode and State/LGA is drawn below the QR matrix.

func (*QRCode) LabelSVG added in v0.2.3

func (q *QRCode) LabelSVG() string

LabelSVG renders a print-ready NIPOST Postcode Badge / Placard SVG containing the QR code, canonical hyphenated postcode, State & LGA administrative details, and coordinates.

func (*QRCode) MatrixSVG added in v0.2.3

func (q *QRCode) MatrixSVG() string

MatrixSVG returns a standalone SVG of the QR code matrix without an outer printable card frame.

func (*QRCode) PNG added in v0.2.3

func (q *QRCode) PNG() ([]byte, error)

PNG encodes the QR code as a PNG image.

func (*QRCode) PrintableHTML added in v0.2.3

func (q *QRCode) PrintableHTML() string

PrintableHTML generates a standalone, print-ready HTML page containing the NIPOST Postcode QR Card with one-click printing and sharing metadata.

func (*QRCode) SVG added in v0.2.3

func (q *QRCode) SVG() string

SVG returns a scalable vector graphics (<svg>) representation of the QR code. If IncludeLabel was set in QROptions, it returns the full printable NIPOST postcode badge SVG.

func (*QRCode) SVGDataURI added in v0.2.3

func (q *QRCode) SVGDataURI() string

SVGDataURI returns a base64-encoded SVG data URI ("data:image/svg+xml;base64,...").

func (*QRCode) SaveHTML added in v0.2.3

func (q *QRCode) SaveHTML(filename string) error

SaveHTML writes the printable HTML label page to a file at filename.

func (*QRCode) SavePNG added in v0.2.3

func (q *QRCode) SavePNG(filename string) error

SavePNG writes the PNG-encoded QR code image to a file at filename.

func (*QRCode) SaveSVG added in v0.2.3

func (q *QRCode) SaveSVG(filename string) error

SaveSVG writes the SVG-encoded QR code to a file at filename.

func (*QRCode) Terminal added in v0.2.3

func (q *QRCode) Terminal() string

Terminal renders the QR code using compact Unicode half-block characters ('▀', '▄', '█', ' ') so that two vertical modules occupy a single terminal row, preserving a 1:1 square aspect ratio.

func (*QRCode) WritePNG added in v0.2.3

func (q *QRCode) WritePNG(w io.Writer) error

WritePNG writes the PNG-encoded QR code to w.

func (*QRCode) WriteSVG added in v0.2.3

func (q *QRCode) WriteSVG(w io.Writer) error

WriteSVG writes the SVG-encoded QR code to w.

type QRErrorCorrectionLevel added in v0.2.3

type QRErrorCorrectionLevel string

QRErrorCorrectionLevel defines the Reed-Solomon error correction capacity of a QR code.

const (
	// QRLevelL recovers up to ~7% of damaged codewords.
	QRLevelL QRErrorCorrectionLevel = "L"
	// QRLevelM recovers up to ~15% of damaged codewords (default).
	QRLevelM QRErrorCorrectionLevel = "M"
	// QRLevelQ recovers up to ~25% of damaged codewords (recommended for printed labels).
	QRLevelQ QRErrorCorrectionLevel = "Q"
	// QRLevelH recovers up to ~30% of damaged codewords.
	QRLevelH QRErrorCorrectionLevel = "H"
)

type QROptions added in v0.2.3

type QROptions struct {
	// Payload controls what content the QR code encodes when scanned. Defaults to QRPayloadPostcode ("postcode").
	Payload QRPayloadType `json:"payload,omitempty"`
	// Level sets the Reed-Solomon error correction level ("L", "M", "Q", "H"). Defaults to "M" (or "Q" when IncludeLabel is true).
	Level QRErrorCorrectionLevel `json:"level,omitempty"`
	// Size is the target width/height in pixels for SVG and PNG rendering. Defaults to 256 (or 400 when IncludeLabel is true).
	Size int `json:"size,omitempty"`
	// QuietZone is the margin around the QR matrix in modules. Defaults to 4. Set to -1 for zero margin.
	QuietZone int `json:"quiet_zone,omitempty"`
	// ForegroundColor is the hex color for dark QR modules (e.g. "#000000" or "#008751"). Defaults to "#000000".
	ForegroundColor string `json:"foreground_color,omitempty"`
	// BackgroundColor is the hex color for light QR background (e.g. "#FFFFFF"). Defaults to "#FFFFFF".
	BackgroundColor string `json:"background_color,omitempty"`
	// IncludeLabel renders a printable NIPOST postcode badge/label with human-readable postcode, State/LGA, and coordinates.
	IncludeLabel bool `json:"include_label,omitempty"`
	// Title overrides the header text on printable labels (defaults to "NIGERIA DIGITAL POSTCODE").
	Title string `json:"title,omitempty"`
	// Subtitle adds a custom street address or recipient line on printable labels.
	Subtitle string `json:"subtitle,omitempty"`
	// InvertASCII inverts dark and light blocks when rendering terminal/ASCII output.
	InvertASCII bool `json:"invert_ascii,omitempty"`
}

QROptions configures how a postcode QR code is encoded and rendered for sharing or printing.

type QRPayloadType added in v0.2.3

type QRPayloadType string

QRPayloadType specifies what data is encoded inside the QR code when scanned.

const (
	// QRPayloadPostcode encodes the canonical hyphenated 11-character postcode (e.g. "EK-01-A03-FK-01"). Default.
	QRPayloadPostcode QRPayloadType = "postcode"
	// QRPayloadCompact encodes the compact 11-character postcode without hyphens (e.g. "EK01A03FK01").
	QRPayloadCompact QRPayloadType = "compact"
	// QRPayloadSpaced encodes the spaced 5-segment display format (e.g. "EK 01 A03 FK 01").
	QRPayloadSpaced QRPayloadType = "spaced"
	// QRPayloadGoogleMaps encodes a direct Google Maps search URL for the postcode location.
	QRPayloadGoogleMaps QRPayloadType = "google-maps"
	// QRPayloadDirections encodes a turn-by-turn Google Maps navigation URL to the postcode.
	QRPayloadDirections QRPayloadType = "directions"
	// QRPayloadAppleMaps encodes an Apple Maps pin URL for iOS/macOS devices.
	QRPayloadAppleMaps QRPayloadType = "apple-maps"
	// QRPayloadOSM encodes an OpenStreetMap pin URL.
	QRPayloadOSM QRPayloadType = "osm"
	// QRPayloadGeoURI encodes an RFC 5870 geo: URI (e.g. "geo:7.621100,5.221500?q=EK-01-A03-FK-01").
	QRPayloadGeoURI QRPayloadType = "geo"
	// QRPayloadJSON encodes a compact JSON object with postcode, state, LGA, coordinates, and map URL.
	QRPayloadJSON QRPayloadType = "json"
	// QRPayloadText encodes a human-readable multi-line address card with postcode, location, and map link.
	QRPayloadText QRPayloadType = "text"
)

type RateLimitInfo

type RateLimitInfo struct {
	Limit         int           // Maximum requests allowed in current window (from X-RateLimit-Limit)
	Remaining     int           // Remaining requests in current window (from X-RateLimit-Remaining)
	CreditBalance int           // Remaining commercial credit balance, or -1 if header not present (from X-Credit-Balance)
	ResetAt       time.Time     // Timestamp when quota resets (from X-RateLimit-Reset or Retry-After)
	RetryAfter    time.Duration // Cooldown duration requested by server (from Retry-After or X-RateLimit-Reset)
}

RateLimitInfo contains the rate-limiting quotas returned by the NIPOST API gateway as documented in https://docs.postcode.gov.ng/authentication.

func ParseRateLimitInfo

func ParseRateLimitInfo(h http.Header) *RateLimitInfo

ParseRateLimitInfo extracts rate limit quotas and cooldown durations from HTTP response headers.

type RecentHouseAddress

type RecentHouseAddress struct {
	Address      string `json:"address,omitempty"`
	Recent       string `json:"recent,omitempty"`
	HouseNumber  string `json:"house_number,omitempty"`
	StreetName   string `json:"street_name,omitempty"`
	LocalityName string `json:"locality_name,omitempty"`
}

RecentHouseAddress contains residential and street metadata (L2+).

type RedactedAddress added in v0.2.3

type RedactedAddress struct {
	RedactedText     string   `json:"redacted_text"`
	MaskedPostcode   string   `json:"masked_postcode,omitempty"`
	MaskedPostcodes  []string `json:"masked_postcodes,omitempty"`
	AreaPrefix       string   `json:"area_prefix,omitempty"`
	StateCode        string   `json:"state_code,omitempty"`
	LGACode          string   `json:"lga_code,omitempty"`
	PIIRedactedCount int      `json:"pii_redacted_count"`
	RedactedTypes    []string `json:"redacted_types,omitempty"`
	NDPACompliant    bool     `json:"ndpa_compliant"`
}

RedactedAddress holds the result of NDPA-compliant PII redaction on an unstructured Nigerian address or log line.

func RedactPII added in v0.2.3

func RedactPII(raw string) RedactedAddress

RedactPII scrubs personal identifiable information (Nigerian phone numbers, email addresses, 11-digit BVN/NIN numbers, and exact building unit numbers in NIPOST postcodes) from raw text while retaining State, LGA, District, and Area routing context in line with the Nigeria Data Protection Act (NDPA).

type Result

type Result[T any] struct {
	Value T
	Err   error
}

type RetryConfig

type RetryConfig struct {
	MaxRetries        int           // Maximum retry attempts for transient errors (default: 3)
	BaseDelay         time.Duration // Initial exponential backoff delay (default: 100ms)
	MaxDelay          time.Duration // Maximum exponential backoff delay (default: 2s)
	DisableRateLimit  bool          // If true, 429 Too Many Requests will not be retried automatically
	MaxRateLimitDelay time.Duration // Maximum sleep duration client will wait for a 429 cooldown (default: 10s)
}

RetryConfig configures backoff, jitter, and rate-limit handling for HTTP requests.

type RetryTransport

type RetryTransport struct {
	Base   http.RoundTripper
	Config RetryConfig
}

func NewRetryTransport

func NewRetryTransport(base http.RoundTripper, cfg RetryConfig) *RetryTransport

func (*RetryTransport) RoundTrip

func (r *RetryTransport) RoundTrip(req *http.Request) (*http.Response, error)

type ReverseParams

type ReverseParams struct {
	Latitude     float64
	Longitude    float64
	MaxDistanceM float64 // Search radius in meters (default 25, max 250)
}

ReverseParams configures reverse geocoding to snap coordinates to the nearest unit.

type ReverseResponse

type ReverseResponse struct {
	Found      bool        `json:"found"`
	Coordinate []float64   `json:"coordinate"` // [lng, lat]
	Unit       *NearbyUnit `json:"unit,omitempty"`
	Area       string      `json:"area,omitempty"`
	District   string      `json:"district,omitempty"`
	State      string      `json:"state,omitempty"`
	Message    string      `json:"message,omitempty"`
	RadiusM    float64     `json:"radius_m"`
}

ReverseResponse represents the result of reverse geocoding a coordinate.

func ReverseCoordinatesOffline added in v0.1.9

func ReverseCoordinatesOffline(lat, lng, maxDistanceM float64) *ReverseResponse

ReverseCoordinatesOffline resolves geographic coordinates to the closest building unit in the local database.

type SegmentDiagnosis added in v0.1.4

type SegmentDiagnosis struct {
	Segment     string   `json:"segment"`               // "Length", "LegacyFormat", "State", "LGA", "District", "Area", "BuildingUnit"
	Input       string   `json:"input"`                 // The actual segment characters received
	Expected    string   `json:"expected"`              // What the NIPOST grammar requires
	Message     string   `json:"message"`               // Human & agent readable explanation
	Suggestions []string `json:"suggestions,omitempty"` // Actionable candidates for agent self-correction
}

SegmentDiagnosis describes an issue identified in a specific segment of a postcode.

type Segments

type Segments struct {
	State    string `json:"state"`
	LGA      string `json:"lga"`
	District string `json:"district"`
	Area     string `json:"area"`
	Unit     string `json:"unit"`
}

Segments represents the 5 administrative components of a Nigerian postcode.

func NormalizeSegments added in v0.1.9

func NormalizeSegments(segs Segments) Segments

NormalizeSegments trims whitespace, converts alpha segments to uppercase, and zero-fills single-digit numeric segments (e.g. "1" -> "01") according to official NIPOST rules.

type Span

type Span interface {
	End()
	RecordError(err error)
	SetAttribute(key, value string)
	SetStatus(code int, msg string)
}

Span encapsulates active span operations without coupling to any trace vendor.

type StateRecord added in v0.1.1

type StateRecord struct {
	Code      string
	Name      string
	Capital   string
	Latitude  float64
	Longitude float64
	Zone      string
}

StateRecord holds reference coordinates and metadata for a Nigerian state.

func LookupState added in v0.2.2

func LookupState(query string) (StateRecord, bool)

LookupState resolves a Nigerian state record by its 2-letter NIPOST code, full state name, capital city name, or common alias (e.g. "LA", "Lagos", "Lagos State", "Ikeja", "FCT", "Abuja").

type SyntheticAddress added in v0.1.4

type SyntheticAddress struct {
	RawText           string  `json:"raw_text"`
	ExpectedState     string  `json:"expected_state"`
	ExpectedStateCode string  `json:"expected_state_code"`
	ExpectedLGA       string  `json:"expected_lga,omitempty"`
	ExpectedPostcode  string  `json:"expected_postcode,omitempty"`
	ExpectedLat       float64 `json:"expected_lat,omitempty"`
	ExpectedLng       float64 `json:"expected_lng,omitempty"`
	NoiseType         string  `json:"noise_type,omitempty"`
}

SyntheticAddress represents a synthesized Nigerian address with ground-truth labels. Used to benchmark and evaluate AI agent entity extraction, validation, and geocoding accuracy.

func GenerateSyntheticDataset added in v0.1.4

func GenerateSyntheticDataset(opts GeneratorOptions) []SyntheticAddress

GenerateSyntheticDataset produces a slice of realistic synthetic Nigerian addresses.

type Telemetry

type Telemetry struct {
	Tracer  Tracer
	Metrics MetricsRecorder
}

Telemetry bundles tracing and metrics hooks.

func NopTelemetry

func NopTelemetry() Telemetry

NopTelemetry provides zero-overhead, no-op telemetry by default.

type ToolDef added in v0.1.4

type ToolDef struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Parameters  map[string]any `json:"parameters"`
}

ToolDef describes an agent-executable function schema compliant with JSON Schema Draft-07.

func DefaultAgentTools added in v0.1.4

func DefaultAgentTools() []ToolDef

DefaultAgentTools returns the standard suite of tool declarations for Nigerian postcode operations.

func (ToolDef) AnthropicTool added in v0.1.4

func (t ToolDef) AnthropicTool() map[string]any

AnthropicTool formats the tool declaration for the Anthropic Claude Messages API.

func (ToolDef) GeminiFunctionDeclaration added in v0.1.4

func (t ToolDef) GeminiFunctionDeclaration() map[string]any

GeminiFunctionDeclaration formats the tool declaration for Google Gemini / Google GenAI SDK.

func (ToolDef) OpenAITool added in v0.1.4

func (t ToolDef) OpenAITool() map[string]any

OpenAITool formats the tool declaration for OpenAI, Mistral, Groq, and Ollama APIs.

type Tracer

type Tracer interface {
	Start(ctx context.Context, operation string) (context.Context, Span)
}

Tracer defines the tracing contract for postcode operations.

type ValidationError added in v0.1.4

type ValidationError struct {
	Report DiagnosticReport
}

ValidationError represents a validation failure enriched with an actionable DiagnosticReport.

func (*ValidationError) Error added in v0.1.4

func (e *ValidationError) Error() string

Directories

Path Synopsis
cmd
postcode module
examples
otelpostcode module
skills

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL