A Python application that extracts blood test data from PDF documents using Google's Gemini AI and optionally updates Google Spreadsheets with the results.
This app was developed to support healthcare professionals within the Servicio Canario de Salud who assist migrant children arriving in the Canary Islands. The tool streamlines the recording of historical blood test results, providing an invaluable data source for both clinical follow-up and future medical research.
The app is live in https://blood-test-parser.vercel.app/.
- PDF Text Extraction: Extract blood test data from PDF documents
- AI-Powered Parsing: Uses Google Gemini AI to interpret and structure blood test results
- Batch Processing: Process single files or entire directories
- Data Validation: Validate extracted data against expected results
- Google Sheets Integration: Automatically update Google Spreadsheets with extracted data
- Patient Matching: Smart patient matching using name combinations
- Error Handling: Comprehensive error handling and logging
- Copy-Paste Support: Generate tab-separated values for manual data entry
-
Clone the repository:
git clone <repository-url> cd blood_test_parser
-
Install dependencies:
uv sync
-
Set up environment variables:
export GOOGLE_API_KEY="your_gemini_api_key"
python main.py path/to/blood_test.pdfpython main.py path/to/blood_tests_directory/python main.py path/to/file.pdf --validate --validation-threshold 85.0- Follow the Google Sheets Setup Guide
- Place your
credentials.jsonfile in the project directory - Run with spreadsheet integration:
python main.py path/to/blood_test.pdf --spreadsheet "YOUR_SPREADSHEET_ID"The system:
- Extracts blood test data from PDFs using AI
- Searches for patients across ALL sheets/tabs in your spreadsheet using the "FILIACION" column
- Matches patients by
NOMBRE + APELLIDOS(with fallback toAPELLIDOS + NOMBRE) - Automatically detects which sheet the patient is in
- Updates the matching row in the correct sheet with blood test results
- Provides copy-paste values for unmatched patients
python main.py [INPUT_PATH] [OPTIONS]
Arguments:
INPUT_PATH Path to PDF file or directory
Options:
--api-key KEY Google API key (or set GOOGLE_API_KEY env var)
--output, -o FILE Output JSON file path
--spreadsheet, -s ID Google Spreadsheet ID or name to update
--credentials FILE Path to Google credentials file (default: credentials.json)
--validate Enable validation against expected results
--validation-threshold N Accuracy threshold for validation (default: 80.0)
--validation-report FILE Path to save detailed validation report
--verbose, -v Enable verbose logging
--help, -h Show help messagepython main.py blood_test.pdf --api-key "your_api_key"python main.py blood_tests/ \
--spreadsheet "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" \
--credentials "my_credentials.json"python main.py blood_tests/ \
--validate \
--validation-threshold 90.0 \
--validation-report "validation_results.json" \
--verboseThe extracted data follows this schema:
{
"NOMBRE": "JUAN",
"APELLIDOS": "GARCIA RODRIGUEZ",
"HOSPITAL": "NEGRIN",
"NRO_HISTORIA_CLINICA": "12345678",
"NRO_MUESTRA": "ABC123456",
"HEMOGLOBINA": 14.2,
"HEMATOCRITO": 42.5,
"VCM": 87.3,
"ADE": 13.2,
"PLAQUETAS": 298,
"LEUCOCITOS": 6.8,
"EOSINOFILOS_TOTALES": 0.12,
"EOSINOFILOS_PORCENTAJE": 1.8,
"GLUCOSA": 98,
"CREATININA": 0.9,
"ALT": 22,
"AST": 18,
"GGT": 28,
"COLESTEROL": 187,
"FERRITINA": 125,
"VIH": 0,
"VHA": null,
"VHC": 0,
"LUES": 1,
"STRONGYLOIDES": null,
"SARAMPION": null,
"SCHISTOSOMA": null
}- Extraction Results:
extraction_results/extraction_results_TIMESTAMP.json - Validation Reports:
validation_reports/validation_report_TIMESTAMP.json - Log Files:
blood_test_parser.log
GOOGLE_API_KEY: Your Google Gemini API key
See GOOGLE_SHEETS_SETUP.md for detailed instructions on:
- Creating a Google Cloud project
- Enabling APIs
- Setting up service account credentials
- Preparing your spreadsheet
- Multi-Sheet Search: Searches across ALL sheets/tabs in the spreadsheet
- Primary Match:
NOMBRE + APELLIDOS→ Search in FILIACION column of each sheet - Fallback Match:
APELLIDOS + NOMBRE→ Search in FILIACION column of each sheet - Case Insensitive: All matching is case-insensitive
- Space Tolerant: Extra spaces are ignored
- Sheet Detection: Automatically identifies which sheet contains the patient
- Duplicate Handling: Multiple matches (across all sheets) are flagged for manual review
- Missing Patient: Unmatched patients generate copy-paste values
The system handles various error scenarios:
- Missing credentials: Clear setup instructions provided
- Spreadsheet access: Permission and sharing guidance
- Patient not found: Copy-paste values for manual entry
- Duplicate patients: Warning with row numbers
- API errors: Detailed error logging
- Network issues: Graceful degradation with manual fallbacks
-
Authentication Errors:
- Verify
credentials.jsonexists and is valid - Check spreadsheet sharing with service account email
- Verify
-
Patient Not Found:
- Verify NOMBRE Y APELLIDO column exists in spreadsheet
- Check patient name formatting (spaces, accents, etc.)
- Use
--verboseflag for detailed matching logs
-
API Rate Limits:
- The system handles rate limiting automatically
- Large batches may take longer to process
-
Column Mapping Issues:
- Ensure spreadsheet headers match expected column names
- Check for typos in column headers
- Use case-insensitive matching
Enable verbose logging for troubleshooting:
python main.py file.pdf --spreadsheet "ID" --verboseOriginally built as a small-scale experiment, this project loads htmx via a local minified file. This bypasses CDN restrictions present in the Servicio Canario de Salud's environment (Microsoft Edge). While migrating to a bundler like Vite or Webpack is planned, the local file remains for now as htmx is our only—and very lightweight—dependency.
As this was originally built as a small-scale experiment, the code in this repo was heavily vibe-coded to prioritise speed and exploration. This means it does not follow coding best practices and should not be used as an example of such.