import { ID, Query, databaseId, tables } from './appwrite'; import { teamRechte } from './permissions'; /** * Ein Ordner ist ein **privater Wissens-Scope**, kein Sortier-Ordner * (konzept-bilder-feed.md §9). Beim Generieren zieht P4 nur die Attribute des * gewählten Ordners – er überschreibt die brand-weite Ebene vollständig, * gemischt wird nicht. */ export type Ordner = { $id: string; brand_id: string; name: string; theme_md?: string; zweck?: string; startwert_modus?: Startwert; ist_default?: boolean; post_count?: number; signal_count?: number; }; /** `sammlung` = nur einsortieren · `wissens_scope` = daraus erstellen. */ export type Zweck = 'sammlung' | 'wissens_scope'; /** Woher die Attribut-Scores beim Anlegen kommen. */ export type Startwert = 'erben' | 'aus_posts' | 'neutral'; export const ZWECKE: { wert: Zweck; titel: string; erklaerung: string }[] = [ { wert: 'wissens_scope', titel: 'Daraus erstellen', erklaerung: 'Generierungen ziehen ihr Wissen aus diesem Ordner. Das ist der Normalfall.', }, { wert: 'sammlung', titel: 'Nur sammeln', erklaerung: 'Reine Ablage zum Sortieren. Beeinflusst keine Generierung.', }, ]; export const STARTWERTE: { wert: Startwert; titel: string; erklaerung: string }[] = [ { wert: 'erben', titel: 'Vom Konto erben', erklaerung: 'Startet mit einer Kopie der bisherigen Scores. Gut, wenn der Ordner die Marke fortsetzt.', }, { wert: 'aus_posts', titel: 'Nur aus den Posts', erklaerung: 'Startet leer und lernt ausschließlich aus dem, was hier landet. Gut, wenn der Ordner bewusst anders aussehen soll als der Rest.', }, ]; export async function ordnerListe(brandId: string): Promise { const res = await tables.listRows({ databaseId, tableId: 'folders', queries: [Query.equal('brand_id', brandId), Query.limit(100)], }); return res.rows as unknown as Ordner[]; } export async function ordnerLesen(id: string): Promise { const row = await tables.getRow({ databaseId, tableId: 'folders', rowId: id }); return row as unknown as Ordner; } export async function ordnerAnlegen( brandId: string, teamId: string, daten: { name: string; zweck: Zweck; startwert_modus: Startwert; theme_md?: string }, ): Promise { const row = await tables.createRow({ databaseId, tableId: 'folders', rowId: ID.unique(), data: { brand_id: brandId, name: daten.name, zweck: daten.zweck, startwert_modus: daten.startwert_modus, theme_md: daten.theme_md ?? '', ist_default: false, post_count: 0, signal_count: 0, }, permissions: teamRechte(teamId), }); const ordner = row as unknown as Ordner; await ordnerInitialisieren(brandId, teamId, ordner.$id, daten.startwert_modus); return ordner; } /** * Füllt `attribute_scores` für einen frischen Ordner. * * `erben` kopiert die brand-weite Ebene (`folder_id = null`) – der Ordner setzt * die Marke fort. `aus_posts` und `neutral` starten leer: dort soll das Wissen * ausschließlich aus dem entstehen, was später hineinkommt. Genau das ist der * Zweck der Ordner – Wissen **segmentieren statt mitteln** * (konzept-bilder-feed.md §9). * * Läuft vorerst im Client statt in der Function `ordner-initialisieren`. Das * ist vertretbar, weil nur eigene Zeilen kopiert werden und die Zeilenrechte * das ohnehin begrenzen – beim Umzug in eine Function ändert sich nur der Ort. */ export async function ordnerInitialisieren( brandId: string, teamId: string, folderId: string, modus: Startwert, ): Promise { if (modus !== 'erben') return 0; const quelle = await tables.listRows({ databaseId, tableId: 'attribute_scores', queries: [Query.equal('brand_id', brandId), Query.isNull('folder_id'), Query.limit(200)], }); const rechte = teamRechte(teamId); let kopiert = 0; for (const z of quelle.rows as unknown as BrandScore[]) { await tables.createRow({ databaseId, tableId: 'attribute_scores', rowId: ID.unique(), data: { brand_id: brandId, attribute_id: z.attribute_id, category_id: z.category_id, folder_id: folderId, score: z.score ?? 5000, start_value: z.score ?? 5000, // Der Ordner erbt den Wert, aber nicht die Sicherheit: k_factor zurück // auf 32, weil im neuen Scope noch nichts belegt ist. k_factor: 32, start_quelle: 'geerbt', used_count: 0, wins: 0, losses: 0, }, permissions: rechte, }); kopiert++; } return kopiert; } type BrandScore = { attribute_id: string; category_id: string; score?: number; }; export async function ordnerLoeschen(id: string): Promise { await tables.deleteRow({ databaseId, tableId: 'folders', rowId: id }); } /** „4 Posts – lernt noch“: ohne Reifegrad ist für den Nutzer nicht erklärbar, * warum zwei gleiche Prompts verschiedene Bilder ergeben (app-aufbau.md §5.3). */ export function reifegrad(o: Ordner): string { const posts = o.post_count ?? 0; const signale = o.signal_count ?? 0; if (posts === 0) return 'leer'; if (signale < 5) return `${posts} Posts – lernt noch`; return `${posts} Posts – eingespielt`; }