This commit is contained in:
urieljarethbusiness-cpu
2026-05-17 10:14:14 -06:00
parent d8773b2508
commit 64b3d15b90
61 changed files with 7612 additions and 0 deletions
+281
View File
@@ -0,0 +1,281 @@
from __future__ import annotations
from pathlib import Path
from fastapi import BackgroundTasks, FastAPI, File, HTTPException, Query, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, PlainTextResponse
from backend.app.config import settings
from backend.app.managers.knowledge_manager import DuplicateSourceError, KnowledgeManager
from backend.app.models.schemas import MarkdownUpdate, SubjectCreate, WeekCreate, WeekUpdate
from backend.app.services.ingestion_service import IngestionService
app = FastAPI(title="Knowledge Station", version="0.1.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["http://127.0.0.1:3000", "http://localhost:3000"],
allow_origin_regex=r"http://(127\.0\.0\.1|localhost):\d+",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
manager = KnowledgeManager()
ingestion = IngestionService(manager)
@app.get("/health")
def health() -> dict[str, str]:
return {"status": "ok"}
@app.get("/settings/status")
def settings_status() -> dict:
return {
"mistral_configured": bool(settings.mistral_api_key),
"deepgram_configured": bool(settings.deepgram_api_key),
"data_dir": str(settings.app_data_dir),
"database_path": str(settings.database_path),
}
@app.post("/subjects")
def create_subject(payload: SubjectCreate) -> dict:
try:
return manager.create_subject(payload.name)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.get("/subjects")
def list_subjects() -> list[dict]:
return manager.list_subjects()
@app.get("/subjects/{subject_id}")
def get_subject(subject_id: int) -> dict:
try:
return manager.get_subject(subject_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/subjects/{subject_id}")
def delete_subject(subject_id: int) -> dict:
try:
manager.delete_subject(subject_id)
return {"status": "deleted"}
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/subjects/{subject_id}/weeks")
def create_week(subject_id: int, payload: WeekCreate) -> dict:
try:
return manager.create_week(subject_id, payload.number, payload.title)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks")
def list_weeks(subject_id: int) -> list[dict]:
try:
manager.get_subject(subject_id)
return manager.list_weeks(subject_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks/{week_number}")
def get_week(subject_id: int, week_number: int) -> dict:
try:
return manager.get_week(subject_id, week_number)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.put("/subjects/{subject_id}/weeks/{week_number}")
def update_week(subject_id: int, week_number: int, payload: WeekUpdate) -> dict:
try:
return manager.update_week(subject_id, week_number, payload.number, payload.title)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.delete("/subjects/{subject_id}/weeks/{week_number}")
def delete_week(subject_id: int, week_number: int) -> dict:
try:
manager.delete_week(subject_id, week_number)
return {"status": "deleted"}
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/subjects/{subject_id}/weeks/{week_number}/files")
def upload_file(
subject_id: int,
week_number: int,
background_tasks: BackgroundTasks,
file: UploadFile = File(...),
use_ocr: bool = Query(False, description="Usar Mistral OCR para PDFs"),
page_ranges: str | None = Query(None, description="Rangos de paginas PDF, por ejemplo: 1-5,8,10-12"),
) -> dict:
try:
source = manager.save_upload(subject_id, week_number, file)
job = manager.create_job(source["id"], "Archivo recibido")
background_tasks.add_task(process_job, job["id"], source["id"], use_ocr, page_ranges)
return {"source": source, "job": job}
except DuplicateSourceError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/jobs/{job_id}")
def get_job(job_id: int) -> dict:
try:
return manager.get_job(job_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/jobs/{job_id}/retry")
def retry_job(
job_id: int,
background_tasks: BackgroundTasks,
use_ocr: bool = Query(False),
page_ranges: str | None = Query(None),
) -> dict:
try:
job = manager.get_job(job_id)
if not job.get("source_id"):
raise HTTPException(status_code=400, detail="El trabajo no tiene fuente asociada")
manager.update_job(job_id, "queued", "Reintento en cola")
background_tasks.add_task(process_job, job_id, job["source_id"], use_ocr, page_ranges)
return manager.get_job(job_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks/{week_number}/jobs")
def list_week_jobs(subject_id: int, week_number: int) -> list[dict]:
try:
return manager.list_jobs_for_week(subject_id, week_number)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks/{week_number}/sources")
def list_sources(subject_id: int, week_number: int) -> list[dict]:
try:
return manager.list_sources(subject_id, week_number)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/sources/{source_id}")
def delete_source(source_id: int) -> dict:
try:
manager.delete_source(source_id)
return {"status": "deleted"}
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/sources/{source_id}/reprocess")
def reprocess_source(
source_id: int,
background_tasks: BackgroundTasks,
use_ocr: bool = Query(False),
page_ranges: str | None = Query(None),
) -> dict:
try:
source = manager.get_source(source_id)
if manager.source_has_active_job(source_id):
raise HTTPException(status_code=409, detail="La fuente ya tiene un trabajo en cola o en proceso")
job = manager.create_job(source["id"], "Reprocesamiento en cola")
background_tasks.add_task(process_job, job["id"], source["id"], use_ocr, page_ranges)
return {"source": source, "job": job}
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks/{week_number}/documents")
def list_documents(subject_id: int, week_number: int) -> list[dict]:
try:
return manager.list_documents(subject_id, week_number)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/documents/{document_id}")
def get_document(document_id: int) -> dict:
try:
return manager.get_document(document_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/documents/{document_id}/markdown", response_class=PlainTextResponse)
def get_document_markdown(document_id: int) -> str:
try:
document = manager.get_document(document_id)
return Path(document["markdown_path"]).read_text(encoding="utf-8")
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.put("/documents/{document_id}/markdown")
def update_document_markdown(document_id: int, payload: MarkdownUpdate) -> dict:
try:
return manager.update_document_markdown(document_id, payload.content)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/documents/{document_id}/download")
def download_document(document_id: int) -> FileResponse:
try:
document = manager.get_document(document_id)
path = Path(document["markdown_path"])
return FileResponse(path, filename=path.name, media_type="text/markdown")
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.delete("/documents/{document_id}")
def delete_document(document_id: int) -> dict:
try:
manager.delete_document(document_id)
return {"status": "deleted"}
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks/{week_number}/export")
def export_week(subject_id: int, week_number: int) -> FileResponse:
try:
path = manager.export_week_markdown(subject_id, week_number)
return FileResponse(path, filename=path.name, media_type="text/markdown")
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/subjects/{subject_id}/weeks/{week_number}/export.zip")
def export_week_zip(subject_id: int, week_number: int) -> FileResponse:
try:
path = manager.export_week_markdown_zip(subject_id, week_number)
return FileResponse(path, filename=path.name, media_type="application/zip")
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
def process_job(job_id: int, source_id: int, use_ocr: bool, page_ranges: str | None = None) -> None:
manager.update_job(job_id, "processing", "Procesando archivo")
manager.update_source_status(source_id, "processing")
try:
document = ingestion.process_source(source_id, use_ocr=use_ocr, page_ranges=page_ranges)
manager.update_job(job_id, "completed", "Documento procesado", document_id=document["id"])
except Exception as exc:
manager.update_source_status(source_id, "failed")
manager.update_job(job_id, "failed", str(exc))