Files
archivdms/internal/storage/ml_classifier.go
T
patrick 9a24ea29e1 FDN-01: repository & projektgerüst
Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
2026-08-11 21:27:53 +02:00

65 lines
2.8 KiB
Go

package storage
import (
"context"
"fmt"
)
// initMLClassifierSchema creates the Naive-Bayes retraining/classification
// tables (ml_classifier_tokens/ml_classifier_classes/ml_classifier_runs) plus
// the assigned_via provenance columns on document_tags/documents that let the
// classifier tell apart manually-set, rule-engine-set and ML-accepted
// assignments. Idempotent, called from (*Store).initSchema AFTER
// initTaxonomySchema (depends on document_types/correspondents/tags/
// document_tags/documents existing). See migrations/020_ml_classifier.sql.
func (s *Store) initMLClassifierSchema(ctx context.Context) error {
_, err := s.db.Exec(ctx, `
CREATE TABLE IF NOT EXISTS ml_classifier_tokens (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('document_types','correspondents','tags')),
entity_id BIGINT NOT NULL,
token TEXT NOT NULL,
count BIGINT NOT NULL DEFAULT 0,
UNIQUE (tenant_id, kind, entity_id, token)
);
CREATE INDEX IF NOT EXISTS idx_ml_tokens_lookup ON ml_classifier_tokens(tenant_id, kind, token);
CREATE TABLE IF NOT EXISTS ml_classifier_classes (
tenant_id BIGINT NOT NULL,
kind TEXT NOT NULL,
entity_id BIGINT NOT NULL,
doc_count BIGINT NOT NULL DEFAULT 0,
total_tokens BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (tenant_id, kind, entity_id)
);
CREATE TABLE IF NOT EXISTS ml_classifier_runs (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
completed_at TIMESTAMPTZ,
doc_count BIGINT NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running','completed','failed','skipped_insufficient_data')),
error TEXT
);
CREATE INDEX IF NOT EXISTS idx_ml_classifier_runs_tenant ON ml_classifier_runs(tenant_id);
`)
if err != nil {
return fmt.Errorf("storage: create ml_classifier tables: %w", err)
}
// Provenance columns: default 'manual' so existing rows are NOT retroactively
// (mis)classified as rule-assigned — conservative default keeps them out of
// automatic ML-accepted reclassification too.
_, err = s.db.Exec(ctx, `
ALTER TABLE document_tags ADD COLUMN IF NOT EXISTS assigned_via TEXT NOT NULL DEFAULT 'manual' CHECK (assigned_via IN ('manual','rule','ml_accepted'));
ALTER TABLE documents ADD COLUMN IF NOT EXISTS doc_type_assigned_via TEXT NOT NULL DEFAULT 'manual' CHECK (doc_type_assigned_via IN ('manual','rule','ml_accepted'));
ALTER TABLE documents ADD COLUMN IF NOT EXISTS correspondent_assigned_via TEXT NOT NULL DEFAULT 'manual' CHECK (correspondent_assigned_via IN ('manual','rule','ml_accepted'));
`)
if err != nil {
return fmt.Errorf("storage: alter documents/document_tags for ml_classifier provenance: %w", err)
}
return nil
}