- Guides
ZIP and PIN codes: preserve the code, not its numeric value
ZIP and postal PIN codes need string storage. Test leading zeros, explicit formats, uncertainty, and JSON/database/export boundaries.
- Author
Udit Goenka- Editorial date
- Reading time
- 25 min

TL;DR: Store postal codes as literal strings under an explicitly selected postal system. Preserve leading zeroes across JSON, database, and export boundaries, never pad uncertain input, and distinguish accepted format from postal assignment, deliverability, or any authentication secret.
A caller supplies a postal code with a leading zero. The application recognizes every character, stores a number, and later reads back a shorter code. A repair routine adds a zero to make the length look right, but the original value is already lost.
The fix starts with field meaning and type. The phone-number capture guide explains the same preservation principle for dialing context. A postal code also identifies something under a named system; it is not an arithmetic quantity.
PIN in this guide means India's postal index number. It does not mean an account, payment, or authentication PIN. The example must never collect a real secret to demonstrate a postal parser.
This guide provides a bounded format classifier and an executable text round-trip fixture. It tests JSON strings, an in-memory SQLite TEXT column, and Python's CSV reader. It does not query a postal assignment database, verify an address, operate a spreadsheet, or report recognition accuracy.
Table of contents
- Select the postal system first
- Keep codes textual at every boundary
- Do not reconstruct lost zeroes
- Define digit and uncertainty policies
- Run the postal preservation fixture
- Separate format from assignment and delivery
- Make the readback repairable
- Audit imports and exports
- Evaluate postal capture outcomes
- Reproduce the boundary that changed the value
- Define an explicit normalization boundary
- Build a task-specific acceptance matrix
- Frequently asked questions
- Ship a named postal contract
Select the postal system first
A code is meaningful under a postal system. Its length alone does not identify that system, and the caller's spoken language does not supply the missing country context. Ask for or use explicitly authorized context before choosing a grammar.
The current USPS ZIP Code basics describes the five-digit ZIP and the ZIP+4 form consisting of five digits, a hyphen, and four digits. Those are specific US representations. They should not become a universal regular expression for every destination country.
For India, an official Ministry of Communications release explicitly describes the six-digit PIN Code system separately from DIGIPIN. This distinction matters because a new address-related identifier should not silently replace the postal field an application actually requested.
The reference names three supported systems: US_ZIP, US_ZIP4, and IN_PIN. For IN_PIN it deliberately checks a six-ASCII-digit teaching shape only. It does not claim that every matching value is assigned, that all first-digit rules have been validated, or that the value identifies a deliverable address.
The Arabic number and date guide addresses a related boundary between locale presentation and a field's actual contract. A caller can use Arabic digit words while supplying a US postal code. The language of the utterance must not override the selected postal system.
Keep the system identity with the code through persistence and confirmation. A string of five characters without its declared system is less useful than a typed field containing both. If the caller changes the destination country, invalidate the old interpretation and revalidate the literal under the new supported contract.
The workflow enforcement guide explains why changed material context invalidates approval. A postal-system change is material even when the visible characters remain identical. A prior confirmation under one system does not automatically confirm an address interpretation under another.

