Workers P2: Roster-Spiegel und gemeinsame Fixture

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-18 01:10:18 +00:00
1 parent 7b1342fbb1
commit 92ee4bfbad
3 files changed
+165

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
// Spiegel des Rollen-Registers (Übersicht §3) für die Hustler-Seite.
// Die Tabelle spiegelt den Dienst (src/roles.js); beide Repos testen gegen dieselbe Fixture workers-roster.json.
// Reine Daten und Funktionen: kein React, kein Appwrite, kein window/document.
// Modell-Kurznamen der Rollen -> Modell-IDs (Übersicht §2.8)
export const MODEL_IDS = Object.freeze({
opus: 'claude-opus-5-5',
sonnet: 'claude-sonnet-5-5',
haiku: 'claude-haiku-5-5',
})
// Etagen von oben nach unten; freigabe (Pult) und tresor haben keinen Worker
export const FLOORS = Object.freeze(['bruecke', 'anfragen', 'ohne_website', 'bewertung', 'gespraeche', 'werkstatt', 'pruefung', 'freigabe', 'tresor', 'keller'])
// Die vier Branchen-Scouts (Ali, Hanna, Sven, Lea) haben dieselben Fähigkeiten
const SCOUT_CAPS = ['web_search', 'web_fetch', 'places', 'osm']
// Tabellenreihenfolge der Übersicht §3; role = Spalte „Einzige Rolle“ wörtlich
const TABELLE = [
{ workerId: 'otto', name: 'Otto', floor: 'bruecke', room: 'bruecke', model: 'opus', role: 'plant die Nacht, verteilt Aufträge, Budget, Morgenbericht', caps: ['plan', 'report'] },
{ workerId: 'rita', name: 'Rita', floor: 'anfragen', room: 'netz', model: 'haiku', role: 'sucht frische „Suche Webdesigner“-Posts über Websuche', caps: ['web_search', 'web_fetch'] },
{ workerId: 'kim', name: 'Kim', floor: 'anfragen', room: 'kleinanzeigen', model: 'haiku', role: 'liest Kleinanzeigen-Suchauftrags-Mails im Alarm-Postfach', caps: ['alert_inbox'] },
{ workerId: 'pia', name: 'Pia', floor: 'anfragen', room: 'portale', model: 'haiku', role: 'liest Projekt-Alerts/RSS der Portale', caps: ['alert_inbox', 'rss'] },
{ workerId: 'finn', name: 'Finn', floor: 'anfragen', room: 'foren', model: 'haiku', role: 'sucht in Foren/Frage-Seiten (nur erlaubte)', caps: ['web_search', 'web_fetch'] },
{ workerId: 'ali', name: 'Ali', floor: 'ohne_website', room: 'aerzte', model: 'haiku', role: 'Branchen-Scout Ärzte & Praxen', caps: SCOUT_CAPS },
{ workerId: 'hanna', name: 'Hanna', floor: 'ohne_website', room: 'handwerk', model: 'haiku', role: 'Branchen-Scout Handwerk', caps: SCOUT_CAPS },
{ workerId: 'sven', name: 'Sven', floor: 'ohne_website', room: 'selbst', model: 'haiku', role: 'Branchen-Scout Selbstständige', caps: SCOUT_CAPS },
{ workerId: 'lea', name: 'Lea', floor: 'ohne_website', room: 'laden', model: 'haiku', role: 'Branchen-Scout Läden & Gastro', caps: SCOUT_CAPS },
{ workerId: 'bea', name: 'Bea', floor: 'bewertung', room: 'waage', model: 'sonnet', role: 'bewertet, verteilt Top 3 / Rest', caps: ['score'] },
{ workerId: 'max', name: 'Max', floor: 'gespraeche', room: 'mail', model: 'sonnet', role: 'schreibt Mails und ist einziger Absender', caps: ['draft', 'send_mail'] },
{ workerId: 'clara', name: 'Clara', floor: 'gespraeche', room: 'forum', model: 'sonnet', role: 'schreibt Antworten auf Anfragen', caps: ['draft', 'pult'] },
{ workerId: 'nora', name: 'Nora', floor: 'gespraeche', room: 'posteingang', model: 'haiku', role: 'liest + sortiert Eingänge', caps: ['read_inbox', 'classify'] },
{ workerId: 'theo', name: 'Theo', floor: 'gespraeche', room: 'abschluss', model: 'sonnet', role: 'Nachrichten rund um den Kauf (Entwürfe)', caps: ['draft', 'pult', 'offer'] },
{ workerId: 'bob', name: 'Bob', floor: 'werkstatt', room: 'tisch1', model: 'opus', role: 'baut Vorschauen (nur nach Ja)', caps: ['build'] },
{ workerId: 'bianca', name: 'Bianca', floor: 'werkstatt', room: 'tisch2', model: 'opus', role: 'baut Vorschauen', caps: ['build'] },
{ workerId: 'ben', name: 'Ben', floor: 'werkstatt', room: 'tisch3', model: 'opus', role: 'baut Vorschauen', caps: ['build'] },
{ workerId: 'paul', name: 'Paul', floor: 'pruefung', room: 'pruefung', model: 'sonnet', role: 'prüft Vorschauen (Screenshots)', caps: ['review', 'screenshot'] },
{ workerId: 'arne', name: 'Arne', floor: 'keller', room: 'archiv', model: 'haiku', role: 'Nie-doppelt-Register + Bot-Lead-Liste', caps: ['register', 'archive'] },
]
// Eingefroren, jede Rolle mit eigener caps-Kopie
export const ROLES = Object.freeze(TABELLE.map((r) => Object.freeze({ ...r, caps: Object.freeze([...r.caps]) })))
// Räume der Etagen anfragen und ohne_website in ROLES-Reihenfolge (Spiegel von src/roles.js des Dienstes).
// Nur diese Such-Räume können „nicht angeschlossen“ (grau) sein, alle anderen Worker meldet der Dienst als
// angeschlossen (Übersicht §11.1 P1-E9). WOMS berechnet `connected` nicht selbst; die Konstante dient Anzeige und Prüfstand.
export const SEARCH_ROOMS = Object.freeze(ROLES.filter((r) => r.floor === 'anfragen' || r.floor === 'ohne_website').map((r) => r.room))
export const FLOOR_TITLES = Object.freeze({
bruecke: 'Kommandobrücke',
anfragen: 'Anfragen',
ohne_website: 'Ohne Website',
bewertung: 'Bewertung',
gespraeche: 'Gespräche',
werkstatt: 'Werkstatt',
pruefung: 'Prüfung',
freigabe: 'Freigabe',
tresor: 'Tresor',
keller: 'Keller',
})
// 19 Worker-Räume plus Pult (Etage freigabe) und Tresor
export const ROOM_TITLES = Object.freeze({
netz: 'Netz-Radar',
kleinanzeigen: 'Kleinanzeigen',
portale: 'Portale',
foren: 'Foren & Fragen',
aerzte: 'Ärzte & Praxen',
handwerk: 'Handwerk',
selbst: 'Selbstständige',
laden: 'Läden & Gastro',
waage: 'Waage',
mail: 'Mail-Stube',
forum: 'Forum-Stube',
posteingang: 'Posteingang',
abschluss: 'Abschluss',
tisch1: 'Tisch 1',
tisch2: 'Tisch 2',
tisch3: 'Tisch 3',
pruefung: 'Prüftisch',
archiv: 'Archiv & Register',
bruecke: 'Brücke',
pult: 'Dein Pult',
tresor: 'Tresor',
})
// Nachschlagen über Map statt Objekt: 'constructor' oder '__proto__' sind keine Treffer
const NACH_ID = new Map(ROLES.map((r) => [r.workerId, r]))
// Rolle zur Kennung; unbekannte Kennung -> null
export function roleOf(workerId) {
return NACH_ID.get(workerId) ?? null
}
// Hut der Figur nach Modell: Kurzname oder voller Modellname, sonst 'haiku'
export function hatForModel(model) {
const m = typeof model === 'string' ? model.match(/opus|sonnet|haiku/) : null
return m ? m[0] : 'haiku'
}
// Otto trägt die Kommandomütze, alle anderen den Hut ihres Modells; unbekannte Kennung -> 'haiku'
export function hatForWorker(workerId) {
if (workerId === 'otto') return 'overseer'
const r = roleOf(workerId)
return r ? hatForModel(r.model) : 'haiku'
}