Skip to main content

Parse airline e-ticket PDFs into strongly-typed JSON with high accuracy.

Project description

eticket-document-sdk

Parse airline e-ticket PDFs and convert them into strongly-typed JSON with very high accuracy.

The SDK primarily targets PDFs that already contain a text layer (the common case for e-ticket receipts, booking confirmations and itineraries). OCR is used only as a fallback when text extraction is insufficient.

from eticket_document_sdk import ETicketParser

parser = ETicketParser()
booking = parser.parse("ticket.pdf")
print(booking.model_dump())
{
  "booking_code": "F65A7Y",
  "ticket_number": "7382421531079",
  "passenger": { "first_name": "YUMIKO", "last_name": "KOHNO" },
  "currency": "JPY",
  "total_price": 467510,
  "segments": [{ "flight_number": "VN311", "origin": "NRT", "destination": "HAN" }]
}

Table of Contents


Installation

pip install eticket-document-sdk

Base install includes PDF text extraction (PyMuPDF + pdfplumber). The OCR fallback backend is a heavy optional extra:

# Only needed for scanned / image-only PDFs
pip install "eticket-document-sdk[ocr]"
Extra Pulls in When you need it
(base) pydantic, pymupdf, pdfplumber, python-dateutil Text-layer PDFs (the common case)
ocr paddleocr, opencv-python, numpy Scanned/image PDFs requiring OCR
dev pytest, pytest-cov, ruff, build Contributing / running tests

Requires Python 3.10+.


Quick Start

from eticket_document_sdk import ETicketParser

parser = ETicketParser()

# 1) From a file path (PDF or image)
booking = parser.parse("ticket.pdf")

# 2) From raw bytes
booking = parser.parse_bytes(pdf_bytes)

# 3) From already-extracted text (no PDF/OCR needed)
booking = parser.parse_text(raw_text)

print(booking.model_dump())          # python types
print(booking.model_dump(mode="json"))  # JSON-serializable (datetimes -> str)

# Convenience top-level accessors:
booking.booking_code     # "F65A7Y"
booking.ticket_number    # "7382421531079"
booking.currency         # "JPY"
booking.total_price      # 467510
booking.passenger.full_name
booking.segments[0].origin

Advanced Usage

Configuration

parser = ETicketParser(
    enable_ocr=True,             # allow OCR fallback (default True)
    ocr_langs=["vi", "en", "ja"],# OCR languages (Vietnamese/English/Japanese)
    debug=False,                 # enable DEBUG logging for the SDK
    strict_validation=False,     # raise ValidationError on invalid output
    text_quality_threshold=0.35, # below this, OCR fallback kicks in
)

Detailed result envelope

parse() returns a Booking. For metadata about how the document was parsed, use the *_detailed variants, which return a ParseResult:

result = parser.parse_detailed("ticket.pdf")

result.success            # True
result.parser             # "vietnam_airlines"
result.extraction_method  # ExtractionMethod.TEXT_LAYER | OCR | RAW_TEXT
result.confidence         # 0.0 .. 1.0 completeness heuristic
result.warnings           # list of non-fatal validation issues
result.booking            # the Booking object

Also available: parse_bytes_detailed(...) and parse_text_detailed(...).

Thread-safety & performance

A single ETicketParser instance is reusable and thread-safe. The PaddleOCR model, compiled regexes and the parser registry are created once and shared, so models are never reloaded between calls. Create one instance and reuse it (e.g. for batch processing — see examples/batch_parse.py).


Data Model

All models are Pydantic v2 (eticket_document_sdk.models, re-exported from eticket_document_sdk.schemas.pydantic_models).

Booking
├── status: TICKETED | CONFIRMED | PENDING | CANCELLED | UNKNOWN
├── passenger: Passenger
│   ├── title, first_name, last_name, full_name
│   ├── membership_number          # frequent-flyer number
│   └── passenger_type             # ADT | CHD | INF
├── segments: list[FlightSegment]
│   ├── segment_id, flight_number, airline
│   ├── origin, destination        # IATA codes
│   ├── departure_datetime, arrival_datetime
│   ├── departure_terminal, arrival_terminal
│   ├── cabin_class, fare_basis, seat
│   ├── free_baggage, status
├── ticket: Ticket
│   ├── booking_code, ticket_number, currency
│   ├── base_fare, taxes, total_price
│   ├── tax_breakdown: list[TaxItem]   # {code, amount}
│   ├── fare_basis, fare_calculation, endorsement
│   ├── issue_date, payment
└── booking_code / ticket_number / currency / total_price   # convenience mirrors

Parser Architecture

The SDK follows a clean, layered pipeline:

            ┌─────────────────────────────────────────────────────────┐
 source ──▶ │ 1. detect type (PDF / IMAGE / TEXT)        core.classifier│
            │ 2. extract text layer (PyMuPDF → pdfplumber) pdf.*         │
            │ 3. measure quality                          pdf.text_extr. │
            │ 4. OCR fallback if needed (PaddleOCR)        ocr.*          │
            │ 5. classify airline → pick parser           core.classifier│
            │ 6. parse fields                             parsers.*       │
            │ 7. validate (business rules)                core.validator  │
            │ 8. return Pydantic Booking                  models.*        │
            └─────────────────────────────────────────────────────────┘