Avoid a catch-all branch that accepts anything else as an international postcode. Unsupported systems should receive a named unsupported outcome and a real alternate route. A permissive string field can preserve input while still lacking the evidence needed to validate it for the intended task.
Keep codes textual at every boundary
The authoritative value is the literal code plus its selected system. A formatted display, grouped readback, or parsed digit sequence is another representation with its own purpose. Keep those surfaces derived from the authoritative candidate rather than allowing a speech formatter to become the source of truth.
RFC 8259 distinguishes JSON strings from numbers. Sending a postal code as a number can lose spelling information before any database sees it. A numeric JSON value cannot tell a downstream service how many leading zeroes the caller originally supplied.
Reject that unsupported type instead of applying str to it. Converting the number back to text preserves the already damaged value, not the original literal. The reference therefore requires a bounded string and raises an explicit error for numbers, booleans, null-like input, and empty strings.
The order-ID and email guide explains why literal identity and presentation cleanup must remain separate. The same generic strip, uppercase, or numeric-cast helper can be safe for one field and destructive for another. A field-specific contract makes those differences visible.
A text database column is necessary but insufficient. An ORM, application mapper, or import routine can coerce the value before insertion. Test the exact value after the actual supported interface, and verify its type as well as its character sequence.
The executable example uses a real in-memory SQLite table with a TEXT column and bound insertion parameters. Its local result demonstrates that particular round trip under the current Python runtime. It does not establish how an unrelated production database schema, driver, or migration behaves.
A CSV field is another boundary. Python's default CSV reader returns strings, so the fixture verifies that its own exported and reread literal survives. A spreadsheet opening the same file can apply its own type inference. The fixture does not claim control over that separate application's import policy.
The observability guide helps connect boundary results without logging full addresses in routine traces. A safe diagnostic can identify which interface changed a field's type or length while retaining sensitive source values only under a justified access policy.
Do not reconstruct lost zeroes
A short value can have several causes: the caller omitted a character, a recognizer missed one, an upstream integer conversion removed zeroes, or the selected system is wrong. Padding to a familiar length chooses one explanation without evidence. It can turn an explicit uncertainty into a plausible but incorrect code.
Treat missing characters as missing. Ask a targeted clarification or request the full code again under a new candidate revision. If an import supplies numeric data with no source literal, report that the original textual representation is unavailable rather than claiming to recover it.
A historical developer question about ZIP conversion describes the same symptom across application boundaries. Its old answers are not current language rules. The useful lesson is to test where the type changed and preserve the literal before conversion.

Keep a distinction between an absent field and the character zero. A field containing a zero is present text; a missing value is missing metadata. Do not replace both with the same sentinel label or count them as the same parser outcome.
The outcome metrics guide explains why missing evidence needs its own denominator and disposition. A system that removes unsupported or incomplete postal attempts from its reports can look accurate while requiring repeated clarification from callers.
A normalization policy may remove an explicitly supported display separator, but it must not invent characters. If the supported canonical ZIP+4 form includes a hyphen, state whether the adapter accepts an unseparated source and how that transformation is confirmed. The narrow reference rejects the unseparated form rather than quietly adding punctuation.
Never infer a postal code from a telephone calling code. The shared-phone identity guide describes why phone context and person context can diverge. Postal destination, current location, account address, and telephone numbering region are separate facts even when a particular workflow often uses them together.
Define digit and uncertainty policies
The default grammar uses explicit ASCII digit classes. Python's regular-expression documentation distinguishes Unicode string matching from ASCII-specific classes and defines fullmatch. A broad digit expression can accept characters that an ASCII-only destination system does not support directly.
The optional mapping policy in the reference maps Unicode decimal digits to ASCII and retains the original string. It deliberately uses the decimal-character category, not every character with a numeric interpretation. A superscript or fraction must not become an invented postal digit through a broad numeric conversion.
The Python Unicode database documentation defines decimal, digit, and numeric operations separately. Record the runtime Unicode database version when that mapping is part of a reproducible contract. A policy should not rely on a newer database while claiming an untested older runtime behaves identically.
CLDR's number formatting documentation describes locale number systems and symbols. Formatting data can help present a value, but it does not establish which postal system the caller meant or whether a particular code is assigned. Keep those responsibilities separate.
Uncertainty must survive syntactic success. The reference accepts an explicit set of uncertain character positions and returns clarification-required even when the candidate has a valid length and shape. A recognizer's best string is not the same as a caller-confirmed literal.

