Connexion · Créer un compte

SKYNET

par henoc Gnaoule · mis à jour il y a 1 min

Skynet est un projet qui pour but de developper une Intelligence artificielle générale

JavaScript Licence MIT 13 fichiers

80 lignes · 3,2 Ko

# -*- coding: utf-8 -*-
"""
app.py — service d'extraction de texte pour les CV de Doobito (tourne sur le VPS).

    POST /extraire   (multipart, champ « fichier », en-tête X-API-Key)  ->  {"ok": true, "texte": "...", "methode": "..."}
    GET  /sante      ->  {"ok": true}

Le service ne conserve rien : le fichier reçu est écrit dans un dossier temporaire, lu par extracteur.py dans un
processus séparé (temps et mémoire bornés), puis supprimé. Rien n'est journalisé du contenu des CV.
"""
import hmac
import json
import os
import subprocess
import sys
import tempfile
import threading

from fastapi import FastAPI, File, Header, HTTPException, UploadFile
from fastapi.responses import JSONResponse

CLE_API = os.environ.get("CV_API_KEY", "")
if len(CLE_API) < 32:
    raise RuntimeError("CV_API_KEY manquante ou trop courte (32 caractères minimum).")

TAILLE_MAX = 3 * 1024 * 1024
DELAI_SANS_OCR = 25
DELAI_AVEC_OCR = 70
SIMULTANES = threading.BoundedSemaphore(int(os.environ.get("CV_SIMULTANES", "3")))
DOSSIER = os.path.dirname(os.path.abspath(__file__))

app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)


def verifier_cle(cle):
    if not cle or not hmac.compare_digest(cle.encode("utf-8"), CLE_API.encode("utf-8")):
        raise HTTPException(status_code=401, detail="Clé invalide.")


@app.get("/sante")
def sante():
    return {"ok": True}


@app.post("/extraire")
def extraire(fichier: UploadFile = File(...), x_api_key: str = Header(default="")):
    verifier_cle(x_api_key)

    contenu = fichier.file.read(TAILLE_MAX + 1)
    if len(contenu) > TAILLE_MAX:
        raise HTTPException(status_code=413, detail="Fichier trop volumineux.")
    if not contenu:
        raise HTTPException(status_code=400, detail="Fichier vide.")

    if not SIMULTANES.acquire(blocking=False):
        raise HTTPException(status_code=503, detail="Service occupé, réessayez.")
    try:
        with tempfile.TemporaryDirectory(prefix="cv_") as dossier:
            chemin = os.path.join(dossier, "cv.bin")   # nom fixe : le nom envoyé par le client n'est jamais utilisé
            with open(chemin, "wb") as f:
                f.write(contenu)

            reponse = None
            for avec_ocr in (False, True):
                commande = [sys.executable, "-I", os.path.join(DOSSIER, "extracteur.py"), chemin] + (["--ocr"] if avec_ocr else [])
                try:
                    resultat = subprocess.run(commande, capture_output=True, timeout=DELAI_AVEC_OCR if avec_ocr else DELAI_SANS_OCR)
                except subprocess.TimeoutExpired:
                    raise HTTPException(status_code=504, detail="Lecture trop longue.")
                if resultat.returncode != 0:
                    raise HTTPException(status_code=422, detail="Fichier illisible.")
                reponse = json.loads(resultat.stdout.decode("utf-8"))
                # OCR seulement si la lecture normale n'a rien donné d'exploitable (CV scanné).
                lettres = sum(1 for c in reponse["texte"] if c.isalpha())
                if lettres >= 60 or avec_ocr or reponse["methode"] not in ("pypdf", "pdfminer", "aucune"):
                    break
        return JSONResponse({"ok": True, "texte": reponse["texte"], "methode": reponse["methode"]})
    finally:
        SIMULTANES.release()