postcode

package module
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 23 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).

Installation

Requires Go 1.27+.

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

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())
    }
}

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).
p.GoogleMapsURL() Method on Postcode Zero-allocation / sub-microsecond Google Maps URL generation.
client.ResolveLocation(ctx, code) Gateway + Offline Resolves building location with Level 3 live enrichment and offline fallback.
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 a coordinate.
client.Reverse(ctx, params) GET /v1/search/reverse Snaps coordinate to nearest active unit (default 25m).
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.

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).
  • 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:

// 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:

# 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)

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.

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")
)
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.

func BatchParseSeq

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

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

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 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 ParseSeq

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

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

func RegisterKnownBuilding added in v0.1.1

func RegisterKnownBuilding(rec BuildingRecord)

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

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 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.

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.

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 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.

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) 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.

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) ResolveLocation added in v0.1.1

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

ResolveLocation resolves an 11-character Nigerian postcode to its geographic Location and Google Maps URL. It queries the NIPOST gateway at the official public Level 3 (or Level 2 for street & administrative address enrichment), checks for any custom point geometry, and leverages the SDK's high-precision offline reference geocoding engine to guarantee reliable coordinates and Google Maps 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.

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 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"`
	Normalized    string             `json:"normalized,omitempty"` // Hyphenated canonical form if valid or 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 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.

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) 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. Format: https://www.google.com/maps/search/?api=1&query=lat,lng

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) 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"`
	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 NearbyParams

type NearbyParams struct {
	Latitude  float64
	Longitude float64
	RadiusM   float64 // Search radius in meters (default 300, max 300)
}

NearbyParams configures location search around a geographic coordinate.

type NearbyResponse

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

NearbyResponse contains location units within the requested radius.

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"`
	Address    string  `json:"address,omitempty"`
}

NearbyUnit represents a building unit found near a coordinate.

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 Parse

func Parse(raw string) (Postcode, error)

Parse extracts and validates segments with 0 heap allocations.

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) 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) 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) District

func (p Postcode) District() string

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) Raw

func (p Postcode) Raw() string

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

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

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 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)
	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 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.

type SegmentDiagnosis added in v0.1.4

type SegmentDiagnosis struct {
	Segment     string   `json:"segment"`               // "Length", "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.

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.

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

Jump to

Keyboard shortcuts

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