Skip to content

API Reference

Automatisch aus den Docstrings generiert. Neue Module bitte hier als :::-Direktive eintragen.

qualdatan_core.facets

Registry

qualdatan_core.facets.registry

Facet registry and discovery.

Drei Quellen werden unterstuetzt:

  1. In-Process-Registrierung ueber :func:register_facet — fuer Tests und fuer Code, der Facets direkt instanziiert.
  2. Python-Entry-Points unter Gruppe qualdatan.facet_types (registriert man via [project.entry-points."qualdatan.facet_types"] in pyproject.toml) — fuer Plugin-Pakete, die neue Typen von Facets einbringen (z.B. einen GraphFacet).
  3. Bundle-YAMLs ueber :class:qualdatan_core.facets.loader.YamlFacetLoader — fuer deklarative Facets, die sich auf vorhandene Typen stuetzen (siehe types.py: TaxonomyFacet, EvidenceFacet, ...). Bundles bringen keine eigenen Typen mit, sondern liefern Konfigurationen der mitgelieferten Typen plus eigener Codes/Prompts.

register_facet

register_facet(facet)

Registriert einen Facet im Process-globalen Index.

Source code in src/qualdatan_core/facets/registry.py
def register_facet(facet: Facet) -> None:
    """Registriert einen Facet im Process-globalen Index."""

    if not isinstance(facet, Facet):  # type: ignore[misc]
        raise TypeError(
            f"register_facet expects an object that satisfies the Facet "
            f"protocol; got {type(facet).__name__}"
        )
    if facet.id in _FACETS:
        raise ValueError(f"Facet '{facet.id}' is already registered")
    _FACETS[facet.id] = facet

unregister_facet

unregister_facet(facet_id)

Entfernt einen registrierten Facet (idempotent).

Source code in src/qualdatan_core/facets/registry.py
def unregister_facet(facet_id: str) -> None:
    """Entfernt einen registrierten Facet (idempotent)."""

    _FACETS.pop(facet_id, None)

get_facet

get_facet(facet_id)

Liefert eine registrierte Facet-Instanz oder wirft KeyError.

Source code in src/qualdatan_core/facets/registry.py
def get_facet(facet_id: str) -> Facet:
    """Liefert eine registrierte Facet-Instanz oder wirft KeyError."""

    return _FACETS[facet_id]

list_facets

list_facets()

Alle aktuell registrierten Facet-Instanzen (stabile Reihenfolge).

Source code in src/qualdatan_core/facets/registry.py
def list_facets() -> list[Facet]:
    """Alle aktuell registrierten Facet-Instanzen (stabile Reihenfolge)."""

    return [_FACETS[k] for k in sorted(_FACETS)]

clear_registry

clear_registry()

Loescht die In-Process-Registry — vor allem fuer Tests.

Source code in src/qualdatan_core/facets/registry.py
def clear_registry() -> None:
    """Loescht die In-Process-Registry — vor allem fuer Tests."""

    _FACETS.clear()

discovered_facet_types

discovered_facet_types()

Sammelt registrierte Facet-Typen aus Entry-Points + Built-ins.

Built-ins (TaxonomyFacet, ...) werden durch types.builtin_facet_types geliefert; Drittpakete koennen weitere Typen via Entry-Point hinzufuegen.

Source code in src/qualdatan_core/facets/registry.py
def discovered_facet_types() -> dict[str, Callable[..., Facet]]:
    """Sammelt registrierte Facet-Typen aus Entry-Points + Built-ins.

    Built-ins (TaxonomyFacet, ...) werden durch ``types.builtin_facet_types``
    geliefert; Drittpakete koennen weitere Typen via Entry-Point hinzufuegen.
    """

    from .types import builtin_facet_types

    types: dict[str, Callable[..., Facet]] = dict(builtin_facet_types())
    for ep in entry_points(group=EntryPointGroup):
        try:
            types[ep.name] = ep.load()
        except Exception as exc:  # pragma: no cover - defensive
            # Wir wollen den Process nicht killen wenn ein einzelnes Plugin
            # bricht; loggen ohne logger-Setup waere zu invasiv hier — die
            # GUI/TUI kann diese Funktion in einen try/except wickeln.
            raise RuntimeError(
                f"Konnte Facet-Typ '{ep.name}' aus Entry-Point '{ep.value}' "
                f"nicht laden: {exc}"
            ) from exc
    return types

register_facets

register_facets(facets)

Convenience: mehrere Facets auf einmal registrieren.

Source code in src/qualdatan_core/facets/registry.py
def register_facets(facets: Iterable[Facet]) -> None:
    """Convenience: mehrere Facets auf einmal registrieren."""

    for f in facets:
        register_facet(f)

Base

qualdatan_core.facets.base

Facet protocol — domain-agnostic building block for qualdatan analyses.

Ein Facet ist ein konfigurierbarer Analyse-Baustein: es bekommt Material (Text / PDF-Text / PDF-Visual / Image), produziert ein Prompt fuer einen LLM und parsed die Antwort zu einer Liste von Kodierungen.

