feat(api): add validator, FastAPI app structure, and health endpoint

Wave 3 tasks complete:
- Task 7: Validator with 4 checks (pflichtfelder, betraege, ustid, pdf_abgleich)
- Task 8: FastAPI app with CORS, exception handlers, JSON logging
- Task 9: Health endpoint returning status and version

Features:
- validate_invoice() runs selected validation checks
- Exception handlers for ExtractionError and generic errors
- GET /health returns {status: healthy, version: 1.0.0}

Tests: 52 validator tests covering all validation rules
This commit is contained in:
m3tm3re
2026-02-04 19:57:12 +01:00
parent c1f603cd46
commit 4791c91f06
6 changed files with 1795 additions and 6 deletions

View File

@@ -1,7 +1,37 @@
"""FastAPI application for ZUGFeRD invoice processing."""
import json
import logging
from datetime import datetime
import uvicorn
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from src.extractor import ExtractionError
from src.models import HealthResponse
class JSONFormatter(logging.Formatter):
def format(self, record):
log_data = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"message": record.getMessage(),
}
if hasattr(record, "data"):
log_data["data"] = record.data
return json.dumps(log_data)
logger = logging.getLogger(__name__)
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
app = FastAPI(
title="ZUGFeRD Service",
@@ -9,6 +39,48 @@ app = FastAPI(
description="REST API for ZUGFeRD invoice extraction and validation",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.exception_handler(ExtractionError)
async def extraction_error_handler(request: Request, exc: ExtractionError):
return JSONResponse(
status_code=400,
content={
"error": exc.error_code,
"message": exc.message,
"details": exc.details,
},
)
@app.exception_handler(Exception)
async def generic_error_handler(request: Request, exc: Exception):
logger.error(f"Internal error: {exc}")
return JSONResponse(
status_code=500,
content={
"error": "internal_error",
"message": "An internal error occurred",
},
)
@app.get("/health", response_model=HealthResponse)
async def health_check() -> HealthResponse:
"""Health check endpoint.
Returns:
HealthResponse with status and version.
"""
return HealthResponse(status="healthy", version="1.0.0")
def run(host: str = "0.0.0.0", port: int = 5000) -> None:
"""Run the FastAPI application.