Validate position types before deduplicating or sorting them. Booleans, strings, repeated positions, and positions outside the source range are invalid metadata under this contract. A malformed uncertainty record should not silently disappear because a set operation happens to merge it with a supported integer.
The small classifier does not maintain an approved workflow state. Its disposition is input evidence for a separate current-value confirmation controller. A correction to a digit, mapping policy, or postal system invalidates that controller's earlier approval; a format-accepted result cannot authorize a downstream address action by itself.
Run the postal preservation fixture
The complete Python 3.11-or-newer example uses standard-library JSON, SQLite, CSV, regular expressions, and Unicode data. It copies uncertainty positions into a tuple and returns an immutable result. Every fixture is synthetic and queries no postal assignment, address, or authentication service.
The supported grammar is intentionally small. A matched six-digit IN_PIN shape does not certify all Indian postal semantics, and a five-digit ZIP match does not establish an assigned code. The result carries assignment not-checked so a caller cannot mistake the local shape test for external verification.
from contextlib import closing
from dataclasses import dataclass, FrozenInstanceError
import csv
from io import StringIO
import json
import re
import sqlite3
import unicodedata
PATTERNS = {'US_ZIP': r'[0-9]{5}', 'US_ZIP4': r'[0-9]{5}-[0-9]{4}',
'IN_PIN': r'[0-9]{6}'}
@dataclass(frozen=True)
class PostalResult:
system: str
original: str
candidate: str
uncertain: tuple
disposition: str
assignment: str = 'not-checked'
def postal(system, raw, uncertain=(), map_decimal=False):
if type(system) is not str or system not in PATTERNS:
raise ValueError('unsupported postal system')
if type(raw) is not str or not 1 <= len(raw) <= 32:
raise ValueError('bounded literal string required; no numeric recovery')
if (type(uncertain) not in (tuple, list)
or any(type(i) is not int or not 0 <= i < len(raw) for i in uncertain)
or len(set(uncertain)) != len(uncertain) or type(map_decimal) is not bool):
raise ValueError('invalid uncertainty or mapping policy')
owned_uncertain = tuple(uncertain)
candidate = raw
if map_decimal:
candidate = ''.join(str(unicodedata.decimal(c))
if unicodedata.category(c) == 'Nd' else c for c in raw)
if owned_uncertain:
disposition = 'clarification-required'
elif re.fullmatch(PATTERNS[system], candidate) is None:
disposition = 'unsupported-format'
else:
disposition = 'format-accepted'
return PostalResult(system, raw, candidate, owned_uncertain, disposition)
def text_round_trip(value):
if type(value) is not str:
raise ValueError('text required')
json_value = json.loads(json.dumps({'postal': value}))['postal']
with closing(sqlite3.connect(':memory:')) as connection, connection:
connection.execute('create table fields (postal TEXT NOT NULL)')
connection.execute('insert into fields values (?)', (json_value,))
database_value = connection.execute('select postal from fields').fetchone()[0]
output = StringIO(newline='')
csv.writer(output).writerow([database_value])
output.seek(0)
csv_value = next(csv.reader(output))[0]
if not all(type(x) is str and x == value for x in (json_value, database_value, csv_value)):
raise AssertionError('literal changed')
return {'json':json_value, 'sqlite_text':database_value, 'csv_reader':csv_value}
def fixtures():
# Synthetic format fixtures; assignment and delivery are never queried.
assert postal('US_ZIP', '01234').candidate == '01234'
assert postal('US_ZIP4', '01234-0001').disposition == 'format-accepted'
assert postal('IN_PIN', '123456').disposition == 'format-accepted'
assert postal('IN_PIN', '000000').assignment == 'not-checked'
assert postal('US_ZIP', '1234').disposition == 'unsupported-format'
assert postal('IN_PIN', '12345').disposition == 'unsupported-format'
assert postal('US_ZIP4', '012340001').disposition == 'unsupported-format'
uncertain = [2]
pending = postal('US_ZIP', '01234', uncertain)
uncertain[0] = 0
assert pending.uncertain == (2,) and pending.disposition == 'clarification-required'
assert postal('US_ZIP', '01234').disposition == 'unsupported-format'
mapped = postal('US_ZIP', '٠١٢٣٤', map_decimal=True)
assert mapped.original == '٠١٢٣٤' and mapped.candidate == '01234'
assert postal('US_ZIP', '²1234', map_decimal=True).disposition == 'unsupported-format'
for raw in (1234, 0, True, None, '', '0'*33):
try:
postal('US_ZIP', raw)
except ValueError:
pass
else:
raise AssertionError('unsupported input accepted')
for unknown in ([True], ['2'], [2, 2], [99]):
try:
postal('US_ZIP', '01234', unknown)
except ValueError:
pass
else:
raise AssertionError('invalid uncertainty accepted')
try:
pending.candidate = '99999'
except FrozenInstanceError:
pass
else:
raise AssertionError('mutable result')
for system in ('UNSUPPORTED', None, 123):
try:
postal(system, '01234')
except ValueError:
pass
else:
raise AssertionError('unsupported system accepted')
assert text_round_trip('01234')['csv_reader'] == '01234'
print(json.dumps({'fixture_class':'Synthetic', 'literal':'01234',
'round_trip':text_round_trip('01234'),
'unicode_version':unicodedata.unidata_version,
'assignment':'not-checked', 'delivery':'not-checked'}))
if __name__ == '__main__':
fixtures()
The program emits a JSON report with the synthetic literal and each actual round-trip result. It also prints the runtime Unicode database version. Inspect those fields rather than treating a test-success message as proof that the promised reusable evidence was emitted.
The Python CSV documentation defines the reader and writer behavior used in this local test. If your production path changes dialect, quoting, encoding, or importer, test that path separately. A CSV reader result is not a guarantee about spreadsheet display or automatic formatting.
The Python SQLite documentation owns the standard-library database interface. The fixture uses bound parameters and a deliberate TEXT declaration, then asserts both type and literal equality. It does not validate a remote database deployment or a data warehouse export.
Separate format from assignment and delivery
A format classifier establishes that the source conforms to its declared shape. Assignment requires the relevant postal authority or supported data source. Deliverability depends on more of the address and the current delivery context. A telephone caller's identity remains a separate question again.
The criteria table uses ✓ for a property the method can establish under its stated local contract. A ✗ means it cannot establish that property alone. These comparisons are about evidence boundaries and do not rank postal services or claim measured recognition performance.
| Method | Preserve literal zeroes | Check selected shape | Establish assignment | Establish delivery |
|---|---|---|---|---|
| String capture | ✓ within supported interface | ✗ alone | ✗ | ✗ |
| Narrow format classifier | ✓ with retained source | ✓ within declared subset | ✗ | ✗ |
| Text round-trip fixture | ✓ for tested path | ✗ alone | ✗ | ✗ |
| Postal authority lookup | ✗ without retained source | ✗ alone | ✓ within its data contract | ✗ by itself |
A current lookup result should retain its source and time boundary. Do not make a permanent truth claim from an old assignment snapshot. If an authority lookup is unavailable, keep unavailable distinct from unassigned rather than replacing missing evidence with a regex pass.
The truthful waiting-speech guide explains why progress language must match observed state. Saying that an address has been verified while only a format check has finished gives the caller a stronger claim than the application can support.
Postal assignment also does not establish account ownership. A person can know a public code or address without authority to alter a record. The workflow confirmation guide applies to the complete business proposal, including the intended action and every material field.
If a lookup returns a suggested correction, treat it as a proposal. Preserve the caller's source address and confirm material changes through the workflow. An external suggestion can be useful while still requiring explicit agreement before it becomes the authoritative destination.
Make the readback repairable
Read a postal code as the field's character sequence, with grouping appropriate to the selected system. A numeric-looking string should not automatically be spoken as a quantity. The confirmation prosody guide explains how pacing can help a caller distinguish similar values.
Keep spoken and stored forms separate. A voice can say individual digits while the stored ZIP+4 retains its hyphen. If the caller says that a character is wrong, repair the authoritative candidate and regenerate the spoken surface from it. Do not patch only the playback text while leaving the stored field unchanged.
The spoken self-correction guide addresses group repair without accumulating abandoned fragments. A postal interaction should retain the corrected intended value and invalidate any readback confirmation for the old candidate.
The yes-but repair guide is relevant when approval and correction occur in the same utterance. A partial yes should not approve a postal field that the caller immediately changes. The current candidate needs the workflow's actual confirmation evidence.
A caller may need time to find an address. The progressive no-input prompt guide distinguishes a pause from abandonment. Offer a supported repeat or alternate route without guessing the missing code or extending an expired operation invisibly.
The DTMF identifier guide helps when the application permits keypad capture. Use one field owner and the provider's actual modality contract. A postcode parser cannot repair an upstream event race that duplicated a key press or mixed late speech into keypad input.
Audit imports and exports
Inspect the entire literal path when a code loses zeroes. Compare the source payload type, application object, database value, API response, export bytes, and final importer. A familiar-looking column on a screen can hide a conversion that already changed the canonical value.
Test the supported path with leading-zero fixtures and exact equality assertions. Include a deliberately numeric input as a negative case. If the program silently converts it back to a string, the test must fail because the original spelling remains unknown.