Key design points:

  • Text-first. OCR is only attempted when the extracted text layer falls below the quality threshold. PyMuPDF is the primary extractor; pdfplumber is the fallback engine.
  • Lazy heavy deps. PyMuPDF, pdfplumber, PaddleOCR, OpenCV and numpy are all imported lazily, so importing the SDK is cheap and the OCR stack is optional.
  • Centralized regex (utils/regex.py) compiled once at import time.
  • Pluggable parsers via a thread-safe registry (see below).

Custom Parser Plugin

Add a new airline without modifying the SDK core. Implement BaseParser and register it:

from eticket_document_sdk import (
    BaseParser, Booking, Ticket, FlightSegment, Passenger, register_parser,
)
from eticket_document_sdk.parsers.generic.parser import GenericExtractors


class JapanAirlinesParser(BaseParser):
    code = "JL"
    name = "japan_airlines"

    def can_parse(self, text: str) -> float:
        # Return 0..1 confidence that this layout is yours.
        return 0.9 if "JAPAN AIRLINES" in text.upper() else 0.0

    def parse(self, text: str) -> Booking:
        return Booking(
            ticket=Ticket(
                booking_code=GenericExtractors.find_pnr(text),
                ticket_number=GenericExtractors.find_ticket_number(text),
                currency=GenericExtractors.find_currency(text),
            ),
            passenger=Passenger(),
            segments=[],
        )


register_parser("JL", JapanAirlinesParser())

# From now on ETicketParser auto-selects it for matching tickets.

The classifier calls can_parse() on every registered parser and selects the highest scorer; airline-specific parsers always win ties against the generic fallback. See examples/custom_parser.py for a runnable version.

This is the same mechanism by which future airlines (ANA, Air France, Emirates, Singapore Airlines, …) are added.


Error Handling

All exceptions derive from ETicketSDKError:

Exception Raised when
DocumentReadError File can't be opened/read, or is empty/corrupt
UnsupportedDocumentError Input type/layout not supported
OCRFailedError OCR needed but disabled/unavailable, or produced nothing
ParserError No parser matched, or a parser failed
ValidationError Output failed business-rule validation (strict mode)
from eticket_document_sdk import ETicketParser, ETicketSDKError

try:
    booking = ETicketParser().parse("ticket.pdf")
except ETicketSDKError as exc:
    print(f"Failed: {exc}")

Validation (core/validator.py) checks ticket-number format (13 digits), PNR format (6 alphanumerics), currency code, flight-number format and segment presence. In non-strict mode, issues are returned as result.warnings; in strict_validation=True mode a ValidationError is raised.


Development Guide

git clone https://github.com/your-org/eticket-document-sdk
cd eticket-document-sdk

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# Run the test suite with coverage (target: 90%+)
pytest --cov=eticket_document_sdk --cov-report=term-missing

# Lint / format
ruff check .
ruff format .

Tests use a bundled real Vietnam Airlines fixture (tests/fixtures/). OCR-path tests use a fake engine, so PaddleOCR is not required to run the suite.

Project layout mirrors the pipeline: core/ (orchestration), pdf/ (extraction), ocr/ (fallback), parsers/ (plugins), models/ + schemas/ (Pydantic), utils/, exceptions/.


Release Guide

  1. Update the version in pyproject.toml and eticket_document_sdk/__init__.py (__version__).
  2. Update the changelog / release notes.
  3. Run the full test suite and linters; ensure coverage ≥ 90%.
  4. Build the distributions:
    python -m build
    
    This produces dist/eticket_document_sdk-<version>-py3-none-any.whl and the sdist .tar.gz.
  5. Smoke-test the wheel in a clean virtualenv:
    pip install dist/eticket_document_sdk-*.whl
    
  6. Publish:
    python -m twine upload dist/*
    
  7. Tag the release: git tag v<version> && git push --tags.

Roadmap

The extractor/parser boundary is deliberately abstract so additional backends can be added without breaking the public API:

  • Cloud OCR / Document AI: Azure Document Intelligence, AWS Textract, Google Document AI — pluggable as alternative OCREngine implementations or text providers.
  • LLM structured outputs: OpenAI and Claude structured outputs as alternative parser plugins behind the same BaseParser contract.

Because selection happens via the registry + classifier, these can be layered in as new strategies while ETicketParser.parse(...) stays unchanged.


License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eticket_document_sdk-1.0.0.tar.gz (277.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eticket_document_sdk-1.0.0-py3-none-any.whl (46.8 kB view details)

Uploaded Python 3

File details

Details for the file eticket_document_sdk-1.0.0.tar.gz.

File metadata

  • Download URL: eticket_document_sdk-1.0.0.tar.gz
  • Upload date:
  • Size: 277.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for eticket_document_sdk-1.0.0.tar.gz
Algorithm Hash digest
SHA256 175c46214d4976b89b4f7818027475ea656a5d4835a6090bf4a06417ede14097
MD5 904aa92879bfccfa48586ea57889fe97
BLAKE2b-256 9d45b5496fabda4f71452dd7a99f2987e59dd66379290f90f69aa18e53d3fdd4

See more details on using hashes here.

File details

Details for the file eticket_document_sdk-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for eticket_document_sdk-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 883dbcc2f26a6692e4250698961c91656a4612481a1032a2c6cbbab699a3dc74
MD5 0244d383144725c98bdc0c23774b5920
BLAKE2b-256 cf26a9bb618f4188e15eb1f930a3d510d1787e73fe913aac9fdc1715294660ed

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page