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.2.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.2.0-py3-none-any.whl (46.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: eticket_document_sdk-1.2.0.tar.gz
  • Upload date:
  • Size: 277.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eticket_document_sdk-1.2.0.tar.gz
Algorithm Hash digest
SHA256 e27e0dd27bd78eb4717f8f44c536a10ff8e34be33c3397171c57d0d30244d4aa
MD5 2c99555c10fad99891482d4fd4afcfb1
BLAKE2b-256 b3834dc4bd8030b9a420e093e2c1c37ce7c6896297553b7ecd4b4b8d603dc624

See more details on using hashes here.

Provenance

The following attestation bundles were made for eticket_document_sdk-1.2.0.tar.gz:

Publisher: publish.yml on hailong2107/eticket-document-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

File hashes

Hashes for eticket_document_sdk-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 60164a703d47c5c4136b2fea1bc59b981d63e410c1d2b28a68d238422be72bf0
MD5 d2ce63168690024f9afc4f2411c761e2
BLAKE2b-256 24b9f218dcad169cbe17175b0992d96ab9d01186d6845c56e4b8a4f59a55e3df

See more details on using hashes here.

Provenance

The following attestation bundles were made for eticket_document_sdk-1.2.0-py3-none-any.whl:

Publisher: publish.yml on hailong2107/eticket-document-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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