An import migration should preserve both source evidence and the chosen interpretation. Do not repair a historical numeric column by padding every row without a verified source or an explicit authoritative mapping. The resulting data can appear more complete while containing invented identifiers.
A cache can also retain an old interpretation. The cache-key guide explains why policy and configuration belong in identity. A format result under US_ZIP should not be reused after the destination system changes to IN_PIN merely because the raw string happens to be similar.
For a repeated-call workflow, stored address data needs its own current-use policy. The memory consent and expiry guide explains why memory and authorization differ. A previous confirmed code is historical evidence, not permanent approval for a new shipment or disclosure.
If a session closes while a lookup is pending, preserve the terminal state. The shutdown semantics guide describes how late results should remain historical instead of reopening action. The postal classifier itself is pure and supplies no session lifecycle; the application must provide that boundary.
Evaluate postal capture outcomes
Run structural fixtures before listening tests. Test exact string preservation, unsupported numeric input, short codes, wrong system, uncertain positions, optional digit mapping, and unassigned-looking values that still match the teaching shape. The expected assignment result remains not-checked.
The regression testing guide provides a repeatable structure for those cases. Preserve the complete published fence and the actual supported serializer configuration. A test against a simplified helper can miss a coercion introduced by the integration path.
Speech evaluation needs consented material and reviewed field labels. The multilingual benchmark guide explains why language cohorts and critical fields need their own criteria. A local regex pass is not a recognition score, and a successful readback recovery is different from a correct first transcript.
Count incomplete, canceled, expired, unsupported, clarified, and confirmed attempts separately. The voice outcome metrics guide explains why successful-only denominators can conceal user effort. Preserve address lookup failures separately from capture errors so the report identifies which boundary failed.
For an application using 60db Agent, place this classifier at the documented structured-input boundary available to the application. It is a public teaching reference rather than a description of private postal logic. Use its narrow assertions to make the actual integration easier to test and explain.
Reproduce the boundary that changed the value
Start an incident investigation with a synthetic literal and its selected system. Record the exact source type before any normalization. If a request body already contains a number, the first supported rejection occurs at that boundary. It would be misleading to attribute all subsequent missing zeroes to a database merely because that is where the shortened value was noticed.
Send the literal through one interface at a time. Compare the JSON-decoded value with the original, then compare the bound database value, then compare the exported field. An exact character assertion identifies the first divergence. A comparison after integer conversion can pass even when the postal identifier has already changed.
Preserve the source interface configuration in the fixture. CSV dialect, text encoding, database type mapping, and application deserialization can all affect interpretation. A defect reproduced with a default reader may disappear when the production importer uses another option, or a local test may pass while the actual application performs a numeric cast afterward.
Treat a spreadsheet as a separate consumer. A file can contain the correct characters while the application opening it changes display or stored cell type. If spreadsheet interoperability is part of the product, define the supported import procedure and test it directly. Do not add a leading apostrophe to every canonical postal string as a universal repair; that can change the value another consumer receives.
A historical numeric column needs an evidence-based recovery plan. Some records may have an authoritative original address source, while others may require clarification. Keep those populations separate. Padding every row to a fixed length creates a uniformly shaped dataset without proving that any lost characters were restored correctly.
Record the difference between a proposed repair and a verified source value. A migration report can list which original source established each recovered literal, which rows remain unresolved, and which downstream systems received the corrected value. It should not call a guessed candidate original simply because it was accepted by the selected grammar.
The local SQLite fixture demonstrates only a deliberately declared TEXT column and the standard-library driver. A production migration may use different constraints, adapters, or storage types. Run the same literal-preservation assertions against that actual path under authorized test conditions before making a compatibility claim.
Keep sensitive addresses out of shared reproductions whenever a synthetic code is sufficient. The key failure is usually a type or character transformation, so a small synthetic fixture can establish it without copying the surrounding recipient name, street address, or order record into a public issue.
Define an explicit normalization boundary
Decide whether the application accepts only canonical input or also a small set of source presentation forms. These are both coherent policies when documented. A canonical-only ZIP+4 boundary can reject an unseparated sequence; a broader adapter can accept it and propose the canonical hyphenated form while retaining the source.
The narrow reference uses canonical-only shape checks. It does not strip spaces, add a hyphen, or pad digits. That keeps the artifact's behavior easy to verify and prevents an apparently successful cleanup operation from concealing a missing character. Expanding the policy requires new fixtures for each supported transformation.
Preserve the mapping policy with the candidate. If one revision maps decimal characters to ASCII and another does not, their results are different conditions even when a particular ASCII source happens to pass both. A report should identify which policy produced the value rather than making normalization an invisible global setting.
The optional decimal mapping is one-character to one-character for the supported Unicode category. This preserves position references used for uncertainty. If a future mapping expands, deletes, or reorders characters, raw uncertainty indices cannot be reused blindly; the adapter needs a supported source-to-candidate position relationship.
Keep uncertain and unsupported distinct. A supported-looking code with one unresolved character requires clarification. A complete string outside the selected grammar is unsupported by this classifier. Those states can lead to different prompts, and neither should silently produce an approved address field.
Do not normalize away a meaningful distinction to increase acceptance. A hyphen and another visually similar Unicode mark are not automatically equivalent under the teaching grammar. A broader policy can explicitly recognize a presentation separator, but it must preserve source evidence and avoid claiming that visual similarity establishes identifier equality.
Changing the selected postal system should re-run the relevant checks. It must not preserve a format-accepted flag from the old system. The current code makes the system part of the immutable result, so a caller cannot mutate that label after classification while keeping the old disposition.
The workflow's approved snapshot should bind the system, canonical candidate, normalization policy, and material address context it actually uses. The pure classifier deliberately supplies no approval object. This separation avoids pretending that a local text check can establish consent for shipment, account modification, or disclosure.

