# retoor <retoor@molodetz.nl>
from __future__ import annotations
import json
import logging
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional
from devplacepy.config import ISSLOP_MEDIA_DIR
from devplacepy.database import get_table
logger = logging.getLogger(__name__)
TABLE_ANALYSES = "isslop_analyses"
TABLE_EVENTS = "isslop_events"
TABLE_FILE_RESULTS = "isslop_file_results"
TABLE_IMAGE_RESULTS = "isslop_image_results"
TABLE_DOM_RESULTS = "isslop_dom_results"
TABLE_REPORTS = "isslop_reports"
EVENT_LIMIT_DEFAULT = 2000
EVENT_PAYLOAD_CAP = 60000
SIGNALS_CAP = 30000
LIST_LIMIT_DEFAULT = 50
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
def create_analysis(uid: str, source_url: str, owner_kind: str, owner_id: str) -> dict[str, Any]:
row: dict[str, Any] = {
"uid": uid,
"owner_kind": owner_kind,
"owner_id": owner_id,
"source_url": source_url,
"source_kind": "unknown",
"status": "pending",
"created_at": utc_now(),
"finished_at": None,
"content_hash": None,
"grade": None,
"slop_score": None,
"origin_score": None,
"quality_deficit_score": None,
"human_percent": None,
"ai_percent": None,
"category": None,
"confidence": None,
"files_total": 0,
"files_analyzed": 0,
"error_message_text": None,
"deleted_at": None,
"deleted_by": None,
}
get_table(TABLE_ANALYSES).insert(row)
return row
def update_analysis(uid: str, **fields: Any) -> None:
fields["uid"] = uid
get_table(TABLE_ANALYSES).update(fields, ["uid"])
def get_analysis(uid: str) -> Optional[dict[str, Any]]:
row = get_table(TABLE_ANALYSES).find_one(uid=uid, deleted_at=None)
return dict(row) if row else None
def list_analyses(owner_kind: str, owner_id: str, limit: int = LIST_LIMIT_DEFAULT) -> list[dict[str, Any]]:
rows = get_table(TABLE_ANALYSES).find(
owner_kind=owner_kind,
owner_id=owner_id,
deleted_at=None,
order_by=["-created_at"],
_limit=limit,
)
return [dict(row) for row in rows]
def claim_guest_analyses(guest_id: str, user_uid: str) -> int:
table = get_table(TABLE_ANALYSES)
rows = list(table.find(owner_kind="guest", owner_id=guest_id, deleted_at=None))
for row in rows:
table.update({"uid": row["uid"], "owner_kind": "user", "owner_id": user_uid}, ["uid"])
if rows:
logger.info("Claimed %d guest isslop analyses for user %s", len(rows), user_uid)
return len(rows)
def insert_event(analysis_uid: str, seq: int, kind: str, message: str, payload: str) -> None:
get_table(TABLE_EVENTS).insert(
{
"analysis_uid": analysis_uid,
"seq": seq,
"kind": kind,
"message": message,
"payload": payload[:EVENT_PAYLOAD_CAP],
"created_at": utc_now(),
}
)
def events_for(analysis_uid: str, after_seq: int = 0, limit: int = EVENT_LIMIT_DEFAULT) -> list[dict[str, Any]]:
rows = get_table(TABLE_EVENTS).find(
analysis_uid=analysis_uid,
seq={">": after_seq},
order_by=["seq"],
_limit=limit,
)
return [dict(row) for row in rows]
def insert_file_result(analysis_uid: str, record: dict[str, Any]) -> None:
record["analysis_uid"] = analysis_uid
get_table(TABLE_FILE_RESULTS).insert(record)
def file_result_for(analysis_uid: str, path: str) -> Optional[dict[str, Any]]:
row = get_table(TABLE_FILE_RESULTS).find_one(analysis_uid=analysis_uid, path=path)
return dict(row) if row else None
def file_results_for(analysis_uid: str) -> list[dict[str, Any]]:
rows = get_table(TABLE_FILE_RESULTS).find(analysis_uid=analysis_uid, order_by=["path"])
return [dict(row) for row in rows]
def insert_image_result(analysis_uid: str, record: dict[str, Any]) -> None:
record["analysis_uid"] = analysis_uid
get_table(TABLE_IMAGE_RESULTS).insert(record)
def image_results_for(analysis_uid: str) -> list[dict[str, Any]]:
rows = get_table(TABLE_IMAGE_RESULTS).find(analysis_uid=analysis_uid, order_by=["-ai_probability"])
return [dict(row) for row in rows]
def insert_dom_result(analysis_uid: str, record: dict[str, Any]) -> None:
record["analysis_uid"] = analysis_uid
get_table(TABLE_DOM_RESULTS).insert(record)
def dom_results_for(analysis_uid: str) -> list[dict[str, Any]]:
rows = get_table(TABLE_DOM_RESULTS).find(analysis_uid=analysis_uid, order_by=["-signal_count"])
return [dict(row) for row in rows]
def save_report(analysis_uid: str, markdown: str, model_used: str) -> None:
get_table(TABLE_REPORTS).upsert(
{
"analysis_uid": analysis_uid,
"markdown": markdown,
"model_used": model_used,
"generated_at": utc_now(),
},
["analysis_uid"],
)
def get_report(analysis_uid: str) -> Optional[dict[str, Any]]:
row = get_table(TABLE_REPORTS).find_one(analysis_uid=analysis_uid)
return dict(row) if row else None
def decode_json(field: Any, fallback: Any) -> Any:
if not field:
return fallback
try:
parsed = json.loads(field)
except (ValueError, TypeError):
return fallback
return parsed if isinstance(parsed, type(fallback)) else fallback
def media_dir_for(uid: str) -> Path:
return ISSLOP_MEDIA_DIR / uid
def reset_evidence(uid: str) -> None:
get_table(TABLE_EVENTS).delete(analysis_uid=uid)
get_table(TABLE_FILE_RESULTS).delete(analysis_uid=uid)
get_table(TABLE_IMAGE_RESULTS).delete(analysis_uid=uid)
get_table(TABLE_DOM_RESULTS).delete(analysis_uid=uid)
get_table(TABLE_REPORTS).delete(analysis_uid=uid)
shutil.rmtree(media_dir_for(uid), ignore_errors=True)
def purge_analysis(uid: str) -> None:
reset_evidence(uid)
get_table(TABLE_ANALYSES).delete(uid=uid)