Commit ·
e002dc4
1
Parent(s): 10bc8b7
Push streamlit app to hugging face
Browse files- .gitattributes +1 -0
- .gitignore +16 -0
- README.md +90 -0
- cli_demo.py +121 -0
- image.png +3 -0
- logo_image.png +3 -0
- nhtsa_api_call.py +22 -0
- requirements.txt +42 -3
- streamlit_app.py +204 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# virtual environments
|
| 2 |
+
venv/
|
| 3 |
+
.venv/
|
| 4 |
+
|
| 5 |
+
# python cache
|
| 6 |
+
__pycache__/
|
| 7 |
+
|
| 8 |
+
# log info and local stuff
|
| 9 |
+
*.log
|
| 10 |
+
.DS_Store
|
| 11 |
+
.idea/
|
| 12 |
+
.vscode/
|
| 13 |
+
|
| 14 |
+
# Environment & secrets
|
| 15 |
+
.env
|
| 16 |
+
env/
|
README.md
CHANGED
|
@@ -17,3 +17,93 @@ Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :hear
|
|
| 17 |
|
| 18 |
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
| 19 |
forums](https://discuss.streamlit.io).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
| 19 |
forums](https://discuss.streamlit.io).
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# Car Dealership VIN Lookup App
|
| 23 |
+
|
| 24 |
+

|
| 25 |
+
|
| 26 |
+
## 📌 Overview
|
| 27 |
+
This is a simple app for a car dealership that allows users to look up vehicle information (year, make, and model) by entering a VIN number.
|
| 28 |
+
|
| 29 |
+
Additionally, the app keeps track of search frequency and lets users view the **Top 3 most looked-up vehicle makes**.
|
| 30 |
+
|
| 31 |
+
All vehicle data is retrieved from the [NHTSA (National Highway Traffic Safety Administration) VIN decoding API](https://vpic.nhtsa.dot.gov/api/), accessed via the helper module [`nhtsa_api_call.py`](./nhtsa_api_call.py).
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## ⚙️ Features
|
| 36 |
+
- 🔍 Lookup vehicle details (year, make, model) by VIN number
|
| 37 |
+
- 📊 View the **Top 3 most searched vehicle makes**
|
| 38 |
+
- 🌐 Fetches real-time data from the **NHTSA VIN API**
|
| 39 |
+
- 🖥️ Simple and interactive command-line interface
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
## 📦 Python Version
|
| 43 |
+
|
| 44 |
+
Tested on: Python 3.10
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
## 🚀 Getting Started
|
| 48 |
+
|
| 49 |
+
**How to run locally**
|
| 50 |
+
|
| 51 |
+
1. Clone the Repository
|
| 52 |
+
git clone https://github.com/averyestopinal/AIPI503PROJECT
|
| 53 |
+
cd AIPI503PROJECT
|
| 54 |
+
|
| 55 |
+
2. Create a virtual environment (optional but recommended):
|
| 56 |
+
python -m venv .venv
|
| 57 |
+
source .venv/bin/activate # macOS/Linux
|
| 58 |
+
.venv\Scripts\activate # Windows
|
| 59 |
+
|
| 60 |
+
3. Install Dependencies
|
| 61 |
+
This app uses only standard Python libraries (no external dependencies).However, make sure you are running Python 3.7+.
|
| 62 |
+
pip install -r requirements.txt
|
| 63 |
+
|
| 64 |
+
4. Run the Streamlit app:
|
| 65 |
+
streamlit run streamlit_app.py
|
| 66 |
+
|
| 67 |
+
**How to Run cli_demo.py**
|
| 68 |
+
This is a simple command-line version of the VIN decoder. Make sure your terminal is in the same directory as cli_demo.py.
|
| 69 |
+
|
| 70 |
+
python cli_demo.py
|
| 71 |
+
|
| 72 |
+
**Link to the Hugging Faces Space**
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
🧩 Project Structure
|
| 78 |
+
.
|
| 79 |
+
├── streamlit_app.py # Entry point for the Streamlit application
|
| 80 |
+
├── cli_demo.py # Command-line interface version of the VIN decoder
|
| 81 |
+
├── nhtsa_api_call.py # Helper module for API requests
|
| 82 |
+
├── car_image.png # Image file used in the Streamlit application
|
| 83 |
+
├── logo_image.py # Logo image file used in the Streamlit application
|
| 84 |
+
├── README.md # Project documentation
|
| 85 |
+
├── requirements.txt # Python Dependencies
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
📡 API Reference
|
| 89 |
+
The app uses the NHTSA VIN Decoder API which provides vehicle details when supplied with a VIN number.
|
| 90 |
+
|
| 91 |
+
Example request: https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVinValues/<VIN>?format=json
|
| 92 |
+
|
| 93 |
+
---
|
| 94 |
+
🙌 Credits
|
| 95 |
+
NHTSA Vehicle API
|
| 96 |
+
Streamlit
|
| 97 |
+
Hugging Face Spaces
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
👨💻 Author
|
| 101 |
+
Developed as part of a learning project by
|
| 102 |
+
1. Avery Estopinal
|
| 103 |
+
2. Sharmil K
|
| 104 |
+
3. Eugenia Tate
|
| 105 |
+
4. Jaideep Aher
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
Contributions are welcome!
|
| 109 |
+
Feel free to fork and submit a pull request 🚀
|
cli_demo.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
cli_demo.py
|
| 3 |
+
|
| 4 |
+
Author: Eugenia Tate
|
| 5 |
+
Date: August 24, 2025
|
| 6 |
+
|
| 7 |
+
A simple command-line application for a car dealership that allows users
|
| 8 |
+
to look up vehicle information (year, make, and model) by VIN number.
|
| 9 |
+
The app also lets a user view top 3 looked up vehicle makes.
|
| 10 |
+
The data is retrieved from the NHTSA (National Highway Traffic Safety
|
| 11 |
+
Administration) VIN decoding API via the helper module `nhtsa_api_call.py`.
|
| 12 |
+
|
| 13 |
+
Usage:
|
| 14 |
+
python cli_demo.py
|
| 15 |
+
"""
|
| 16 |
+
import nhtsa_api_call, re, csv, os
|
| 17 |
+
import pandas as pd
|
| 18 |
+
|
| 19 |
+
TOP3 = 3
|
| 20 |
+
|
| 21 |
+
def is_valid_vin (vin):
|
| 22 |
+
"""
|
| 23 |
+
Check whether vin is valid.
|
| 24 |
+
A valid VIN is a VIN of 17 alphanumeric characters long and can not
|
| 25 |
+
include I, O, Q.
|
| 26 |
+
"""
|
| 27 |
+
vin = vin.upper()
|
| 28 |
+
if len(vin) != 17:
|
| 29 |
+
return False
|
| 30 |
+
if not re.match(r'^[A-HJ-NPR-Z0-9]{17}$', vin):
|
| 31 |
+
return False
|
| 32 |
+
return True
|
| 33 |
+
|
| 34 |
+
def save_vehicle(year, make, model, filename = "vin_lookup_history.csv"):
|
| 35 |
+
"""Append VIN lookup result to a CSV file."""
|
| 36 |
+
file_exists = os.path.isfile(filename)
|
| 37 |
+
with open(filename, "a", newline="") as f:
|
| 38 |
+
writer = csv.writer(f)
|
| 39 |
+
if not file_exists:
|
| 40 |
+
# add first row
|
| 41 |
+
writer.writerow(["year", "make", "model"])
|
| 42 |
+
writer.writerow([year, make, model])
|
| 43 |
+
|
| 44 |
+
def display_top3(filename = "vin_lookup_history.csv"):
|
| 45 |
+
"""Show top 3 car makes by lookup frequency"""
|
| 46 |
+
# if file does not exist then there is no history yet
|
| 47 |
+
if not os.path.isfile(filename):
|
| 48 |
+
print("\nNo VIN lookup history found. Please look up some VINs first.\n")
|
| 49 |
+
return
|
| 50 |
+
# create a dataframe using file data
|
| 51 |
+
df = pd.read_csv(filename)
|
| 52 |
+
# save number each make appears in the log as a series
|
| 53 |
+
counts = df['make'].value_counts()
|
| 54 |
+
# file exists but no data has been added yet
|
| 55 |
+
if len(counts) == 0:
|
| 56 |
+
print("\n No data in the log yet.\n")
|
| 57 |
+
return
|
| 58 |
+
# if there are fewer than 3 data entries in the log then the program will
|
| 59 |
+
# at least display 1 or 2 most looked up makes
|
| 60 |
+
if len(counts) < TOP3:
|
| 61 |
+
print(f"\nNot enough data for displaying top 3. Showing top {len(counts)} instead:\n")
|
| 62 |
+
top_makes = counts.head(len(counts))
|
| 63 |
+
else:
|
| 64 |
+
print(f"\n Top {TOP3} Car Makes from Lookups:\n")
|
| 65 |
+
top_makes = counts.head(TOP3)
|
| 66 |
+
|
| 67 |
+
for make, count in top_makes.items():
|
| 68 |
+
print(f" {make}: {count} lookups")
|
| 69 |
+
# an empty line print to space out CLI prompts
|
| 70 |
+
print()
|
| 71 |
+
|
| 72 |
+
def main():
|
| 73 |
+
"""
|
| 74 |
+
Run the command-line VIN lookup loop.
|
| 75 |
+
|
| 76 |
+
Prompts the user for a VIN number, calls the NHTSA API,
|
| 77 |
+
and prints out the vehicle information if found.
|
| 78 |
+
Enter '0' to exit.
|
| 79 |
+
"""
|
| 80 |
+
print("🚗 Welcome to the Car Dealership VIN Lookup!\n")
|
| 81 |
+
|
| 82 |
+
while True:
|
| 83 |
+
print("Choose an option from the menu:")
|
| 84 |
+
print("1. Lookup a VIN")
|
| 85 |
+
print("2. Show Top 3 Most Looked-up Makes")
|
| 86 |
+
print("0. Exit")
|
| 87 |
+
choice = input("Enter your choice: ").strip()
|
| 88 |
+
|
| 89 |
+
# exit condition gets checked first
|
| 90 |
+
if choice == "0":
|
| 91 |
+
print("👋 You chose to exit VIN lookup. Goodbye!")
|
| 92 |
+
break
|
| 93 |
+
# user selected to add a VIN
|
| 94 |
+
elif choice == "1":
|
| 95 |
+
vin = input("Please enter a valid VIN or 0 (zero) to exit: \n").strip()
|
| 96 |
+
if vin == "0":
|
| 97 |
+
continue
|
| 98 |
+
# check if vin is valid, and if not print error message
|
| 99 |
+
if not is_valid_vin(vin):
|
| 100 |
+
print("Invalid VIN format. Must be 17 letters/numbers (no I, O, Q). Try again.\n")
|
| 101 |
+
continue
|
| 102 |
+
# vin is valid, perform VIN lookup
|
| 103 |
+
else:
|
| 104 |
+
year, make, model = nhtsa_api_call.get_vehicle_info(vin)
|
| 105 |
+
if year and make and model:
|
| 106 |
+
print(f"✅ VIN {vin}")
|
| 107 |
+
print(f" Year: {year}")
|
| 108 |
+
print(f" Make: {make}")
|
| 109 |
+
print(f" Model: {model}")
|
| 110 |
+
save_vehicle(year, make, model) #save the data in the lookup history file
|
| 111 |
+
else:
|
| 112 |
+
print(f"❌ VIN {vin} not found in database. Try again.")
|
| 113 |
+
# user selected to view top3 vehicle makes
|
| 114 |
+
elif choice == "2":
|
| 115 |
+
display_top3()
|
| 116 |
+
# user entered something other than 1,2 or 0
|
| 117 |
+
else:
|
| 118 |
+
print("Invalid option. Please choose 1, 2, or 0.\n")
|
| 119 |
+
|
| 120 |
+
if __name__ == "__main__":
|
| 121 |
+
main()
|
image.png
ADDED
|
Git LFS Details
|
logo_image.png
ADDED
|
Git LFS Details
|
nhtsa_api_call.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
|
| 3 |
+
def get_vehicle_info(vin):
|
| 4 |
+
"""
|
| 5 |
+
This function takes the parameter "vin" and uses the NHTSA Decode VIN API to retrive year, make, and model
|
| 6 |
+
The API returns a JSON file which is not a dictionary so the file must be searched with a for loop by "Variable"
|
| 7 |
+
For more API information visit https://vpic.nhtsa.dot.gov/api/
|
| 8 |
+
"""
|
| 9 |
+
url = f'https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVin/{vin}?format=json'
|
| 10 |
+
response = requests.get(url);
|
| 11 |
+
data = response.json()
|
| 12 |
+
|
| 13 |
+
for item in data["Results"]:
|
| 14 |
+
if item["Variable"] in ["Model Year"]:
|
| 15 |
+
year = item["Value"]
|
| 16 |
+
if item["Variable"] in ["Make"]:
|
| 17 |
+
make = item["Value"]
|
| 18 |
+
if item["Variable"] in ["Model"]:
|
| 19 |
+
model = item["Value"]
|
| 20 |
+
|
| 21 |
+
return year, make, model
|
| 22 |
+
|
requirements.txt
CHANGED
|
@@ -1,3 +1,42 @@
|
|
| 1 |
-
altair
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
altair==5.5.0
|
| 2 |
+
attrs==25.3.0
|
| 3 |
+
blinker==1.9.0
|
| 4 |
+
cachetools==6.2.0
|
| 5 |
+
certifi==2025.8.3
|
| 6 |
+
charset-normalizer==3.4.3
|
| 7 |
+
click==8.2.1
|
| 8 |
+
contourpy==1.3.2
|
| 9 |
+
cycler==0.12.1
|
| 10 |
+
fonttools==4.59.1
|
| 11 |
+
gitdb==4.0.12
|
| 12 |
+
GitPython==3.1.45
|
| 13 |
+
idna==3.10
|
| 14 |
+
Jinja2==3.1.6
|
| 15 |
+
jsonschema==4.25.1
|
| 16 |
+
jsonschema-specifications==2025.4.1
|
| 17 |
+
kiwisolver==1.4.9
|
| 18 |
+
MarkupSafe==3.0.2
|
| 19 |
+
matplotlib==3.10.5
|
| 20 |
+
narwhals==2.2.0
|
| 21 |
+
numpy==2.2.6
|
| 22 |
+
packaging==25.0
|
| 23 |
+
pandas==2.3.2
|
| 24 |
+
pillow==11.3.0
|
| 25 |
+
protobuf==6.32.0
|
| 26 |
+
pyarrow==21.0.0
|
| 27 |
+
pydeck==0.9.1
|
| 28 |
+
pyparsing==3.2.3
|
| 29 |
+
python-dateutil==2.9.0.post0
|
| 30 |
+
pytz==2025.2
|
| 31 |
+
referencing==0.36.2
|
| 32 |
+
requests==2.32.5
|
| 33 |
+
rpds-py==0.27.0
|
| 34 |
+
six==1.17.0
|
| 35 |
+
smmap==5.0.2
|
| 36 |
+
streamlit==1.49.0
|
| 37 |
+
tenacity==9.1.2
|
| 38 |
+
toml==0.10.2
|
| 39 |
+
tornado==6.5.2
|
| 40 |
+
typing_extensions==4.15.0
|
| 41 |
+
tzdata==2025.2
|
| 42 |
+
urllib3==2.5.0
|
streamlit_app.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
VIN Decoder Streamlit App
|
| 3 |
+
|
| 4 |
+
This app allows users to input a Vehicle Identification Number (VIN) and fetch vehicle
|
| 5 |
+
information (year, make, model). The data is retrieved from the NHTSA (National Highway Traffic Safety
|
| 6 |
+
Administration) VIN decoding API via the helper module `nhtsa_api_call.py`.
|
| 7 |
+
Useful for car dealerships who need quick VIN lookups.
|
| 8 |
+
|
| 9 |
+
Usage:
|
| 10 |
+
streamlit run streamlit_vin_decoder.py
|
| 11 |
+
|
| 12 |
+
Author: Sharmil Nanjappa
|
| 13 |
+
Date: August 25, 2025
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import streamlit as st # Import the Streamlit library for creating the web app
|
| 17 |
+
import nhtsa_api_call # Import the API call function from external file
|
| 18 |
+
import os
|
| 19 |
+
import re
|
| 20 |
+
import pandas as pd
|
| 21 |
+
import matplotlib.pyplot as plt
|
| 22 |
+
from collections import Counter
|
| 23 |
+
|
| 24 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Gets the current directory
|
| 25 |
+
logo_img_file = os.path.join(BASE_DIR, "logo_image.png")
|
| 26 |
+
car_img_file = os.path.join(BASE_DIR, "car_image.png")
|
| 27 |
+
TOP3 = 3 # Define top N makes to display
|
| 28 |
+
|
| 29 |
+
# Check if the 'make_history' key is already in Streamlit's session state
|
| 30 |
+
if "make_history" not in st.session_state:
|
| 31 |
+
# If not, initialize it as an empty list to store searched vehicle makes
|
| 32 |
+
st.session_state.make_history = []
|
| 33 |
+
|
| 34 |
+
# Define a function to track the vehicle make each time a VIN is decoded
|
| 35 |
+
def track_make(make):
|
| 36 |
+
"""
|
| 37 |
+
Store the searched make in session state for pie chart stats.
|
| 38 |
+
"""
|
| 39 |
+
# Proceed only if a valid make value is provided
|
| 40 |
+
if make:
|
| 41 |
+
# Capitalize and append the make to the session history list
|
| 42 |
+
st.session_state.make_history.append(make.title())
|
| 43 |
+
# Optional: Display the current list of searched makes in the sidebar for debugging
|
| 44 |
+
#st.sidebar.write("Debug – History:", st.session_state.make_history)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def show_top_3_makes():
|
| 48 |
+
"""
|
| 49 |
+
Display a pie chart of the top 3 most searched vehicle makes.
|
| 50 |
+
|
| 51 |
+
if not st.session_state.make_history:
|
| 52 |
+
st.warning("No vehicle makes have been searched yet.")
|
| 53 |
+
return
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
if not st.session_state.make_history:
|
| 59 |
+
st.warning("No vehicle makes have been searched yet.")
|
| 60 |
+
return
|
| 61 |
+
|
| 62 |
+
# Count how many times each make was searched
|
| 63 |
+
counts = Counter(st.session_state.make_history)
|
| 64 |
+
|
| 65 |
+
if len(counts) < TOP3:
|
| 66 |
+
st.warning(f"Not enough data to display Top 3. Showing Top {len(counts)} instead.")
|
| 67 |
+
|
| 68 |
+
# Get the top N (3 or fewer)
|
| 69 |
+
top_n = counts.most_common(min(len(counts), TOP3))
|
| 70 |
+
|
| 71 |
+
labels = [item[0].upper() for item in top_n] # Label for the vehicle make, displayed in upper case for each slice of the pie chart
|
| 72 |
+
sizes = [item[1] for item in top_n] # Count of each make in the Top3. Used to determine how large each slice of the pie chart is.
|
| 73 |
+
colors = ['#4682B4', '#1E3F66', '#0B1F3A'][:len(labels)] #[colors of pie chart slices][match slice count]
|
| 74 |
+
with st.container():
|
| 75 |
+
|
| 76 |
+
st.markdown("""
|
| 77 |
+
<div style="
|
| 78 |
+
max-width: 700px;
|
| 79 |
+
margin: 0 auto;
|
| 80 |
+
padding: 12px 24px;
|
| 81 |
+
border: 1px solid #ccc;
|
| 82 |
+
border-radius: 10px;
|
| 83 |
+
background-color: #f9f9f9;
|
| 84 |
+
text-align: center;
|
| 85 |
+
font-size: 16px;
|
| 86 |
+
font-weight: 500;
|
| 87 |
+
">
|
| 88 |
+
Pie chart displaying the Top 3 Vehicles<br>
|
| 89 |
+
searched based on their Make
|
| 90 |
+
</div>
|
| 91 |
+
""", unsafe_allow_html=True)
|
| 92 |
+
|
| 93 |
+
fig, ax = plt.subplots(figsize=(3,3), dpi=150)
|
| 94 |
+
|
| 95 |
+
# Draw pie chart with borders and percentage labels
|
| 96 |
+
wedges, texts, autotexts = ax.pie(
|
| 97 |
+
sizes,
|
| 98 |
+
labels=labels,
|
| 99 |
+
colors=colors,
|
| 100 |
+
autopct='%1.1f%%',
|
| 101 |
+
startangle=90, # autopct shows % and startangle starts the first slice from the top
|
| 102 |
+
wedgeprops={'edgecolor': 'white', 'linewidth': 1}
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
ax.axis('equal') # Forces the chart to be a perfect circle
|
| 106 |
+
|
| 107 |
+
# Style text and labels
|
| 108 |
+
for text in texts: #The labels of each pie slice
|
| 109 |
+
text.set_fontsize(5)
|
| 110 |
+
text.set_color('#000') # text color: Black
|
| 111 |
+
text.set_fontweight('semibold')
|
| 112 |
+
|
| 113 |
+
for autotext in autotexts: #The percentages shown on each slice
|
| 114 |
+
autotext.set_fontsize(5)
|
| 115 |
+
autotext.set_color('#fff') # text color: White
|
| 116 |
+
|
| 117 |
+
st.pyplot(fig)
|
| 118 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
| 119 |
+
|
| 120 |
+
def get_vin(vin):
|
| 121 |
+
"""
|
| 122 |
+
Decodes the VIN and displays model,make, model year.
|
| 123 |
+
Validates VIN format and displays vehicle details or error message.
|
| 124 |
+
"""
|
| 125 |
+
status_area = st.empty()
|
| 126 |
+
col1, col2 = st.columns([1, 1])
|
| 127 |
+
|
| 128 |
+
if len(vin) != 17:
|
| 129 |
+
status_area.error(f"VIN must be exactly 17 characters long.")
|
| 130 |
+
return
|
| 131 |
+
elif not re.match(r'^[A-HJ-NPR-Z0-9]{17}$', vin):
|
| 132 |
+
status_area.error(f"VIN must be alpha-numeric and cannot include letters I, O, Q.")
|
| 133 |
+
return
|
| 134 |
+
else:
|
| 135 |
+
status_area.info("Decoding VIN...")
|
| 136 |
+
|
| 137 |
+
try:
|
| 138 |
+
year, make, model = nhtsa_api_call.get_vehicle_info(vin)
|
| 139 |
+
|
| 140 |
+
if not all([year, make, model]) or any([v is None for v in [year, make, model]]):
|
| 141 |
+
status_area.warning(f"Incomplete vehicle data returned. Please verify the VIN and try again.")
|
| 142 |
+
else:
|
| 143 |
+
status_area.success("VIN decoded successfully!")
|
| 144 |
+
|
| 145 |
+
track_make(make)
|
| 146 |
+
with col1:
|
| 147 |
+
st.image(car_img_file, use_container_width=True)
|
| 148 |
+
|
| 149 |
+
with col2:
|
| 150 |
+
st.write(f"Below are the vehicle details :")
|
| 151 |
+
st.write("🔍 Vehicle Information")
|
| 152 |
+
st.write(f"**Make:** {make}")
|
| 153 |
+
st.write(f"**Model:** {model}")
|
| 154 |
+
st.write(f"**Model Year:** {year}")
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
status_area.error(f"An error occurred while decoding the VIN: {e}")
|
| 158 |
+
|
| 159 |
+
def main():
|
| 160 |
+
"""
|
| 161 |
+
Sets Streamlitcomponents for the page layout, handles user input, and calls get_vin function.
|
| 162 |
+
"""
|
| 163 |
+
|
| 164 |
+
col1, col2 = st.columns([1, 2])
|
| 165 |
+
with col1:
|
| 166 |
+
st.image(logo_img_file, width=180)
|
| 167 |
+
with col2:
|
| 168 |
+
st.markdown("""
|
| 169 |
+
<div style='text-align: left; margin-top: 30px;'>
|
| 170 |
+
<h1 style='font-size: 38px;'>Vehicle VIN Decoder</h1>
|
| 171 |
+
</div>
|
| 172 |
+
""", unsafe_allow_html=True)
|
| 173 |
+
st.write("*" * 50)
|
| 174 |
+
|
| 175 |
+
st.markdown("<h5 style='text-align: center;'>Welcome to the Car Dealership VIN Lookup!</h5>", unsafe_allow_html=True)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
with st.expander("ℹ️ About this app"):
|
| 179 |
+
st.write("This app is designed to help car dealerships and automotive professionals effortlessly decode Vehicle Identification Numbers (VINs) to retrieve key vehicle details such as model,make and model year. By simply entering a 17-character VIN, users can access verified vehicle information. The data is fetched in real-time from the National Highway Traffic Safety Administration (NHTSA). Whether you're validating trade-ins, checking vehicle specs, or streamlining inventory intake, this tool delivers quick and reliable insights.")
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
st.markdown("Enter a **17-character VIN** below to decode the vehicle info.")
|
| 184 |
+
|
| 185 |
+
vin = st.text_input("Enter VIN:", max_chars=17).strip().upper()
|
| 186 |
+
if st.button("Decode VIN", use_container_width=True):
|
| 187 |
+
if vin:
|
| 188 |
+
get_vin(vin)
|
| 189 |
+
else:
|
| 190 |
+
st.error(f"❌ Please enter a VIN before fetching the vehicle information.")
|
| 191 |
+
|
| 192 |
+
# Show top 3 searched makes
|
| 193 |
+
st.markdown("---")
|
| 194 |
+
if st.button("Top 3 Makes Searched", use_container_width=True):
|
| 195 |
+
show_top_3_makes()
|
| 196 |
+
|
| 197 |
+
st.markdown("___")
|
| 198 |
+
st.markdown("<p style='text-align: center; font-size: 14px;'>© 2025 Car Dealership VIN Lookup", unsafe_allow_html=True) #Footer for the webpage
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
if __name__ == "__main__":
|
| 204 |
+
main()
|