Build a task-specific acceptance matrix
Choose fixture families by the failure they can expose. Leading-zero strings detect numeric coercion. Wrong-system strings detect accidental grammar selection. Unicode decimal input detects undeclared mapping. Uncertain positions detect a workflow that treats plausible syntax as resolved intent.
Add fixtures for every declared disposition. If unsupported systems raise an exception while unsupported shapes return a result object, document that difference and assert it. A caller of the API needs to know whether to choose another supported system or ask the user to repair the current field.
A good boundary suite includes exact minimum and maximum supported input sizes, not just a short happy path. The reference rejects empty strings and oversized raw text before parsing. Those local limits are teaching-resource bounds, not postal standards, and should not be presented as official maximum postcode lengths worldwide.
Check immutability through both the original source container and the returned object. The fixture mutates the caller's uncertainty list after classification and verifies the retained tuple. It also attempts property rebinding. These tests establish a supported owned result instead of relying on the word frozen alone.
A voice trial requires a different matrix. Include callers correcting one digit, switching the destination system, pausing to find an address, and rejecting a readback. Use consented material and reviewed expected values. The parser can pass every structural test while the interaction still makes repair difficult.
Keep first-pass capture and eventual confirmed capture separate. A successful correction is useful recovery evidence, but it does not make the initial transcript correct. Report the effort required to reach a current candidate, along with canceled and expired attempts, rather than describing only the easiest successful subset.
Finally, identify the owning evidence for each release criterion. Literal preservation belongs to interface tests, grammar behavior belongs to the classifier, assignment belongs to its authority adapter, and delivery belongs to a separate operational observation. A release report is easier to trust when each result names the boundary that actually established it.
An acceptance matrix should name both the transport and the consumer. The published CSV assertion proves that Python's default reader returns the same field text after its writer emits the record. It does not establish how a spreadsheet's import dialog, a regional separator setting, or a third-party analytics connector will interpret the file. Those consumers need their own reproducible checks using the exact configuration that will be deployed. Preserve a machine-readable string contract alongside any human-facing export instructions.
The SQLite fixture is similarly bounded. Its TEXT column and bound string parameter preserve this synthetic literal in an in-memory database. A production migration might add a numeric cast, a generated column, a validation trigger, or a downstream schema mapper. Review those actual operations before extending the fixture's conclusion. A successful local round trip is evidence for the operations executed, rather than a certification of every persistence path bearing the same product name.
Give failed boundary checks an actionable owner. If JSON receives a number instead of a string, repair the producer schema and refuse to reconstruct the original spelling. If the database output differs from its input string, inspect the write and read transformations. If only a spreadsheet display loses zeroes, retain the source string and investigate that consumer's import behavior. Keeping these cases separate helps an engineer reproduce the defect without repeatedly asking the caller for information the capture layer already preserved.
Finally, keep the synthetic fixture literal in the engineering test suite rather than treating it as a verified postal assignment. Its purpose is to expose irreversible representation changes. The assignment and address-delivery columns remain explicitly untested after every transport assertion succeeds.
Frequently asked questions
Why store ZIP and postal PIN codes as strings?
Their characters identify postal context rather than an arithmetic value. Numeric conversion can remove leading zeroes or other representation details. Preserve the literal and selected postal system, then validate the documented shape without reconstructing characters that were lost upstream.
Which PIN does this guide mean?
It means the Indian postal index number. It does not mean a secret account, card, or authentication PIN. Keep those fields and workflows separate, and use only synthetic postal values in examples rather than collecting a real secret to test a parser.
How long is a US ZIP code?
The supported basic form contains five digits. The reference enforces that ASCII shape only. Matching it does not establish that the code is assigned or that a complete address is deliverable. An authority lookup supplies a different layer of evidence.
What does ZIP+4 add?
USPS describes a form with five digits, a hyphen, and four additional digits. The reference requires that literal structure. It does not infer the added segment or silently insert a separator into arbitrary input; a broader normalization policy needs its own explicit contract.
How long is an Indian postal PIN?
The official Ministry of Communications source describes a six-digit PIN Code system. The example checks a six-digit teaching shape and leaves assignment untested. It does not claim a universal address validator or treat DIGIPIN as an interchangeable value for the same field.
Can a lost leading zero be restored safely?
Only with independent authoritative source evidence that establishes the original value. A short numeric value alone is insufficient. Ask for clarification or report the damaged source representation. Padding based on expected length can invent a plausible but incorrect code.
Why reject a short code instead of padding it?
A missing character can have several causes, including omission, recognition error, a wrong selected system, or prior coercion. Padding chooses one explanation without evidence. Preserve the uncertainty and repair the affected field through the current confirmation workflow.
Should numeric JSON input be accepted?
Not under this literal-preservation contract. The numeric value no longer establishes the original spelling, even if converting it to text produces a plausible length. Require a string at the boundary and retain a separate unsupported-type disposition for numeric input.
Does format-accepted mean assigned?
No. It means the candidate matches the explicitly selected teaching shape. Assignment belongs to the relevant postal authority or supported dataset. Keep assignment not-checked until that boundary actually supplies evidence, and do not turn an unavailable lookup into an assumed assignment.
Does an assigned code prove delivery?
No. Delivery depends on the complete address and applicable postal context. A postal-code lookup can contribute useful evidence without guaranteeing that a particular item reaches a recipient. State which address and delivery checks were performed rather than borrowing them from syntax acceptance.
Can one regex cover every country?
A regex can implement a bounded documented subset, but postal systems have different structures and semantics. Select the supported system first. Unsupported systems need an explicit alternate route instead of a catch-all acceptance branch that pretends any text is verified.
Should numeric-looking codes use numeric storage?
No. Their use as identifiers requires preservation of character sequences. Check JSON types, database mapping, and import/export behavior end to end. A text column does not protect the value if application code converts it to a number before insertion.
How should uncertainty be represented?
Retain explicit uncertain positions or another supported ambiguity record alongside the candidate. Valid length must not erase that evidence. The reference returns clarification-required when any supported uncertain position remains, even if the string otherwise matches its selected grammar.
Can Unicode decimal digits be mapped?
Yes, under an explicit policy that preserves the original string and records the mapping. The reference optionally maps decimal digit characters to ASCII. It does not convert every numeric-looking Unicode character, infer a country, or treat normalization as postal assignment verification.
What if the caller changes country context?
Revalidate the candidate under the newly selected supported system and invalidate earlier workflow approval. The same characters can have different meaning under different contracts. A cached result from the previous system must not become current merely because its input string matches.
When does a repair invalidate approval?
A material change to the literal, selected system, mapping policy, or interpretation requires a current confirmation under the workflow's rules. The pure classifier does not manage approval itself. Its format result is evidence for a separate versioned candidate controller.
How can an export lose zeroes?
A serializer or importer can infer a numeric type and discard textual spelling. The example verifies Python JSON, SQLite TEXT, and CSV-reader behavior only. A spreadsheet is a separate importer with its own policy and must be tested through its actual supported workflow.
Does the CSV fixture certify spreadsheet safety?
No. It checks a specific standard-library writer and reader round trip. Spreadsheet type inference, formula handling, display formatting, and import settings are outside that local result. Keep the exported literal and test the actual consumer before making a broader compatibility claim.
Should authentication PINs enter these fixtures?
No. They have a different security and business purpose. This guide's PIN is explicitly postal, and every fixture is synthetic. A postal format pass cannot authenticate a caller or authorize access to an account record.
Which cases establish the parser boundary?
Use leading zeroes, numeric input, short values, wrong systems, ZIP+4 separators, uncertain positions, supported decimal mappings, unsupported numeric characters, and exact round trips. Keep assignment and delivery explicitly untested. Then add integration tests for the actual data interfaces you deploy.
Ship a named postal contract
Select the postal system, preserve the literal string, and keep uncertain characters visible. Run the complete fixture and inspect its emitted JSON, database, and CSV-reader values. A correct local format result is useful evidence without being an address or delivery guarantee.
Audit every import and export that can change the field's type. Treat repair as a new current candidate and keep capture separate from authentication. The result is a small, repeatable contract that preserves what the caller supplied and makes missing evidence explicit.