Facets sind selbst inhaltsfrei — die konkrete Taxonomie / Evidenz-Skala / Codeliste kommt aus dem Bundle, das den Facet instanziiert. So funktioniert derselbe Facet-Code fuer "IFC-Bauelemente" wie fuer "ICD-10-Diagnosen" oder "BPMN-Akteursrollen".

Material

Bases: str, Enum

Materialart, die ein Facet konsumieren kann.

CodeContribution dataclass

CodeContribution(id, label, description='', color_hint=None)

Ein Code, den ein Facet ins Codebook einbringt.

id ist die ID innerhalb des Bundle-Namespaces (z.B. IFC-WALL). Die Bundle-ID + Facet-ID praefixiert das Code-Owner-System bei Bedarf.

FacetContext dataclass

FacetContext(material, material_kind, source_label='', metadata=dict(), model=None, max_tokens=None)

Kontext, den der Orchestrator beim Aufruf eines Facets liefert.

Facet

Bases: Protocol

Statisches Protocol fuer Facet-Implementierungen.

Concrete Facets koennen Python-Klassen (z.B. plugins) oder deklarativ aus YAML geladene Objekte sein (siehe types.py).

build_prompt

build_prompt(ctx)

Erzeugt den LLM-Prompt fuer das uebergebene Material.

Source code in src/qualdatan_core/facets/base.py
def build_prompt(self, ctx: FacetContext) -> str:
    """Erzeugt den LLM-Prompt fuer das uebergebene Material."""
    ...

parse_response

parse_response(raw, ctx)

Wandelt die LLM-Antwort in CodedSegments um.

Source code in src/qualdatan_core/facets/base.py
def parse_response(self, raw: str | Mapping[str, Any], ctx: FacetContext) -> list[CodedSegment]:
    """Wandelt die LLM-Antwort in CodedSegments um."""
    ...

Types

qualdatan_core.facets.types

Built-in Facet types (declarative, configured via YAML).

Diese Klassen sind das, was Bundles benutzen, um eigene Domaenen zu beschreiben — z.B. IFC-Bauelemente als TaxonomyFacet mit der IFC-Klassen-Liste als Choices, LOG-Evidenz als EvidenceFacet mit einer 5-stufigen Skala.

Alle Typen erfuellen das :class:Facet-Protocol aus base.py. Sie sind mit Absicht spezifisch genug, um wirklich nuetzlich zu sein, aber generisch genug, um in beliebigen Domaenen zu funktionieren.

Der Code hier liefert nur die generischen Schemata und einen sehr einfachen, heuristischen Default fuer build_prompt / parse_response. Bundles koennen Prompt-Templates ueberschreiben (Feld prompt_template im YAML), und Phase-D wird die Rohbausteine durch eine richtige LLM-Wrapper-Schicht in qualdatan_core/llm ergaenzen.

TaxonomyFacet dataclass

TaxonomyFacet(id, label, input_kinds, codebook_contribution, description='', prompt_template='Klassifiziere folgendes Material gegen die Taxonomie \'{label}\'.\nErlaubte Codes: {codes_csv}\n\nMaterial:\n{material}\n\nAntworte als JSON-Liste von Objekten {{"code_id": "…", "text": "…", "char_start": int, "char_end": int}}.')

Wandelt Material in Kodierungen anhand einer Taxonomie.

Beispiel: IFC-Klassen, ICD-10-Diagnosen, BPMN-Aktivitaetstypen.

EvidenceFacet dataclass

EvidenceFacet(id, label, input_kinds, codebook_contribution, description='', prompt_template='Bewerte Material auf der Skala \'{label}\'.\nStufen (von niedrig nach hoch): {codes_list}\n\nMaterial:\n{material}\n\nAntworte als JSON: {{"code_id": "…", "justification": "…"}}.')

Stuft Material auf einer ordinalen Skala ein (z.B. LOG-01..05).

ActorRoleFacet dataclass

ActorRoleFacet(id, label, input_kinds, codebook_contribution, description='', prompt_template='Identifiziere Akteure und ordne jedem eine Rolle aus \'{label}\' zu.\nVerfuegbare Rollen: {codes_csv}\n\nMaterial:\n{material}\n\nAntworte als JSON-Liste {{"code_id": "<rolle>", "text": "<akteur>", "char_start": int, "char_end": int}}.')

Findet Akteure im Material und ordnet sie Rollen zu.

ProcessStepFacet dataclass

ProcessStepFacet(id, label, input_kinds, codebook_contribution, description='', prompt_template='Identifiziere Prozessschritte aus \'{label}\' im Material und gib sie in der Reihenfolge ihres Auftretens zurueck.\nErlaubte Schritte: {codes_csv}\n\nMaterial:\n{material}\n\nAntworte als JSON-Liste {{"code_id": "…", "text": "<auszug>", "char_start": int, "char_end": int, "sequence": int}}.')

Zerlegt Material in eine Sequenz definierter Prozessschritte.

FreeCodingFacet dataclass

