christiangeorgelucas/vin-tools
v0.1.1PythonThe vin-tools package provides a comprehensive set of tools for decoding Vehicle Identification Numbers (VINs) according to ISO standards and SAE specifications. It enables users to validate VIN formats, verify check digits, decode manufacturer information, and generate anonymized VINs for data aggregation, making it ideal for automotive data analysis and compliance applications.
Published by Christian George Lucas· MIT
Use cases
- •Validate VIN formats for compliance checks
- •Decode manufacturer details for automotive databases
- •Generate anonymized VINs for privacy-preserving analytics
- •Verify check digits for VIN integrity
- •Disambiguate model year codes for accurate vehicle records
Nodes (7)
The DecodeManufacturerDetails node decodes manufacturer-specific information such as body, engine, model, plant, transmission, and serial number from a given VIN. It only supports a limited number of manufacturers, returning empty fields and a supported flag set to false for all other VINs.
View source
from gen.messages_pb2 import VinInput, ManufacturerDetailsResult
from gen.axiom_context import AxiomContext
from nodes._common import normalize_vin, build_vin, is_manufacturer_known, detail_to_str, NormalizationError
def decode_manufacturer_details(ax: AxiomContext, input: VinInput) -> ManufacturerDetailsResult:
"""Decode manufacturer-specific body/engine/model/plant/transmission/
serial from the VDS and VIS. Only a handful of manufacturers have a
dedicated decoder available (currently: Lada/AvtoVAZ, Nissan, Opel,
Renault, Dafra, Bajaj, and Ford Australia) — `supported=false` for every
other VIN is expected, not an error.
"""
try:
normalized = normalize_vin(input.vin)
vin_obj = build_vin(normalized)
except NormalizationError as exc:
return ManufacturerDetailsResult(error=exc.code)
details = vin_obj.details
supported = bool(details) and any(
bool(getattr(details, field))
for field in ("body", "engine", "model", "plant", "transmission", "serial")
)
if not supported:
manufacturer = vin_obj.manufacturer if is_manufacturer_known(vin_obj) else ""
return ManufacturerDetailsResult(supported=False, manufacturer=manufacturer, error="")
def field(name: str) -> str:
return detail_to_str(getattr(details, name))
return ManufacturerDetailsResult(
supported=True,
manufacturer=vin_obj.manufacturer,
body=field("body"),
engine=field("engine"),
model=field("model"),
plant=field("plant"),
transmission=field("transmission"),
serial=field("serial"),
error="",
)
The DecodeModelYear node decodes the model-year code from a VIN's position-10 character and resolves any ambiguity using the NHTSA/SAE position-7 rule, providing both a list of candidate years and the most likely year based on the decoded information.
View source
from gen.messages_pb2 import VinInput, ModelYearResult
from gen.axiom_context import AxiomContext
from nodes._common import normalize_vin, build_vin, decode_model_year as _decode_model_year, NormalizationError
def decode_model_year(ax: AxiomContext, input: VinInput) -> ModelYearResult:
"""Decode the VIN position-10 model-year code and disambiguate it using
the NHTSA/SAE position-7 rule (49 CFR 565.16, note to Table VII): a
numeric position-7 character means the position-10 code refers to the
1980-2009 cycle, an alphabetic one means 2010-2039. Both the raw
candidate set (every year the 30-year-cycle code alone is consistent
with) and the disambiguated single most-likely year are returned.
"""
try:
normalized = normalize_vin(input.vin)
vin_obj = build_vin(normalized)
except NormalizationError as exc:
return ModelYearResult(error=exc.code)
year_code, candidates, most_likely, pos7, error = _decode_model_year(vin_obj)
return ModelYearResult(
year_code=year_code,
candidate_years=candidates,
most_likely_year=most_likely,
position_7_character=pos7,
error=error,
)
The DecodeVin node performs a comprehensive decoding of a Vehicle Identification Number (VIN), extracting information such as the manufacturer, region, model year, and various manufacturer-specific details. It consolidates the results from multiple decoding processes into a single output, while also validating the VIN structure.
View source
from gen.messages_pb2 import VinInput, VinDecodeResult
from gen.axiom_context import AxiomContext
from nodes._common import (
normalize_vin,
build_vin,
is_manufacturer_known,
compute_expected_check_digit,
detail_to_str,
decode_model_year as _decode_model_year,
NormalizationError,
)
def decode_vin(ax: AxiomContext, input: VinInput) -> VinDecodeResult:
"""Full decode of one VIN in a single envelope: WMI (manufacturer,
region, country), the SAE position-9 check digit, the position-10 model
year (with the position-7 disambiguation rule applied), the Squish
(Pattern) VIN, and manufacturer-specific body/engine/model/plant/
transmission/serial when available. Equivalent to calling every other
node in this package and combining the results.
"""
try:
normalized = normalize_vin(input.vin)
vin_obj = build_vin(normalized)
except NormalizationError as exc:
return VinDecodeResult(vin=(input.vin or "").strip().upper()[:64], format_valid=False, error=exc.code, detail=exc.detail)
known = is_manufacturer_known(vin_obj)
manufacturer = vin_obj.manufacturer if known else ""
rule_applies = bool(getattr(vin_obj.assembler, "uses_sae_checkdigit", True))
actual_check_digit = vin_obj.vds[5] if len(vin_obj.vds) > 5 else ""
expected_check_digit = compute_expected_check_digit(normalized)
year_code, candidate_years, most_likely_year, _pos7, year_error = _decode_model_year(vin_obj)
details = vin_obj.details
manufacturer_details_supported = bool(details) and any(
bool(getattr(details, field))
for field in ("body", "engine", "model", "plant", "transmission", "serial")
)
def detail_field(name: str) -> str:
if not manufacturer_details_supported:
return ""
return detail_to_str(getattr(details, name))
return VinDecodeResult(
vin=normalized,
format_valid=True,
wmi=vin_obj.wmi,
manufacturer=manufacturer,
manufacturer_known=known,
region=vin_obj.region or "",
country=vin_obj.country or "",
is_small_volume_manufacturer=vin_obj.manufacturer_is_small,
vds=vin_obj.vds,
vis=vin_obj.vis,
checkdigit_rule_applies=rule_applies,
checkdigit_valid=(actual_check_digit == expected_check_digit),
expected_check_digit=expected_check_digit,
candidate_model_years=candidate_years,
most_likely_model_year=most_likely_year,
squish_vin=vin_obj.squish_vin,
manufacturer_details_supported=manufacturer_details_supported,
body=detail_field("body"),
engine=detail_field("engine"),
model=detail_field("model"),
plant=detail_field("plant"),
transmission=detail_field("transmission"),
serial=detail_field("serial"),
error="",
detail=(year_error or ""),
)
The DecodeWmi node decodes the World Manufacturer Identifier (WMI) from a Vehicle Identification Number (VIN) to extract the manufacturer, region, and country of origin according to ISO 3780 standards. It also determines if the manufacturer is low-volume, producing fewer than 1000 vehicles per year, and indicates if the manufacturer is known based on a reference database.
View source
from gen.messages_pb2 import VinInput, WmiResult
from gen.axiom_context import AxiomContext
from nodes._common import normalize_vin, build_vin, is_manufacturer_known, NormalizationError
def decode_wmi(ax: AxiomContext, input: VinInput) -> WmiResult:
"""Decode the WMI (World Manufacturer Identifier, VIN positions 1-3) per
ISO 3780: manufacturer, region, and country of origin, plus whether the
3rd character marks a low-volume manufacturer (fewer than 1000
vehicles/year).
"""
try:
normalized = normalize_vin(input.vin)
vin_obj = build_vin(normalized)
except NormalizationError as exc:
return WmiResult(error=exc.code)
known = is_manufacturer_known(vin_obj)
manufacturer = vin_obj.manufacturer if known else ""
return WmiResult(
wmi=vin_obj.wmi,
manufacturer=manufacturer,
region=vin_obj.region or "",
country=vin_obj.country or "",
is_small_volume_manufacturer=vin_obj.manufacturer_is_small,
manufacturer_known=known,
error="",
)
The SquishVin node generates a Squish (Pattern) VIN by extracting specific positions from a standard VIN, effectively anonymizing vehicle data for aggregation and privacy-preserving analytics.
View source
from gen.messages_pb2 import VinInput, SquishVinResult
from gen.axiom_context import AxiomContext
from nodes._common import normalize_vin, build_vin, NormalizationError
def squish_vin(ax: AxiomContext, input: VinInput) -> SquishVinResult:
"""Produce the Squish (Pattern) VIN: positions 1-8 plus 10-11, omitting
the position-9 check digit and the position-12-17 sequential serial.
Groups vehicles of the same make/model/year/plant while dropping the
individually-identifying tail — useful for anonymized aggregation.
"""
try:
normalized = normalize_vin(input.vin)
vin_obj = build_vin(normalized)
except NormalizationError as exc:
return SquishVinResult(error=exc.code)
return SquishVinResult(squish_vin=vin_obj.squish_vin, error="")
The ValidateVinFormat node checks if a Vehicle Identification Number (VIN) adheres to the structural requirements set by ISO 3779, ensuring it is exactly 17 characters long and contains only valid alphanumeric characters, excluding I, O, and Q. It returns a validation result indicating whether the VIN format is valid or not, along with error details for malformed inputs.
View source
from gen.messages_pb2 import VinInput, FormatValidationResult
from gen.axiom_context import AxiomContext
from nodes._common import normalize_vin, NormalizationError
def validate_vin_format(ax: AxiomContext, input: VinInput) -> FormatValidationResult:
"""Validate a VIN's structural form per ISO 3779: exactly 17 characters,
drawn only from the alphanumeric set that excludes I, O, and Q. Input is
whitespace-trimmed and upper-cased before checking. Does NOT check the
position-9 check digit (see VerifyCheckDigit) — this is form only.
"""
try:
normalized = normalize_vin(input.vin)
except NormalizationError as exc:
# Echo back a bounded preview only — never the full raw input, which
# could be far larger than any real VIN (see normalize_vin's own cap).
preview = (input.vin or "").strip().upper()[:64]
return FormatValidationResult(
valid=False,
normalized_vin=preview,
error=exc.code,
detail=exc.detail,
)
return FormatValidationResult(valid=True, normalized_vin=normalized, error="", detail="")
The VerifyCheckDigit node validates the position-9 check digit of a VIN according to the SAE J853 / ISO 3779 standard by calculating the expected check digit and comparing it to the actual check digit present in the VIN. It also indicates whether the check digit rule applies based on the manufacturer's conventions.
View source
from gen.messages_pb2 import VinInput, CheckDigitResult
from gen.axiom_context import AxiomContext
from nodes._common import normalize_vin, build_vin, compute_expected_check_digit, NormalizationError
def verify_check_digit(ax: AxiomContext, input: VinInput) -> CheckDigitResult:
"""Verify the SAE J853 / ISO 3779 position-9 check digit: transliterate
every character to a digit, take a weighted sum, then modulus 11 (a
remainder of 10 is written as the letter X). Not every manufacturer or
market follows this convention (notably Ford Australia, and VINs outside
the North American market generally) — `rule_applies` reports whether
this VIN's manufacturer is known to.
"""
try:
normalized = normalize_vin(input.vin)
vin_obj = build_vin(normalized)
except NormalizationError as exc:
return CheckDigitResult(error=exc.code)
rule_applies = bool(getattr(vin_obj.assembler, "uses_sae_checkdigit", True))
actual = vin_obj.vds[5] if len(vin_obj.vds) > 5 else ""
expected = compute_expected_check_digit(normalized)
return CheckDigitResult(
rule_applies=rule_applies,
actual_check_digit=actual,
expected_check_digit=expected,
valid=(actual == expected),
error="",
)
Messages (8)
Download .protoA boolean indicating whether the check digit rule applies to the VIN's manufacturer.
The actual check digit extracted from the VIN at position 9.
The expected check digit calculated using the algorithm.
A boolean indicating whether the actual check digit matches the expected check digit.
An error message if any issues occurred during the verification process.
A boolean indicating whether the VIN format is valid.
The normalized version of the VIN if valid, or a preview of the input if invalid.
A machine-readable error code indicating the type of validation failure.
A human-readable description providing more context about the validation error.
A boolean indicating whether the VIN is supported for decoding.
The name of the manufacturer associated with the decoded VIN.
The body type of the vehicle as decoded from the VIN.
The engine specifications of the vehicle as decoded from the VIN.
The model of the vehicle as decoded from the VIN.
The manufacturing plant where the vehicle was produced, as decoded from the VIN.
The transmission type of the vehicle as decoded from the VIN.
The serial number of the vehicle as decoded from the VIN.
An error message if any issues occurred during the decoding process.
The decoded model-year code derived from the VIN.
A list of all possible years that the model-year code could represent.
The single most likely year determined from the decoded model-year code and the position-7 rule.
The character in position-7 of the VIN that indicates the 30-year cycle for the model-year code.
An error message indicating if there was an unrecognized year code or other issues during decoding.
The resulting Squish VIN, which includes positions 1-8 and 10-11 of the original VIN.
An error message indicating any issues encountered during the VIN normalization process.
The normalized Vehicle Identification Number being decoded.
Indicates whether the VIN has a valid structural format.
The World Manufacturer Identifier part of the VIN.
The name of the manufacturer associated with the VIN.
A boolean indicating if the manufacturer is recognized.
The region where the vehicle manufacturer is located.
The country of the vehicle manufacturer.
Indicates if the manufacturer is classified as a small volume manufacturer.
The Vehicle Descriptor Section of the VIN.
The Vehicle Identifier Section of the VIN.
Indicates if the SAE check digit rule applies to this VIN.
A boolean indicating if the check digit is valid.
The expected value of the check digit based on the VIN.
A list of potential model years derived from the VIN.
The most probable model year based on the VIN decoding.
The Squish VIN representation derived from the original VIN.
Indicates if manufacturer-specific details are available for this VIN.
Details about the vehicle's body type.
Details regarding the engine specifications.
The specific model of the vehicle.
The manufacturing plant where the vehicle was produced.
Details about the vehicle's transmission type.
The serial number associated with the vehicle.
Any error message related to the decoding process.
Additional details or errors related to the model year decoding.
The Vehicle Identification Number (VIN) input for decoding manufacturer details.
The decoded World Manufacturer Identifier extracted from the VIN.
The name of the manufacturer associated with the WMI, if known.
The geographical region where the manufacturer is located.
The country of origin for the manufacturer.
A boolean indicating whether the manufacturer produces fewer than 1000 vehicles per year.
A boolean indicating whether the specific manufacturer is known based on the reference database.
An error message if the decoding process encounters an issue.
Use as a tool in any AI agent
christiangeorgelucas/vin-tools is callable as a tool from any MCP client (Claude, Cursor, or your own agent) via Axiom's hosted MCP server — no install, no download. Add the server, then your agent can search and invoke it, or pin it as a typed tool.
claude mcp add --transport http axiom https://api.axiomide.com/mcp --header "Authorization: Bearer YOUR_AXIOM_API_KEY"Replace YOUR_AXIOM_API_KEY with a key you create in Console → API Keys.
MCP server setup & other clients →Use this package
Sign in to import christiangeorgelucas/vin-tools into the graph editor — drop it into a flow and hit run. Or call it directly via the API Reference with your API key.
Sign in to useOpen editor