Server-side application for the SmartMailbox project. This FastAPI application receives images from the client, processes them to identify mail recipients, and provides feedback.
This project uses pyproject.toml to manage dependencies.
You can use either the standard venv with pip or the faster uv tool.
-
Create and activate a virtual environment: From the
serverdirectory, run:# Create the environment python -m venv .venv # Activate the environment # On Windows .venv\\Scripts\\activate # On macOS/Linux source .venv/bin/activate
-
Install Dependencies: With the virtual environment activated, run:
pip install .
-
Create and activate a virtual environment: First, install
uvif you don't have it:pip install uv. From theserverdirectory, run:# Create and activate the environment uv venv # Activate the environment # On Windows .venv\\Scripts\\activate # On macOS/Linux source .venv/bin/activate
-
Install Dependencies: With the virtual environment activated, run:
uv pip install .
Ensure your virtual environment is activated.
You can run the application from the server directory.
uvicorn app.main:app --reloadpython -m app.main{
"success": boolean, // true = success, false = fail
"recipient_ids": [], // Comma-separated List of matched IDs: [1,2,3]
"best_image_id": number,
"fail_reason": string // reason for failure
}- A match is valid if you can identify the recipient with reasonable certainty
- Accept approximate matches if you are confident it refers to the same person (e.g. handwriting ambiguity, abbreviated names, missing middle name, name order swapped, ...)
- If addressed to a household (e.g. "Familie Schneider"), include ALL recipients sharing that household_id
- If multiple recipients share the same name and there is no other clue to the correct one, include all of them
- "best_image_id" must be the 0-based index of the image that showed the address most clearly and completely or is most likely to be useful to a human viewer. Always provide this.
- "fail_reason" must be empty string "" on success; on failure describe briefly why matching failed
MODEL_NAME := qwen3.5:2b, etc.
TEST_TO_RUN := 1 | -5 | 20- | 34-65 | -6, 23-54, 70, 72-
One model:
tmux new -s modeltests
cd ~/SmartMailbox/server
source .venv/bin/activate
python -m tests.model.main MODEL_NAME [TESTS_TO_RUN]
Ctrl+B
d
tmux attach -t modeltestsAll:
tmux new -s modeltests
cd ~/SmartMailbox/server
source .venv/bin/activate
python -m tests.model.run_all_tests
Ctrl+B
d
tmux attach -t modeltestsmail types: postcard, letter, letter with window
writing: printed, handwriting, cursive handwriting
receivers: one, family, multiple, wrong person, french receiver
image qualities: perfect, slightly blurred, very blurred, cut off
Naming convention: l{letter_id}{format}{writing_style}_{quality}.png (e.g. l1_lpp.png, l10_pcv.png)
- letter_id: int
- format:
- LETTER: l
- POSTCARD: p
- WINDOWED_LETTER: w
- writing_style:
- PRINT: p
- CURSIVE: c
- HAND: h
- quality:
- PERFECT: p
- SLIGHTLY_BLURRED: s
- VERY_BLURRED: v
- CUT_OFF: c
Command to take image on Pi:
rpicam-still -o l0_lhp -e png -t 1500tbd
- id (Int, key)
- firstname (Text)
- middlename (Text)
- surname (Text)
- title (Text)
- email (Text | NULL (household email is used))
- household (Int, foreign key -> Households)
- id (Int, key)
- email (Text) # default if failed match
- country (Text)
- zipcode (Text)
- city (Text)
- street (Text)
- house_number (Text)
- (index)
- time (Real)
- tesseract_time (Real | NULL)
- llama_time (Real | NULL)
- match_found ("SUCCESS" | "FAIL")
- correct_recipient_ids (Bool) # all found recipient ids match all recipient ids for the test_case in the TestRecipientSolutions table
- correct_best_image_id (Bool)
- model_test_id (Int, foreign key -> ModelTests.id)
- complete_response (Text)
- extracted_text (Text) # empty for qwen
- error_msg (Text)
- id (Int, key)
- model (Int, foreign key -> Models.id)
- test_case_id (Int, foreign key -> TestCases.id)
- id (Int, key)
- name (Text) # llama3.2:1b, llama3.2:3b, llama3.3:70b, qwen3.5:9b(2b/4b/27b/35b/122b)
- family ("Qwen3" | "Llama")
- id (Int, key)
- letter_id (Int, foreign key -> Letters.id)
- image_selection ("ALL" | "PERFECT" | "SLIGHTLY_BLURRED" | "VERY_BLURRED" | "CUT_OFF")
- household_id (Int, foreign key)
- (index)
- model ("Qwen3" | "Llama", foreign key -> Models.family)
- prompt (Text)
- test_case_id (Int, foreign key -> TestCases.id, not unique)
- recipient_id (Int, foreign key -> Images.id, not unique)
- id (Int, key)
- format ("LETTER", "POSTCARD", "WINDOWED_LETTER")
- writing_style ("HAND", "PRINT", "CURSIVE")
- address (Text)
- id (Int, key)
- letter_id (Int, foreign key -> Letters.id)
- quality ("PERFECT" | "SLIGHTLY_BLURRED" | "VERY_BLURRED" | "CUT_OFF")
- image_path (Text)
You are a mail sorting assistant specializing in physical letter delivery.
INPUTS:
- List of registered recipients in JSON format
- Images of the same mail piece (indexed from 0)
TASK:
1. Read the recipient name and address visible in the images
2. Identify which image shows the address most clearly
3. Match the name against the recipient list
4. Return a single JSON object
MATCHING INSTRUCTIONS:
- A match is valid if you can identify the recipient with reasonable certainty
- Accept approximate matches if you are confident it refers to the same person (e.g. handwriting ambiguity, abbreviated names, missing middle name, name order swapped, ...)
- If addressed to a household (e.g. "Familie Schneider"), include ALL recipients sharing that household_id
- If multiple recipients share the same name and there is no other clue to the correct one, include all of them
- "best_image_id" must be the 0-based index of the image that showed the address most clearly and completely or is most likely to be useful to a human viewer. Always provide this.
- "fail_reason" must be empty string "" on success; on failure describe briefly why matching failed
---
EXAMPLE 1:
Images: [image 0 shows clearly: "Dr. Anna Müller, Hauptstraße 12, 76131 Karlsruhe"]
Recipients:
[{"recipient_id": 3, "household_id": 1, "email": "mueller@example.com", "firstname": "Anna", "middlename": "", "surname": "Müller", "title": "Dr.", "country": "DE", "zipcode": "76131", "city": "Karlsruhe", "street": "Hauptstraße", "house_number": "12"},
{"recipient_id": 7, "household_id": 2, "email": "k.mueller@example.com", "firstname": "Klaus", "middlename": "", "surname": "Müller", "title": "", "country": "DE", "zipcode": "76131", "city": "Karlsruhe", "street": "Gartenweg", "house_number": "3"}]
Output:
{"success": true, "recipient_ids": [3], "best_image_id": 0, "fail_reason": ""}
EXAMPLE 2:
Images: [image 0 is very blurred, image 1 shows partially: "Fam. Schneider, Rosenstr. 4, Berlin"]
Recipients:
[{"recipient_id": 11, "household_id": 5, "email": "schneider@example.com", "firstname": "Hans", "middlename": "", "surname": "Schneider", "title": "", "country": "DE", "zipcode": "10115", "city": "Berlin", "street": "Rosenstraße", "house_number": "4"},
{"recipient_id": 12, "household_id": 5, "email": "schneider@example.com", "firstname": "Maria", "middlename": "", "surname": "Schneider", "title": "", "country": "DE", "zipcode": "10115", "city": "Berlin", "street": "Rosenstraße", "house_number": "4"}]
Output:
{"success": true, "recipient_ids": [11, 12], "best_image_id": 1, "fail_reason": ""}
EXAMPLE 3:
Images: [image 0 is completely blurred, image 1 shows only "76131 Karlsruhe"]
Recipients: [{"recipient_id": 5, ...}]
Output:
{"success": false, "recipient_ids": [], "best_image_id": 1, "fail_reason": "address unreadable in all images"}
---
Recipients:
You are a mail sorting assistant specializing in physical letter delivery.
INPUTS:
- List of registered recipients in JSON format
- Extracted text from images of the same mail piece (via OCR), each labeled with an image index
TASK:
1. Read the recipient name and address in the extracted text
2. Identify which image shows the address most clearly
3. Match the name against the recipient list
4. Return a single JSON object
MATCHING INSTRUCTIONS:
- A match is valid if you can identify the recipient with reasonable certainty
- Accept approximate matches if you are confident it refers to the same person (e.g. handwriting ambiguity, abbreviated names, missing middle name, name order swapped, ...)
- If addressed to a household (e.g. "Familie Schneider"), include ALL recipients sharing that household_id
- If multiple recipients share the same name and there is no other clue to the correct one, include all of them
- "best_image_id" must be the 0-based index of the image that showed the address most clearly and completely or is most likely to be useful to a human viewer. Always provide this.
- "fail_reason" must be empty string "" on success; on failure describe briefly why matching failed
---
EXAMPLE 1:
Images: [image 0 shows clearly: "Dr. Anna Müller, Hauptstraße 12, 76131 Karlsruhe"]
Recipients:
[{"recipient_id": 3, "household_id": 1, "email": "mueller@example.com", "firstname": "Anna", "middlename": "", "surname": "Müller", "title": "Dr.", "country": "DE", "zipcode": "76131", "city": "Karlsruhe", "street": "Hauptstraße", "house_number": "12"},
{"recipient_id": 7, "household_id": 2, "email": "k.mueller@example.com", "firstname": "Klaus", "middlename": "", "surname": "Müller", "title": "", "country": "DE", "zipcode": "76131", "city": "Karlsruhe", "street": "Gartenweg", "house_number": "3"}]
Output:
{"success": true, "recipient_ids": [3], "best_image_id": 0, "fail_reason": ""}
EXAMPLE 2:
Images: [image 0 is very blurred, image 1 shows partially: "Fam. Schneider, Rosenstr. 4, Berlin"]
Recipients:
[{"recipient_id": 11, "household_id": 5, "email": "schneider@example.com", "firstname": "Hans", "middlename": "", "surname": "Schneider", "title": "", "country": "DE", "zipcode": "10115", "city": "Berlin", "street": "Rosenstraße", "house_number": "4"},
{"recipient_id": 12, "household_id": 5, "email": "schneider@example.com", "firstname": "Maria", "middlename": "", "surname": "Schneider", "title": "", "country": "DE", "zipcode": "10115", "city": "Berlin", "street": "Rosenstraße", "house_number": "4"}]
Output:
{"success": true, "recipient_ids": [11, 12], "best_image_id": 1, "fail_reason": ""}
EXAMPLE 3:
Images: [image 0 is completely blurred, image 1 shows only "76131 Karlsruhe"]
Recipients: [{"recipient_id": 5, ...}]
Output:
{"success": false, "recipient_ids": [], "best_image_id": 1, "fail_reason": "address unreadable in all images"}
---
Recipients: