Trend-Radar + Pruefstand: Box beobachtet Modell-/Engine-Trends und testet Kandidaten selbst
- trend-radar-feed.sh (Cron Sa 05:15): Live-Puls ALLER Rollen mit Vorwochen-Vergleich, Engine-A/B gegen neuen llama.cpp-Build (Fruehwarnung vor So-Auto-Update), mechanische Watch-Liste (MMQ-Issue, ROCm-Releases, Community-Grid), HF-Kandidaten-Suche mit Zitat-Gate -> max. 1 Pruefstand-Kandidat/Woche - pruefstand.sh + harness.py (Cron So 01:00, no-agent): volles Programm je Kandidat - 6 Coding-Aufgaben mit echten Unit-Tests, 4 Agenten-Aufgaben (Tool-Loop + Endzustand), 4 Recherche-Fragen mit Zitat-Ehrlichkeits-Gate, Deutsch-Richter, Vision-Testbild, Tempo kurz+lang; Baseline der Amtsinhaber als Vergleichsmassstab - Leitplanken (User-Entscheid 17.07.): Download-Deckel 35 GB (drueber -> Karte), 1 Kandidat/Woche, Cache mit Auto-Aufraeumen, RAM-/Platz-Wache, Bench-Port :5899, Radar/Pruefstand EMPFEHLEN nur - Rollen-Wechsel bleibt Commander-Klick - E2E bewiesen (Mock-LLM): coding 6/6, recherche 4/4 inkl. Zitat-Gate, Agent-Roundtrip, Richter-Parsing; Suiten-Selbsttest mit Referenzloesungen 6/6 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"prompt": "Im Arbeitsverzeichnis liegen Notiz-Dateien im Ordner `notizen/`. Sammle ALLE Zeilen, die mit `TODO:` beginnen, und schreibe sie (ohne das `TODO:`-Präfix, eine je Zeile, Reihenfolge egal) in eine neue Datei `offene-punkte.txt` im Wurzelverzeichnis. Rufe danach `fertig` auf.",
|
||||
"max_schritte": 8,
|
||||
"sandkasten": {
|
||||
"notizen/montag.txt": "Besprechung war ok.\nTODO: Backup-Log prüfen\nSonst nichts.\n",
|
||||
"notizen/dienstag.txt": "TODO: Firmware-Update Router\nWetter schlecht.\n",
|
||||
"notizen/mittwoch.txt": "Nichts offen heute.\n"
|
||||
},
|
||||
"checks": [
|
||||
{"datei": "offene-punkte.txt", "regex": "Backup-Log prüfen"},
|
||||
{"datei": "offene-punkte.txt", "regex": "Firmware-Update Router"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"prompt": "Die Datei `dienst/config.ini` ist kaputt. In `anleitung.txt` steht, welche Werte gelten sollen. Repariere die config.ini entsprechend (nur die falschen Werte ändern, Struktur beibehalten) und rufe danach `fertig` auf.",
|
||||
"max_schritte": 8,
|
||||
"sandkasten": {
|
||||
"anleitung.txt": "Sollwerte für dienst/config.ini:\n- port muss 9001 sein (Zahl, nicht Text!)\n- loglevel muss info sein\n- Der Eintrag host bleibt unverändert.\n",
|
||||
"dienst/config.ini": "[server]\nhost = 127.0.0.1\nport = abc\nloglevel = debug\n"
|
||||
},
|
||||
"checks": [
|
||||
{"datei": "dienst/config.ini", "regex": "^port\\s*=\\s*9001\\s*$"},
|
||||
{"datei": "dienst/config.ini", "regex": "^loglevel\\s*=\\s*info\\s*$"},
|
||||
{"datei": "dienst/config.ini", "regex": "^host\\s*=\\s*127\\.0\\.0\\.1\\s*$"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"prompt": "Lies die Messwerte in `messwerte.csv` (Spalten: zeit;temperatur_c). Erstelle einen Bericht `bericht.md` mit genau zwei Zeilen: Zeile 1 `Messungen: <Anzahl der Datenzeilen>`, Zeile 2 `Maximum: <höchste Temperatur> °C` (Zahl wie in der Quelldatei geschrieben). Rufe danach `fertig` auf.",
|
||||
"max_schritte": 8,
|
||||
"sandkasten": {
|
||||
"messwerte.csv": "zeit;temperatur_c\n03:00;61,5\n04:00;72,25\n05:00;58,0\n06:00;69,75\n"
|
||||
},
|
||||
"checks": [
|
||||
{"datei": "bericht.md", "regex": "Messungen:\\s*4"},
|
||||
{"datei": "bericht.md", "regex": "Maximum:\\s*72,25\\s*°?C"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"prompt": "In `regeln.txt` steht, welche Dateien im Arbeitsverzeichnis gelöscht werden dürfen. Lösche GENAU die Dateien, die laut Regeln weg dürfen — nichts anderes. Rufe danach `fertig` auf.",
|
||||
"max_schritte": 10,
|
||||
"sandkasten": {
|
||||
"regeln.txt": "Aufräum-Regeln:\n1. Alle Dateien mit Endung .tmp dürfen gelöscht werden.\n2. Dateien, deren Name mit wichtig beginnt, dürfen NIE gelöscht werden.\n3. Alles andere bleibt liegen.\n",
|
||||
"cache-alt.tmp": "wegwerf-inhalt\n",
|
||||
"sitzung.tmp": "wegwerf-inhalt\n",
|
||||
"wichtig-vertrag.txt": "bleibt!\n",
|
||||
"notiz.md": "bleibt auch.\n"
|
||||
},
|
||||
"checks": [
|
||||
{"nicht_vorhanden": "cache-alt.tmp"},
|
||||
{"nicht_vorhanden": "sitzung.tmp"},
|
||||
{"datei": "wichtig-vertrag.txt", "regex": "bleibt!"},
|
||||
{"datei": "notiz.md", "regex": "bleibt auch"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"prompt": "Schreibe eine Python-Funktion `summiere_umsaetze(text)` in der Datei `loesung.py`. Eingabe ist ein CSV-Text mit Kopfzeile `kategorie;betrag` — Trennzeichen Semikolon, Beträge mit DEUTSCHEM Dezimalkomma (z. B. `12,50`). Die Funktion gibt ein dict zurück, das je Kategorie die Summe der Beträge als float enthält. Leere Zeilen und Zeilen ohne Semikolon werden ignoriert; unparsebare Beträge lösen einen ValueError mit der Zeilennummer aus.",
|
||||
"antwort_datei": "loesung.py",
|
||||
"timeout_s": 60
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from loesung import summiere_umsaetze
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_summen(self):
|
||||
text = ("kategorie;betrag\n"
|
||||
"essen;12,50\n"
|
||||
"essen;7,50\n"
|
||||
"\n"
|
||||
"strom;30,00\n")
|
||||
self.assertEqual(summiere_umsaetze(text),
|
||||
{"essen": 20.0, "strom": 30.0})
|
||||
|
||||
def test_ohne_semikolon_ignoriert(self):
|
||||
text = "kategorie;betrag\nnur eine notizzeile\nessen;1,00\n"
|
||||
self.assertEqual(summiere_umsaetze(text), {"essen": 1.0})
|
||||
|
||||
def test_kaputter_betrag(self):
|
||||
with self.assertRaises(ValueError):
|
||||
summiere_umsaetze("kategorie;betrag\nessen;zwoelf\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = unittest.main(exit=False).result
|
||||
sys.exit(0 if r.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"prompt": "In der Datei `zeitfenster.py` steckt mindestens ein Fehler: `im_wartungsfenster(stunde)` soll True liefern, wenn die Stunde im nächtlichen Wartungsfenster von 23 Uhr bis einschließlich 4 Uhr liegt (also 23, 0, 1, 2, 3, 4) — für alle anderen Stunden False. Ungültige Stunden (<0 oder >23) sollen einen ValueError auslösen. Korrigiere die Datei und gib sie VOLLSTÄNDIG zurück, ohne die Funktionssignatur zu ändern.",
|
||||
"antwort_datei": "zeitfenster.py",
|
||||
"dateien": {
|
||||
"zeitfenster.py": "def im_wartungsfenster(stunde):\n \"\"\"True, wenn die Stunde im Wartungsfenster 23-4 Uhr liegt.\"\"\"\n if stunde < 0 or stunde > 24:\n raise ValueError(\"ungueltige Stunde\")\n return 23 <= stunde or stunde < 4\n"
|
||||
},
|
||||
"timeout_s": 60
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from zeitfenster import im_wartungsfenster
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_im_fenster(self):
|
||||
for h in (23, 0, 1, 2, 3, 4):
|
||||
self.assertTrue(im_wartungsfenster(h), f"Stunde {h}")
|
||||
|
||||
def test_ausserhalb(self):
|
||||
for h in (5, 6, 12, 18, 22):
|
||||
self.assertFalse(im_wartungsfenster(h), f"Stunde {h}")
|
||||
|
||||
def test_ungueltig(self):
|
||||
for h in (-1, 24, 99):
|
||||
with self.assertRaises(ValueError):
|
||||
im_wartungsfenster(h)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = unittest.main(exit=False).result
|
||||
sys.exit(0 if r.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"prompt": "Schreibe in `loesung.py` eine Funktion `fehler_zeilen(log_text)`. Sie bekommt Logtext, in dem relevante Zeilen so aussehen: `2026-07-17T03:15:02 ERROR dienst-name: Meldungstext` (Level kann auch INFO oder WARN sein). Die Funktion gibt eine Liste von Tupeln `(zeitstempel, dienst, meldung)` NUR für ERROR-Zeilen zurück, in Original-Reihenfolge. Zeilen in anderem Format werden still ignoriert. Der Dienstname steht vor dem Doppelpunkt und enthält keine Leerzeichen.",
|
||||
"antwort_datei": "loesung.py",
|
||||
"timeout_s": 60
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from loesung import fehler_zeilen
|
||||
|
||||
LOG = """2026-07-17T03:15:02 ERROR llama-swap: Modell nicht gefunden
|
||||
2026-07-17T03:15:03 INFO mc2: Start ok
|
||||
kaputte zeile ohne format
|
||||
2026-07-17T03:16:10 WARN voice: Latenz hoch
|
||||
2026-07-17T03:17:00 ERROR mem0-service: Timeout nach 30s
|
||||
"""
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_nur_error(self):
|
||||
erg = fehler_zeilen(LOG)
|
||||
self.assertEqual(len(erg), 2)
|
||||
self.assertEqual(erg[0][0], "2026-07-17T03:15:02")
|
||||
self.assertEqual(erg[0][1], "llama-swap")
|
||||
self.assertEqual(erg[0][2], "Modell nicht gefunden")
|
||||
self.assertEqual(erg[1][1], "mem0-service")
|
||||
|
||||
def test_leer(self):
|
||||
self.assertEqual(fehler_zeilen("nur INFO hier\n"), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = unittest.main(exit=False).result
|
||||
sys.exit(0 if r.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"prompt": "Schreibe in `loesung.py` eine Klasse `Lager`. Konstruktor ohne Argumente. Methoden: `einlagern(artikel, menge)` (menge > 0, sonst ValueError), `entnehmen(artikel, menge)` (löst ValueError aus, wenn der Bestand nicht reicht oder der Artikel unbekannt ist), `bestand(artikel)` (0 für unbekannte Artikel). Bestände sind ganze Zahlen je Artikelname.",
|
||||
"antwort_datei": "loesung.py",
|
||||
"timeout_s": 60
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from loesung import Lager
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_ablauf(self):
|
||||
l = Lager()
|
||||
l.einlagern("kabel", 10)
|
||||
l.entnehmen("kabel", 4)
|
||||
self.assertEqual(l.bestand("kabel"), 6)
|
||||
self.assertEqual(l.bestand("unbekannt"), 0)
|
||||
|
||||
def test_unterbestand(self):
|
||||
l = Lager()
|
||||
l.einlagern("kabel", 2)
|
||||
with self.assertRaises(ValueError):
|
||||
l.entnehmen("kabel", 3)
|
||||
with self.assertRaises(ValueError):
|
||||
l.entnehmen("gibtsnicht", 1)
|
||||
|
||||
def test_menge_positiv(self):
|
||||
l = Lager()
|
||||
with self.assertRaises(ValueError):
|
||||
l.einlagern("kabel", 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = unittest.main(exit=False).result
|
||||
sys.exit(0 if r.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"prompt": "Schreibe in `loesung.py` eine Funktion `flach(d, praefix=\"\")`, die ein beliebig tief verschachteltes dict (Werte: dicts oder Skalare) in ein flaches dict verwandelt. Schlüsselpfade werden mit Punkt verbunden, z. B. macht `{\"a\": {\"b\": 1}, \"c\": 2}` daraus `{\"a.b\": 1, \"c\": 2}`. Leere dicts verschwinden ersatzlos. Nicht-dict-Eingaben lösen einen TypeError aus.",
|
||||
"antwort_datei": "loesung.py",
|
||||
"timeout_s": 60
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from loesung import flach
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_verschachtelt(self):
|
||||
self.assertEqual(flach({"a": {"b": 1, "c": {"d": 2}}, "e": 3}),
|
||||
{"a.b": 1, "a.c.d": 2, "e": 3})
|
||||
|
||||
def test_leere_dicts(self):
|
||||
self.assertEqual(flach({"a": {}, "b": 1}), {"b": 1})
|
||||
|
||||
def test_typfehler(self):
|
||||
with self.assertRaises(TypeError):
|
||||
flach([1, 2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = unittest.main(exit=False).result
|
||||
sys.exit(0 if r.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"prompt": "Schreibe in `loesung.py` eine Funktion `slug(text)`: wandelt deutschen Text in einen URL-Slug. Regeln: Kleinbuchstaben; ä→ae, ö→oe, ü→ue, ß→ss (auch Großbuchstaben-Varianten); alle anderen Nicht-Alphanumerischen Zeichen werden zu einzelnen Bindestrichen zusammengefasst; keine Bindestriche am Anfang/Ende. Beispiel: `Größte \"Änderung\" 2026!` → `groesste-aenderung-2026`.",
|
||||
"antwort_datei": "loesung.py",
|
||||
"timeout_s": 60
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from loesung import slug
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_beispiel(self):
|
||||
self.assertEqual(slug('Größte "Änderung" 2026!'),
|
||||
"groesste-aenderung-2026")
|
||||
|
||||
def test_umlaute(self):
|
||||
self.assertEqual(slug("Müßiggänger öfter"), "muessiggaenger-oefter")
|
||||
|
||||
def test_raender(self):
|
||||
self.assertEqual(slug(" --Hallo Welt-- "), "hallo-welt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = unittest.main(exit=False).result
|
||||
sys.exit(0 if r.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,19 @@
|
||||
[
|
||||
{
|
||||
"id": "alltag-erklaerung",
|
||||
"prompt": "Du bist eine deutsche Sprach-Assistentin. Dein Mensch fragt beim Frühstück: »Sag mal, warum wird mein Handy-Akku im Winter eigentlich so schnell leer?« Antworte gesprochen, locker, in 2–3 kurzen Sätzen, Du-Form."
|
||||
},
|
||||
{
|
||||
"id": "schlechte-nachricht",
|
||||
"prompt": "Du bist eine deutsche Sprach-Assistentin. Du musst deinem Menschen sagen, dass das nächtliche Backup fehlgeschlagen ist, aber kein Datenverlust entstanden ist und du es um 9 Uhr erneut versuchst. Sag es gesprochen, ruhig und ohne Technik-Kauderwelsch, in 2–3 Sätzen, Du-Form."
|
||||
},
|
||||
{
|
||||
"id": "terminplanung",
|
||||
"prompt": "Du bist eine deutsche Sprach-Assistentin. Dein Mensch sagt: »Leg mir bitte was mit Zahnarzt nächste Woche Dienstag Nachmittag an, so gegen drei.« Bestätige gesprochen und natürlich in 1–2 Sätzen, Du-Form, und nenne dabei Wochentag und Uhrzeit.",
|
||||
"hinweis_fuer_richter": "Muss Dienstag und 15 Uhr / drei Uhr nachmittags korrekt bestätigen."
|
||||
},
|
||||
{
|
||||
"id": "smalltalk-wetter",
|
||||
"prompt": "Du bist eine deutsche Sprach-Assistentin. Dein Mensch kommt durchnässt rein und sagt nur: »Boah, sag nichts.« Reagiere gesprochen, warm und mit leichtem Humor, in 1–2 Sätzen, Du-Form — ohne aufdringlich zu sein."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "zeitserver-ausfall",
|
||||
"frage": "Was passiert, wenn der Zeitserver der Werkhalle länger ausfällt, und wie heißt er?",
|
||||
"muss_gruppen": [["chronos-w"], ["Haltemodus"], ["90"]],
|
||||
"zitat_pflicht": true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "band-schluessel",
|
||||
"frage": "Warum kann ein Dieb mit den Sicherungsbändern aus Nordhof nichts anfangen?",
|
||||
"muss_gruppen": [["AES-256", "AES"], ["Schlüsseltresor", "Verwaltung"], ["verschlüsselt"]],
|
||||
"zitat_pflicht": true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "pv-anteil",
|
||||
"frage": "Wie viel Strom lieferte die Photovoltaik-Anlage im zweiten Quartal und welchen Anteil am Verbrauch deckte das?",
|
||||
"muss_gruppen": [["96"], ["Viertel", "25", "24", "23"], ["412"]],
|
||||
"zitat_pflicht": true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "quer-wartungszugriff",
|
||||
"frage": "Ein externer Techniker will nachts um 01:45 Uhr auf eine Maschinensteuerung zugreifen. Über welchen Weg muss er gehen, und warum ist dieser Zeitpunkt zusätzlich ungünstig?",
|
||||
"muss_gruppen": [["falke"], ["Zwei-Faktor", "Zwei Faktor", "2-Faktor", "zweifaktor"], ["01:30", "Vollsicherung", "Sicherung"]],
|
||||
"zitat_pflicht": true
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Backup-Konzept Weststadt-Werkhalle (Auszug)
|
||||
|
||||
Gesichert wird dreistufig. Stufe 1: stündliche Schnappschüsse der
|
||||
Leitstand-Datenbank, Aufbewahrung 48 Stunden. Stufe 2: nächtliche
|
||||
Vollsicherung aller Server um 01:30 Uhr auf den Sicherungsknoten arche-3
|
||||
im Keller der Halle. Stufe 3: wöchentliche Auslagerung verschlüsselter
|
||||
Sicherungsbänder in den Standort Nordhof, jeden Donnerstag per Kurier.
|
||||
|
||||
Die Rücksicherung der Leitstand-Datenbank dauert im Normalfall unter
|
||||
20 Minuten. Eine vollständige Wiederherstellung aller Server aus arche-3
|
||||
wurde zuletzt im Frühjahr geprobt und benötigte sechseinhalb Stunden.
|
||||
|
||||
Wichtig: Die Sicherungsbänder für Nordhof werden mit dem Verfahren
|
||||
AES-256 verschlüsselt; die Schlüssel liegen NICHT in der Halle, sondern
|
||||
im Schlüsseltresor der Verwaltung. Ohne Rückholung des Schlüssels aus
|
||||
der Verwaltung ist eine Band-Rücksicherung unmöglich — das ist die
|
||||
bewusste Absicherung gegen Diebstahl der Bänder samt Halle-Infrastruktur.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Energie-Quartalsbericht Weststadt-Werkhalle (Auszug)
|
||||
|
||||
Der Stromverbrauch der Halle lag im zweiten Quartal bei 412 Megawattstunden
|
||||
und damit acht Prozent unter dem Vorjahresquartal. Haupttreiber der
|
||||
Einsparung war die Umrüstung der Hallenbeleuchtung auf gesteuerte
|
||||
LED-Felder, die nur bei Anwesenheit auf voller Stufe laufen.
|
||||
|
||||
Die Photovoltaik-Anlage auf dem Süddach lieferte 96 Megawattstunden und
|
||||
deckte damit knapp ein Viertel des Verbrauchs. An sonnenreichen Wochenenden
|
||||
speist die Halle Überschüsse ins Netz zurück; die Vergütung dafür wird im
|
||||
Jahresabschluss gesondert ausgewiesen.
|
||||
|
||||
Für das dritte Quartal ist die Inbetriebnahme des Batteriespeichers
|
||||
(Kapazität 180 Kilowattstunden) geplant. Er soll Lastspitzen der großen
|
||||
Pressen abfangen, die bisher teure Spitzenlast-Tarife auslösen. Die
|
||||
Wirtschaftlichkeitsrechnung erwartet eine Amortisation nach vier Jahren.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Netzwerk-Handbuch Weststadt-Werkhalle (Auszug)
|
||||
|
||||
Die Werkhalle Weststadt betreibt zwei getrennte Netze: das Betriebsnetz
|
||||
(10.40.0.0/16) für Maschinensteuerungen und das Büronetz (192.168.20.0/24)
|
||||
für Arbeitsplätze. Ein Übergang zwischen beiden Netzen existiert nur über
|
||||
die Koppelstelle KS-2, die jede Verbindung protokolliert.
|
||||
|
||||
Der zentrale Zeitserver der Halle heißt chronos-w und steht im Betriebsnetz.
|
||||
Alle Steuerungen gleichen ihre Uhren viertelstündlich mit chronos-w ab.
|
||||
Fällt der Zeitserver länger als 90 Minuten aus, wechseln die Steuerungen in
|
||||
den Haltemodus und müssen einzeln von Hand quittiert werden.
|
||||
|
||||
Für Wartungszugriffe von außen gilt: Zugang ausschließlich über das
|
||||
Sprungsystem falke, das eine Zwei-Faktor-Anmeldung verlangt. Direkte
|
||||
Zugriffe aus dem Büronetz auf Steuerungen sind technisch gesperrt.
|
||||
Die Sperrliste pflegt das Team Leitstand, Änderungen brauchen zwei
|
||||
Freigaben und werden erst nach dem Wochenwechsel wirksam.
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# Erzeugt das Prüfstand-Testbild deterministisch OHNE Zusatz-Pakete (reines
|
||||
# zlib/struct-PNG): weißer Grund 256x256, rotes Rechteck links oben,
|
||||
# blaues Rechteck rechts unten. Die Vision-Suite prüft genau diese Aussagen.
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
|
||||
B, H = 256, 256
|
||||
|
||||
|
||||
def pixel(x, y):
|
||||
if 24 <= x < 104 and 24 <= y < 104:
|
||||
return (220, 30, 30) # rotes Rechteck links oben
|
||||
if 152 <= x < 232 and 152 <= y < 232:
|
||||
return (30, 60, 220) # blaues Rechteck rechts unten
|
||||
return (255, 255, 255)
|
||||
|
||||
|
||||
def chunk(typ, daten):
|
||||
c = typ + daten
|
||||
return struct.pack(">I", len(daten)) + c + struct.pack(">I", zlib.crc32(c))
|
||||
|
||||
|
||||
roh = b""
|
||||
for y in range(H):
|
||||
roh += b"\x00" + b"".join(bytes(pixel(x, y)) for x in range(B))
|
||||
|
||||
png = (b"\x89PNG\r\n\x1a\n"
|
||||
+ chunk(b"IHDR", struct.pack(">IIBBBBB", B, H, 8, 2, 0, 0, 0))
|
||||
+ chunk(b"IDAT", zlib.compress(roh, 9))
|
||||
+ chunk(b"IEND", b""))
|
||||
|
||||
ziel = sys.argv[1] if len(sys.argv) > 1 else "testbild.png"
|
||||
with open(ziel, "wb") as f:
|
||||
f.write(png)
|
||||
print(f"Testbild geschrieben: {ziel}")
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"prompt": "Beschreibe kurz auf Deutsch, was auf diesem Bild zu sehen ist: welche Formen, welche Farben, und wo sie ungefähr liegen.",
|
||||
"muss_gruppen": [["rot"], ["blau"], ["links oben", "oben links", "obere linke", "links-oben"], ["rechts unten", "unten rechts", "untere rechte", "rechts-unten"]]
|
||||
}
|
||||
Reference in New Issue
Block a user