168 lines
5.2 KiB
TypeScript
168 lines
5.2 KiB
TypeScript
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<Ordner[]> {
|
||
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<Ordner> {
|
||
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<Ordner> {
|
||
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<number> {
|
||
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<void> {
|
||
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`;
|
||
}
|