FreeCodingFacet(id, label, input_kinds, codebook_contribution=(), description='', prompt_template='Fuehre offene Kodierung am Material durch (Methode \'{label}\').\nBekannte Seed-Codes (optional, du darfst neue erfinden): {codes_csv}\n\nMaterial:\n{material}\n\nAntworte als JSON-Liste {{"code_id": "…", "code_label": "…", "text": "…", "char_start": int, "char_end": int}}.')

Offene Kodierung — Modell darf eigene Codes vergeben.

Optional kann ein Set an Seed-Codes mitgegeben werden, an dem das Modell sich orientieren soll.

builtin_facet_types

builtin_facet_types()

Map type-Strings aus YAML auf die zugehoerigen Built-in-Klassen.

Source code in src/qualdatan_core/facets/types.py
def builtin_facet_types() -> dict[str, Callable[..., Facet]]:
    """Map ``type``-Strings aus YAML auf die zugehoerigen Built-in-Klassen."""

    # Lokal importieren, um Zirkularitaet (coding.visual_facet -> ..facets.types)
    # zu vermeiden.
    from ..coding.visual_facet import VisualEvidenceFacet, VisualTaxonomyFacet

    return {
        "taxonomy":        TaxonomyFacet.from_yaml,
        "evidence":        EvidenceFacet.from_yaml,
        "actor_role":      ActorRoleFacet.from_yaml,
        "process_step":    ProcessStepFacet.from_yaml,
        "free_coding":     FreeCodingFacet.from_yaml,
        "visual_taxonomy": VisualTaxonomyFacet.from_yaml,
        "visual_evidence": VisualEvidenceFacet.from_yaml,
    }

Loader

qualdatan_core.facets.loader

Load Facet instances from YAML files (bundle data).

YAML-Schema (Beispiel bim-basic/facets/ifc-elements.yaml):

id: ifc-elements
type: taxonomy
label: "IFC-Bauelemente"
description: "Klassifikation visuell erkennbarer Bauteile nach IfcClass."
input_kinds: [pdf_visual]
codes:
  - { id: IFC-WALL,  label: "Wand" }
  - { id: IFC-SLAB,  label: "Decke" }
prompt_template: |
  ...optional, sonst Default aus types.py...

Der Loader weiss, welche Typen es gibt, indem er :func:discovered_facet_types fragt — das schliesst Built-ins und ueber Entry-Points registrierte Plugin- Typen ein.

FacetLoadError

Bases: ValueError

Fehler beim Laden eines Facet-YAML.

load_facet_from_dict

load_facet_from_dict(data)

Instanziiert einen Facet aus einer geparsten YAML-Mapping-Struktur.

Source code in src/qualdatan_core/facets/loader.py
def load_facet_from_dict(data: Mapping[str, Any]) -> Facet:
    """Instanziiert einen Facet aus einer geparsten YAML-Mapping-Struktur."""

    if "type" not in data:
        raise FacetLoadError("Facet-YAML fehlt 'type' (z.B. 'taxonomy', 'evidence', ...)")
    if "id" not in data:
        raise FacetLoadError("Facet-YAML fehlt 'id'")

    type_name = data["type"]
    types = discovered_facet_types()
    if type_name not in types:
        raise FacetLoadError(
            f"Unbekannter Facet-Typ '{type_name}'. "
            f"Verfuegbar: {sorted(types)}"
        )
    factory = types[type_name]
    try:
        return factory(data)
    except Exception as exc:
        raise FacetLoadError(
            f"Konnte Facet '{data.get('id')}' (type='{type_name}') nicht "
            f"instanziieren: {exc}"
        ) from exc

load_facet_from_yaml

load_facet_from_yaml(path)

Liest ein einzelnes Facet-YAML und instanziiert den Facet.

Source code in src/qualdatan_core/facets/loader.py
def load_facet_from_yaml(path: Path) -> Facet:
    """Liest ein einzelnes Facet-YAML und instanziiert den Facet."""

    with Path(path).open("r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
    if not isinstance(data, Mapping):
        raise FacetLoadError(f"{path}: Top-Level muss ein YAML-Mapping sein")
    return load_facet_from_dict(data)

load_facets_from_dir

load_facets_from_dir(directory)

Laedt alle *.yaml / *.yml aus einem Verzeichnis (rekursiv).

Source code in src/qualdatan_core/facets/loader.py
def load_facets_from_dir(directory: Path) -> list[Facet]:
    """Laedt alle ``*.yaml`` / ``*.yml`` aus einem Verzeichnis (rekursiv)."""

    directory = Path(directory)
    if not directory.exists():
        return []
    found: list[Facet] = []
    for ext in ("*.yaml", "*.yml"):
        for path in sorted(directory.rglob(ext)):
            found.append(load_facet_from_yaml(path))
    return found

load_facets

load_facets(paths)

Laedt Facets aus einer Mischung von Dateien und Verzeichnissen.

Source code in src/qualdatan_core/facets/loader.py
def load_facets(paths: Iterable[Path]) -> list[Facet]:
    """Laedt Facets aus einer Mischung von Dateien und Verzeichnissen."""

    out: list[Facet] = []
    for p in paths:
        p = Path(p)
        if p.is_dir():
            out.extend(load_facets_from_dir(p))
        else:
            out.append(load_facet_from_yaml(p))
    return out