Files
irrlichtandClaude Opus 5 cf89e012b2 Revisionsmodul, Embeddings und Container
Drei Schritte, die zusammen Phase 2 lauffaehig machen.

Revisionen (migrations/p2-004, revisionen.py)
    Ingest schreibt jede geaenderte Fassung eines Items nach
    raw_item_versionen fort. Daraus faellt ein Befund ab, den die
    Spezifikation nicht vorsehen konnte: welches Haus seine Schlagzeile
    nachtraeglich umschreibt, und wie. 206 von 3407 Items, acht
    Einstufungen von 'formal' bis 'neufassung'. Kein Modell, kein Lexikon,
    kein Netzzugriff - nur ein Wortvergleich zweier Fassungen.

    Fortschreibungen sind ueber ticker_markiert und kopf_stabil abziehbar,
    absichtlich als zwei Merkmale statt einer Kategorie: das eine ist eine
    Selbstauskunft des Hauses, das andere eine Beobachtung am Satzbau.

Embeddings (embeddings.py)
    multilingual-e5-large, 1024 Dimensionen, MIT. Jede Zeile wird auf feste
    128 Token aufgefuellt, nicht nur bis zur laengsten des Stapels - sonst
    haengt ein Vektor davon ab, mit welchen Nachbarn er kodiert wurde, und
    ein Slot-Lauf liefert andere letzte Stellen als ein Backfill.
    Nachgewiesen: 64/64 bitgleich einzeln wie im vollen Stapel, groesste
    Abweichung 0.00e+00, und im Betrieb 26 Slot-Vektoren unveraendert
    gegen den 3407er-Backfill.

    Darum transformers statt sentence-transformers: letzteres kapselt genau
    diese Stelle weg.

Container (Containerfile, phase2.py, deploy/)
    Debian trixie wie der Postgres-Container, torch aus dem CPU-Index.
    Die regelbasierten Module liefern darin fuer alle 3407 Items bitgleich
    dieselbe Nutzlast wie auf der Entwicklungsmaschine - Portabilitaet
    gemessen, nicht behauptet.

version.py bekommt den Schluessel 'revision'. Weil der Hash ueber den ganzen
Komponentensatz laeuft, aendert das auch die Version des Dublettenmoduls,
obwohl an dessen Verfahren nichts anders ist. Das ist die in version.py
beschriebene Semantik der Spezifikation, kein Versehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NupWEBPzsyohVPG47HsTz8
2026-09-08 07:41:27 +02:00

72 lines
2.5 KiB
Python

"""coder_version: was hinter dem Hash steht.
Format aus der Spezifikation: p2-<ISO-Datum>-<hash8>.
Der Hash bildet alle Bestandteile, die das Ergebnis beeinflussen. Aendert
sich einer, aendert sich die Version, und alte Codings bleiben unangetastet
liegen. Das Datum ist das der letzten Aenderung an den Komponenten, nicht das
des Laufs - sonst waere die Version taeglich neu.
Anmerkung zur Spezifikation: sie sieht *eine* Version ueber alle fuenf Module
vor. Ein neues Sentimentlexikon entwertet damit auch die Embeddings, obwohl
es sie nicht beruehrt. Das ist teuer, aber es ist die festgelegte Semantik;
wer das aendern will, aendert die Spezifikation, nicht diese Datei.
"""
import hashlib
import json
import normalisierung
# Stand der Komponenten. Module, die es noch nicht gibt, stehen auf null -
# sie tragen dann nichts zum Hash bei, ausser dass sie erwaehnt sind.
KOMPONENTEN = {
"stand": "2026-09-07",
"normalisierung": normalisierung.REGELWERK_VERSION,
# Wird vom jeweiligen Modul beim Lauf eingesetzt (siehe komponenten()).
# Die Einstellungen stehen dort, wo sie wirken, nicht hier - sonst
# gerieten die beiden auseinander, und der Hash behauptete eine
# Gleichheit, die es nicht gibt.
"dubletten": None,
# Ausserhalb der Spezifikation, siehe migrations/p2-004-revisionen.sql.
"revision": None,
"akteure": None,
"geo": None,
"tonalitaet": None,
"embedding": None,
}
def komponenten(**module):
"""Kopie der Komponenten mit eingesetzten Modulangaben.
Ein Modul kennt seine Einstellungen selbst und reicht sie hier herein.
Aendert es eine Schwelle, aendert sich der Hash - genau so ist es
gemeint.
"""
k = dict(KOMPONENTEN)
k.update(module)
return k
def coder_version(komponenten=None):
k = KOMPONENTEN if komponenten is None else komponenten
# sort_keys: die Reihenfolge im Quelltext darf den Hash nicht bewegen.
roh = json.dumps(k, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
hash8 = hashlib.blake2b(roh.encode("utf-8"), digest_size=4).hexdigest()
return f"p2-{k['stand']}-{hash8}"
def eintragen(conn, komponenten=None):
"""Version in coder_versionen festhalten. Ohne das sagt der Hash nichts."""
from psycopg.types.json import Jsonb
k = KOMPONENTEN if komponenten is None else komponenten
v = coder_version(k)
conn.execute(
"insert into coder_versionen (version, komponenten) values (%s, %s)"
" on conflict (version) do nothing",
(v, Jsonb(k)),
)
return v