Stage 0: Repository und Architekturgrundlage
- Pyproject.toml mit FastAPI, Pydantic v2, SQLAlchemy, httpx, asyncio, BeautifulSoup4, selectolax, trafilatura, uvicorn, pytest-asyncio - Multi-stage Dockerfile (Python 3.12-slim, Non-Root-User nsct) - docker-compose.yml (nsct-api + postgres + optional searxng) - .env.example mit allen Config-Parametern - Config-System: AppSettings mit LLMConfig, VisionConfig, AudioConfig, DatabaseConfig — komplett aus Environment, keine Hardcodes - Strukturiertes Logging mit research_id/llm_request_id Tracking - Pydantic v2 Schemas: SearchQuery, Source, Claim, EvidenceRelation, CitationEdge, ResearchReport - SQLAlchemy 2.0 Declarative Models + async Engine Factory - SSRF-Schutz: URL-Validation, IP-Blocklist (RFC1918, Cloud Metadata, file://, ftp://) - Provider-Interfaces: LLMProvider, VisionProvider, AudioProvider, SearchProvider, ContentFetcher als ABCs - Health-Endpoints: /health, /ready (LLM-Connect-Test), /providers - FastAPI App mit CORS, lifespan (LLM Pre-Flight) - CLI-Stub mit Entry-Points: nsct, nsct-core, nsct-api - 6 Test-Cases: /health, /ready, /providers + No-Secrets-Test - Vollständige Dokumentation: README, ARCHITECTURE, SECURITY, METHODOLOGY, API, DEPLOYMENT - .gitignore (Python, Docker, IDE, .env)
This commit is contained in:
184
src/nsct/models/schemas.py
Normal file
184
src/nsct/models/schemas.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""Pydantic v2 schemas for NSCT data objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ClaimType(str, Enum):
|
||||
"""Types of claims that can be extracted from sources."""
|
||||
|
||||
FACTUAL = "factual"
|
||||
OPINION = "opinion"
|
||||
PREDICTION = "prediction"
|
||||
EVALUATION = "evaluation"
|
||||
COMPARISON = "comparison"
|
||||
|
||||
|
||||
class EvidenceRelationType(str, Enum):
|
||||
"""Relationship types between two claims."""
|
||||
|
||||
AGREES = "agrees"
|
||||
DISAGREES = "disagrees"
|
||||
NEUTRAL = "neutral"
|
||||
PARTIALLY_AGREES = "partially_agrees"
|
||||
PARTIALLY_DISAGREES = "partially_disagrees"
|
||||
CONTRADICTS = "contradicts"
|
||||
|
||||
|
||||
class SourceType(str, Enum):
|
||||
"""Classification of a source."""
|
||||
|
||||
NEWS = "news"
|
||||
ACADEMIC = "academic"
|
||||
BLOG = "blog"
|
||||
GOVERNMENT = "government"
|
||||
CORPORATE = "corporate"
|
||||
SOCIAL_MEDIA = "social_media"
|
||||
DOCUMENT = "document"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class EdgeRelation(str, Enum):
|
||||
"""Relationship between two sources."""
|
||||
|
||||
CITATION = "citation"
|
||||
CORROBORATION = "corroboration"
|
||||
CONTRADICTION = "contradiction"
|
||||
DEPENDS_ON = "depends_on"
|
||||
SUPPLEMENTS = "supplements"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SearchQuery
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class SearchQuery(BaseModel):
|
||||
"""Represents a user-initiated search/research query."""
|
||||
|
||||
id: UUID = Field(default_factory=uuid4)
|
||||
research_id: UUID = Field(default_factory=uuid4)
|
||||
query: str = Field(..., min_length=1, description="The search query text.")
|
||||
purpose: str | None = Field(default=None, description="Intent behind the query.")
|
||||
language: str = Field(default="de", description="Language code, e.g. 'de', 'en'.")
|
||||
category: str | None = Field(default=None, description="Optional category for grouping.")
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
model_config = {"frozen": True}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Source(BaseModel):
|
||||
"""A single retrieved source (web page, document, etc.)."""
|
||||
|
||||
id: UUID = Field(default_factory=uuid4)
|
||||
url: str = Field(..., description="Original URL.")
|
||||
canonical_url: str | None = Field(default=None, description="Canonical URL after redirect resolution.")
|
||||
domain: str = Field(..., description="Extracted domain.")
|
||||
title: str | None = Field(default=None)
|
||||
author: str | None = Field(default=None)
|
||||
publisher: str | None = Field(default=None)
|
||||
publication_date: datetime | None = Field(default=None)
|
||||
retrieved_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
content_type: str | None = Field(default=None, description="MIME type.")
|
||||
source_type: SourceType | None = Field(default=None)
|
||||
language: str = Field(default="unknown")
|
||||
content_hash: str | None = Field(default=None, description="SHA-256 hash of raw content.")
|
||||
parent_source_id: UUID | None = Field(default=None, description="Parent source for mirrors/canonical pairs.")
|
||||
content: str | None = Field(default=None, description="Extracted text content.")
|
||||
|
||||
model_config = {"frozen": True}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Claim
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Claim(BaseModel):
|
||||
"""A factual assertion extracted from a Source."""
|
||||
|
||||
id: UUID = Field(default_factory=uuid4)
|
||||
source_id: UUID = Field(..., description="UUID of the Source this claim came from.")
|
||||
claim: str = Field(..., description="Original claim text.")
|
||||
normalized_claim: str | None = Field(default=None, description="De-biased / neutral claim wording.")
|
||||
claim_type: ClaimType = Field(default=ClaimType.FACTUAL)
|
||||
subject: str | None = Field(default=None)
|
||||
predicate: str | None = Field(default=None)
|
||||
object: str | None = Field(default=None)
|
||||
evidence_span: str | None = Field(default=None, description="Span of text in the source that supports this claim.")
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0, description="Extraction confidence (0-1).")
|
||||
event_date: datetime | None = Field(default=None)
|
||||
|
||||
model_config = {"frozen": True}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EvidenceRelation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class EvidenceRelation(BaseModel):
|
||||
"""Relationship between two claims."""
|
||||
|
||||
claim_a: UUID = Field(..., description="UUID of the first claim.")
|
||||
claim_b: UUID = Field(..., description="UUID of the second claim.")
|
||||
relation: EvidenceRelationType = Field(...)
|
||||
confidence: float = Field(default=0.5, ge=0.0, le=1.0)
|
||||
reason: str | None = Field(default=None, description="Free-text explanation of the relation.")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CitationEdge
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class CitationEdge(BaseModel):
|
||||
"""Directed edge between two sources indicating a relationship."""
|
||||
|
||||
source_from: UUID = Field(..., description="UUID of the citing source.")
|
||||
source_to: UUID = Field(..., description="UUID of the cited source.")
|
||||
relation: EdgeRelation = Field(...)
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ResearchReport
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ResearchReport(BaseModel):
|
||||
"""Final aggregated research report."""
|
||||
|
||||
research_id: UUID = Field(default_factory=uuid4)
|
||||
query: str = Field(...)
|
||||
summary: str = Field(default="", description="Human-readable summary.")
|
||||
findings: list[str] = Field(default_factory=list, description="List of key findings.")
|
||||
disagreements: list[dict[str, Any]] = Field(
|
||||
default_factory=list, description="Conflicting claims with details."
|
||||
)
|
||||
uncertainties: list[str] = Field(default_factory=list, description="Known uncertainties.")
|
||||
source_statistics: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Stats: counts per source type, language, etc."
|
||||
)
|
||||
methodology: str = Field(
|
||||
default="", description="Description of the methodology used to produce the report."
|
||||
)
|
||||
generated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
model_config = {"frozen": True}
|
||||
Reference in New Issue
Block a user