Explorer
/tmp/restic-stage/research-apps/content-extraction/content-extraction-app.py
← Zurück ↓ Download
from pathlib import Path
from urllib.parse import unquote_plus

from fastapi import FastAPI, Request, HTTPException, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

import config
import db

BASE_DIR = Path(__file__).parent
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))

app = FastAPI(title=config.APP_TITLE)


@app.on_event("startup")
def on_startup():
    db.init_db()


def _render_search_page(request: Request, q: str = "", sort: str = "score"):
    results = []
    q = unquote_plus(q or "").strip()
    sort = (sort or "score").strip().lower()
    search_info = {"query": q, "expanded_terms": [], "total_count": 0, "shown_count": 0, "per_term_counts": [], "has_more": False, "limit": 50, "total_matches": 0, "returned_count": 0, "sort": sort}
    if q:
        results, search_info = db.search_videos(q, limit=50, sort=sort)
        search_info["sort"] = sort
    return templates.TemplateResponse(request, "index.html", {
        "app_title": config.APP_TITLE,
        "app_tagline": config.APP_TAGLINE,
        "source_count": db.count_sources(),
        "item_count": db.count_items(),
        "last_import": db.get_last_import_stats(),
        "query": q,
        "results": results,
        "search_info": search_info,
        "sort": search_info.get("sort", sort),
    })


@app.get("/", response_class=HTMLResponse)
def index(request: Request, q: str = "", sort: str = "score"):
    return _render_search_page(request, q, sort)


@app.get("/search", response_class=HTMLResponse)
def search(request: Request, q: str = "", sort: str = "score"):
    return _render_search_page(request, q, sort)


@app.get("/videos/{source_id}", response_class=HTMLResponse)
def video_detail_page(request: Request, source_id: int):
    source = db.get_source(source_id)
    if source is None:
        raise HTTPException(status_code=404, detail="Video nicht gefunden")
    return templates.TemplateResponse(request, "video_detail.html", {
        "app_title": config.APP_TITLE,
        "source": source,
    })


@app.get("/health")
def health():
    return {"status": "ok", "app": "content-extraction"}


@app.get("/sources", response_class=HTMLResponse)
def sources_page(
    request: Request,
    q: str = "",
    channel: str = "",
    language: str = "",
    transcript_status: str = "",
    is_high_value: str = "",
    has_artifact_path: str = "",
    has_item: str = "",
):
    filters = {
        "q": q, "channel": channel, "language": language,
        "transcript_status": transcript_status, "is_high_value": is_high_value,
        "has_artifact_path": has_artifact_path, "has_item": has_item,
    }
    sources, total = db.list_sources(filters)
    dropdowns = db.get_distinct_source_values()
    return templates.TemplateResponse(request, "sources.html", {
        "app_title": config.APP_TITLE,
        "sources": sources,
        "total": total,
        "max_results": db.MAX_RESULTS,
        "filters": filters,
        "channels": dropdowns["channels"],
        "languages": dropdowns["languages"],
        "transcript_statuses": dropdowns["transcript_statuses"],
    })


@app.post("/admin/import-sources")
def admin_import_sources():
    """Manuell angestossener, read-only Import aus YouTube Research.
    Keine automatische Ausfuehrung -- nur ueber diesen Endpunkt/Button."""
    stats = db.import_sources_from_youtube_research()
    return {"import": stats}


@app.get("/admin/import-status")
def admin_import_status():
    return {"last_import": db.get_last_import_stats()}


@app.post("/sources/{source_id}/create-item")
def create_item(source_id: int):
    item, created = db.create_item_from_source(source_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Quelle nicht gefunden")
    return {"item": item, "created": created}


@app.get("/items", response_class=HTMLResponse)
def items_page(
    request: Request,
    q: str = "",
    content_type: str = "",
    status: str = "",
    imitation_score: str = "",
    effort: str = "",
    feasibility: str = "",
    expected_value: str = "",
):
    filters = {
        "q": q, "content_type": content_type, "status": status,
        "imitation_score": imitation_score, "effort": effort,
        "feasibility": feasibility, "expected_value": expected_value,
    }
    items, total = db.list_items(filters)
    return templates.TemplateResponse(request, "items.html", {
        "app_title": config.APP_TITLE,
        "items": items,
        "total": total,
        "max_results": db.MAX_RESULTS,
        "filters": filters,
        "content_types": db.CONTENT_TYPES,
        "status_values": db.STATUS_VALUES,
    })


@app.get("/items/{item_id}", response_class=HTMLResponse)
def item_detail_page(request: Request, item_id: int):
    item = db.get_item_detail(item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Kandidat nicht gefunden")
    return templates.TemplateResponse(request, "item_detail.html", {
        "app_title": config.APP_TITLE,
        "item": item,
        "content_types": db.CONTENT_TYPES,
        "status_values": db.STATUS_VALUES,
        "level_values": db.LEVEL_VALUES,
    })


@app.post("/items/{item_id}/rating")
def save_rating(
    item_id: int,
    content_type: str = Form(None),
    status: str = Form(None),
    nachahmbarkeit: int = Form(None),
    aufwand: str = Form(None),
    umsetzbarkeit: str = Form(None),
    erwarteter_nutzen: str = Form(None),
    rationale: str = Form(None),
):
    try:
        item = db.save_item_rating(
            item_id,
            content_type=content_type or None,
            status=status or None,
            nachahmbarkeit=nachahmbarkeit,
            aufwand=aufwand or None,
            umsetzbarkeit=umsetzbarkeit or None,
            erwarteter_nutzen=erwarteter_nutzen or None,
            rationale=rationale or None,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    if item is None:
        raise HTTPException(status_code=404, detail="Kandidat nicht gefunden")
    return {"item": item}


@app.get("/matrix", response_class=HTMLResponse)
def matrix_page(request: Request):
    """Read-only Auswertungsansicht ueber vorhandene ce_items/ce_ratings.
    Erzeugt keine neuen Daten, keine automatische Bewertung."""
    return templates.TemplateResponse(request, "matrix.html", {
        "app_title": config.APP_TITLE,
        "rows": db.list_content_matrix(),
    })