Compare commits
7 Commits
79eafe2fa7
...
feature/ta
| Author | SHA1 | Date | |
|---|---|---|---|
| e57aa374ea | |||
| eb3f77f092 | |||
| b396e54080 | |||
| c4baf68595 | |||
| 0a1be647c4 | |||
| 01e5ae8b5c | |||
| adafab61ae |
1
.dev_session/SESSION_INFO
Normal file
1
.dev_session/SESSION_INFO
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"task_id": "2f488f42-8544-819a-8407-f29748b3e0b8", "token": "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"}
|
||||||
14
NOTION_TASK_SUMMARY.md
Normal file
14
NOTION_TASK_SUMMARY.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
**Task Summary: Add a Share Button `[2f488f42]`**
|
||||||
|
|
||||||
|
**Status:** ✅ Done
|
||||||
|
|
||||||
|
**Project:** Meeting Assistant (Transcription Tool)
|
||||||
|
|
||||||
|
**Changes Implemented:**
|
||||||
|
- A new "Share" button has been successfully added to the toolbar in the transcript detail view.
|
||||||
|
- The button is visually aligned with the existing "Copy" and "Download" actions, utilizing the `Share2` icon for consistency.
|
||||||
|
- The feature is UI-only as per the requirements; clicking the button currently triggers a placeholder alert. No backend or sharing logic has been implemented yet.
|
||||||
|
|
||||||
|
**Next Steps:**
|
||||||
|
- The functionality for sharing can be implemented in a future task.
|
||||||
|
- The change is ready to be committed and pushed.
|
||||||
@@ -2,40 +2,41 @@
|
|||||||
|
|
||||||
## Übersicht
|
## Übersicht
|
||||||
|
|
||||||
`dev_session.py` ist ein interaktives Kommandozeilen-Tool (CLI), das den Entwicklungs-Workflow durch die Integration mit Notion optimiert. Es ermöglicht Entwicklern, ein Projekt und einen Task auszuwählen und den Task-Status automatisch zu aktualisieren.
|
`dev_session.py` ist ein Kommandozeilen-Tool (CLI), das den Entwicklungs-Workflow durch die Integration mit Notion und Git optimiert. Es dient als Brücke zwischen der interaktiven Gemini CLI-Sitzung und dem Projektmanagement in Notion.
|
||||||
|
|
||||||
**Wichtige Architekturänderung:** Das Skript `dev_session.py` startet die Gemini CLI nicht mehr direkt. Stattdessen generiert es einen formatierten Kontext und gibt diesen auf der Standardausgabe aus. Das übergeordnete `start-gemini.sh`-Skript fängt diesen Kontext auf und startet dann die Gemini CLI in einer sauberen, interaktiven Sitzung mit dem korrekten Startkontext. Dieser zweistufige Prozess stellt sicher, dass die Gemini CLI mit allen erforderlichen Tools und in der korrekten Umgebung läuft.
|
**Wichtige Architekturänderung:** Das Skript `dev_session.py` startet die Gemini CLI nicht mehr direkt. Stattdessen durchläuft der Entwickler einen interaktiven Setup-Prozess, an dessen Ende ein Kontext für die Gemini CLI generiert wird. Das übergeordnete `start-gemini.sh`-Skript fängt diesen Kontext auf und startet die Gemini CLI in einer sauberen, isolierten Docker-Sitzung. Dieser zweistufige Prozess gewährleistet eine stabile und kontextbezogene Entwicklungsumgebung.
|
||||||
|
|
||||||
## Funktionen
|
## Funktionen
|
||||||
|
|
||||||
* **Interaktive Projekt- und Task-Auswahl:** Wähle Projekte und Tasks direkt aus deinen Notion-Datenbanken.
|
* **Interaktive Projekt- und Task-Auswahl:** Wähle Projekte und Tasks direkt aus deinen Notion-Datenbanken.
|
||||||
* **Automatisches Status-Update:** Der Status des ausgewählten Tasks wird automatisch auf 'Doing' (oder den ersten verfügbaren Status in neuen Projekten) gesetzt, um deinen Fortschritt in Notion widerzuspiegeln.
|
* **Automatischer Start-Status:** Setzt den Status des ausgewählten Tasks beim Start der Sitzung automatisch auf 'Doing'.
|
||||||
* **Task-Erstellung:** Erstelle neue Tasks direkt über das CLI im Kontext des ausgewählten Projekts.
|
* **Task-Erstellung:** Erstelle neue Tasks direkt über das CLI im Kontext des ausgewählten Projekts.
|
||||||
* **Dynamische Status-Erkennung:** Fragt Notion API nach verfügbaren Status-Optionen ab, um Kompatibilität mit verschiedenen Notion-Templates zu gewährleisten.
|
* **Agent-gesteuerte Statusberichte:** Ermöglicht dem Gemini-Agenten, nach Abschluss eines Arbeitspakets einen detaillierten Statusbericht an Notion zu senden, Code zu committen und zu pushen.
|
||||||
* **Gemini CLI Kontext-Generierung:** Erzeugt einen strukturierten Kontext-Prompt, der alle relevanten Informationen (Projekt, Task, Task-ID, vorgeschlagener Git-Branch-Name) für die Gemini CLI enthält.
|
* **Automatisches Commit-Feedback:** Ein `post-commit` Git-Hook sendet jede Commit-Nachricht als kurzen Kommentar an den Notion-Task.
|
||||||
* **Vorgeschlagene Git Branch-Benennung:** Erstellt einen konsistenten Git-Branch-Namen basierend auf der Task-ID und dem Task-Titel.
|
* **Kontext-Generierung für Gemini CLI:** Erzeugt einen strukturierten Start-Prompt mit allen relevanten Informationen (Projekt, Task, Beschreibung, Git-Branch).
|
||||||
|
* **Vorgeschlagene Git Branch-Benennung:** Erstellt einen konsistenten Git-Branch-Namen basierend auf der Task-ID und dem Titel.
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
* **Docker:** Das gesamte Setup ist containerisiert, um Abhängigkeitskonflikte zu vermeiden.
|
* **Docker:** Das gesamte Setup ist containerisiert.
|
||||||
* **Notion Integration Token:** Ein Notion API-Token mit Zugriff auf Ihre "Projects"- und "Tasks"-Datenbanken.
|
* **Notion Integration Token:** Ein Notion API-Token mit Zugriff auf Ihre "Projects"- und "Tasks"-Datenbanken.
|
||||||
|
|
||||||
## Installation und Einrichtung
|
## Installation und Einrichtung
|
||||||
|
|
||||||
Das Setup ist so konzipiert, dass es mit minimalem Aufwand sofort einsatzbereit ist.
|
|
||||||
|
|
||||||
1. **Notion API Key:**
|
1. **Notion API Key:**
|
||||||
* Stellen Sie sicher, dass Sie einen Notion Integration Token haben.
|
* Stellen Sie sicher, dass Sie einen Notion Integration Token haben.
|
||||||
* Das Skript fragt beim ersten Start interaktiv nach dem Token. Sie können es auch als Umgebungsvariable `NOTION_API_KEY` in einer `.env`-Datei im Hauptverzeichnis speichern, um diesen Schritt zu überspringen.
|
* Das Skript fragt beim ersten Start interaktiv nach dem Token. Alternativ kann er als Umgebungsvariable `NOTION_API_KEY` in einer `.env`-Datei im Hauptverzeichnis gespeichert werden.
|
||||||
* Gewähren Sie Ihrer Notion-Integration Zugriff auf die relevanten "Projects"- und "Tasks"-Datenbanken.
|
* Gewähren Sie Ihrer Notion-Integration Zugriff auf die relevanten Datenbanken.
|
||||||
|
|
||||||
2. **Abhängigkeiten:** Alle notwendigen Abhängigkeiten (Python, `requests`, `python-dotenv`) sind im Dockerfile (`gemini.Dockerfile`) definiert und werden automatisch im Container installiert. Es ist keine manuelle Installation via `pip` erforderlich.
|
2. **Abhängigkeiten:** Alle Abhängigkeiten sind im `gemini.Dockerfile` definiert und werden automatisch im Container installiert.
|
||||||
|
|
||||||
## Nutzung
|
## Der Entwicklungs-Workflow
|
||||||
|
|
||||||
|
Der Workflow ist in drei Hauptphasen unterteilt: Sitzung starten, in der Sitzung arbeiten und Ergebnisse zurückmelden.
|
||||||
|
|
||||||
### 1. Sitzung starten
|
### 1. Sitzung starten
|
||||||
|
|
||||||
Das `start-gemini.sh`-Skript orchestriert den gesamten Startvorgang.
|
Der gesamte Startvorgang wird über das `start-gemini.sh`-Skript orchestriert.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start-gemini.sh
|
./start-gemini.sh
|
||||||
@@ -44,64 +45,72 @@ Das `start-gemini.sh`-Skript orchestriert den gesamten Startvorgang.
|
|||||||
**Was im Hintergrund passiert:**
|
**Was im Hintergrund passiert:**
|
||||||
|
|
||||||
1. Das Skript führt `dev_session.py` in einem temporären Docker-Container aus.
|
1. Das Skript führt `dev_session.py` in einem temporären Docker-Container aus.
|
||||||
2. Sie durchlaufen den interaktiven Auswahlprozess für Projekt und Task wie gewohnt.
|
2. Sie durchlaufen den interaktiven Auswahlprozess für Ihr Projekt und Ihren Task in Notion.
|
||||||
3. Nach Ihrer Auswahl gibt `dev_session.py` den generierten Kontext für die Gemini CLI aus und beendet sich.
|
3. Nach der Auswahl generiert `dev_session.py` den Kontext, speichert die Session-Informationen (z.B. Task-ID) in `.dev_session/SESSION_INFO`, installiert die Git-Hooks und gibt den Kontext an die Standardausgabe aus.
|
||||||
4. `start-gemini.sh` fängt diese Ausgabe ab und extrahiert den Kontext.
|
4. `start-gemini.sh` fängt diese Ausgabe ab, extrahiert den Kontext und startet einen neuen, sauberen Docker-Container (`gemini-session`) mit der Gemini CLI, wobei der Kontext als Start-Prompt übergeben wird.
|
||||||
5. Anschließend startet das Skript einen neuen, sauberen Docker-Container mit der Gemini CLI und übergibt den extrahierten Kontext als Start-Prompt.
|
|
||||||
|
|
||||||
Dieser Prozess stellt sicher, dass Ihre interaktive Gemini-Sitzung von der Notion-API-Logik entkoppelt ist und in einer stabilen Umgebung läuft.
|
Dieser Prozess stellt sicher, dass Ihre interaktive Gemini-Sitzung von der Notion-Logik entkoppelt ist und mit allen relevanten Informationen beginnt.
|
||||||
|
|
||||||
### 2. Sitzung abschließen
|
### 2. Arbeiten in der Gemini CLI
|
||||||
|
|
||||||
Wenn Sie die Arbeit an einem Task beendet haben, können Sie die Sitzung über den folgenden Befehl abschließen. Dieser Befehl muss **im Terminal des Hosts** ausgeführt werden, während der Container (`gemini-session`) noch läuft.
|
Nach dem Start befinden Sie sich in der Gemini CLI. Hier findet die eigentliche Entwicklungsarbeit statt.
|
||||||
|
|
||||||
|
* **Commits:** Wenn Sie oder der Agent `git commit` ausführen, greift der `post-commit`-Hook. Er liest die Task-ID aus der Session-Datei und sendet die Commit-Nachricht automatisch als kurzen Kommentar an den verknüpften Notion-Task. Dies sorgt für eine granulare Nachverfolgung des Fortschritts.
|
||||||
|
|
||||||
|
### 3. Fortschritt melden und Änderungen pushen (Agent-gesteuerter Workflow)
|
||||||
|
|
||||||
|
Dies ist der primäre Weg, um ein logisches Arbeitspaket abzuschließen und den Fortschritt zu dokumentieren. Anstatt Git-Befehle und Notion-Updates manuell zu koordinieren, geben Sie dem Agenten eine übergeordnete Anweisung.
|
||||||
|
|
||||||
|
**Beispiel-Anweisung an den Gemini-Agenten:**
|
||||||
|
|
||||||
|
> "Okay, die Implementierung ist abgeschlossen. Fasse die Arbeit zusammen, erstelle einen Statusbericht für Notion, committe alle Änderungen und pushe sie."
|
||||||
|
|
||||||
|
**Was der Agent im Hintergrund tut:**
|
||||||
|
|
||||||
|
1. **Informationen sammeln:** Der Agent führt einen kurzen Dialog mit Ihnen, um den neuen Status (`Bereit für Review`, `Blockiert` etc.) und eventuelle offene To-Dos zu erfragen.
|
||||||
|
2. **Git-Änderungen analysieren:** Der Agent generiert eine Zusammenfassung der geänderten Dateien und der neuen Commit-Nachrichten.
|
||||||
|
3. **Notion-Update (nicht-interaktiv):** Der Agent ruft `dev_session.py` mit den gesammelten Informationen als Parameter auf.
|
||||||
|
```bash
|
||||||
|
python3 dev_session.py --report-status \
|
||||||
|
--status "Ready for Review" \
|
||||||
|
--todos "- Finale Doku prüfen" \
|
||||||
|
--git-changes "..." \
|
||||||
|
--commit-messages "..."
|
||||||
|
```
|
||||||
|
4. **Bericht erstellen:** Das Skript formatiert diese Informationen zu einem sauberen Status-Update, postet es als Kommentar an den Notion-Task und aktualisiert gleichzeitig dessen Status-Feld.
|
||||||
|
5. **Git-Operationen:** Nachdem Notion aktualisiert wurde, führt der Agent die `git add`, `git commit` und `git push` Befehle aus.
|
||||||
|
|
||||||
|
Dieser Prozess stellt sicher, dass der Code-Stand im Repository immer synchron mit dem dokumentierten Fortschritt in Notion ist, ohne dass Sie die Gemini CLI verlassen müssen.
|
||||||
|
|
||||||
|
### 4. Sitzung abschließen
|
||||||
|
|
||||||
|
Wenn der gesamte Task abgeschlossen ist, können Sie die Sitzung über den folgenden Befehl **vom Host-Terminal aus** beenden.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it gemini-session python3 dev_session.py --done
|
docker exec -it gemini-session python3 dev_session.py --done
|
||||||
```
|
```
|
||||||
|
|
||||||
Dieser Befehl:
|
Dieser Befehl:
|
||||||
* Setzt den Status des Notion-Tasks auf "Done" (oder den finalen Status in Ihrer Konfiguration).
|
* Setzt den Status des Notion-Tasks auf "Done".
|
||||||
* Löscht die lokale Session-Datei (`.session_info.json`).
|
* Löscht die lokale Session-Datei (`.dev_session/SESSION_INFO`).
|
||||||
* Deinstalliert den Git-Hook.
|
* Deinstalliert den `post-commit` Git-Hook.
|
||||||
|
|
||||||
### 3. Automatisches Notion-Feedback (Git-Hook)
|
|
||||||
|
|
||||||
Beim Starten einer Sitzung wird automatisch ein `post-commit`-Git-Hook installiert.
|
|
||||||
|
|
||||||
* **Funktion:** Nach jedem `git commit` wird die Commit-Nachricht automatisch als Kommentar an den verknüpften Notion-Task gesendet.
|
|
||||||
* **Voraussetzung:** Funktioniert nur für Commits, die im `gemini-session`-Container oder auf dem Host-System (mit installiertem `requests`) gemacht werden, während die Session aktiv ist.
|
|
||||||
* **Deinstallation:** Der Hook wird beim Abschluss der Sitzung mit `--done` automatisch wieder entfernt.
|
|
||||||
|
|
||||||
### Beispiel-Interaktion (gekürzt)
|
|
||||||
|
|
||||||
Die interaktive Auswahl von Projekt und Task bleibt unverändert:
|
|
||||||
|
|
||||||
```
|
|
||||||
Starte interaktive Entwicklungs-Session...
|
|
||||||
Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): ****************
|
|
||||||
Suche nach Datenbank 'Projects [UT]' in Notion...
|
|
||||||
Datenbank 'Projects [UT]' gefunden mit ID: <PROJEKTE_DB_ID>
|
|
||||||
|
|
||||||
An welchem Projekt möchtest du arbeiten?
|
|
||||||
[1] My Awesome Project
|
|
||||||
[2] Sync Engine
|
|
||||||
[...]
|
|
||||||
Bitte wähle eine Nummer: 2
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Nach der Auswahl wird automatisch die Gemini CLI gestartet.
|
|
||||||
|
|
||||||
## Git Branch Benennungs-Konvention
|
## Git Branch Benennungs-Konvention
|
||||||
|
|
||||||
Das Skript schlägt automatisch einen Git-Branch-Namen vor, der dem Muster `feature/task-{kurze_task_id}-{task_titel_slug}` folgt.
|
Das Skript schlägt automatisch einen Git-Branch-Namen vor, der dem Muster `feature/task-{kurze_task_id}-{task_titel_slug}` folgt, um eine direkte Verbindung zwischen Code und Task herzustellen.
|
||||||
* `feature/task-`: Ein Präfix, das den Branch-Typ und die Beziehung zu einem Notion-Task anzeigt.
|
|
||||||
* `{kurze_task_id}`: Die ersten 8 Zeichen der Notion Task ID, für eine eindeutige Referenz.
|
|
||||||
* `{task_titel_slug}`: Eine "Slugified"-Version des Task-Titels (Kleinbuchstaben, Leerzeichen durch Bindestriche ersetzt, Sonderzeichen entfernt).
|
|
||||||
|
|
||||||
**Beispiel:** `feature/task-2f388f42-create-readmemd-for-devsessionpy`
|
**Beispiel:** `feature/task-2f388f42-update-readme-for-reporting`
|
||||||
|
|
||||||
## Wichtige Hinweise
|
## Entwicklung & Troubleshooting des Start-Skripts
|
||||||
|
|
||||||
|
Das `start-gemini.sh`-Skript wurde entwickelt, um mehrere Herausforderungen zu lösen:
|
||||||
|
|
||||||
|
* **Problem: Fehlende Interaktivität:** Frühe Versionen leiteten die Ausgabe von `dev_session.py` direkt in eine Variable um, was die interaktiven `input()`-Aufforderungen blockierte.
|
||||||
|
* **Lösung:** Einsatz des `tee`-Befehls, der die Ausgabe gleichzeitig auf dem Bildschirm anzeigt und in eine temporäre Datei schreibt, aus der der Kontext später gelesen wird.
|
||||||
|
|
||||||
|
* **Problem: Fehlerhafter Start-Parameter:** Die Gemini CLI erwartet den initialen Prompt über `--prompt-interactive`.
|
||||||
|
* **Lösung:** Korrektur des Arguments im `docker run`-Befehl.
|
||||||
|
|
||||||
|
* **Problem: Konflikt durch nicht aufgeräumte Container:** Vorzeitig beendete Skripte hinterließen Container, die den nächsten Start blockierten.
|
||||||
|
* **Lösung:** Explizite `docker rm -f`-Befehle am Anfang des Skripts, um alte Container zu bereinigen.
|
||||||
|
|
||||||
* Stelle sicher, dass deine Notion-Datenbanken die Property `Status` mit mindestens einer Statusoption haben.
|
|
||||||
* Der `Readme Path` wird dynamisch aus dem in Notion ausgewählten Projekt geladen. Falls in Notion kein spezifischer Pfad hinterlegt ist, wird standardmäßig `readme.md` verwendet. Dies eliminiert die Notwendigkeit einer manuellen `project_to_path_map` im Skript.
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ Der **Meeting Assistant** ist eine leistungsstarke Suite zur Transkription und B
|
|||||||
|
|
||||||
## 2. Key Features (v0.6.0)
|
## 2. Key Features (v0.6.0)
|
||||||
|
|
||||||
### 🚀 **NEU:** AI Insights auf Knopfdruck
|
### 🚀 **NEU:** AI Insights & Translation
|
||||||
|
* **Übersetzung (DE/EN):** Übersetzt das gesamte Transkript mit einem Klick ins Englische.
|
||||||
* **Meeting-Protokoll:** Erstellt automatisch ein formelles Protokoll (Meeting Minutes) mit Agenda, Entscheidungen und nächsten Schritten.
|
* **Meeting-Protokoll:** Erstellt automatisch ein formelles Protokoll (Meeting Minutes) mit Agenda, Entscheidungen und nächsten Schritten.
|
||||||
* **Action Items:** Extrahiert eine Aufgabenliste mit Verantwortlichen und Fälligkeiten direkt aus dem Gespräch.
|
* **Action Items:** Extrahiert eine Aufgabenliste mit Verantwortlichen und Fälligkeiten direkt aus dem Gespräch.
|
||||||
* **Rollenbasierte Zusammenfassungen:** Generiert spezifische Zusammenfassungen, z.B. eine "Sales Summary", die sich auf Kundenbedürfnisse, Kaufsignale und nächste Schritte für das Vertriebsteam konzentriert.
|
* **Rollenbasierte Zusammenfassungen:** Generiert spezifische Zusammenfassungen, z.B. eine "Sales Summary", die sich auf Kundenbedürfnisse, Kaufsignale und nächste Schritte für das Vertriebsteam konzentriert.
|
||||||
@@ -51,6 +52,7 @@ Der **Meeting Assistant** ist eine leistungsstarke Suite zur Transkription und B
|
|||||||
| `GET` | `/meetings` | Liste aller Meetings. |
|
| `GET` | `/meetings` | Liste aller Meetings. |
|
||||||
| `POST` | `/upload` | Audio-Upload & Prozess-Start. |
|
| `POST` | `/upload` | Audio-Upload & Prozess-Start. |
|
||||||
| `POST` | `/meetings/{id}/insights` | **Neu:** Generiert eine Analyse (z.B. Protokoll, Action Items). |
|
| `POST` | `/meetings/{id}/insights` | **Neu:** Generiert eine Analyse (z.B. Protokoll, Action Items). |
|
||||||
|
| `POST` | `/meetings/{id}/translate` | **Neu:** Übersetzt das Transkript in eine Zielsprache (aktuell: 'English'). |
|
||||||
| `POST` | `/meetings/{id}/rename_speaker` | Globale Umbenennung in der DB. |
|
| `POST` | `/meetings/{id}/rename_speaker` | Globale Umbenennung in der DB. |
|
||||||
| `PUT` | `/chunks/{id}` | Speichert manuelle Text-Korrekturen. |
|
| `PUT` | `/chunks/{id}` | Speichert manuelle Text-Korrekturen. |
|
||||||
| `DELETE` | `/meetings/{id}` | Vollständiges Löschen. |
|
| `DELETE` | `/meetings/{id}` | Vollständiges Löschen. |
|
||||||
@@ -62,3 +64,18 @@ Der **Meeting Assistant** ist eine leistungsstarke Suite zur Transkription und B
|
|||||||
* **v0.7: Search:** Globale Suche über alle Transkripte hinweg.
|
* **v0.7: Search:** Globale Suche über alle Transkripte hinweg.
|
||||||
* **v0.8: Q&A an das Meeting:** Ermöglicht, Fragen direkt an das Transkript zu stellen ("Was wurde zu Thema X beschlossen?").
|
* **v0.8: Q&A an das Meeting:** Ermöglicht, Fragen direkt an das Transkript zu stellen ("Was wurde zu Thema X beschlossen?").
|
||||||
* **v0.9: Export-Formate:** Export der Ergebnisse in verschiedene Formate (z.B. PDF, DOCX).
|
* **v0.9: Export-Formate:** Export der Ergebnisse in verschiedene Formate (z.B. PDF, DOCX).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Development Notes & Troubleshooting
|
||||||
|
|
||||||
|
Bei der Implementierung der AI-Insights-Funktion (v0.6.0) traten mehrere Probleme auf, deren Lösungen für die zukünftige Entwicklung wichtig sind:
|
||||||
|
|
||||||
|
* **Isolierung von Microservices:** Der Versuch, eine zentrale `helpers.py`-Datei aus dem `transcription-app`-Container zu importieren, schlug mit einem `ModuleNotFoundError` fehl.
|
||||||
|
* **Lösung:** Kritische Funktionen (wie der Gemini-API-Client) wurden in eine lokale Bibliothek (`/lib/gemini_client.py`) innerhalb des Service-Backends dupliziert, um den Service eigenständig zu machen.
|
||||||
|
* **API-Schlüssel in Docker:** Der neue, isolierte Service konnte den API-Schlüssel nicht aus einer Datei lesen.
|
||||||
|
* **Lösung:** Der `GEMINI_API_KEY` wurde als Umgebungsvariable über die `docker-compose.yml`-Datei an den Container übergeben. Dies ist die Best Practice für die Bereitstellung von "Secrets" für containerisierte Anwendungen. **Wichtig:** Ein `docker-compose restart` reicht nicht aus, um die Änderung zu übernehmen; ein `docker-compose up -d --force-recreate <service_name>` ist erforderlich.
|
||||||
|
* **Modell-Kompatibilität:** API-Aufrufe schlugen mit `404 NOT_FOUND` fehl, weil versucht wurde, ein nicht zum API-Schlüssel passendes Modell (`gemini-1.5-flash`) zu verwenden.
|
||||||
|
* **Lösung:** Der Modellname wurde auf das im Projekt etablierte und funktionierende Modell `gemini-2.0-flash` korrigiert.
|
||||||
|
* **Datenformatierung:** Die KI lieferte generische Antworten, weil das an sie übergebene Transkript leer war.
|
||||||
|
* **Lösung:** Die Analyse des rohen JSON-Outputs aus der Datenbank (`debug_chunks`-Endpunkt) zeigte, dass die Formatierungslogik die `absolute_seconds` zur korrekten chronologischen Sortierung verwenden muss. Die `_format_transcript`-Funktion wurde entsprechend angepasst.
|
||||||
|
|||||||
251
alt_roboplanet-gtm-strategy-2026-01-14.md
Normal file
251
alt_roboplanet-gtm-strategy-2026-01-14.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# GTM Strategy
|
||||||
|
|
||||||
|
**Recherche-URL:** https://www.inmotionrobotic.com/de/puma
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# GTM STRATEGY REPORT
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
Dieser Go-to-Market (GTM)-Strategiebericht konzentriert sich auf die Markteinführung des PUMA M20, eines kompakten, geländegängigen Quadruped-Roboters für Inspektions-, Logistik- und Sicherheitsanwendungen. Die Strategie zielt auf Chemie- und Petrochemieanlagen, Energieversorgungsunternehmen sowie Logistikzentren und große Lagerhäuser ab. Der PUMA M20 wird als Lösung zur Verbesserung der Sicherheit, Effizienz und Kosteneffektivität in diesen Branchen positioniert, wobei der Fokus auf der "Dynamic Hybrid Service"-Logik liegt: Der Roboter detektiert die Gefahr, Wackler beseitigt sie.
|
||||||
|
|
||||||
|
## 2. Product Analysis
|
||||||
|
Der PUMA M20 zeichnet sich durch seine All-Terrain-Mobilität, Wetterfestigkeit und kompakte Bauweise aus. Er ist mit fortschrittlichen Sensoren und Rechenleistung ausgestattet, was ihn ideal für autonome Inspektionen und Sicherheitsüberwachung in anspruchsvollen Umgebungen macht.
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
* All-Terrain-Mobilität: Bewältigt Treppen, Schotter, Schlamm und Stahlroste.
|
||||||
|
* Wetterfestigkeit: IP66-Zertifizierung für Staub- und Wasserdichtigkeit.
|
||||||
|
* Kompakte Bauweise: Passt durch 50 cm breite Gänge und ist rucksackgroß.
|
||||||
|
* Autonome Navigation: SLAM-Navigation für autonome Missionen und Rückkehr zur Basis.
|
||||||
|
* 360°-Umgebungserfassung: Duale LiDAR-Systeme und Weitwinkelkameras.
|
||||||
|
* Nachtsichtfähigkeit: Optionale Nacht- und Wärmebildkameras.
|
||||||
|
* Hohe Rechenleistung: Duale Octa-Core-Prozessoren mit 16 GB RAM und 128 GB Speicher.
|
||||||
|
* Flexible Nutzlastoptionen: LiDAR, Wärmebild, PTZ, Gassensoren, Beacons.
|
||||||
|
* Flottenmanagement und API-Integrationen: Für Datenexport und zentrale Steuerung.
|
||||||
|
* Lange Betriebsdauer: Bis zu 3 Stunden, erweiterbar durch Hot-Swap-Batterien.
|
||||||
|
* Hohe Traglast: 12 kg Nennlast, 50 kg maximale Tragfähigkeit.
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
* Maximale Steigung: 45° (abhängig vom Untergrund)
|
||||||
|
* Maximale Geschwindigkeit: 5 m/s
|
||||||
|
* Schritt-Höhe: 22 cm (kontinuierlich)
|
||||||
|
* Betriebstemperatur: -20°C bis 55°C
|
||||||
|
* Schutzart: IP66
|
||||||
|
* Abmessungen: Rucksackgröße, passt durch 50 cm breite Gänge
|
||||||
|
* Gewicht: 33kg
|
||||||
|
|
||||||
|
## 3. Technical Specifications (Hard Facts)
|
||||||
|
|
||||||
|
| Spezifikation | Wert | Einheit |
|
||||||
|
| :--------------------- | :------------------------------------ | :--------------- |
|
||||||
|
| Akkulaufzeit | 180 | Minuten |
|
||||||
|
| Ladezeit | N/A | Minuten |
|
||||||
|
| Gewicht | 33 | kg |
|
||||||
|
| Breite | 50 | cm |
|
||||||
|
| Max. Steigung | 45 | Grad |
|
||||||
|
| IP-Schutzart | IP66 | |
|
||||||
|
| Kletterhöhe | 25 | cm |
|
||||||
|
| Navigation | SLAM, LiDAR | |
|
||||||
|
| Konnektivität | Gigabit Ethernet, USB 3.0 | |
|
||||||
|
| Max. Zuladung | 50 | kg |
|
||||||
|
| Kamera-Typen | Weitwinkel | |
|
||||||
|
| Nachtsicht | Ja | |
|
||||||
|
| Maximale Geschwindigkeit | 5 | m/s |
|
||||||
|
| Kontinuierliche Geschwindigkeit | 3 | m/s |
|
||||||
|
| Betriebstemperatur | -20 bis 55 | °C |
|
||||||
|
| LiDAR Linien | 96 | Linien |
|
||||||
|
| Externe Leistungsabgabe | 300 | W |
|
||||||
|
| Schritt-Höhe (kontinuierlich) | 22 | cm |
|
||||||
|
|
||||||
|
## 4. Phase 2: ICP Discovery
|
||||||
|
|
||||||
|
**ICPs (Ideal Customer Profiles):**
|
||||||
|
|
||||||
|
* **Chemie- und Petrochemieanlagen:** Anlagen dieser Art erfordern regelmäßige Inspektionen auf Lecks, Korrosion und strukturelle Integrität. Der PUMA M20 kann diese Aufgaben autonom durchführen, auch in schwer zugänglichen oder gefährlichen Bereichen, und so die Sicherheit erhöhen und Ausfallzeiten reduzieren. Die Fähigkeit zur Gassensorik ist hier besonders wertvoll.
|
||||||
|
* **Energieversorgungsunternehmen (z.B. Windparks, Solarparks, Umspannwerke):** Weitläufige Anlagen wie Wind- und Solarparks oder Umspannwerke profitieren von der autonomen Überwachungsfähigkeit des PUMA M20. Er kann Zäune patrouillieren, Einbruchsversuche erkennen, Schäden an Anlagen frühzeitig identifizieren (z.B. durch Wärmebildkameras) und so die Sicherheit und Effizienz erhöhen. Die All-Terrain-Mobilität ist hier entscheidend.
|
||||||
|
* **Logistikzentren und große Lagerhäuser:** Der PUMA M20 kann in Logistikzentren und Lagerhäusern für die Überwachung von Sicherheitsbereichen, die Inspektion von Regalen und die Unterstützung bei Inventurprozessen eingesetzt werden. Seine Fähigkeit, Nutzlasten zu tragen, ermöglicht auch den Transport von kleinen Gütern oder Werkzeugen. Die kompakte Bauweise ermöglicht den Einsatz auch in engen Gängen.
|
||||||
|
|
||||||
|
**Data Proxies:**
|
||||||
|
|
||||||
|
* **Websites von Chemie- und Petrochemieunternehmen:** Suche nach Erwähnungen von 'Anlageninspektion', 'Sicherheitsüberwachung', 'Drohneninspektion', 'Robotik', 'Digitalisierung' und 'Predictive Maintenance' im Kontext von Quadruped Robotern.
|
||||||
|
* **LinkedIn-Profile von Head of Security, Werkschutzleitern, Instandhaltungsleitern in Energieversorgungsunternehmen:** Verwendung von LinkedIn Sales Navigator, um Profile mit den genannten Titeln und Schlüsselwörtern wie 'Robotik', 'Sicherheit', 'Inspektion', 'Autonome Systeme', 'Perimeter Protection' und 'IoT' zu finden.
|
||||||
|
* **Branchenpublikationen und Fachmessen für Logistik und Sicherheit:** Analyse von Artikeln, Whitepapers und Ausstellerlisten auf relevante Unternehmen, die an Robotik-Lösungen für Sicherheitsüberwachung, Inspektion und Materialtransport interessiert sein könnten. Suche nach Unternehmen, die bereits in Automatisierung investieren.
|
||||||
|
|
||||||
|
## 5. Target Accounts
|
||||||
|
|
||||||
|
* **Chemie- und Petrochemieanlagen:** BASF SE, Bayer AG, Evonik Industries AG, LANXESS AG, Covestro AG
|
||||||
|
* **Energieversorgungsunternehmen (z.B. Windparks, Solarparks, Umspannwerke):** E.ON SE, RWE AG, EnBW Energie Baden-Württemberg AG, Vattenfall GmbH, innogy SE (Teil von E.ON)
|
||||||
|
* **Logistikzentren und große Lagerhäuser:** Deutsche Post DHL Group, DB Schenker, Kühne + Nagel, Dachser SE, Amazon (Logistikzentren in DACH)
|
||||||
|
|
||||||
|
## 6. Strategy Matrix
|
||||||
|
|
||||||
|
| Segment | Pain Point | Angle | Differentiation |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| Chemie- und Petrochemieanlagen | Unzureichende Sicherheitsüberwachung großer, komplexer Anlagen; Gefahrstofferkennung; schwer zugängliche Bereiche. | Der Roboter sieht die Gefahr (Gassensoren, Wärmebild), Wackler beseitigt sie. (Automated Perimeter Protection). Autonome Inspektion von schwer zugänglichen Bereichen und frühzeitige Erkennung von Gefahrenstoffen durch den Roboter. Bei Bedarf Intervention durch Wackler Security. | All-Terrain-Mobilität, flexible Nutzlastoptionen (Gassensoren), 360°-Umgebungserfassung, NSL-Aufschaltung und Interventionsdienst durch Wackler Security. |
|
||||||
|
| Energieversorgungsunternehmen (z.B. Windparks, Solarparks, Umspannwerke) | Hohe Inspektionskosten; schwer zugängliches Gelände; Notwendigkeit kontinuierlicher Überwachung gegen Vandalismus und Diebstahl. | Der Roboter sieht die Gefahr, Wackler beseitigt sie. (Automated Perimeter Protection). Autonome Patrouillen zur Überwachung von Anlagen, Erkennung von Schäden oder unbefugtem Zutritt. Alarmierung der Wackler Security bei Bedarf. | Wetterfestigkeit, lange Betriebsdauer, autonome Navigation, NSL-Aufschaltung und Interventionsdienst durch Wackler Security. |
|
||||||
|
| Logistikzentren und große Lagerhäuser | Ineffiziente Überwachung großer Lagerflächen; Diebstahlprävention; Überprüfung von Sicherheitsvorschriften. | Der Roboter sieht die Gefahr, Wackler beseitigt sie. (Automated Perimeter Protection). Autonome Überwachung zur Diebstahlprävention und Einhaltung von Sicherheitsvorschriften. Bei Auffälligkeiten erfolgt die Intervention durch Wackler Security. | Kompakte Bauweise, autonome Navigation, Flottenmanagement, NSL-Aufschaltung und Interventionsdienst durch Wackler Security. |
|
||||||
|
|
||||||
|
## 7. Next Steps
|
||||||
|
|
||||||
|
1. **Leadgenerierung:** Nutzung der identifizierten Data Proxies, um qualifizierte Leads zu generieren.
|
||||||
|
2. **Content-Erstellung:** Entwicklung von zielgerichteten Inhalten (Fallstudien, Whitepapers, Webinare), die die Vorteile des PUMA M20 und des "Dynamic Hybrid Service"-Ansatzes hervorheben.
|
||||||
|
3. **Direktvertrieb:** Ansprache der Target Accounts durch personalisierte E-Mails und Telefonanrufe.
|
||||||
|
4. **Pilotprojekte:** Durchführung von Pilotprojekten mit ausgewählten Kunden, um den Wert des PUMA M20 in der Praxis zu demonstrieren.
|
||||||
|
5. **Partnerschaften:** Aufbau von Partnerschaften mit relevanten Technologieanbietern und Systemintegratoren.
|
||||||
|
|
||||||
|
## 8. Hybrid Service Logic
|
||||||
|
|
||||||
|
Der PUMA M20 ist ein Sicherheits- und Inspektionsroboter. Daher gilt folgende Logik:
|
||||||
|
|
||||||
|
* **Robot Role:** "Detektion & Präsenz". Der Roboter patrouilliert, nutzt Wärmebild/Sensoren, geht in Gefahrenbereiche. Er wird nicht müde und hat keine Angst.
|
||||||
|
* **Human Role (Wackler Security):** "Bewertung & Intervention".
|
||||||
|
* NSL (Notruf- & Serviceleitstelle): Bewertet den Video-Feed des Roboters bei Alarm.
|
||||||
|
* Revierwachdienst: Fährt raus zur Intervention, wenn der Roboter einen Eindringling oder Schaden meldet.
|
||||||
|
* **Pitch:** "Der Roboter sieht die Gefahr, Wackler beseitigt sie. (Automated Perimeter Protection)."
|
||||||
|
|
||||||
|
# SALES ENABLEMENT & VISUALS (PHASE 6)
|
||||||
|
|
||||||
|
## Kill-Critique Battlecards
|
||||||
|
|
||||||
|
### Persona: Head of Security / Werkschutzleiter
|
||||||
|
> **Objection:** "Die Implementierung von Robotern ist zu teuer und der ROI ist unklar."
|
||||||
|
|
||||||
|
**Response:** Wir bieten nicht nur einen Roboter, sondern ein umfassendes Sicherheitssystem. Durch die Kombination aus autonomer Roboterpatrouille und der Interventionskraft von Wackler Security reduzieren Sie Ihre Sicherheitskosten langfristig. Der Roboter übernimmt monotone Überwachungsaufgaben, während unsere Experten sich auf die Bewertung und Intervention konzentrieren. Dies führt zu einer effizienteren Nutzung Ihrer Ressourcen und einer höheren Sicherheit. Wir können Ihnen eine detaillierte ROI-Analyse basierend auf Ihren spezifischen Anforderungen erstellen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Anlagenleiter / Betriebsleiter
|
||||||
|
> **Objection:** "Ich befürchte, dass der Roboter den Betriebsablauf stört und Ausfallzeiten verursacht."
|
||||||
|
|
||||||
|
**Response:** Unser Roboter ist so konzipiert, dass er den Betrieb nicht stört. Seine kompakte Bauweise und autonome Navigation ermöglichen es ihm, sich sicher in Ihrer Anlage zu bewegen, ohne den laufenden Betrieb zu beeinträchtigen. Im Gegenteil, durch die kontinuierliche Überwachung und frühzeitige Erkennung von Problemen kann er Ausfallzeiten sogar reduzieren. Wir bieten eine gründliche Schulung und Integration, um sicherzustellen, dass der Roboter reibungslos in Ihre bestehenden Prozesse integriert wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Instandhaltungsleiter
|
||||||
|
> **Objection:** "Ich bin skeptisch, ob der Roboter zuverlässig ist und ob die Wartung kompliziert ist."
|
||||||
|
|
||||||
|
**Response:** Unser Roboter ist für den industriellen Einsatz konzipiert und verfügt über eine robuste Bauweise und wetterfeste Komponenten. Die Wartung ist unkompliziert und kann von Ihrem Team durchgeführt werden. Wir bieten umfassende Schulungen und Support, um sicherzustellen, dass Sie den Roboter optimal nutzen können. Darüber hinaus bieten wir optionale Wartungsverträge an, um Ihnen zusätzliche Sicherheit zu geben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Einkaufsleiter
|
||||||
|
> **Objection:** "Das Budget ist begrenzt und ich muss sicherstellen, dass wir die kosteneffizienteste Lösung erhalten."
|
||||||
|
|
||||||
|
**Response:** Wir verstehen, dass das Budget eine wichtige Rolle spielt. Unser Angebot ist darauf ausgerichtet, Ihnen eine kosteneffiziente Lösung zu bieten, die langfristig Ihre Sicherheitskosten senkt. Durch die Automatisierung von Überwachungsaufgaben und die Reduzierung von Risiken können Sie erhebliche Einsparungen erzielen. Wir bieten flexible Finanzierungsoptionen und können Ihnen eine detaillierte Kosten-Nutzen-Analyse erstellen, um Ihnen bei Ihrer Entscheidung zu helfen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Innovationsmanager / Digitalisierungsbeauftragter
|
||||||
|
> **Objection:** "Ich bin mir nicht sicher, ob der Roboter wirklich einen Mehrwert für unser Unternehmen bietet."
|
||||||
|
|
||||||
|
**Response:** Unser Roboter ist mehr als nur ein Gadget. Er ist ein integraler Bestandteil einer umfassenden Sicherheitsstrategie, die Ihnen hilft, Ihre Anlagen besser zu schützen, Risiken zu reduzieren und die Effizienz zu steigern. Durch die Integration von modernster Technologie und der Expertise von Wackler Security bieten wir Ihnen eine einzigartige Lösung, die Ihnen einen Wettbewerbsvorteil verschafft. Wir laden Sie gerne zu einem Pilotprojekt ein, um die Vorteile des Roboters in Ihrer eigenen Umgebung zu erleben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visual Briefings (Prompts)
|
||||||
|
|
||||||
|
### Roboter in Chemieanlage
|
||||||
|
*Context: Demonstration des Roboters in einer typischen Chemie- oder Petrochemieanlage.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Erstelle ein Foto-realistisches Bild eines Quadruped-Roboters, der autonom durch eine Chemieanlage navigiert. Der Roboter sollte mit Gassensoren und einer Wärmebildkamera ausgestattet sein. Im Hintergrund sind Rohrleitungen, Tanks und Produktionsanlagen zu sehen. Das Bild soll die Fähigkeit des Roboters zur autonomen Inspektion und Gefahrstofferkennung hervorheben. Füge im Hintergrund einen Wackler Security Mitarbeiter hinzu, der auf dem Weg zu einer Intervention ist.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Roboter in Windpark
|
||||||
|
*Context: Darstellung des Roboters bei der Überwachung eines Windparks.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Erstelle ein Foto-realistisches Bild eines Quadruped-Roboters, der in einem Windpark patrouilliert. Der Roboter sollte wetterfest sein und über eine lange Akkulaufzeit verfügen. Im Hintergrund sind Windkraftanlagen und ein weiter Himmel zu sehen. Das Bild soll die Fähigkeit des Roboters zur kontinuierlichen Überwachung und Erkennung von Vandalismus oder Diebstahl hervorheben. Zeige im Hintergrund einen Wackler Security Wagen, der auf dem Weg zum Einsatzort ist.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Roboter in Logistikzentrum
|
||||||
|
*Context: Visualisierung des Roboters bei der Überwachung eines Logistikzentrums.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Erstelle ein Foto-realistisches Bild eines Quadruped-Roboters, der autonom durch ein Logistikzentrum navigiert. Der Roboter sollte kompakt sein und über eine 360°-Umgebungserfassung verfügen. Im Hintergrund sind Regale, Gabelstapler und Mitarbeiter zu sehen. Das Bild soll die Fähigkeit des Roboters zur Diebstahlprävention und Einhaltung von Sicherheitsvorschriften hervorheben. Zeige im Hintergrund einen Mitarbeiter der Wackler NSL, der einen Alarm bearbeitet.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# VERTICAL LANDING PAGES (PHASE 7)
|
||||||
|
|
||||||
|
## Chemie- und Petrochemieanlagen
|
||||||
|
**Headline:** Maximale Anlagensicherheit: Autonome Inspektion trifft auf menschliche Expertise
|
||||||
|
|
||||||
|
**Subline:** Reduzieren Sie Risiken und Ausfallzeiten mit dem PUMA M20 und der Wackler Security. 24/7 Überwachung, Detektion von Gefahrenstoffen und schnelle Intervention.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Frühzeitige Erkennung von Lecks und Korrosion durch Gassensoren und Wärmebildkameras.
|
||||||
|
- Autonome Inspektion schwer zugänglicher Bereiche, auch in explosionsgefährdeten Zonen.
|
||||||
|
- Nahtlose Integration in die Wackler Notruf- und Serviceleitstelle (NSL) für sofortige Alarmierung.
|
||||||
|
- Schnelle Intervention durch Wackler Security bei erkannten Gefahren oder unbefugtem Zutritt.
|
||||||
|
- All-Terrain-Mobilität für Inspektionen auf dem gesamten Werksgelände.
|
||||||
|
|
||||||
|
**CTA:** Jetzt Sicherheitslösung konfigurieren!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Energieversorgungsunternehmen (Windparks, Solarparks, Umspannwerke)
|
||||||
|
**Headline:** Autonome Überwachung für Ihre Energieanlagen: Der PUMA M20 macht den Unterschied
|
||||||
|
|
||||||
|
**Subline:** Schützen Sie Ihre Anlagen vor Vandalismus, Diebstahl und Umweltschäden mit dem PUMA M20 und der Wackler Security. Kontinuierliche Überwachung, auch in unwegsamem Gelände.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Autonome Patrouillen zur Überwachung von Zäunen und Anlagen.
|
||||||
|
- Früherkennung von Schäden durch Wärmebildkameras und andere Sensoren.
|
||||||
|
- Abschreckung von Vandalismus und Diebstahl durch permanente Präsenz.
|
||||||
|
- Wetterfeste Konstruktion für den zuverlässigen Einsatz im Freien.
|
||||||
|
- Aufschaltung auf die Wackler NSL und schnelle Intervention bei Alarmen.
|
||||||
|
|
||||||
|
**CTA:** Sichern Sie Ihre Energieanlagen!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BUSINESS CASE & ROI (PHASE 8)
|
||||||
|
|
||||||
|
## Chemie- und Petrochemieanlagen
|
||||||
|
**Cost Driver:** Regelmäßige manuelle Inspektionen auf Lecks, Korrosion und strukturelle Integrität sind zeitaufwendig und kostspielig. Mitarbeiter müssen in potenziell gefährliche Bereiche vordringen. Stillstandzeiten durch Inspektionen verursachen Produktionsausfälle.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Der PUMA M20 kann Inspektionen autonom und kontinuierlich durchführen, wodurch die Häufigkeit manueller Inspektionen reduziert wird. Frühzeitige Erkennung von Problemen (z.B. Lecks) ermöglicht rechtzeitige Reparaturen und verhindert größere Schäden und Ausfallzeiten. Kontinuierliche Gasüberwachung verbessert die Sicherheit und reduziert das Risiko von Unfällen. Durch die Integration in die Wackler Security NSL kann im Alarmfall direkt interveniert werden.
|
||||||
|
|
||||||
|
**Risk Argument:** Reduzierung des Risikos von Unfällen und Umweltschäden durch frühzeitige Erkennung von Lecks und anderen Problemen. Verbesserung der Compliance mit Sicherheitsvorschriften und -standards. Minimierung von Produktionsausfällen durch proaktive Wartung und Reparaturen. Der Roboter sieht die Gefahr, Wackler beseitigt sie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Energieversorgungsunternehmen (z.B. Windparks, Solarparks, Umspannwerke)
|
||||||
|
**Cost Driver:** Weitläufige Anlagen erfordern umfangreiche Patrouillen zur Überwachung der Sicherheit und zur Erkennung von Schäden. Manuelle Inspektionen sind zeitaufwendig und personalintensiv. Die Überwachung von Zäunen und Anlagen in abgelegenen Gebieten ist schwierig und teuer.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Der PUMA M20 kann autonom Zäune patrouillieren, Einbruchsversuche erkennen und Schäden an Anlagen frühzeitig identifizieren (z.B. durch Wärmebildkameras). Dies reduziert den Bedarf an manuellen Patrouillen und ermöglicht eine schnellere Reaktion auf Sicherheitsvorfälle. Die All-Terrain-Mobilität ermöglicht den Einsatz in unwegsamem Gelände. Durch die Integration in die Wackler Security NSL kann im Alarmfall direkt interveniert werden.
|
||||||
|
|
||||||
|
**Risk Argument:** Verbesserung der Sicherheit durch kontinuierliche Überwachung und schnelle Reaktion auf Sicherheitsvorfälle. Reduzierung des Risikos von Diebstahl, Vandalismus und Sabotage. Minimierung von Ausfallzeiten durch frühzeitige Erkennung von Schäden an Anlagen. Der Roboter sieht die Gefahr, Wackler beseitigt sie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logistikzentren und große Lagerhäuser
|
||||||
|
**Cost Driver:** Die Überwachung von Sicherheitsbereichen, die Inspektion von Regalen und die Unterstützung bei Inventurprozessen sind personalintensiv. Die manuelle Inspektion von Regalen ist zeitaufwendig und birgt das Risiko von Unfällen. Die Inventur ist ein zeitaufwendiger und fehleranfälliger Prozess.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Der PUMA M20 kann Sicherheitsbereiche autonom überwachen, Regale inspizieren und bei Inventurprozessen unterstützen. Dies reduziert den Bedarf an manuellem Personal und verbessert die Effizienz. Die Fähigkeit, Nutzlasten zu tragen, ermöglicht den Transport von kleinen Gütern oder Werkzeugen. Die kompakte Bauweise ermöglicht den Einsatz auch in engen Gängen. Durch die Integration in die Wackler Security NSL kann im Alarmfall direkt interveniert werden.
|
||||||
|
|
||||||
|
**Risk Argument:** Verbesserung der Sicherheit durch kontinuierliche Überwachung und schnelle Reaktion auf Sicherheitsvorfälle. Reduzierung des Risikos von Diebstahl und Vandalismus. Optimierung der Inventurprozesse und Reduzierung von Fehlbeständen. Der Roboter sieht die Gefahr, Wackler beseitigt sie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# FEATURE-TO-VALUE TRANSLATOR (PHASE 9)
|
||||||
|
|
||||||
|
| Feature | The Story (Benefit) | Headline |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| All-Terrain-Mobilität: Bewältigt Treppen, Schotter, Schlamm und Stahlroste. | So what? Der Roboter kann sich in anspruchsvollem Gelände bewegen. So what? Er erreicht Bereiche, die für Menschen unzugänglich oder gefährlich sind. | Erschließt unzugängliche Bereiche für Inspektion und Sicherheit. |
|
||||||
|
| Wetterfestigkeit: IP66-Zertifizierung für Staub- und Wasserdichtigkeit. | So what? Der Roboter ist vor Umwelteinflüssen geschützt. So what? Er kann auch bei widrigen Bedingungen zuverlässig eingesetzt werden. | Zuverlässige Überwachung bei jedem Wetter. |
|
||||||
|
| Kompakte Bauweise: Passt durch 50 cm breite Gänge und ist rucksackgroß. | So what? Der Roboter ist wendig und mobil. So what? Er kann auch in beengten Umgebungen eingesetzt und leicht transportiert werden. | Überwachung auch in den engsten Bereichen. |
|
||||||
|
| Autonome Navigation: SLAM-Navigation für autonome Missionen und Rückkehr zur Basis. | So what? Der Roboter kann selbstständig navigieren und Aufgaben erledigen. So what? Das reduziert den Bedarf an manueller Steuerung und spart Zeit. | Autonome Patrouillen rund um die Uhr. |
|
||||||
|
| 360°-Umgebungserfassung: Duale LiDAR-Systeme und Weitwinkelkameras. | So what? Der Roboter hat ein umfassendes Situationsbewusstsein. So what? Er erkennt Gefahren und Veränderungen in seiner Umgebung zuverlässig. | Lückenlose Überwachung dank Rundumsicht. |
|
||||||
|
| Nachtsichtfähigkeit: Optionale Nacht- und Wärmebildkameras. | So what? Der Roboter kann auch bei Dunkelheit und schlechten Sichtverhältnissen eingesetzt werden. So what? Er erkennt Wärmequellen und potenzielle Gefahren auch im Verborgenen. | Sicherheit rund um die Uhr, auch im Dunkeln. |
|
||||||
|
| Hohe Rechenleistung: Duale Octa-Core-Prozessoren mit 16 GB RAM und 128 GB Speicher. | So what? Der Roboter kann komplexe Daten schnell verarbeiten. So what? Er ermöglicht Echtzeit-Analysen und schnelle Reaktionen auf Ereignisse. | Intelligente Analysen in Echtzeit. |
|
||||||
|
| Flexible Nutzlastoptionen: LiDAR, Wärmebild, PTZ, Gassensoren, Beacons. | So what? Der Roboter kann an verschiedene Aufgaben angepasst werden. So what? Er ist vielseitig einsetzbar und kann für unterschiedliche Inspektions- und Sicherheitsanforderungen konfiguriert werden. | Anpassbare Sensoren für jede Sicherheitsanforderung. |
|
||||||
|
| Flottenmanagement und API-Integrationen: Für Datenexport und zentrale Steuerung. | So what? Der Roboter kann in bestehende Systeme integriert und zentral verwaltet werden. So what? Das ermöglicht eine effiziente Überwachung und Steuerung mehrerer Roboter. | Zentrale Steuerung für maximale Effizienz. |
|
||||||
|
| Lange Betriebsdauer: Bis zu 3 Stunden, erweiterbar durch Hot-Swap-Batterien. | So what? Der Roboter kann lange autonom arbeiten. So what? Er minimiert Ausfallzeiten und ermöglicht kontinuierliche Überwachung. | Kontinuierliche Überwachung ohne Unterbrechung. |
|
||||||
|
| Hohe Traglast: 12 kg Nennlast, 50 kg maximale Tragfähigkeit. | So what? Der Roboter kann schwere Ausrüstung transportieren. So what? Er kann zusätzliche Sensoren oder Werkzeuge für spezielle Aufgaben mitführen. | Transportiert schwere Lasten für erweiterte Funktionalität. |
|
||||||
53
branchenschema_roboplanet.txt
Normal file
53
branchenschema_roboplanet.txt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
Land- und Forstwirtschaft, Fischerei Land- und Forstwirtschaft
|
||||||
|
Bergbau und Gewinnung von Steinen und Erden Bergbau
|
||||||
|
Bergbau und Gewinnung von Steinen und Erden Energie
|
||||||
|
Verarbeitendes Gewerbe Holz- Papier- und Druckerzeugnisse
|
||||||
|
Verarbeitendes Gewerbe Industrie
|
||||||
|
Verarbeitendes Gewerbe Kunststoff und Gummiwaren
|
||||||
|
Verarbeitendes Gewerbe Lebensmittelindustrie
|
||||||
|
Verarbeitendes Gewerbe Maschinenbau und Fahrzeugbau
|
||||||
|
Verarbeitendes Gewerbe Automobilzulieferer
|
||||||
|
Verarbeitendes Gewerbe Leder- Textil- und Bekleidungsindustrie
|
||||||
|
Verarbeitendes Gewerbe Pharma
|
||||||
|
Energieversorgung öffentliche Energieversorgung
|
||||||
|
Energieversorgung private Energieversorgung
|
||||||
|
Wasserversorgung, Abwasser- und Abfallentsorgung Abfallentsorgung
|
||||||
|
Wasserversorgung, Abwasser- und Abfallentsorgung Wasserversorgung
|
||||||
|
Baugewerbe Architektur-/Ingenieurbüro
|
||||||
|
Baugewerbe Bau- und Baunebengewerbe
|
||||||
|
Handel, Instandhaltung und Reparatur von Fahrzeugen Baumärkte
|
||||||
|
Handel, Instandhaltung und Reparatur von Fahrzeugen Automobilbranche
|
||||||
|
Handel, Instandhaltung und Reparatur von Fahrzeugen Groß- und Einzelhandel
|
||||||
|
Verkehr und Lagerei Transport und Verkehr
|
||||||
|
Gastgewerbe Gaststätten
|
||||||
|
Gastgewerbe Hotelbetriebe
|
||||||
|
Information und Kommunikation PR, Presse und Marketing
|
||||||
|
Information und Kommunikation Verlage
|
||||||
|
Erbringung von Finanz- und Versicherungsdienstleistungen Banken/Versicherungen
|
||||||
|
Erbringung von Finanz- und Versicherungsdienstleistungen Finanzdienstleister
|
||||||
|
Grundstücks- und Wohnungswesen Hausverwaltung
|
||||||
|
Grundstücks- und Wohnungswesen Immobilien
|
||||||
|
Erbringung von freiberufl., wissenschaftl. und techn. DL Dienstleister freiber., wissensch., technisch
|
||||||
|
Erbringung von freiberufl., wissenschaftl. und techn. DL Elektrotechnik
|
||||||
|
Erbringung von freiberufl., wissenschaftl. und techn. DL Wissenschaftliche Einrichtungen
|
||||||
|
Erbringung von sonstigen wirtschaftl. DL Consulter
|
||||||
|
Erbringung von sonstigen wirtschaftl. DL Dienstleister wirtschaftlich
|
||||||
|
Öffentliche Verwaltung, Verteidigung, Sozialversicherung Bund und Kommunen
|
||||||
|
Öffentliche Verwaltung, Verteidigung, Sozialversicherung Verwaltung öffentlich
|
||||||
|
Erziehung und Unterricht Bildungseinrichtung
|
||||||
|
Erziehung und Unterricht Hochschuleinrichtung
|
||||||
|
Erziehung und Unterricht Kindergärten
|
||||||
|
Erziehung und Unterricht Schulen
|
||||||
|
Gesundheits- und Sozialwesen Soziale Einrichtung
|
||||||
|
Gesundheits- und Sozialwesen Gesundheitswesen
|
||||||
|
Gesundheits- und Sozialwesen Kleinere Medizinische Einrichtungen
|
||||||
|
Gesundheits- und Sozialwesen Kliniken - öffentlich
|
||||||
|
Gesundheits- und Sozialwesen Kliniken - privat
|
||||||
|
Gesundheits- und Sozialwesen Senioreneinrichtungen - öffentlich
|
||||||
|
Gesundheits- und Sozialwesen Senioreneinrichtungen - privat
|
||||||
|
Kunst, Unterhaltung und Erholung Kultur- und Freizeiteinrichtung
|
||||||
|
Erbringung von sonstigen Dienstleistungen Gebäudedienstleister
|
||||||
|
Erbringung von sonstigen Dienstleistungen IT-Unternehmen
|
||||||
|
Erbringung von sonstigen Dienstleistungen Dienstleister sonstige
|
||||||
|
Erbringung von sonstigen Dienstleistungen Steuer- und Rechtsberatung
|
||||||
|
Erbringung von sonstigen Dienstleistungen Verwaltung sonstige
|
||||||
45
check_db.py
Normal file
45
check_db.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
dbs = [
|
||||||
|
"/app/companies_v4_notion_sync.db",
|
||||||
|
"/app/companies_v3_final.db",
|
||||||
|
"/app/company-explorer/companies_v3_fixed_2.db",
|
||||||
|
"/app/company-explorer/companies.db"
|
||||||
|
]
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for db_path in dbs:
|
||||||
|
if not os.path.exists(db_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Checking {db_path}...")
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get column names
|
||||||
|
cursor.execute("PRAGMA table_info(companies)")
|
||||||
|
columns = [info[1] for info in cursor.fetchall()]
|
||||||
|
print(f"Columns: {columns}")
|
||||||
|
|
||||||
|
cursor.execute("SELECT * FROM companies WHERE name LIKE '%Wolfra%'")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
if rows:
|
||||||
|
print(f"Found {len(rows)} rows in {db_path}:")
|
||||||
|
for row in rows:
|
||||||
|
# Create a dict for easier reading
|
||||||
|
row_dict = dict(zip(columns, row))
|
||||||
|
print(row_dict)
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
print("No matching rows found.")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading {db_path}: {e}")
|
||||||
|
print("-" * 20)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print("No 'Wolfra' company found in any checked database.")
|
||||||
36
check_db_content.py
Normal file
36
check_db_content.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'company-explorer')))
|
||||||
|
|
||||||
|
from backend.database import SessionLocal, Company
|
||||||
|
|
||||||
|
def check_db_content():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
print("--- Checking content of 'companies' table ---")
|
||||||
|
companies = db.query(Company).limit(5).all()
|
||||||
|
|
||||||
|
if not companies:
|
||||||
|
print("!!! FATAL: The 'companies' table is EMPTY.")
|
||||||
|
# Let's check if the table is there at all
|
||||||
|
try:
|
||||||
|
count = db.query(Company).count()
|
||||||
|
print(f"Row count is confirmed to be {count}.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"!!! Could not even count rows. The table might be corrupt. Error: {e}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Found {len(companies)} companies. Data seems to be present.")
|
||||||
|
for company in companies:
|
||||||
|
print(f" - ID: {company.id}, Name: {company.name}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
check_db_content()
|
||||||
58
check_notion_token.py
Normal file
58
check_notion_token.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import requests
|
||||||
|
from getpass import getpass
|
||||||
|
|
||||||
|
# Interaktive und sichere Abfrage des Tokens
|
||||||
|
print("--- Notion API Token Gültigkeits-Check ---")
|
||||||
|
notion_token = getpass("Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): ")
|
||||||
|
|
||||||
|
if not notion_token:
|
||||||
|
print("\nFehler: Kein Token eingegeben.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Der einfachste API-Endpunkt, um die Authentifizierung zu testen
|
||||||
|
url = "https://api.notion.com/v1/users/me"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {notion_token}",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n... Sende Test-Anfrage an Notion...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# --- TEST 1: Grundlegende Authentifizierung ---
|
||||||
|
print("\n[TEST 1/2] Prüfe grundlegende Authentifizierung (/users/me)...")
|
||||||
|
user_response = requests.get("https://api.notion.com/v1/users/me", headers=headers)
|
||||||
|
user_response.raise_for_status()
|
||||||
|
print("✅ ERFOLG! Der API Token ist gültig.")
|
||||||
|
|
||||||
|
# --- TEST 2: Suche nach der 'Projects' Datenbank ---
|
||||||
|
print("\n[TEST 2/2] Versuche, die 'Projects'-Datenbank über die Suche zu finden (/search)...")
|
||||||
|
search_url = "https://api.notion.com/v1/search"
|
||||||
|
search_payload = {
|
||||||
|
"query": "Projects",
|
||||||
|
"filter": {"value": "database", "property": "object"}
|
||||||
|
}
|
||||||
|
search_response = requests.post(search_url, headers=headers, json=search_payload)
|
||||||
|
search_response.raise_for_status()
|
||||||
|
|
||||||
|
results = search_response.json().get("results", [])
|
||||||
|
if not results:
|
||||||
|
print("🟡 WARNUNG: Die Suche war erfolgreich, hat aber keine Datenbank namens 'Projects' gefunden.")
|
||||||
|
else:
|
||||||
|
print("✅✅✅ ERFOLG! Die Suche funktioniert und hat die 'Projects'-Datenbank gefunden.")
|
||||||
|
print("Gefundene Datenbanken:")
|
||||||
|
for db in results:
|
||||||
|
print(f"- ID: {db['id']}, Titel: {db.get('title', [{}])[0].get('plain_text', 'N/A')}")
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
print(f"\n❌ FEHLER! Einer der Tests ist fehlgeschlagen.")
|
||||||
|
print(f"URL: {e.request.url}")
|
||||||
|
print(f"HTTP Status Code: {e.response.status_code}")
|
||||||
|
print("Antwort von Notion:")
|
||||||
|
try:
|
||||||
|
print(e.response.json())
|
||||||
|
except:
|
||||||
|
print(e.response.text)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"\n❌ FEHLER! Ein Netzwerk- oder Verbindungsfehler ist aufgetreten: {e}")
|
||||||
28
check_schema.py
Normal file
28
check_schema.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
db_path = "/app/company-explorer/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
for table in ['signals', 'enrichment_data']:
|
||||||
|
print(f"\nSchema of {table}:")
|
||||||
|
cursor.execute(f"PRAGMA table_info({table})")
|
||||||
|
for col in cursor.fetchall():
|
||||||
|
print(col)
|
||||||
|
|
||||||
|
print(f"\nContent of {table} for company_id=12 (guessing FK):")
|
||||||
|
# Try to find FK column
|
||||||
|
cursor.execute(f"PRAGMA table_info({table})")
|
||||||
|
cols = [c[1] for c in cursor.fetchall()]
|
||||||
|
fk_col = next((c for c in cols if 'company_id' in c or 'account_id' in c), None)
|
||||||
|
|
||||||
|
if fk_col:
|
||||||
|
cursor.execute(f"SELECT * FROM {table} WHERE {fk_col}=12")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(dict(zip(cols, row)))
|
||||||
|
else:
|
||||||
|
print(f"Could not guess FK column for {table}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
21
check_tables.py
Normal file
21
check_tables.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
db_path = "/app/company-explorer/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||||
|
tables = cursor.fetchall()
|
||||||
|
print(f"Tables in {db_path}: {tables}")
|
||||||
|
|
||||||
|
# Check content of 'signals' if it exists
|
||||||
|
if ('signals',) in tables:
|
||||||
|
print("\nChecking 'signals' table for Wolfra (id=12)...")
|
||||||
|
cursor.execute("SELECT * FROM signals WHERE account_id=12")
|
||||||
|
columns = [desc[0] for desc in cursor.description]
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(dict(zip(columns, row)))
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
137
company-explorer/backend/tests/test_classification_service.py
Normal file
137
company-explorer/backend/tests/test_classification_service.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
# Ensure the app's root is in the path to allow imports
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
||||||
|
|
||||||
|
from backend.services.classification import ClassificationService
|
||||||
|
|
||||||
|
class TestClassificationService(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up a new ClassificationService instance for each test."""
|
||||||
|
self.service = ClassificationService()
|
||||||
|
|
||||||
|
def test_plausibility_check_hospital_beds(self):
|
||||||
|
"""
|
||||||
|
Tests the _is_metric_plausible method with rules for hospital beds.
|
||||||
|
"""
|
||||||
|
# Plausible value
|
||||||
|
self.assertTrue(self.service._is_metric_plausible("# Planbetten (Krankenhaus)", 150))
|
||||||
|
|
||||||
|
# Implausible value (too low)
|
||||||
|
self.assertFalse(self.service._is_metric_plausible("# Planbetten (Krankenhaus)", 11))
|
||||||
|
|
||||||
|
# Edge case: exactly the minimum
|
||||||
|
self.assertTrue(self.service._is_metric_plausible("# Planbetten (Krankenhaus)", 20))
|
||||||
|
|
||||||
|
# No value
|
||||||
|
self.assertTrue(self.service._is_metric_plausible("# Planbetten (Krankenhaus)", None))
|
||||||
|
|
||||||
|
def test_plausibility_check_no_rule(self):
|
||||||
|
"""
|
||||||
|
Tests that metrics without a specific rule are always considered plausible.
|
||||||
|
"""
|
||||||
|
self.assertTrue(self.service._is_metric_plausible("Some New Metric", 5))
|
||||||
|
self.assertTrue(self.service._is_metric_plausible("Another Metric", 100000))
|
||||||
|
|
||||||
|
@patch('backend.services.classification.run_serp_search')
|
||||||
|
@patch('backend.services.classification.scrape_website_content')
|
||||||
|
@patch('backend.services.classification.ClassificationService._get_wikipedia_content')
|
||||||
|
def test_source_prioritization_erding_case(self, mock_get_wiki, mock_scrape_web, mock_serp):
|
||||||
|
"""
|
||||||
|
Tests that a high-quality Wikipedia result is chosen over a low-quality website result.
|
||||||
|
"""
|
||||||
|
# --- Mocks Setup ---
|
||||||
|
# Mock website to return a bad, implausible value
|
||||||
|
mock_scrape_web.return_value = "Auf unseren 11 Stationen..."
|
||||||
|
# Mock Wikipedia to return a good, plausible value
|
||||||
|
mock_get_wiki.return_value = "Das Klinikum hat 352 Betten."
|
||||||
|
# Mock SerpAPI to return nothing
|
||||||
|
mock_serp.return_value = None
|
||||||
|
|
||||||
|
# Mock the LLM to return different values based on the source content
|
||||||
|
def llm_side_effect(content, search_term, industry_name):
|
||||||
|
if "11 Stationen" in content:
|
||||||
|
return {"raw_text_segment": "11 Stationen", "raw_value": 11, "raw_unit": "Stationen", "confidence_score": 0.6, "calculated_metric_value": 11}
|
||||||
|
if "352 Betten" in content:
|
||||||
|
return {"raw_text_segment": "352 Betten", "raw_value": 352, "raw_unit": "Betten", "confidence_score": 0.95, "calculated_metric_value": 352}
|
||||||
|
return None
|
||||||
|
|
||||||
|
# We need to patch the LLM call within the service instance for the test
|
||||||
|
self.service._run_llm_metric_extraction_prompt = MagicMock(side_effect=llm_side_effect)
|
||||||
|
|
||||||
|
# --- Test Execution ---
|
||||||
|
mock_company = MagicMock()
|
||||||
|
mock_company.website = "http://example.com"
|
||||||
|
mock_company.id = 1
|
||||||
|
|
||||||
|
# We need a mock DB session
|
||||||
|
mock_db = MagicMock()
|
||||||
|
|
||||||
|
results = self.service._extract_and_calculate_metric_cascade(
|
||||||
|
db=mock_db,
|
||||||
|
company=mock_company,
|
||||||
|
industry_name="Krankenhaus",
|
||||||
|
search_term="# Planbetten (Krankenhaus)",
|
||||||
|
standardization_logic=None,
|
||||||
|
standardized_unit="Betten"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Assertions ---
|
||||||
|
self.assertIsNotNone(results)
|
||||||
|
self.assertEqual(results['calculated_metric_value'], 352)
|
||||||
|
self.assertEqual(results['metric_source'], 'wikipedia')
|
||||||
|
|
||||||
|
@patch('backend.services.classification.run_serp_search')
|
||||||
|
@patch('backend.services.classification.scrape_website_content')
|
||||||
|
def test_targeted_extraction_spetec_case(self, mock_scrape_web, mock_serp):
|
||||||
|
"""
|
||||||
|
Tests that the correct value is extracted when a text snippet contains multiple numbers.
|
||||||
|
"""
|
||||||
|
# --- Mocks Setup ---
|
||||||
|
# Mock website content with ambiguous numbers
|
||||||
|
mock_scrape_web.return_value = "Wir haben 65 Mitarbeiter auf einer Fläche von 8.000 m²."
|
||||||
|
mock_serp.return_value = None
|
||||||
|
|
||||||
|
|
||||||
|
# Mock the LLM to return the full snippet, letting the parser do the work
|
||||||
|
# The improved prompt should guide the LLM to provide the correct 'raw_value' as a hint
|
||||||
|
llm_result = {
|
||||||
|
"raw_text_segment": "65 Mitarbeiter auf einer Fläche von 8.000 m²",
|
||||||
|
"raw_value": "8000", # The crucial hint from the improved prompt
|
||||||
|
"raw_unit": "m²",
|
||||||
|
"confidence_score": 0.9,
|
||||||
|
"calculated_metric_value": 8000.0
|
||||||
|
}
|
||||||
|
self.service._run_llm_metric_extraction_prompt = MagicMock(return_value=llm_result)
|
||||||
|
|
||||||
|
# --- Test Execution ---
|
||||||
|
mock_company = MagicMock()
|
||||||
|
mock_company.website = "http://spetec.com"
|
||||||
|
mock_company.id = 2
|
||||||
|
mock_db = MagicMock()
|
||||||
|
|
||||||
|
# Set up a mock for _get_wikipedia_content to return None, so we only test the website part
|
||||||
|
self.service._get_wikipedia_content = MagicMock(return_value=None)
|
||||||
|
|
||||||
|
results = self.service._extract_and_calculate_metric_cascade(
|
||||||
|
db=mock_db,
|
||||||
|
company=mock_company,
|
||||||
|
industry_name="Laborausstattung",
|
||||||
|
search_term="Fabrikhalle (m²)",
|
||||||
|
standardization_logic=None,
|
||||||
|
standardized_unit="m²"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Assertions ---
|
||||||
|
self.assertIsNotNone(results)
|
||||||
|
self.assertEqual(results['calculated_metric_value'], 8000.0)
|
||||||
|
self.assertEqual(results['metric_source'], 'website')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
48
company-explorer/debug_standardization_logic.py
Normal file
48
company-explorer/debug_standardization_logic.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from backend.database import Industry
|
||||||
|
from backend.services.classification import ClassificationService
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Setup DB connection directly to the file
|
||||||
|
# Note: We need to use the absolute path that works inside the container or relative if running locally
|
||||||
|
# Assuming we run this via 'docker exec' or locally if paths align.
|
||||||
|
# For safety, I'll use the path from config but typically inside container it's /app/...
|
||||||
|
DB_URL = "sqlite:////app/companies_v3_fixed_2.db"
|
||||||
|
|
||||||
|
engine = create_engine(DB_URL)
|
||||||
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
def test_logic():
|
||||||
|
print("--- DEBUGGING STANDARDIZATION LOGIC ---")
|
||||||
|
|
||||||
|
industry_name = "Healthcare - Hospital"
|
||||||
|
industry = db.query(Industry).filter(Industry.name == industry_name).first()
|
||||||
|
|
||||||
|
if not industry:
|
||||||
|
print(f"ERROR: Industry '{industry_name}' not found!")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Industry: {industry.name}")
|
||||||
|
print(f"Search Term: {industry.scraper_search_term}")
|
||||||
|
print(f"Standardization Logic (Raw DB Value): '{industry.standardization_logic}'")
|
||||||
|
|
||||||
|
if not industry.standardization_logic:
|
||||||
|
print("CRITICAL: Standardization logic is empty/null! That explains the null result.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Initialize Service to test the exact method
|
||||||
|
service = ClassificationService()
|
||||||
|
test_value = 352.0
|
||||||
|
|
||||||
|
print(f"\nTesting calculation with value: {test_value}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = service._parse_standardization_logic(industry.standardization_logic, test_value)
|
||||||
|
print(f"Result: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Exception during parsing: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_logic()
|
||||||
58
debug_notion_properties.py
Normal file
58
debug_notion_properties.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from getpass import getpass
|
||||||
|
|
||||||
|
def inspect_database_properties(db_id: str):
|
||||||
|
"""Liest die Eigenschaften (Spalten) einer Notion-Datenbank aus."""
|
||||||
|
print(f"--- Untersuche Eigenschaften von Notion DB: {db_id} ---")
|
||||||
|
token = getpass("Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): ")
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
print("\nFehler: Kein Token eingegeben. Abbruch.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n... Lese Struktur von Datenbank {db_id}...")
|
||||||
|
|
||||||
|
url = f"https://api.notion.com/v1/databases/{db_id}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
database_info = response.json()
|
||||||
|
properties = database_info.get("properties", {})
|
||||||
|
|
||||||
|
print("\n✅ Erfolgreich! Folgende Spalten (Properties) wurden gefunden:")
|
||||||
|
print("--------------------------------------------------")
|
||||||
|
for name, details in properties.items():
|
||||||
|
prop_type = details.get("type")
|
||||||
|
print(f"Spaltenname: '{name}' (Typ: {prop_type})")
|
||||||
|
if prop_type == "relation":
|
||||||
|
relation_details = details.get("relation", {})
|
||||||
|
print(f" -> Verknüpft mit Datenbank-ID: {relation_details.get('database_id')}")
|
||||||
|
# Gib die verfügbaren Optionen für Status- und Select-Felder aus
|
||||||
|
elif prop_type in ["status", "select", "multi_select"]:
|
||||||
|
options = details.get(prop_type, {}).get("options", [])
|
||||||
|
if options:
|
||||||
|
print(f" -> Verfügbare Optionen:")
|
||||||
|
for option in options:
|
||||||
|
print(f" - '{option.get('name')}'")
|
||||||
|
print("--------------------------------------------------")
|
||||||
|
print("Bitte finde den korrekten Namen der Spalte, die zu den Projekten verknüpft ist, und den exakten Namen für den 'In Bearbeitung'-Status.")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"\n❌ FEHLER! Konnte die Datenbankstruktur nicht lesen: {e}")
|
||||||
|
if hasattr(e, 'response') and e.response is not None:
|
||||||
|
print(f"HTTP Status Code: {e.response.status_code}")
|
||||||
|
try:
|
||||||
|
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Antwort des Servers: {e.response.text}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tasks_db_id = "2e888f42-8544-8153-beac-e604719029cf" # Die ID für "Tasks [UT]"
|
||||||
|
inspect_database_properties(tasks_db_id)
|
||||||
63
debug_notion_search.py
Normal file
63
debug_notion_search.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from getpass import getpass
|
||||||
|
|
||||||
|
def debug_search_databases():
|
||||||
|
print("--- Notion Datenbank Such-Debugger ---")
|
||||||
|
token = getpass("Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): ")
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
print("\nFehler: Kein Token eingegeben. Abbruch.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n... Sende Suchanfrage an Notion für alle Datenbanken...")
|
||||||
|
|
||||||
|
url = "https://api.notion.com/v1/search"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"filter": {
|
||||||
|
"value": "database",
|
||||||
|
"property": "object"
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"direction": "ascending",
|
||||||
|
"timestamp": "last_edited_time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=payload)
|
||||||
|
response.raise_for_status() # Hebt HTTPError für 4xx/5xx Statuscodes hervor
|
||||||
|
|
||||||
|
results = response.json().get("results", [])
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("\nKeine Datenbanken gefunden, auf die die Integration Zugriff hat.")
|
||||||
|
print("Bitte stelle sicher, dass die Integration auf Top-Level-Seiten geteilt ist.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\nGefundene Datenbanken ({len(results)} insgesamt):")
|
||||||
|
print("--------------------------------------------------")
|
||||||
|
for db in results:
|
||||||
|
db_id = db["id"]
|
||||||
|
db_title_parts = db.get("title", [])
|
||||||
|
db_title = db_title_parts[0].get("plain_text", "(Unbenannt)") if db_title_parts else "(Unbenannt)"
|
||||||
|
print(f"Titel: '{db_title}'\n ID: {db_id}\n")
|
||||||
|
print("--------------------------------------------------")
|
||||||
|
print("Bitte überprüfe die genauen Titel und IDs für 'Projects' und 'All Tasks'.")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"\n❌ FEHLER! Fehler bei der Suche nach Datenbanken: {e}")
|
||||||
|
if hasattr(e, 'response') and e.response is not None:
|
||||||
|
print(f"HTTP Status Code: {e.response.status_code}")
|
||||||
|
try:
|
||||||
|
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Antwort des Servers: {e.response.text}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
debug_search_databases()
|
||||||
339
dev_session.py
339
dev_session.py
@@ -97,6 +97,84 @@ def get_page_property(page: Dict, prop_name: str, prop_type: str = "rich_text")
|
|||||||
# Hier könnten weitere Typen wie 'select', 'number' etc. behandelt werden
|
# Hier könnten weitere Typen wie 'select', 'number' etc. behandelt werden
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_page_content(token: str, page_id: str) -> str:
|
||||||
|
"""Ruft den gesamten Textinhalt einer Notion-Seite ab, indem es die Blöcke zusammenfügt, mit Paginierung."""
|
||||||
|
url = f"https://api.notion.com/v1/blocks/{page_id}/children"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
full_text = []
|
||||||
|
next_cursor = None
|
||||||
|
has_more = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
while has_more:
|
||||||
|
params = {"page_size": 100} # Max page size
|
||||||
|
if next_cursor:
|
||||||
|
params["start_cursor"] = next_cursor
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
blocks = data.get("results", [])
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
block_type = block["type"]
|
||||||
|
text_content = ""
|
||||||
|
|
||||||
|
if block_type in ["paragraph", "heading_1", "heading_2", "heading_3",
|
||||||
|
"bulleted_list_item", "numbered_list_item", "to_do", "callout"]:
|
||||||
|
rich_text_array = block[block_type].get("rich_text", [])
|
||||||
|
for rich_text in rich_text_array:
|
||||||
|
text_content += rich_text.get("plain_text", "")
|
||||||
|
elif block_type == "code":
|
||||||
|
rich_text_array = block["code"].get("rich_text", [])
|
||||||
|
for rich_text in rich_text_array:
|
||||||
|
text_content += rich_text.get("plain_text", "")
|
||||||
|
text_content = f"```\n{text_content}\n```" # Markdown für Codeblöcke
|
||||||
|
elif block_type == "unsupported":
|
||||||
|
text_content = "[Unsupported Block Type]"
|
||||||
|
|
||||||
|
if text_content:
|
||||||
|
# Füge grundlegende Formatierung für bessere Lesbarkeit hinzu
|
||||||
|
if block_type == "heading_1":
|
||||||
|
full_text.append(f"# {text_content}")
|
||||||
|
elif block_type == "heading_2":
|
||||||
|
full_text.append(f"## {text_content}")
|
||||||
|
elif block_type == "heading_3":
|
||||||
|
full_text.append(f"### {text_content}")
|
||||||
|
elif block_type == "bulleted_list_item":
|
||||||
|
full_text.append(f"- {text_content}")
|
||||||
|
elif block_type == "numbered_list_item":
|
||||||
|
full_text.append(f"1. {text_content}") # Einfache Nummerierung
|
||||||
|
elif block_type == "to_do":
|
||||||
|
checked = "[x]" if block["to_do"].get("checked") else "[ ]"
|
||||||
|
full_text.append(f"{checked} {text_content}")
|
||||||
|
elif block_type == "callout":
|
||||||
|
# Extrahiere Icon und Text
|
||||||
|
icon = block["callout"].get("icon", {}).get("emoji", "")
|
||||||
|
full_text.append(f"> {icon} {text_content}")
|
||||||
|
else: # paragraph und andere Standardtexte
|
||||||
|
full_text.append(text_content)
|
||||||
|
|
||||||
|
next_cursor = data.get("next_cursor")
|
||||||
|
has_more = data.get("has_more", False) and next_cursor
|
||||||
|
|
||||||
|
return "\n".join(full_text)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Fehler beim Abrufen des Seiteninhalts für Page-ID {page_id}: {e}")
|
||||||
|
try:
|
||||||
|
if e.response: # Wenn eine Antwort vorhanden ist
|
||||||
|
error_details = e.response.json()
|
||||||
|
print(f"Notion API Fehlerdetails: {json.dumps(error_details, indent=2)}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Notion API Rohantwort: {e.response.text}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_database_status_options(token: str, db_id: str) -> List[str]:
|
def get_database_status_options(token: str, db_id: str) -> List[str]:
|
||||||
"""Ruft die verfügbaren Status-Optionen für eine Datenbank-Eigenschaft ab."""
|
"""Ruft die verfügbaren Status-Optionen für eine Datenbank-Eigenschaft ab."""
|
||||||
url = f"https://api.notion.com/v1/databases/{db_id}"
|
url = f"https://api.notion.com/v1/databases/{db_id}"
|
||||||
@@ -216,10 +294,36 @@ def add_comment_to_notion_task(token: str, task_id: str, comment: str) -> bool:
|
|||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
response = requests.post(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
# Kein print, da dies vom Git-Hook im Hintergrund aufgerufen wird
|
print(f"✅ Kommentar erfolgreich zum Notion-Task hinzugefügt.")
|
||||||
return True
|
return True
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException as e:
|
||||||
# Fehler unterdrücken, um den Commit-Prozess nicht zu blockieren
|
print(f"❌ FEHLER beim Hinzufügen des Kommentars zum Notion-Task: {e}")
|
||||||
|
try:
|
||||||
|
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Antwort des Servers: {e.response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def append_blocks_to_notion_page(token: str, page_id: str, blocks: List[Dict]) -> bool:
|
||||||
|
"""Hängt Inhaltsblöcke an eine Notion-Seite an."""
|
||||||
|
url = f"https://api.notion.com/v1/blocks/{page_id}/children"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
payload = {"children": blocks}
|
||||||
|
try:
|
||||||
|
response = requests.patch(url, headers=headers, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
print(f"✅ Statusbericht erfolgreich an die Notion-Task-Seite angehängt.")
|
||||||
|
return True
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"❌ FEHLER beim Anhängen des Statusberichts an die Notion-Seite: {e}")
|
||||||
|
try:
|
||||||
|
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Antwort des Servers: {e.response.text}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# --- Session Management ---
|
# --- Session Management ---
|
||||||
@@ -330,23 +434,206 @@ def select_task(token: str, project_id: str, tasks_db_id: str) -> Optional[Dict]
|
|||||||
print("Ungültige Eingabe. Bitte eine Zahl eingeben.")
|
print("Ungültige Eingabe. Bitte eine Zahl eingeben.")
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# --- Git Summary Generation ---
|
||||||
|
|
||||||
|
def generate_git_summary() -> Tuple[str, str]:
|
||||||
|
"""Generiert eine Zusammenfassung der Git-Änderungen und Commit-Nachrichten seit dem letzten Push zum Main-Branch."""
|
||||||
|
try:
|
||||||
|
# Finde den aktuellen Main-Branch Namen (master oder main)
|
||||||
|
try:
|
||||||
|
main_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip()
|
||||||
|
if main_branch not in ["main", "master"]:
|
||||||
|
# Versuche, den Remote-Tracking-Branch für main/master zu finden
|
||||||
|
result = subprocess.run(["git", "branch", "-r"], capture_output=True, text=True)
|
||||||
|
if "origin/main" in result.stdout:
|
||||||
|
main_branch = "origin/main"
|
||||||
|
elif "origin/master" in result.stdout:
|
||||||
|
main_branch = "origin/master"
|
||||||
|
else:
|
||||||
|
print("Warnung: Konnte keinen 'main' oder 'master' Branch finden. Git-Zusammenfassung wird möglicherweise unvollständig sein.")
|
||||||
|
main_branch = "HEAD~1" # Fallback zum letzten Commit, falls kein Main-Branch gefunden wird
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
main_branch = "HEAD~1" # Fallback, falls gar kein Branch gefunden wird
|
||||||
|
|
||||||
|
# Git diff --stat
|
||||||
|
diff_stat_cmd = ["git", "diff", "--stat", f"{main_branch}...HEAD"]
|
||||||
|
diff_stat = subprocess.check_output(diff_stat_cmd).decode("utf-8").strip()
|
||||||
|
|
||||||
|
# Git log --pretty
|
||||||
|
commit_log_cmd = ["git", "log", "--pretty=format:- %s", f"{main_branch}...HEAD"]
|
||||||
|
commit_messages = subprocess.check_output(commit_log_cmd).decode("utf-8").strip()
|
||||||
|
|
||||||
|
return diff_stat, commit_messages
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ FEHLER beim Generieren der Git-Zusammenfassung: {e}")
|
||||||
|
return "", ""
|
||||||
|
|
||||||
|
# --- Report Status to Notion ---
|
||||||
|
|
||||||
|
def report_status_to_notion(
|
||||||
|
status_override: Optional[str],
|
||||||
|
todos_override: Optional[str],
|
||||||
|
git_changes_override: Optional[str],
|
||||||
|
commit_messages_override: Optional[str],
|
||||||
|
summary_override: Optional[str]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Erstellt einen Statusbericht für den Notion-Task, entweder interaktiv oder mit überschriebenen Werten.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(SESSION_FILE_PATH):
|
||||||
|
print("❌ FEHLER: Keine aktive Session gefunden. Kann keinen Statusbericht erstellen.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(SESSION_FILE_PATH, "r") as f:
|
||||||
|
session_data = json.load(f)
|
||||||
|
task_id = session_data.get("task_id")
|
||||||
|
token = session_data.get("token")
|
||||||
|
|
||||||
|
if not (task_id and token):
|
||||||
|
print("❌ FEHLER: Session-Daten unvollständig. Kann keinen Statusbericht erstellen.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"--- Erstelle Statusbericht für Task {task_id} ---")
|
||||||
|
|
||||||
|
# Git-Zusammenfassung generieren (immer, wenn nicht explizit überschrieben)
|
||||||
|
actual_git_changes = git_changes_override
|
||||||
|
actual_commit_messages = commit_messages_override
|
||||||
|
if not git_changes_override or not commit_messages_override:
|
||||||
|
print("Generiere Git-Zusammenfassung...")
|
||||||
|
diff_stat, commit_log = generate_git_summary()
|
||||||
|
if not git_changes_override:
|
||||||
|
actual_git_changes = diff_stat
|
||||||
|
if not commit_messages_override:
|
||||||
|
actual_commit_messages = commit_log
|
||||||
|
|
||||||
|
# Status abfragen oder übernehmen
|
||||||
|
actual_status = status_override
|
||||||
|
if not actual_status:
|
||||||
|
tasks_db_id = find_database_by_title(token, "Tasks [UT]")
|
||||||
|
if tasks_db_id:
|
||||||
|
status_options = get_database_status_options(token, tasks_db_id)
|
||||||
|
if status_options:
|
||||||
|
print("\nBitte wähle den neuen Status des Tasks:")
|
||||||
|
for i, option in enumerate(status_options):
|
||||||
|
print(f"[{i+1}] {option}")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = int(input("Wähle eine Nummer: "))
|
||||||
|
if 1 <= choice <= len(status_options):
|
||||||
|
actual_status = status_options[choice - 1]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Ungültige Auswahl.")
|
||||||
|
except ValueError:
|
||||||
|
print("Ungültige Eingabe. Bitte eine Zahl eingeben.")
|
||||||
|
else:
|
||||||
|
print("Warnung: Konnte Status-Optionen nicht abrufen. Bitte Status manuell eingeben.")
|
||||||
|
actual_status = input("Bitte gib den neuen Status manuell ein: ")
|
||||||
|
|
||||||
|
if not actual_status:
|
||||||
|
print("❌ FEHLER: Kein Status festgelegt. Abbruch des Berichts.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Detaillierte Zusammenfassung abfragen oder übernehmen
|
||||||
|
actual_summary = summary_override
|
||||||
|
if not actual_summary:
|
||||||
|
print("\nBitte gib eine Zusammenfassung der Arbeit ein (was wurde getan, Ergebnisse, Probleme etc.).")
|
||||||
|
user_summary_lines = []
|
||||||
|
while True:
|
||||||
|
line = input()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
user_summary_lines.append(line)
|
||||||
|
actual_summary = "\n".join(user_summary_lines)
|
||||||
|
|
||||||
|
# To-Dos abfragen oder übernehmen
|
||||||
|
actual_todos = todos_override
|
||||||
|
if not actual_todos:
|
||||||
|
user_todos = input("\nGibt es offene To-Dos oder nächste Schritte? (Leer lassen zum Überspringen): ")
|
||||||
|
actual_todos = user_todos.strip()
|
||||||
|
|
||||||
|
# Kommentar zusammenstellen
|
||||||
|
report_lines = []
|
||||||
|
# Diese Zeilen werden jetzt innerhalb des Code-Blocks formatiert
|
||||||
|
report_lines.append(f"Neuer Status: {actual_status}")
|
||||||
|
|
||||||
|
if actual_summary:
|
||||||
|
report_lines.append("\nArbeitszusammenfassung:")
|
||||||
|
report_lines.append(actual_summary)
|
||||||
|
|
||||||
|
if actual_git_changes or actual_commit_messages:
|
||||||
|
report_lines.append("\nTechnische Änderungen (Git):")
|
||||||
|
if actual_git_changes:
|
||||||
|
report_lines.append(f"```{actual_git_changes}```")
|
||||||
|
if actual_commit_messages:
|
||||||
|
report_lines.append("\nCommit Nachrichten:")
|
||||||
|
report_lines.append(f"```{actual_commit_messages}```")
|
||||||
|
|
||||||
|
if actual_todos:
|
||||||
|
report_lines.append("\nOffene To-Dos / Nächste Schritte:")
|
||||||
|
for todo_item in actual_todos.split('\n'):
|
||||||
|
report_lines.append(f"- {todo_item.strip()}")
|
||||||
|
|
||||||
|
report_content = "\n".join(report_lines)
|
||||||
|
|
||||||
|
# Notion Blöcke für die API erstellen
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||||
|
notion_blocks = [
|
||||||
|
{
|
||||||
|
"object": "block",
|
||||||
|
"type": "heading_2",
|
||||||
|
"heading_2": {
|
||||||
|
"rich_text": [{"type": "text", "text": {"content": f"🤖 Status-Update ({timestamp})"}}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "block",
|
||||||
|
"type": "code",
|
||||||
|
"code": {
|
||||||
|
"rich_text": [{"type": "text", "text": {"content": report_content}}],
|
||||||
|
"language": "yaml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Notion aktualisieren
|
||||||
|
append_blocks_to_notion_page(token, task_id, notion_blocks)
|
||||||
|
update_notion_task_status(token, task_id, actual_status)
|
||||||
|
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
|
print(f"❌ FEHLER beim Lesen der Session-Informationen für Statusbericht: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Unerwarteter Fehler beim Erstellen des Statusberichts: {e}")
|
||||||
|
|
||||||
|
|
||||||
# --- Context Generation ---
|
# --- Context Generation ---
|
||||||
|
|
||||||
def generate_cli_context(project_title: str, task_title: str, task_id: str, readme_path: Optional[str]) -> str:
|
|
||||||
|
def generate_cli_context(project_title: str, task_title: str, task_id: str, readme_path: Optional[str], task_description: Optional[str]) -> str:
|
||||||
"""Erstellt den reinen Kontext-String für die Gemini CLI."""
|
"""Erstellt den reinen Kontext-String für die Gemini CLI."""
|
||||||
|
|
||||||
# Fallback, falls kein Pfad in Notion gesetzt ist
|
# Fallback, falls kein Pfad in Notion gesetzt ist
|
||||||
if not readme_path:
|
if not readme_path:
|
||||||
readme_path = "readme.md"
|
readme_path = "readme.md"
|
||||||
|
|
||||||
|
description_part = ""
|
||||||
|
if task_description:
|
||||||
|
description_part = (
|
||||||
|
f"\n**Aufgabenbeschreibung:**\n"
|
||||||
|
f"```\n{task_description}\n```\n"
|
||||||
|
)
|
||||||
|
|
||||||
context = (
|
context = (
|
||||||
f"Ich arbeite jetzt am Projekt '{project_title}'. Der Fokus liegt auf dem Task '{task_title}'.\n\n"
|
f"Ich arbeite jetzt am Projekt '{project_title}'. Der Fokus liegt auf dem Task '{task_title}'.\n"
|
||||||
|
f"{description_part}\n"
|
||||||
"Die relevanten Dateien für dieses Projekt sind wahrscheinlich:\n"
|
"Die relevanten Dateien für dieses Projekt sind wahrscheinlich:\n"
|
||||||
"- Die primäre Projektdokumentation: @readme.md\n"
|
"- Die primäre Projektdokumentation: @readme.md\n"
|
||||||
f"- Die spezifische Dokumentation für dieses Modul: @{readme_path}\n"
|
f"- Die spezifische Dokumentation für dieses Modul: @{readme_path}\n\n"
|
||||||
f"- Der Haupt-Code befindet sich wahrscheinlich in: @dev_session.py\n\n"
|
f"Mein Ziel ist es, den Task '{task_title}' umzusetzen. Alle Commits für diesen Task sollen die Kennung `[{task_id.split('-')[0]}]` enthalten.\n\n"
|
||||||
f"Mein Ziel ist es, den Task '{task_title}' umzusetzen. Alle Commits für diesen Task sollen die Kennung `[{task_id.split('-')[0]}]` enthalten."
|
"**WICHTIGER BEFEHL:** Bevor du mit der Implementierung oder einer Code-Änderung beginnst, fasse die Aufgabe in deinen eigenen Worten zusammen, erstelle einen detaillierten, schrittweisen Plan zur Lösung und **warte auf meine explizite Bestätigung**, bevor du den ersten Schritt ausführst."
|
||||||
)
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -480,6 +767,9 @@ def start_interactive_session():
|
|||||||
task_id = selected_task["id"]
|
task_id = selected_task["id"]
|
||||||
print(f"\nTask '{task_title}' ausgewählt.")
|
print(f"\nTask '{task_title}' ausgewählt.")
|
||||||
|
|
||||||
|
# NEU: Lade die Task-Beschreibung
|
||||||
|
task_description = get_page_content(token, task_id)
|
||||||
|
|
||||||
# Session-Informationen für den Git-Hook speichern
|
# Session-Informationen für den Git-Hook speichern
|
||||||
save_session_info(task_id, token)
|
save_session_info(task_id, token)
|
||||||
|
|
||||||
@@ -505,7 +795,7 @@ def start_interactive_session():
|
|||||||
print("------------------------------------------------------------------")
|
print("------------------------------------------------------------------")
|
||||||
|
|
||||||
# CLI-Kontext generieren und an stdout ausgeben, damit das Startskript ihn aufgreifen kann
|
# CLI-Kontext generieren und an stdout ausgeben, damit das Startskript ihn aufgreifen kann
|
||||||
cli_context = generate_cli_context(project_title, task_title, task_id, readme_path)
|
cli_context = generate_cli_context(project_title, task_title, task_id, readme_path, task_description)
|
||||||
print("\n---GEMINI_CLI_CONTEXT_START---")
|
print("\n---GEMINI_CLI_CONTEXT_START---")
|
||||||
print(cli_context)
|
print(cli_context)
|
||||||
print("---GEMINI_CLI_CONTEXT_END---")
|
print("---GEMINI_CLI_CONTEXT_END---")
|
||||||
@@ -519,11 +809,42 @@ def main():
|
|||||||
"""Hauptfunktion des Skripts."""
|
"""Hauptfunktion des Skripts."""
|
||||||
parser = argparse.ArgumentParser(description="Interaktiver Session-Manager für die Gemini-Entwicklung mit Notion-Integration.")
|
parser = argparse.ArgumentParser(description="Interaktiver Session-Manager für die Gemini-Entwicklung mit Notion-Integration.")
|
||||||
parser.add_argument("--done", action="store_true", help="Schließt die aktuelle Entwicklungs-Session ab.")
|
parser.add_argument("--done", action="store_true", help="Schließt die aktuelle Entwicklungs-Session ab.")
|
||||||
|
parser.add_argument("--add-comment", type=str, help="Fügt einen Kommentar zum aktuellen Notion-Task hinzu.")
|
||||||
|
parser.add_argument("--report-status", action="store_true", help="Erstellt einen Statusbericht für den Notion-Task.")
|
||||||
|
parser.add_argument("--status", type=str, help="Status, der im Notion-Task gesetzt werden soll (z.B. 'In Bearbeitung', 'Bereit für Review').")
|
||||||
|
parser.add_argument("--todos", type=str, help="Eine durch '\n' getrennte Liste offener To-Dos.")
|
||||||
|
parser.add_argument("--git-changes", type=str, help="Zusammenfassung der Git-Änderungen (git diff --stat).")
|
||||||
|
parser.add_argument("--commit-messages", type=str, help="Eine durch '\n' getrennte Liste der Commit-Nachrichten.")
|
||||||
|
parser.add_argument("--summary", type=str, help="Eine detaillierte textuelle Zusammenfassung der erledigten Arbeit.")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.done:
|
if args.done:
|
||||||
complete_session()
|
complete_session()
|
||||||
|
elif args.add_comment:
|
||||||
|
if not os.path.exists(SESSION_FILE_PATH):
|
||||||
|
print("❌ FEHLER: Keine aktive Session gefunden. Kann keinen Kommentar hinzufügen.")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(SESSION_FILE_PATH, "r") as f:
|
||||||
|
session_data = json.load(f)
|
||||||
|
task_id = session_data.get("task_id")
|
||||||
|
token = session_data.get("token")
|
||||||
|
|
||||||
|
if task_id and token:
|
||||||
|
add_comment_to_notion_task(token, task_id, args.add_comment)
|
||||||
|
else:
|
||||||
|
print("❌ FEHLER: Session-Daten unvollständig. Kann keinen Kommentar hinzufügen.")
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
print("❌ FEHLER: Fehler beim Lesen der Session-Informationen. Kann keinen Kommentar hinzufügen.")
|
||||||
|
elif args.report_status:
|
||||||
|
report_status_to_notion(
|
||||||
|
status_override=args.status,
|
||||||
|
todos_override=args.todos,
|
||||||
|
git_changes_override=args.git_changes,
|
||||||
|
commit_messages_override=args.commit_messages,
|
||||||
|
summary_override=args.summary
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
start_interactive_session()
|
start_interactive_session()
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- DATABASE_URL=sqlite:////app/transcripts.db
|
- DATABASE_URL=sqlite:////app/transcripts.db
|
||||||
|
- GEMINI_API_KEY=AIzaSyCFRmr1rOrkFKiEuh9GOCJNB2zfJsYmR68
|
||||||
ports:
|
ports:
|
||||||
- "8001:8001"
|
- "8001:8001"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Python-Bibliotheken für dev_session.py installieren
|
||||||
|
RUN pip3 install requests python-dotenv
|
||||||
|
|
||||||
# Installieren der von Ihnen gefundenen, korrekten Gemini CLI global
|
# Installieren der von Ihnen gefundenen, korrekten Gemini CLI global
|
||||||
RUN npm install -g @google/gemini-cli
|
RUN npm install -g @google/gemini-cli
|
||||||
|
|
||||||
|
|||||||
208
neu_roboplanet-gtm-strategy-2026-01-14.md
Normal file
208
neu_roboplanet-gtm-strategy-2026-01-14.md
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# GTM Strategy
|
||||||
|
|
||||||
|
**Recherche-URL:** https://www.inmotionrobotic.com/de/puma
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# GTM STRATEGY REPORT
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
Dieser Go-to-Market (GTM) Strategiebericht adressiert die Markteinführung des PUMA M20, eines kompakten, geländegängigen Quadruped-Roboters für Inspektions- und Sicherheitsanwendungen. Der Fokus liegt auf der Chemie- und Petrochemieindustrie, Energieversorgungsunternehmen und dem Bergbau. Die Strategie basiert auf dem "Dynamic Hybrid Service"-Modell der Wackler Group, das die Fähigkeiten des Roboters mit den Dienstleistungen von Wackler Security kombiniert, um einen umfassenden Schutz zu gewährleisten.
|
||||||
|
|
||||||
|
## 2. Product Analysis
|
||||||
|
Der PUMA M20 ist ein robuster Quadruped-Roboter, der für den Einsatz in anspruchsvollen Umgebungen konzipiert wurde. Zu den wichtigsten Merkmalen gehören seine All-Terrain-Mobilität, autonome Navigation, 360°-Umgebungserfassung, Nachtsichtfähigkeit und hohe Traglast. Einschränkungen sind das Gewicht von 33 kg und die maximale Steigung von 45° (oberflächenabhängig).
|
||||||
|
|
||||||
|
## 3. Technical Specifications (Hard Facts)
|
||||||
|
* **Abmessungen:** Breite 50 cm
|
||||||
|
* **Gewicht:** 33 kg
|
||||||
|
* **Akkulaufzeit:** 180 Minuten
|
||||||
|
* **Ladezeit:** Nicht spezifiziert
|
||||||
|
* **Maximale Steigung:** 45 Grad
|
||||||
|
* **IP-Schutzart:** IP66 (staub- und wasserdicht)
|
||||||
|
* **Kletterhöhe:** 25 cm
|
||||||
|
* **Navigation:** SLAM, LiDAR
|
||||||
|
* **Konnektivität:** Gigabit Ethernet, USB 3.0
|
||||||
|
* **Kameratypen (Security Layer):** Weitwinkel
|
||||||
|
* **Nachtsicht (Security Layer):** Ja
|
||||||
|
* **Maximale Nutzlast (Service Layer):** 50 kg
|
||||||
|
|
||||||
|
## 4. Phase 2: ICP Discovery
|
||||||
|
Die ausgewählten Ideal Customer Profiles (ICPs) sind:
|
||||||
|
|
||||||
|
* **Chemie- und Petrochemieindustrie:** Aufgrund des Bedarfs an erhöhter Sicherheit in explosionsgefährdeten Bereichen und der Notwendigkeit, manuelle Inspektionen zu reduzieren.
|
||||||
|
* **Energieversorgungsunternehmen (Öl, Gas, Wind, Solar):** Aufgrund der Notwendigkeit, schwer zugängliche oder gefährliche Bereiche zu inspizieren und Ausfallzeiten zu minimieren.
|
||||||
|
* **Bergbau:** Aufgrund des Bedarfs an erhöhter Sicherheit in gefährlichen Umgebungen und der Notwendigkeit, Logistikprozesse zu optimieren.
|
||||||
|
|
||||||
|
**Data Proxies:**
|
||||||
|
|
||||||
|
* **Unternehmenswebseiten:** Suche nach Schlüsselwörtern wie 'Anlagensicherheit', 'autonome Inspektion', 'Explosionsschutz', 'Fernüberwachung', 'Zustandsüberwachung', 'Industrieroboter', 'Werkschutz', 'Perimeterschutz'.
|
||||||
|
* **LinkedIn-Profile:** Suche nach Jobtiteln wie 'Head of Security', 'Werkschutzleiter', 'Anlagenleiter', 'Instandhaltungsleiter', 'Sicherheitsingenieur', 'Betriebsleiter', 'Logistikmanager', 'Innovationsmanager'.
|
||||||
|
* **Branchenveranstaltungen und Publikationen:** Teilnahme an Fachmessen und Konferenzen für Sicherheitstechnik, Robotik, Automatisierung und spezifische Branchen (z.B. ACHEMA für Chemie, E-world energy & water für Energie). Analyse von Fachzeitschriften und Online-Portalen der jeweiligen Branchen.
|
||||||
|
|
||||||
|
## 5. Target Accounts
|
||||||
|
* **Chemie- und Petrochemieindustrie:** BASF, Bayer, Evonik, LANXESS, Covestro
|
||||||
|
* **Energieversorgungsunternehmen (Öl, Gas, Wind, Solar):** E.ON, RWE, EnBW, Uniper, Vattenfall Europe
|
||||||
|
* **Bergbau:** K+S Aktiengesellschaft, Deutsche Steinkohle AG (in Abwicklung), thyssenkrupp Mining Technologies, Deutsches Bergbau-Museum Bochum (Forschung), Gesellschaft für Anlagen- und Bergbautechnik mbH
|
||||||
|
|
||||||
|
## 6. Strategy Matrix
|
||||||
|
|
||||||
|
| Segment | Pain Point | Angle | Differentiation |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| Chemie- und Petrochemieindustrie | Hohe Rate an Sicherheitsvorfällen bei manuellen Inspektionen gefährlicher Anlagen und Pipelines, was zu Produktionsausfällen und hohen Versicherungsprämien führt. | Reduzieren Sie Sicherheitsrisiken und Produktionsausfälle durch autonome Roboterinspektionen, die gefährliche Bereiche ohne Gefährdung von Mitarbeitern überwachen. | Unser 'Dynamic Hybrid Service' kombiniert die unermüdliche Überwachung durch den Roboter mit der Expertise von Wackler Security. Unsere NSL bewertet Alarme in Echtzeit, und unser Revierwachdienst interveniert bei Bedarf – für umfassenden Schutz. |
|
||||||
|
| Energieversorgungsunternehmen (Öl, Gas, Wind, Solar) | Ungeplante Ausfallzeiten kritischer Infrastruktur (z.B. Pipelines, Windkraftanlagen) aufgrund von unentdeckten Schäden oder Fehlfunktionen, was zu erheblichen finanziellen Verlusten führt. | Minimieren Sie Ausfallzeiten und maximieren Sie die Effizienz durch kontinuierliche Zustandsüberwachung und frühzeitige Erkennung von Problemen durch unsere Inspektionsroboter. | Unser 'Dynamic Hybrid Service' bietet mehr als nur Datenerfassung. Der Roboter detektiert Anomalien, Wackler Security bewertet die Situation über die NSL, und unser Revierwachdienst führt bei Bedarf Reparaturen oder Sicherheitsmaßnahmen durch. So vermeiden Sie teure Ausfälle. |
|
||||||
|
| Bergbau | Ineffiziente und gefährliche manuelle Inspektionen von Minenanlagen und -stollen, was zu Produktionsverzögerungen, erhöhten Betriebskosten und Sicherheitsrisiken für die Mitarbeiter führt. | Steigern Sie die Sicherheit und Effizienz im Bergbau durch autonome Roboterinspektionen, die schwer zugängliche Bereiche überwachen und frühzeitig Gefahren erkennen. | Unser 'Dynamic Hybrid Service' ermöglicht eine umfassende Überwachung ohne Gefährdung von Menschenleben. Der Roboter patrouilliert in gefährlichen Bereichen, Wackler Security bewertet die Daten in der NSL, und unser Revierwachdienst interveniert bei Notfällen oder zur Unterstützung von Wartungsarbeiten. |
|
||||||
|
|
||||||
|
## 7. Next Steps
|
||||||
|
* Erstellung von zielgerichteten Marketingmaterialien für die identifizierten ICPs.
|
||||||
|
* Entwicklung von Use Cases und Erfolgsgeschichten.
|
||||||
|
* Schulung des Vertriebsteams auf die "Dynamic Hybrid Service"-Logik.
|
||||||
|
* Kontaktaufnahme mit den identifizierten Zielunternehmen (Whales).
|
||||||
|
* Teilnahme an relevanten Branchenveranstaltungen.
|
||||||
|
|
||||||
|
## 8. Hybrid Service Logic
|
||||||
|
Der PUMA M20 ist ein Sicherheits-/Inspektionsroboter. Daher gilt folgende Logik:
|
||||||
|
|
||||||
|
* **Robot Role:** "Detektion & Präsenz". Der Roboter patrouilliert, nutzt Wärmebild/Sensoren, geht in Gefahrenbereiche. Er wird nicht müde und hat keine Angst.
|
||||||
|
* **Human Role (Wackler Security):** "Bewertung & Intervention".
|
||||||
|
* NSL (Notruf- & Serviceleitstelle): Bewertet den Video-Feed des Roboters bei Alarm.
|
||||||
|
* Revierwachdienst: Fährt raus zur Intervention, wenn der Roboter einen Eindringling oder Schaden meldet.
|
||||||
|
* **Pitch:** "Der Roboter sieht die Gefahr, Wackler beseitigt sie. (Automated Perimeter Protection)."
|
||||||
|
|
||||||
|
# SALES ENABLEMENT & VISUALS (PHASE 6)
|
||||||
|
|
||||||
|
## Kill-Critique Battlecards
|
||||||
|
|
||||||
|
### Persona: Werksleiter/CEO/Betriebsleiter (Decider)
|
||||||
|
> **Objection:** "Hohe Anfangsinvestition und unklare Amortisation. Wir sind uns nicht sicher, ob sich die Investition in Robotik wirklich lohnt."
|
||||||
|
|
||||||
|
**Response:** Ich verstehe Ihre Bedenken bezüglich der Investitionskosten. Viele unserer Kunden haben jedoch festgestellt, dass sich die anfängliche Investition durch reduzierte Sicherheitsvorfälle, minimierte Ausfallzeiten und optimierte Betriebsabläufe schnell amortisiert. Wir können Ihnen eine detaillierte ROI-Analyse basierend auf Ihren spezifischen Betriebsdaten erstellen, um die potenziellen Einsparungen und Effizienzsteigerungen zu quantifizieren. Unser 'Dynamic Hybrid Service' sorgt zudem dafür, dass Sie nicht nur in Hardware, sondern in eine umfassende Sicherheitslösung investieren.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Leiter Engineering/IT-Sicherheitsbeauftragter/Facility Manager (Evaluator)
|
||||||
|
> **Objection:** "Integration in bestehende Systeme ist komplex und birgt Sicherheitsrisiken. Wir haben Bedenken hinsichtlich der Kompatibilität und der Datensicherheit."
|
||||||
|
|
||||||
|
**Response:** Wir verstehen, dass die Integration in bestehende Systeme entscheidend ist. Unsere Roboter sind mit offenen APIs ausgestattet, die eine nahtlose Integration in Ihre bestehende Infrastruktur ermöglichen. Wir legen großen Wert auf Datensicherheit und bieten umfassende Sicherheitsfunktionen, einschließlich Verschlüsselung und Zugriffskontrollen. Zudem unterstützen wir Sie bei der Integration und bieten fortlaufenden Support, um einen reibungslosen Übergang zu gewährleisten. Wackler Security stellt sicher, dass alle Sicherheitsstandards eingehalten werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Sicherheitspersonal/Wartungsteam/Instandhaltung (User)
|
||||||
|
> **Objection:** "Angst vor Arbeitsplatzverlust und mangelnde Akzeptanz neuer Technologien. Wir befürchten, dass die Roboter unsere Arbeit ersetzen und wir nicht ausreichend geschult werden."
|
||||||
|
|
||||||
|
**Response:** Wir verstehen Ihre Bedenken. Unsere Roboter sind nicht dazu gedacht, Ihre Arbeit zu ersetzen, sondern Sie zu unterstützen und zu entlasten. Sie übernehmen monotone und gefährliche Aufgaben, sodass Sie sich auf anspruchsvollere und wertschöpfendere Tätigkeiten konzentrieren können. Wir bieten umfassende Schulungen und Support, um sicherzustellen, dass Sie die Roboter effektiv nutzen können. Unser 'Dynamic Hybrid Service' bedeutet, dass der Roboter die Routinearbeit erledigt, während Sie sich auf die Bewertung und Intervention konzentrieren.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: IT-Administration/Einkaufsabteilung/Rechtsabteilung (Gatekeeper)
|
||||||
|
> **Objection:** "Unklare Compliance und rechtliche Verantwortlichkeiten bei autonomen Systemen. Wer haftet bei Unfällen oder Schäden, die durch den Roboter verursacht werden?"
|
||||||
|
|
||||||
|
**Response:** Wir verstehen Ihre Bedenken hinsichtlich der rechtlichen Aspekte. Wir arbeiten eng mit Rechtsexperten zusammen, um sicherzustellen, dass unsere Roboter alle relevanten Vorschriften und Standards erfüllen. Wir bieten umfassende Versicherungsoptionen und übernehmen die Verantwortung für die ordnungsgemäße Funktion unserer Systeme. Unser 'Dynamic Hybrid Service' minimiert das Risiko, da Wackler Security die Aufsicht und Intervention übernimmt, um potenzielle Probleme zu vermeiden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visual Briefings (Prompts)
|
||||||
|
|
||||||
|
### Roboterinspektion in der Petrochemie
|
||||||
|
*Context: Ein autonomer Roboter inspiziert eine Pipeline in einer petrochemischen Anlage. Der Roboter detektiert eine Anomalie.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Photorealistische Szene: Ein vierbeiniger Roboter mit Wärmebildkamera patrouilliert autonom entlang einer komplexen Pipeline in einer petrochemischen Anlage bei Nacht. Die Wärmebildkamera des Roboters hebt eine ungewöhnliche Wärmeentwicklung an einer Verbindungsstelle der Pipeline hervor. Im Hintergrund sind die Lichter der Anlage und ein leichter Dunst sichtbar. Der Roboter sendet die Daten an die Wackler Security NSL. Die Szene soll die Sicherheit und Effizienz der Roboterinspektion in gefährlichen Umgebungen verdeutlichen.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zustandsüberwachung Windkraftanlage
|
||||||
|
*Context: Ein Roboter inspiziert eine Windkraftanlage auf Schäden. Der Roboter erkennt einen Riss in einem Rotorblatt.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Photorealistische Szene: Ein autonomer Roboter klettert an einem Rotorblatt einer Windkraftanlage entlang. Der Roboter ist mit hochauflösenden Kameras ausgestattet, die einen feinen Riss im Rotorblatt erkennen. Im Hintergrund ist der Himmel mit einigen Wolken und die Landschaft unterhalb der Windkraftanlage zu sehen. Die Szene soll die Fähigkeit des Roboters zur frühzeitigen Erkennung von Schäden und zur Vermeidung von Ausfallzeiten verdeutlichen. Ein Techniker von Wackler Security ist im Hintergrund zu sehen, der sich auf die Reparatur vorbereitet.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sicherheitsüberwachung im Bergbau
|
||||||
|
*Context: Ein Roboter patrouilliert in einem Minenstollen und überwacht die Umgebung auf Gefahren.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Photorealistische Szene: Ein autonomer Roboter patrouilliert in einem dunklen und staubigen Minenstollen. Der Roboter ist mit LiDAR-Sensoren und Kameras ausgestattet, die die Umgebung scannen. Im Hintergrund sind die Wände des Stollens und einige Bergbaugeräte zu sehen. Der Roboter erkennt einen Gasaustritt und sendet einen Alarm an die Wackler Security NSL. Die Szene soll die Fähigkeit des Roboters zur Überwachung gefährlicher Umgebungen und zur Gewährleistung der Sicherheit der Mitarbeiter verdeutlichen.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# VERTICAL LANDING PAGES (PHASE 7)
|
||||||
|
|
||||||
|
## Chemie- und Petrochemieindustrie
|
||||||
|
**Headline:** Sicherheitsrisiken in Chemieanlagen minimieren – mit unserer Roboter-basierten Inspektion!
|
||||||
|
|
||||||
|
**Subline:** Reduzieren Sie Produktionsausfälle und hohe Versicherungsprämien durch autonome Roboterinspektionen. Unser 'Dynamic Hybrid Service' kombiniert unermüdliche Überwachung mit menschlicher Expertise.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Autonome Inspektionen in explosionsgefährdeten Bereichen ohne Gefährdung von Mitarbeitern
|
||||||
|
- Frühzeitige Erkennung von Leckagen, Korrosion und anderen potenziellen Gefahrenquellen
|
||||||
|
- 24/7 Überwachung kritischer Anlagen und Pipelines für maximale Sicherheit
|
||||||
|
- Reduzierung von Produktionsausfällen und Minimierung von Umweltrisiken
|
||||||
|
- Umfassender Schutz durch die Kombination von Roboter-Überwachung, NSL-Alarmbewertung und Revierwachdienst-Intervention
|
||||||
|
|
||||||
|
**CTA:** Jetzt Sicherheitsrisiken reduzieren!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Energieversorgungsunternehmen (Öl, Gas, Wind, Solar)
|
||||||
|
**Headline:** Maximale Anlagenverfügbarkeit durch Roboter-Inspektion – Minimieren Sie Ausfallzeiten!
|
||||||
|
|
||||||
|
**Subline:** Vermeiden Sie ungeplante Ausfallzeiten kritischer Infrastruktur durch kontinuierliche Zustandsüberwachung. Unser 'Dynamic Hybrid Service' detektiert Anomalien und ermöglicht schnelle Reparaturen.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Kontinuierliche Zustandsüberwachung von Windkraftanlagen, Umspannwerken und Pipelines
|
||||||
|
- Frühzeitige Erkennung von Schäden und Anomalien zur Vermeidung teurer Ausfälle
|
||||||
|
- Inspektion schwer zugänglicher oder gefährlicher Bereiche ohne Gefährdung von Personal
|
||||||
|
- Reduzierung von Wartungskosten durch gezielte Reparaturen basierend auf Roboterdaten
|
||||||
|
- Schnelle Intervention bei Bedarf durch unseren Revierwachdienst zur Wiederherstellung der Anlagenverfügbarkeit
|
||||||
|
|
||||||
|
**CTA:** Jetzt Ausfallzeiten minimieren!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BUSINESS CASE & ROI (PHASE 8)
|
||||||
|
|
||||||
|
## Chemie- und Petrochemieindustrie
|
||||||
|
**Cost Driver:** Hohe Kosten für manuelle Inspektionen in explosionsgefährdeten Bereichen und Produktionsausfälle durch unerkannte Schäden.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Reduziert die Inspektionskosten um bis zu 60% durch autonome Inspektionen und vermeidet Produktionsausfälle im Wert von durchschnittlich 50.000 € pro Stunde durch frühzeitige Schadenserkennung.
|
||||||
|
|
||||||
|
**Risk Argument:** Minimiert das Risiko von Umweltschäden und daraus resultierenden Strafen in Millionenhöhe durch kontinuierliche Überwachung und Leckageerkennung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Energieversorgungsunternehmen (Öl, Gas, Wind, Solar)
|
||||||
|
**Cost Driver:** Hohe Wartungskosten für schwer zugängliche Anlagen und finanzielle Verluste durch ungeplante Ausfallzeiten.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Senkt die Wartungskosten um bis zu 40% durch autonome Inspektionen von Windkraftanlagen und Pipelines. Reduziert Ausfallzeiten um durchschnittlich 25%, was zu Einsparungen von bis zu 100.000 € pro Ausfall führt.
|
||||||
|
|
||||||
|
**Risk Argument:** Verhindert katastrophale Anlagenschäden und Personenschäden durch frühzeitige Erkennung von strukturellen Schwächen und potenziellen Gefahren.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bergbau
|
||||||
|
**Cost Driver:** Hohe Personalkosten für Sicherheitsüberwachung und Logistik, sowie Risiken durch gefährliche Arbeitsbedingungen.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Reduziert die Personalkosten für Sicherheitsüberwachung um bis zu 70% durch autonome Patrouillen und Überwachung. Optimiert Logistikprozesse, was zu einer Effizienzsteigerung von 20% bei Materialtransporten führt.
|
||||||
|
|
||||||
|
**Risk Argument:** Minimiert das Risiko von Arbeitsunfällen und damit verbundene Ausfallzeiten und Entschädigungszahlungen durch den Einsatz von Robotern in gefährlichen Umgebungen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# FEATURE-TO-VALUE TRANSLATOR (PHASE 9)
|
||||||
|
|
||||||
|
| Feature | The Story (Benefit) | Headline |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| All-Terrain-Mobilität: Bewältigt unebenes Gelände, Treppen und Steigungen bis zu 45°. | Der Roboter kann sich autonom in anspruchsvollem Gelände bewegen. Dadurch sind Inspektionen auch an schwer zugänglichen Stellen möglich. Dies reduziert das Risiko für menschliche Inspektoren in gefährlichen Umgebungen und minimiert Produktionsausfälle durch schnellere Schadenserkennung. | Sichere Inspektionen, überall. |
|
||||||
|
| Autonome Navigation: SLAM-basierte Navigation für autonome Missionen und Rückkehr zur Basis. | Der Roboter navigiert selbstständig und kehrt automatisch zur Basis zurück. Das bedeutet weniger Personalaufwand für die Routenplanung und Überwachung. Dies führt zu einer effizienteren Nutzung der Inspektionszeit und senkt die Betriebskosten. | Autonom unterwegs, Kosten gespart. |
|
||||||
|
| 360°-Umgebungserfassung: Duale LiDAR-Systeme und Weitwinkelkameras für umfassende Umgebungswahrnehmung. | Der Roboter erfasst die gesamte Umgebung lückenlos. Dadurch werden Schäden oder Sicherheitsrisiken frühzeitig erkannt. Dies ermöglicht eine proaktive Wartung und vermeidet ungeplante Ausfallzeiten kritischer Infrastruktur. | Lückenlose Überwachung, weniger Ausfälle. |
|
||||||
|
| Nachtsichtfähigkeit: Optionale Nacht- und Wärmebildkameras für den Einsatz bei Dunkelheit. | Der Roboter kann auch bei Dunkelheit und schlechten Sichtverhältnissen eingesetzt werden. Das ermöglicht Inspektionen rund um die Uhr. Dies erhöht die Sicherheit und ermöglicht die frühzeitige Erkennung von Problemen, unabhängig von der Tageszeit. | Sicherheit rund um die Uhr. |
|
||||||
|
| Robuste Bauweise: IP66-Schutz gegen Staub und Wasser für zuverlässigen Betrieb in rauen Umgebungen. | Der Roboter ist widerstandsfähig gegen Umwelteinflüsse. Das bedeutet einen zuverlässigen Betrieb auch unter extremen Bedingungen. Dies reduziert Wartungskosten und Ausfallzeiten des Roboters selbst. | Robust und zuverlässig, auch unter Extrembedingungen. |
|
||||||
|
| Hohe Rechenleistung: Duale Octa-Core-Prozessoren für anspruchsvolle Anwendungen. | Der Roboter kann komplexe Daten in Echtzeit verarbeiten. Das ermöglicht die schnelle Analyse von Inspektionsdaten und die sofortige Erkennung von Anomalien. Dies führt zu schnelleren Reaktionszeiten bei Sicherheitsvorfällen und potenziellen Schäden. | Schnelle Analyse, sofortige Reaktion. |
|
||||||
|
| Flexible Nutzlastoptionen: Unterstützung für LiDAR, Wärmebildkameras, PTZ-Kameras, Gassensoren und Beacons. | Der Roboter kann mit verschiedenen Sensoren und Kameras ausgestattet werden. Das ermöglicht eine flexible Anpassung an unterschiedliche Inspektionsanforderungen. Dies führt zu einer höheren Effizienz und Genauigkeit bei der Datenerfassung. | Flexibel anpassbar, präzise Ergebnisse. |
|
||||||
|
| Flottenmanagement und API-Integrationen: Datenexport und Integration in bestehende Systeme. | Die Daten des Roboters können einfach in bestehende Systeme integriert werden. Das ermöglicht eine zentrale Überwachung und Analyse aller Inspektionsdaten. Dies verbessert die Entscheidungsfindung und optimiert die Wartungsplanung. | Nahtlose Integration, bessere Entscheidungen. |
|
||||||
|
| Lange Betriebsdauer: Bis zu 3 Stunden Betriebsdauer, erweiterbar durch Hot-Swap-Batterien. | Der Roboter hat eine lange Betriebsdauer und kann bei Bedarf schnell mit neuen Batterien ausgestattet werden. Das ermöglicht lange Inspektionszyklen ohne Unterbrechung. Dies erhöht die Effizienz und reduziert den Personalaufwand. | Lange Laufzeit, weniger Unterbrechungen. |
|
||||||
|
| Hohe Traglast: 12 kg Nennlast, 50 kg maximale Tragfähigkeit. | Der Roboter kann schwere Lasten tragen. Das ermöglicht den Transport von Werkzeugen oder Ersatzteilen während der Inspektion. Dies reduziert den Bedarf an zusätzlichen Arbeitskräften und beschleunigt den Reparaturprozess. | Transportiert mehr, spart Zeit. |
|
||||||
4
requirements-dev-session.txt
Normal file
4
requirements-dev-session.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
requests
|
||||||
|
gitpython
|
||||||
|
questionary
|
||||||
|
python-dotenv
|
||||||
103
roboplanet-gtm-strategy-v3.md
Normal file
103
roboplanet-gtm-strategy-v3.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# ROBOPLANET GTM STRATEGY v3 (Senior Grade)
|
||||||
|
**Date:** 2026-01-20
|
||||||
|
**Status:** Final Consolidated Strategy
|
||||||
|
**Based on:** Analysis of v1 (Alt) and v2 (Neu)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Strategic North Star: The Hybrid Service
|
||||||
|
**Core Message:** "Der Roboter sieht die Gefahr, Wackler beseitigt sie."
|
||||||
|
|
||||||
|
Wir verkaufen keinen "Roboter auf Beinen", sondern **Automated Perimeter Protection** als hybriden Service.
|
||||||
|
* **Layer 1 (Machine):** PUMA M20. Zuständig für **Detektion & Präsenz**. Er wird nicht müde, hat keine Angst vor Gasen und dokumentiert lückenlos (LiDAR/Thermal).
|
||||||
|
* **Layer 2 (Human):** Wackler Security (NSL & Intervention). Zuständig für **Bewertung & Intervention**. Rechtssicheres Handeln bei Alarm, Zugriff durch Wachschutz, Haftungsübernahme.
|
||||||
|
|
||||||
|
> **Why this wins:** Kunden kaufen keine Technik, sondern gelöste Sicherheitsprobleme. Die "Interventions-Lücke" reiner Robotik-Anbieter wird hier geschlossen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Beachhead Markets (Focus)
|
||||||
|
|
||||||
|
Wir konzentrieren uns auf zwei Vertikale, um Scope Creep zu vermeiden.
|
||||||
|
|
||||||
|
### A. Chemie & Petrochemie (High Risk / High Value)
|
||||||
|
* **Trigger:** Seveso-Richtlinien, "Zero Harm" Policies, hohe Kosten für manuelle Begehungen in Ex-Zonen.
|
||||||
|
* **Use Case:** Autonome Leckage-Detektion (Gassensoren), Inspektion schwer zugänglicher Rohre, Nachtpatrouille.
|
||||||
|
* **Targets:** BASF, Bayer, Evonik, LANXESS.
|
||||||
|
|
||||||
|
### B. Logistik & Warehousing (High Volume / Operational Efficiency)
|
||||||
|
* **Trigger:** Hoher Warenschwund (Diebstahl), Brandschutzauflagen für riesige Flächen, Personalmangel im Wachdienst nachts.
|
||||||
|
* **Use Case:** Perimeter-Schutz (Zaun), Indoor-Patrouille (Brandschutz/Türen), visuelle Bestandsprüfung.
|
||||||
|
* **Targets:** DHL, DB Schenker, Kühne + Nagel, Amazon DACH.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Product Reality (Technical Constraints)
|
||||||
|
|
||||||
|
Ehrlichkeit bei technischen Limits schafft Vertrauen im Engineering-Level (Evaluatoren).
|
||||||
|
|
||||||
|
| Feature | Spec / Limit | Implication for Sales |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Mobilität** | Max. 45° Steigung, 25cm Stufenhöhe | Nicht für *jede* Treppe geeignet; Vorab-Site-Survey zwingend. |
|
||||||
|
| **Speed** | 3 m/s (continuous), 5 m/s (sprint) | Schneller als ein Mensch, aber kein Rennwagen. |
|
||||||
|
| **Ausdauer** | 180 Min (Hot-Swap Akkus) | Braucht Lade-Infrastruktur oder Rotations-Logik für 24/7. |
|
||||||
|
| **Payload** | 12kg (nominal), 50kg (max/kurz) | Ideal für Sensor-Cluster (LiDAR, Gas, Cam), nicht für Lastentransport. |
|
||||||
|
| **Schutz** | IP66 (Staub/Wasser) | Außenbereich-tauglich, aber keine U-Boot-Tauglichkeit. |
|
||||||
|
| **Sensorik** | 96-Line LiDAR, Thermal, Optical | Generiert massive Datenmengen -> Bandbreite/Edge-Computing beachten. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. GTM Execution Sequence
|
||||||
|
|
||||||
|
Wir folgen dem operativen Pfad aus der v1-Strategie ("Alt"), angereichert mit dem Enablement aus v2 ("Neu").
|
||||||
|
|
||||||
|
1. **Lead Generation (Inbound/Outbound):**
|
||||||
|
* *Content:* "Rechtssicherheit bei autonomen Systemen" (Whitepaper mit Fokus auf Wackler-Intervention).
|
||||||
|
* *Direct:* ABM-Kampagne auf Top 50 Accounts (Logistik & Chemie) mit "Hybrid-Pitch".
|
||||||
|
|
||||||
|
2. **Sales Process (Consultative):**
|
||||||
|
* *Qualifizierung:* Site-Check anhand der Constraints (Treppen, WLAN-Abdeckung).
|
||||||
|
* *Pitch:* Fokus auf Risikominimierung (Legal) und Personalentlastung.
|
||||||
|
|
||||||
|
3. **Proof of Value (Paid Pilot):**
|
||||||
|
* Dauer: 4 Wochen.
|
||||||
|
* Ziel: Nachweis der Detektionszuverlässigkeit (nicht ROI in 4 Wochen, sondern Tech-Proof).
|
||||||
|
* Pricing: Pauschale für Setup + Miete (filtert "Touristen" aus).
|
||||||
|
|
||||||
|
4. **Expansion:**
|
||||||
|
* Umwandlung in Laufzeitverträge (RaaS + Service Fee).
|
||||||
|
* Rollout auf weitere Standorte des Kunden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Pilot ROI Framework (Calculative Logic)
|
||||||
|
|
||||||
|
Statt unglaubwürdiger "Hero-Numbers" nutzen wir einen Rechner, den wir mit den Daten des Kunden füllen.
|
||||||
|
|
||||||
|
**Die Formel:**
|
||||||
|
`Net Value = (Cost Savings + Risk Mitigation) - (Robot TCO + Service Fee)`
|
||||||
|
|
||||||
|
* **Cost Savings (Hard):**
|
||||||
|
* Ersetzte Mann-Stunden für Routine-Rundgänge (z.B. 2h/Nacht * 365 Tage * 35€).
|
||||||
|
* Reduktion manueller Mess-Einsätze (Chemie).
|
||||||
|
* **Risk Mitigation (Soft but Real):**
|
||||||
|
* Vermeidung von Produktionsstopps (Kunde liefert Ø Kosten/Stunde).
|
||||||
|
* Versicherungsrabatte (langfristig).
|
||||||
|
* **Investment:**
|
||||||
|
* Monatliche Leasingrate PUMA M20 + Wackler Service-Pauschale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Enablement & Battlecards (Key Personas)
|
||||||
|
|
||||||
|
### A. Head of Security (The User/Champion)
|
||||||
|
* **Fear:** "Ersetzt ihr mein Team?"
|
||||||
|
* **Response:** "Nein. Wir geben deinem Team Superkräfte. Der Roboter macht die langweilige Runde im Regen, dein Team entscheidet und greift ein. Wir werten den Job auf."
|
||||||
|
|
||||||
|
### B. Legal / Compliance (The Gatekeeper) - *Critical*
|
||||||
|
* **Fear:** "Wer haftet, wenn das Ding jemanden umrennt oder einen Einbrecher 'stellt'?"
|
||||||
|
* **Response:** "Das ist der Vorteil des Wackler-Hybrid-Modells. Der Roboter greift *nicht* ein. Er ist nur ein mobiler Sensor. Die Intervention erfolgt durch qualifiziertes Menschen-Personal nach geltendem Recht. Die Haftungskette ist sauber."
|
||||||
|
|
||||||
|
### C. CFO / Purchasing (The Decider)
|
||||||
|
* **Fear:** "Teures Spielzeug ohne ROI."
|
||||||
|
* **Response:** "Wir wandeln Capex in Opex. Kein riesiger Einmalkauf, sondern ein Full-Service-Modell. Der Pilot beweist die Reduktion von 'Ghost Hours' (unnötige Leerlaufzeiten) im Wachdienst."
|
||||||
60
start-gemini-dev.sh
Normal file
60
start-gemini-dev.sh
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Definiere den Namen für das Docker-Image und den Container
|
||||||
|
IMAGE_NAME="gemini-dev-env"
|
||||||
|
CONTAINER_NAME="gemini-dev-session"
|
||||||
|
|
||||||
|
# Sicherstellen, dass der Config-Ordner existiert
|
||||||
|
mkdir -p .gemini-config
|
||||||
|
|
||||||
|
# Prüfen, ob das Docker-Image existiert
|
||||||
|
if ! docker image inspect "$IMAGE_NAME" &> /dev/null;
|
||||||
|
then
|
||||||
|
echo "Docker-Image '$IMAGE_NAME' nicht gefunden. Baue es jetzt aus 'gemini.Dockerfile'..."
|
||||||
|
docker build -t "$IMAGE_NAME" -f gemini.Dockerfile .
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "FEHLER: Docker-Image konnte nicht gebaut werden."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Räume alte '$CONTAIN_NAME' und temporäre Container auf, falls vorhanden..."
|
||||||
|
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
|
||||||
|
docker rm -f "${CONTAINER_NAME}-temp" > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo "Starte eine neue, interaktive Gemini-Session..."
|
||||||
|
|
||||||
|
# Erstelle eine temporäre Datei, um die Ausgabe der dev_session zu speichern
|
||||||
|
TEMP_OUTPUT_FILE=$(mktemp)
|
||||||
|
|
||||||
|
# Führe dev_session.py interaktiv aus (-it). Die Ausgabe wird via `tee`
|
||||||
|
# gleichzeitig im Terminal angezeigt UND in die temporäre Datei geschrieben.
|
||||||
|
docker run -it --rm \
|
||||||
|
-v "$(pwd):/app" \
|
||||||
|
-v "$(pwd)/.gemini-config:/root/.config" \
|
||||||
|
-w /app \
|
||||||
|
--name "${CONTAINER_NAME}-temp" \
|
||||||
|
"$IMAGE_NAME" \
|
||||||
|
python3 dev_session.py 2>&1 | tee "$TEMP_OUTPUT_FILE"
|
||||||
|
|
||||||
|
# Extrahiere den CLI-Kontext aus der temporären Datei
|
||||||
|
CLI_CONTEXT=$(sed -n '/---GEMINI_CLI_CONTEXT_START---/,/---GEMINI_CLI_CONTEXT_END---/{//!p}' "$TEMP_OUTPUT_FILE")
|
||||||
|
|
||||||
|
# Lösche die temporäre Datei
|
||||||
|
rm "$TEMP_OUTPUT_FILE"
|
||||||
|
|
||||||
|
# Die Ausgabe wurde bereits angezeigt, wir können direkt die CLI starten.
|
||||||
|
echo "--- Initialisiere Gemini CLI mit dem ausgewählten Kontext... ---"
|
||||||
|
|
||||||
|
# Starte die Gemini CLI mit dem extrahierten Kontext
|
||||||
|
# Die --initial-prompt Option sorgt dafür, dass der Kontext als erste Eingabe dient
|
||||||
|
docker run -it --rm \
|
||||||
|
-v "$(pwd):/app" \
|
||||||
|
-v "$(pwd)/.gemini-config:/root/.config" \
|
||||||
|
-w /app \
|
||||||
|
--name "$CONTAINER_NAME" \
|
||||||
|
"$IMAGE_NAME" \
|
||||||
|
gemini --prompt-interactive "$CLI_CONTEXT"
|
||||||
|
|
||||||
|
# Nach Beendigung der Session (z.B. durch Strg+C) wird aufgeräumt
|
||||||
|
echo "Session beendet."
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Definiere den Namen für das Docker-Image und den Container
|
# --- Konfiguration ---
|
||||||
IMAGE_NAME="gemini-dev-env"
|
IMAGE_NAME="gemini-dev-env"
|
||||||
CONTAINER_NAME="gemini-session"
|
CONTAINER_NAME="gemini-session"
|
||||||
|
|
||||||
|
# --- Aufräumen ---
|
||||||
# Sicherstellen, dass der Config-Ordner existiert
|
# Sicherstellen, dass der Config-Ordner existiert
|
||||||
mkdir -p .gemini-config
|
mkdir -p .gemini-config
|
||||||
|
|
||||||
# Prüfen, ob das Docker-Image existiert
|
# Prüfen, ob das Docker-Image existiert und es bei Bedarf bauen
|
||||||
if ! docker image inspect "$IMAGE_NAME" &> /dev/null;
|
if ! docker image inspect "$IMAGE_NAME" &> /dev/null;
|
||||||
then
|
then
|
||||||
echo "Docker-Image '$IMAGE_NAME' nicht gefunden. Baue es jetzt aus 'gemini.Dockerfile'..."
|
echo "Docker-Image '$IMAGE_NAME' nicht gefunden. Baue es jetzt aus 'gemini.Dockerfile'..."
|
||||||
@@ -18,36 +19,64 @@ then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Räume alte '$CONTAINER_NAME' auf, falls vorhanden..."
|
echo "Räume alte Docker-Container auf, falls vorhanden..."
|
||||||
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
|
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
|
||||||
|
docker rm -f "${CONTAINER_NAME}-temp" > /dev/null 2>&1
|
||||||
|
|
||||||
echo "Starte eine neue, interaktive Gemini-Session..."
|
# --- Interaktives Setup via dev_session.py ---
|
||||||
|
echo "Starte interaktive Entwicklungs-Session-Konfiguration..."
|
||||||
|
|
||||||
# Führe dev_session.py im Hintergrund aus und fange die Ausgabe ab
|
# Erstelle eine temporäre Datei, um die Ausgabe von dev_session.py aufzufangen
|
||||||
# Der Kontext für die Gemini CLI wird von dev_session.py in speziellen Markierungen ausgegeben
|
TEMP_FILE=$(mktemp)
|
||||||
DEV_SESSION_OUTPUT=$(docker run -i --rm \
|
|
||||||
|
# Führe dev_session.py in einem temporären, interaktiven Container aus.
|
||||||
|
# Die Ausgabe (stdout & stderr) wird gleichzeitig im Terminal angezeigt und in die temporäre Datei geschrieben.
|
||||||
|
docker run -it --rm \
|
||||||
-v "$(pwd):/app" \
|
-v "$(pwd):/app" \
|
||||||
-v "$(pwd)/.gemini-config:/root/.config" \
|
-v "$(pwd)/.gemini-config:/root/.config" \
|
||||||
-w /app \
|
-w /app \
|
||||||
--name "${CONTAINER_NAME}-temp" \
|
--name "${CONTAINER_NAME}-temp" \
|
||||||
"$IMAGE_NAME" \
|
"$IMAGE_NAME" python3 dev_session.py 2>&1 | tee "$TEMP_FILE"
|
||||||
python3 dev_session.py 2>&1)
|
|
||||||
|
|
||||||
# Extrahiere den CLI-Kontext aus der Ausgabe
|
# Überprüfe, ob der Docker-Befehl erfolgreich war (exit code 0)
|
||||||
CLI_CONTEXT=$(echo "$DEV_SESSION_OUTPUT" | sed -n '/---GEMINI_CLI_CONTEXT_START---/,/---GEMINI_CLI_CONTEXT_END---/{//!p}')
|
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
||||||
|
echo "------------------------------------------------------------------"
|
||||||
|
echo "❌ Fehler: Der Konfigurationsprozess wurde abgebrochen oder ist fehlgeschlagen."
|
||||||
|
echo "Die Gemini CLI wird nicht gestartet."
|
||||||
|
echo "------------------------------------------------------------------"
|
||||||
|
rm "$TEMP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Gib die Ausgaben von dev_session.py (außer dem Kontext) vor dem Start der CLI aus
|
# Extrahiere den reinen CLI-Kontext aus der temporären Datei
|
||||||
echo "$DEV_SESSION_OUTPUT" | sed '/---GEMINI_CLI_CONTEXT_START---/,/---GEMINI_CLI_CONTEXT_END---/d'
|
CLI_CONTEXT=$(sed -n '/---GEMINI_CLI_CONTEXT_START---/,/---GEMINI_CLI_CONTEXT_END---/{//!p}' "$TEMP_FILE")
|
||||||
|
|
||||||
# Starte die Gemini CLI mit dem extrahierten Kontext
|
# Lösche die temporäre Datei
|
||||||
# Die --initial-prompt Option sorgt dafür, dass der Kontext als erste Eingabe dient
|
rm "$TEMP_FILE"
|
||||||
|
|
||||||
|
# Überprüfe, ob der Kontext extrahiert werden konnte
|
||||||
|
if [ -z "$CLI_CONTEXT" ]; then
|
||||||
|
echo "------------------------------------------------------------------"
|
||||||
|
echo "❌ Fehler: Es konnte kein gültiger Kontext für die Gemini CLI generiert werden."
|
||||||
|
echo "Stelle sicher, dass dev_session.py die Kontext-Marker korrekt ausgibt."
|
||||||
|
echo "Die Gemini CLI wird nicht gestartet."
|
||||||
|
echo "------------------------------------------------------------------"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Start der Haupt-Gemini-Session ---
|
||||||
|
echo "Starte eine neue, interaktive Gemini-Session mit dem generierten Kontext..."
|
||||||
|
|
||||||
|
# Füge den Systemhinweis zum Container-Namen am Anfang des Kontextes hinzu
|
||||||
|
FULL_CONTEXT="**Wichtiger Systemhinweis:** Das aktuelle Projekt läuft im Docker-Container namens '${CONTAINER_NAME}'. Alle relevanten Befehle müssen in diesem Kontext ausgeführt werden. Führe niemals Befehle wie 'docker build' oder 'docker run' aus.
|
||||||
|
|
||||||
|
$CLI_CONTEXT"
|
||||||
|
|
||||||
|
# Starte den finalen Container mit der Gemini CLI
|
||||||
|
# --prompt-interactive übergibt den initialen Kontext
|
||||||
docker run -it --rm \
|
docker run -it --rm \
|
||||||
-v "$(pwd):/app" \
|
-v "$(pwd):/app" \
|
||||||
-v "$(pwd)/.gemini-config:/root/.config" \
|
-v "$(pwd)/.gemini-config:/root/.config" \
|
||||||
-w /app \
|
-w /app \
|
||||||
--name "$CONTAINER_NAME" \
|
--name "$CONTAINER_NAME" \
|
||||||
"$IMAGE_NAME" \
|
"$IMAGE_NAME" gemini --prompt-interactive "$FULL_CONTEXT"
|
||||||
gemini --initial-prompt "$CLI_CONTEXT"
|
|
||||||
|
|
||||||
# Nach Beendigung der Session (z.B. durch Strg+C) wird aufgeräumt
|
|
||||||
echo "Session beendet."
|
|
||||||
|
|||||||
34
test_export.py
Normal file
34
test_export.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
|
||||||
|
def test_export_endpoint():
|
||||||
|
# The app runs on port 8000 inside the container.
|
||||||
|
# The root_path is /ce, so the full URL is http://localhost:8000/ce/api/companies/export
|
||||||
|
url = "http://localhost:8000/ce/api/companies/export"
|
||||||
|
|
||||||
|
print(f"--- Testing Export Endpoint: GET {url} ---")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status() # Will raise an exception for 4xx/5xx errors
|
||||||
|
|
||||||
|
# Print the first few hundred characters to verify content
|
||||||
|
print("\n--- Response Headers ---")
|
||||||
|
print(response.headers)
|
||||||
|
|
||||||
|
print("\n--- CSV Output (first 500 chars) ---")
|
||||||
|
print(response.text[:500])
|
||||||
|
|
||||||
|
# A simple check
|
||||||
|
if "Metric Value" in response.text and "Source URL" in response.text:
|
||||||
|
print("\n[SUCCESS] New columns found in export.")
|
||||||
|
else:
|
||||||
|
print("\n[FAILURE] New columns seem to be missing from the export.")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"\n[FAILURE] Could not connect to the endpoint: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_export_endpoint()
|
||||||
|
|
||||||
@@ -121,6 +121,28 @@ def create_insight(meeting_id: int, payload: InsightRequest, db: Session = Depen
|
|||||||
print(f"ERROR: Unexpected error in create_insight: {e}")
|
print(f"ERROR: Unexpected error in create_insight: {e}")
|
||||||
raise HTTPException(status_code=500, detail="An internal error occurred while generating the insight.")
|
raise HTTPException(status_code=500, detail="An internal error occurred while generating the insight.")
|
||||||
|
|
||||||
|
class TranslationRequest(BaseModel):
|
||||||
|
target_language: str
|
||||||
|
|
||||||
|
@app.post("/api/meetings/{meeting_id}/translate")
|
||||||
|
def translate_meeting_transcript(meeting_id: int, payload: TranslationRequest, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Triggers the translation of a meeting's transcript.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# For now, we only support English
|
||||||
|
if payload.target_language.lower() != 'english':
|
||||||
|
raise HTTPException(status_code=400, detail="Currently, only translation to English is supported.")
|
||||||
|
|
||||||
|
from .services.translation_service import translate_transcript
|
||||||
|
translation = translate_transcript(db, meeting_id, payload.target_language)
|
||||||
|
return translation
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Unexpected error in translate_meeting_transcript: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="An internal error occurred during translation.")
|
||||||
|
|
||||||
class RenameRequest(BaseModel):
|
class RenameRequest(BaseModel):
|
||||||
old_name: str
|
old_name: str
|
||||||
new_name: str
|
new_name: str
|
||||||
|
|||||||
133
transcription-tool/backend/lib/gemini_client.py
Normal file
133
transcription-tool/backend/lib/gemini_client.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
# --- KI UMSCHALTUNG: Google Generative AI (Dual Support) ---
|
||||||
|
# This is a simplified, self-contained version for the transcription tool.
|
||||||
|
|
||||||
|
HAS_NEW_GENAI = False
|
||||||
|
HAS_OLD_GENAI = False
|
||||||
|
|
||||||
|
# 1. New library (google-genai)
|
||||||
|
try:
|
||||||
|
from google import genai
|
||||||
|
from google.genai import types
|
||||||
|
HAS_NEW_GENAI = True
|
||||||
|
logging.info("Library 'google.genai' (v1.0+) loaded.")
|
||||||
|
except ImportError:
|
||||||
|
logging.warning("Library 'google.genai' not found. Trying fallback.")
|
||||||
|
|
||||||
|
# 2. Old library (google-generativeai)
|
||||||
|
try:
|
||||||
|
import google.generativeai as old_genai
|
||||||
|
HAS_OLD_GENAI = True
|
||||||
|
logging.info("Library 'google.generativeai' (Legacy) loaded.")
|
||||||
|
except ImportError:
|
||||||
|
logging.warning("Library 'google.generativeai' not found.")
|
||||||
|
|
||||||
|
HAS_GEMINI = HAS_NEW_GENAI or HAS_OLD_GENAI
|
||||||
|
|
||||||
|
# A simple retry decorator, as the global one is not available
|
||||||
|
def retry_on_failure(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
max_retries = 3
|
||||||
|
base_delay = 5
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
if attempt > 0:
|
||||||
|
logging.warning(f"Retrying attempt {attempt + 1}/{max_retries} for '{func.__name__}'...")
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
wait_time = base_delay * (2 ** attempt) + random.uniform(0, 1)
|
||||||
|
time.sleep(wait_time)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def _get_gemini_api_key():
|
||||||
|
"""Gets the Gemini API key from environment variables."""
|
||||||
|
api_key = os.environ.get("GEMINI_API_KEY") or os.environ.get("OPENAI_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
raise ValueError("GEMINI_API_KEY or OPENAI_API_KEY environment variable not set.")
|
||||||
|
return api_key
|
||||||
|
|
||||||
|
@retry_on_failure
|
||||||
|
def call_gemini_flash(prompt: str, system_instruction: str = None, temperature: float = 0.3, json_mode: bool = False):
|
||||||
|
"""
|
||||||
|
Calls the Gemini Flash model to generate text content.
|
||||||
|
This is a focused, local version of the function.
|
||||||
|
"""
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
api_key = _get_gemini_api_key()
|
||||||
|
|
||||||
|
if not HAS_GEMINI:
|
||||||
|
raise ImportError("No Google Generative AI library is available (google-genai or google-generativeai).")
|
||||||
|
|
||||||
|
# The legacy library was noted as preferred in the original helpers.py
|
||||||
|
if HAS_OLD_GENAI:
|
||||||
|
try:
|
||||||
|
old_genai.configure(api_key=api_key)
|
||||||
|
generation_config = {
|
||||||
|
"temperature": temperature,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"top_k": 40,
|
||||||
|
"max_output_tokens": 8192,
|
||||||
|
}
|
||||||
|
if json_mode:
|
||||||
|
generation_config["response_mime_type"] = "application/json"
|
||||||
|
|
||||||
|
model = old_genai.GenerativeModel(
|
||||||
|
model_name="gemini-1.5-flash", # Using 1.5 as it's the modern standard
|
||||||
|
generation_config=generation_config,
|
||||||
|
system_instruction=system_instruction
|
||||||
|
)
|
||||||
|
response = model.generate_content([prompt])
|
||||||
|
return response.text.strip()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error with legacy GenAI Lib: {e}")
|
||||||
|
if not HAS_NEW_GENAI: raise e
|
||||||
|
# Fallthrough to new lib if legacy fails
|
||||||
|
|
||||||
|
# Fallback to the new library
|
||||||
|
if HAS_NEW_GENAI:
|
||||||
|
try:
|
||||||
|
# CORRECT: Use the Client-based API for the new library
|
||||||
|
client = genai.Client(api_key=api_key)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"temperature": temperature,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"top_k": 40,
|
||||||
|
"max_output_tokens": 8192,
|
||||||
|
}
|
||||||
|
if json_mode:
|
||||||
|
config["response_mime_type"] = "application/json"
|
||||||
|
|
||||||
|
# Construct the contents list, including the system instruction if provided
|
||||||
|
contents = []
|
||||||
|
if system_instruction:
|
||||||
|
# Note: The new API doesn't have a direct 'system_instruction' parameter
|
||||||
|
# in generate_content. It's typically passed as the first message.
|
||||||
|
# This is an adaptation. For a more robust solution, one would
|
||||||
|
# structure prompts with roles.
|
||||||
|
contents.append({'role': 'system', 'parts': [{'text': system_instruction}]})
|
||||||
|
contents.append({'role': 'user', 'parts': [{'text': prompt}]})
|
||||||
|
|
||||||
|
# Use the client to generate content
|
||||||
|
response = client.models.generate_content(
|
||||||
|
model="models/gemini-2.0-flash-001", # CORRECTED: Using the project's standard model
|
||||||
|
contents=contents,
|
||||||
|
config=config
|
||||||
|
)
|
||||||
|
return response.text.strip()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error with new GenAI Lib: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
raise RuntimeError("Both Gemini libraries failed or are unavailable.")
|
||||||
@@ -111,3 +111,38 @@ Please provide the output in Markdown format.
|
|||||||
|
|
||||||
# You can add more prompts here for other analysis types.
|
# You can add more prompts here for other analysis types.
|
||||||
# For example, a prompt for a technical summary, a marketing summary, etc.
|
# For example, a prompt for a technical summary, a marketing summary, etc.
|
||||||
|
|
||||||
|
TRANSLATE_TRANSCRIPT_PROMPT = """
|
||||||
|
You are a highly accurate and fluent translator.
|
||||||
|
Your task is to translate the given meeting transcript into {target_language}.
|
||||||
|
Maintain the original format (who said what) as closely as possible.
|
||||||
|
|
||||||
|
**Transcript:**
|
||||||
|
---
|
||||||
|
{transcript}
|
||||||
|
---
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
Provide only the translated text. Do not add any commentary or additional formatting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_PROMPTS = {
|
||||||
|
"meeting_minutes": MEETING_MINUTES_PROMPT,
|
||||||
|
"action_items": ACTION_ITEMS_PROMPT,
|
||||||
|
"sales_summary": SALES_SUMMARY_PROMPT,
|
||||||
|
"translate_transcript": TRANSLATE_TRANSCRIPT_PROMPT,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_prompt(prompt_type: str, context: dict = None) -> str:
|
||||||
|
"""
|
||||||
|
Retrieves a prompt by its type and formats it with the given context.
|
||||||
|
"""
|
||||||
|
prompt_template = _PROMPTS.get(prompt_type)
|
||||||
|
if not prompt_template:
|
||||||
|
raise ValueError(f"Unknown prompt type: {prompt_type}")
|
||||||
|
|
||||||
|
if context:
|
||||||
|
return prompt_template.format(**context)
|
||||||
|
|
||||||
|
return prompt_template
|
||||||
|
|
||||||
|
|||||||
@@ -2,53 +2,58 @@ import sys
|
|||||||
import os
|
import os
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from .. import database
|
from .. import database
|
||||||
from .. import prompt_library
|
from ..prompt_library import get_prompt
|
||||||
|
import logging
|
||||||
|
from .llm_service import call_gemini_api
|
||||||
|
|
||||||
# Add project root to path to allow importing from 'helpers'
|
logging.basicConfig(level=logging.INFO)
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
|
logger = logging.getLogger(__name__)
|
||||||
from helpers import call_gemini_flash
|
|
||||||
|
|
||||||
def _format_transcript(chunks: list[database.TranscriptChunk]) -> str:
|
def _format_transcript(chunks: list[database.TranscriptChunk]) -> str:
|
||||||
"""
|
"""
|
||||||
Formats the transcript chunks into a single, human-readable string.
|
Formats the transcript chunks into a single, human-readable string,
|
||||||
Example: "[00:00:01] Speaker A: Hello world."
|
sorted chronologically using the absolute_seconds timestamp.
|
||||||
"""
|
"""
|
||||||
full_transcript = []
|
all_messages = []
|
||||||
# Sort chunks by their index to ensure correct order
|
|
||||||
sorted_chunks = sorted(chunks, key=lambda c: c.chunk_index)
|
for chunk in chunks:
|
||||||
|
|
||||||
for chunk in sorted_chunks:
|
|
||||||
if not chunk.json_content:
|
if not chunk.json_content:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for item in chunk.json_content:
|
# The content can be a list of dicts, or sometimes a list containing a list of dicts
|
||||||
# json_content can be a list of dicts
|
content_list = chunk.json_content
|
||||||
|
if content_list and isinstance(content_list[0], list):
|
||||||
|
content_list = content_list[0]
|
||||||
|
|
||||||
|
for item in content_list:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
speaker = item.get('speaker', 'Unknown')
|
all_messages.append(item)
|
||||||
start_time = item.get('start', 0)
|
|
||||||
text = item.get('line', '')
|
|
||||||
|
|
||||||
# Format timestamp from seconds to HH:MM:SS
|
# Sort all messages from all chunks chronologically
|
||||||
hours, remainder = divmod(int(start_time), 3600)
|
# Use a default of 0 for absolute_seconds if the key is missing
|
||||||
minutes, seconds = divmod(remainder, 60)
|
sorted_messages = sorted(all_messages, key=lambda msg: msg.get('absolute_seconds', 0))
|
||||||
timestamp = f"{hours:02}:{minutes:02}:{seconds:02}"
|
|
||||||
|
|
||||||
full_transcript.append(f"[{timestamp}] {speaker}: {text}")
|
full_transcript = []
|
||||||
|
for msg in sorted_messages:
|
||||||
|
speaker = msg.get('speaker', 'Unknown')
|
||||||
|
text = msg.get('text', '') # Changed from 'line' to 'text' to match the JSON
|
||||||
|
|
||||||
|
# Use the reliable absolute_seconds for timestamp calculation
|
||||||
|
absolute_seconds = msg.get('absolute_seconds', 0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
time_in_seconds = float(absolute_seconds)
|
||||||
|
hours, remainder = divmod(int(time_in_seconds), 3600)
|
||||||
|
minutes, seconds = divmod(remainder, 60)
|
||||||
|
timestamp = f"{hours:02}:{minutes:02}:{seconds:02}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
timestamp = "00:00:00"
|
||||||
|
|
||||||
|
full_transcript.append(f"[{timestamp}] {speaker}: {text}")
|
||||||
|
|
||||||
return "\n".join(full_transcript)
|
return "\n".join(full_transcript)
|
||||||
|
|
||||||
def get_prompt_by_type(insight_type: str) -> str:
|
|
||||||
"""
|
|
||||||
Returns the corresponding prompt from the prompt_library based on the type.
|
|
||||||
"""
|
|
||||||
if insight_type == "meeting_minutes":
|
|
||||||
return prompt_library.MEETING_MINUTES_PROMPT
|
|
||||||
elif insight_type == "action_items":
|
|
||||||
return prompt_library.ACTION_ITEMS_PROMPT
|
|
||||||
elif insight_type == "sales_summary":
|
|
||||||
return prompt_library.SALES_SUMMARY_PROMPT
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unknown insight type: {insight_type}")
|
|
||||||
|
|
||||||
def generate_insight(db: Session, meeting_id: int, insight_type: str) -> database.AnalysisResult:
|
def generate_insight(db: Session, meeting_id: int, insight_type: str) -> database.AnalysisResult:
|
||||||
"""
|
"""
|
||||||
@@ -62,7 +67,10 @@ def generate_insight(db: Session, meeting_id: int, insight_type: str) -> databas
|
|||||||
).first()
|
).first()
|
||||||
|
|
||||||
if existing_insight:
|
if existing_insight:
|
||||||
return existing_insight
|
# Before returning, let's delete it so user can regenerate
|
||||||
|
db.delete(existing_insight)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
# 2. Get the meeting and its transcript
|
# 2. Get the meeting and its transcript
|
||||||
meeting = db.query(database.Meeting).filter(database.Meeting.id == meeting_id).first()
|
meeting = db.query(database.Meeting).filter(database.Meeting.id == meeting_id).first()
|
||||||
@@ -74,14 +82,15 @@ def generate_insight(db: Session, meeting_id: int, insight_type: str) -> databas
|
|||||||
|
|
||||||
# 3. Format the transcript and select the prompt
|
# 3. Format the transcript and select the prompt
|
||||||
transcript_text = _format_transcript(meeting.chunks)
|
transcript_text = _format_transcript(meeting.chunks)
|
||||||
|
|
||||||
if not transcript_text.strip():
|
if not transcript_text.strip():
|
||||||
raise ValueError(f"Transcript for meeting {meeting_id} is empty.")
|
# This can happen if all chunks are empty or malformed
|
||||||
|
raise ValueError(f"Formatted transcript for meeting {meeting_id} is empty or could not be processed.")
|
||||||
|
|
||||||
prompt_template = get_prompt_by_type(insight_type)
|
prompt_template = get_prompt(insight_type)
|
||||||
final_prompt = prompt_template.format(transcript_text=transcript_text)
|
final_prompt = prompt_template.format(transcript_text=transcript_text)
|
||||||
|
|
||||||
# 4. Call the AI model
|
# 4. Call the AI model
|
||||||
# Update meeting status
|
|
||||||
meeting.status = "ANALYZING"
|
meeting.status = "ANALYZING"
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@@ -105,6 +114,5 @@ def generate_insight(db: Session, meeting_id: int, insight_type: str) -> databas
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
meeting.status = "ERROR"
|
meeting.status = "ERROR"
|
||||||
db.commit()
|
db.commit()
|
||||||
# Log the error properly in a real application
|
logger.error(f"Error generating insight for meeting {meeting_id}: {e}")
|
||||||
print(f"Error generating insight for meeting {meeting_id}: {e}")
|
|
||||||
raise
|
raise
|
||||||
|
|||||||
91
transcription-tool/backend/services/llm_service.py
Normal file
91
transcription-tool/backend/services/llm_service.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
def call_gemini_api(prompt: str, retries: int = 3, timeout: int = 600) -> str:
|
||||||
|
"""
|
||||||
|
Calls the Gemini Pro API with a given prompt.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: The text prompt to send to the API.
|
||||||
|
retries: The number of times to retry on failure.
|
||||||
|
timeout: The request timeout in seconds.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The text response from the API or an empty string if the response is malformed.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: If the API call fails after all retries.
|
||||||
|
"""
|
||||||
|
api_key = os.getenv("GEMINI_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
logging.error("GEMINI_API_KEY environment variable not set.")
|
||||||
|
raise ValueError("API key not found.")
|
||||||
|
|
||||||
|
url = f"https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent?key={api_key}"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {
|
||||||
|
"contents": [{
|
||||||
|
"parts": [{"text": prompt}]
|
||||||
|
}],
|
||||||
|
"generationConfig": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"topK": 40,
|
||||||
|
"topP": 0.95,
|
||||||
|
"maxOutputTokens": 8192,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt in range(retries):
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if 'candidates' in result and result['candidates']:
|
||||||
|
candidate = result['candidates'][0]
|
||||||
|
if 'content' in candidate and 'parts' in candidate['content']:
|
||||||
|
# Check for safety ratings
|
||||||
|
if 'safetyRatings' in candidate:
|
||||||
|
blocked = any(r.get('blocked') for r in candidate['safetyRatings'])
|
||||||
|
if blocked:
|
||||||
|
logging.error(f"API call blocked due to safety ratings: {candidate['safetyRatings']}")
|
||||||
|
# Provide a more specific error or return a specific string
|
||||||
|
return "[Blocked by Safety Filter]"
|
||||||
|
return candidate['content']['parts'][0]['text']
|
||||||
|
|
||||||
|
# Handle cases where the response is valid but doesn't contain expected content
|
||||||
|
if 'promptFeedback' in result and result['promptFeedback'].get('blockReason'):
|
||||||
|
reason = result['promptFeedback']['blockReason']
|
||||||
|
logging.error(f"Prompt was blocked by the API. Reason: {reason}")
|
||||||
|
return f"[Prompt Blocked: {reason}]"
|
||||||
|
|
||||||
|
logging.warning(f"Unexpected API response structure on attempt {attempt+1}: {result}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code in [500, 502, 503, 504] and attempt < retries - 1:
|
||||||
|
wait_time = (2 ** attempt) * 2 # Exponential backoff
|
||||||
|
logging.warning(f"Server Error {e.response.status_code}. Retrying in {wait_time}s...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
continue
|
||||||
|
logging.error(f"HTTP Error calling Gemini API: {e.response.status_code} {e.response.text}")
|
||||||
|
raise
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
if attempt < retries - 1:
|
||||||
|
wait_time = (2 ** attempt) * 2
|
||||||
|
logging.warning(f"Connection Error: {e}. Retrying in {wait_time}s...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
continue
|
||||||
|
logging.error(f"Final Connection Error calling Gemini API: {e}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"An unexpected error occurred: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return "" # Should not be reached if retries are exhausted
|
||||||
64
transcription-tool/backend/services/translation_service.py
Normal file
64
transcription-tool/backend/services/translation_service.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from ..database import Meeting, AnalysisResult
|
||||||
|
from .llm_service import call_gemini_api
|
||||||
|
from ..prompt_library import get_prompt
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
def _format_transcript_for_translation(chunks: List[Any]) -> str:
|
||||||
|
"""Formats the transcript into a single string for the translation prompt."""
|
||||||
|
full_transcript = []
|
||||||
|
# Ensure chunks are treated correctly, whether they are dicts or objects
|
||||||
|
for chunk in chunks:
|
||||||
|
json_content = getattr(chunk, 'json_content', None)
|
||||||
|
if not json_content:
|
||||||
|
continue
|
||||||
|
for line in json_content:
|
||||||
|
speaker = line.get("speaker", "Unknown")
|
||||||
|
text = line.get("text", "")
|
||||||
|
full_transcript.append(f"{speaker}: {text}")
|
||||||
|
return "\n".join(full_transcript)
|
||||||
|
|
||||||
|
def translate_transcript(db: Session, meeting_id: int, target_language: str) -> AnalysisResult:
|
||||||
|
"""
|
||||||
|
Translates the transcript of a meeting and stores it.
|
||||||
|
"""
|
||||||
|
meeting = db.query(Meeting).filter(Meeting.id == meeting_id).first()
|
||||||
|
if not meeting:
|
||||||
|
raise ValueError("Meeting not found")
|
||||||
|
|
||||||
|
prompt_key = f"translation_{target_language.lower()}"
|
||||||
|
|
||||||
|
# Check if translation already exists
|
||||||
|
existing_translation = db.query(AnalysisResult).filter(
|
||||||
|
AnalysisResult.meeting_id == meeting_id,
|
||||||
|
AnalysisResult.prompt_key == prompt_key
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_translation:
|
||||||
|
return existing_translation
|
||||||
|
|
||||||
|
# Prepare transcript
|
||||||
|
transcript_text = _format_transcript_for_translation(meeting.chunks)
|
||||||
|
if not transcript_text:
|
||||||
|
raise ValueError("Transcript is empty, cannot translate.")
|
||||||
|
|
||||||
|
# Get prompt from library
|
||||||
|
prompt = get_prompt('translate_transcript', {
|
||||||
|
'transcript': transcript_text,
|
||||||
|
'target_language': target_language
|
||||||
|
})
|
||||||
|
|
||||||
|
# Call Gemini API using the new service function
|
||||||
|
translated_text = call_gemini_api(prompt)
|
||||||
|
|
||||||
|
# Store result
|
||||||
|
translation_result = AnalysisResult(
|
||||||
|
meeting_id=meeting_id,
|
||||||
|
prompt_key=prompt_key,
|
||||||
|
result_text=translated_text
|
||||||
|
)
|
||||||
|
db.add(translation_result)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(translation_result)
|
||||||
|
|
||||||
|
return translation_result
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Upload, FileText, Clock, CheckCircle2, Loader2, AlertCircle, Trash2, ArrowLeft, Copy, Edit3, X, Scissors, Users, Wand2 } from 'lucide-react'
|
import { Upload, FileText, Clock, CheckCircle2, Loader2, AlertCircle, Trash2, ArrowLeft, Copy, Edit3, X, Scissors, Users, Wand2, Share2 } from 'lucide-react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
const API_BASE = '/tr/api'
|
const API_BASE = '/tr/api'
|
||||||
@@ -84,6 +84,10 @@ export default function App() {
|
|||||||
const [insightResult, setInsightResult] = useState('')
|
const [insightResult, setInsightResult] = useState('')
|
||||||
const [insightTitle, setInsightTitle] = useState('')
|
const [insightTitle, setInsightTitle] = useState('')
|
||||||
|
|
||||||
|
// Translation State
|
||||||
|
const [translationResult, setTranslationResult] = useState<AnalysisResult | null>(null)
|
||||||
|
const [activeTab, setActiveTab] = useState<'transcript' | 'translation'>('transcript');
|
||||||
|
|
||||||
const fetchMeetings = async () => {
|
const fetchMeetings = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`${API_BASE}/meetings`)
|
const res = await axios.get(`${API_BASE}/meetings`)
|
||||||
@@ -170,6 +174,43 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTranslate = async () => {
|
||||||
|
if (!detailMeeting) return;
|
||||||
|
setInsightLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use a consistent key for the translation type
|
||||||
|
const translationType = 'translation_english';
|
||||||
|
|
||||||
|
// Check if result already exists in the main meeting object
|
||||||
|
const existing = detailMeeting.analysis_results?.find(r => r.prompt_key === translationType);
|
||||||
|
if (existing) {
|
||||||
|
setTranslationResult(existing);
|
||||||
|
// Here you might want to switch to a "Translation" tab or show a modal
|
||||||
|
alert("Translation already exists and has been loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, generate it
|
||||||
|
const res = await axios.post(`${API_BASE}/meetings/${detailMeeting.id}/translate`, {
|
||||||
|
target_language: 'English'
|
||||||
|
});
|
||||||
|
|
||||||
|
// The API returns an AnalysisResult object, let's store it
|
||||||
|
setTranslationResult(res.data);
|
||||||
|
|
||||||
|
// Also refresh the main meeting detail to include this new result for persistence
|
||||||
|
fetchDetail(detailMeeting.id);
|
||||||
|
setActiveTab('translation');
|
||||||
|
alert("Translation successful!");
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
console.error("Failed to generate translation", e);
|
||||||
|
alert(`Error: Could not translate.`);
|
||||||
|
} finally {
|
||||||
|
setInsightLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- GLOBAL SPEAKER MANAGEMENT ---
|
// --- GLOBAL SPEAKER MANAGEMENT ---
|
||||||
|
|
||||||
@@ -332,6 +373,27 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<Copy className="h-5 w-5" />
|
<Copy className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const transcriptText = flatMessages.map(m => `[${m.display_time}] ${m.speaker}: ${m.text}`).join('\n');
|
||||||
|
const filename = `${detailMeeting.title}.txt`;
|
||||||
|
const blob = new Blob([transcriptText], { type: 'text/plain;charset=utf-8' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(link.href);
|
||||||
|
}}
|
||||||
|
className="text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800 p-2 rounded" title="Download Full Transcript as TXT"
|
||||||
|
>
|
||||||
|
<FileText className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => alert("Share functionality coming soon!")} // Placeholder
|
||||||
|
className="text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800 p-2 rounded" title="Share Transcript"
|
||||||
|
>
|
||||||
|
<Share2 className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
<button onClick={(e) => handleDeleteMeeting(e, detailMeeting.id)} className="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 p-2 rounded"><Trash2 className="h-5 w-5" /></button>
|
<button onClick={(e) => handleDeleteMeeting(e, detailMeeting.id)} className="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 p-2 rounded"><Trash2 className="h-5 w-5" /></button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -357,17 +419,17 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* AI INSIGHTS PANEL */}
|
|
||||||
<div className="mb-6 bg-purple-50 dark:bg-purple-900/20 p-4 rounded-xl border border-purple-100 dark:border-purple-800">
|
<div className="mb-6 bg-purple-50 dark:bg-purple-900/20 p-4 rounded-xl border border-purple-100 dark:border-purple-800">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<Wand2 className="h-5 w-5 text-purple-600 dark:text-purple-300" />
|
<Wand2 className="h-5 w-5 text-purple-600 dark:text-purple-300" />
|
||||||
<h3 className="font-bold text-purple-800 dark:text-purple-200">AI Insights</h3>
|
<h3 className="font-bold text-purple-800 dark:text-purple-200">AI Tools</h3>
|
||||||
{insightLoading && <Loader2 className="h-4 w-4 animate-spin text-purple-500" />}
|
{insightLoading && <Loader2 className="h-4 w-4 animate-spin text-purple-500" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<button disabled={insightLoading} onClick={() => handleGenerateInsight('meeting_minutes', 'Meeting Protocol')} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Generate Protocol</button>
|
<button disabled={insightLoading} onClick={() => handleGenerateInsight('meeting_minutes', 'Meeting Protocol')} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Generate Protocol</button>
|
||||||
<button disabled={insightLoading} onClick={() => handleGenerateInsight('action_items', 'Action Items')} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Extract Action Items</button>
|
<button disabled={insightLoading} onClick={() => handleGenerateInsight('action_items', 'Action Items')} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Extract Action Items</button>
|
||||||
<button disabled={insightLoading} onClick={() => handleGenerateInsight('sales_summary', 'Sales Summary')} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Sales Summary</button>
|
<button disabled={insightLoading} onClick={() => handleGenerateInsight('sales_summary', 'Sales Summary')} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Sales Summary</button>
|
||||||
|
<button disabled={insightLoading} onClick={() => handleTranslate()} className="text-sm font-medium px-4 py-2 bg-white dark:bg-slate-800 border dark:border-slate-700 rounded-lg shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 disabled:opacity-50 transition-colors">Translate (EN)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -381,116 +443,143 @@ export default function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm overflow-hidden">
|
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm overflow-hidden">
|
||||||
{flatMessages.length > 0 ? (
|
{/* TAB Navigation */}
|
||||||
<div className="divide-y divide-slate-100 dark:divide-slate-800">
|
<div className="flex border-b border-slate-200 dark:border-slate-800">
|
||||||
{flatMessages.map((msg, uniqueIdx) => {
|
<button
|
||||||
const isEditingSpeaker = editingRow?.chunkId === msg._chunkId && editingRow?.idx === msg._idx && editingRow?.field === 'speaker';
|
onClick={() => setActiveTab('transcript')}
|
||||||
const isEditingText = editingRow?.chunkId === msg._chunkId && editingRow?.idx === msg._idx && editingRow?.field === 'text';
|
className={clsx("px-4 py-3 font-medium text-sm", activeTab === 'transcript' ? "text-blue-600 border-b-2 border-blue-600" : "text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800/50")}
|
||||||
|
>
|
||||||
|
Original Transcript
|
||||||
|
</button>
|
||||||
|
{translationResult && (
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('translation')}
|
||||||
|
className={clsx("px-4 py-3 font-medium text-sm", activeTab === 'translation' ? "text-blue-600 border-b-2 border-blue-600" : "text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800/50")}
|
||||||
|
>
|
||||||
|
English Translation
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
{/* Conditional Content */}
|
||||||
<div key={uniqueIdx} className="p-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors flex gap-4 group relative">
|
{activeTab === 'transcript' && (
|
||||||
{/* Time */}
|
flatMessages.length > 0 ? (
|
||||||
<div className="w-16 pt-1 flex flex-col items-end gap-1 flex-shrink-0">
|
<div className="divide-y divide-slate-100 dark:divide-slate-800">
|
||||||
<span className="text-xs text-slate-400 font-mono">{msg.display_time || "00:00"}</span>
|
{flatMessages.map((msg, uniqueIdx) => {
|
||||||
</div>
|
const isEditingSpeaker = editingRow?.chunkId === msg._chunkId && editingRow?.idx === msg._idx && editingRow?.field === 'speaker';
|
||||||
|
const isEditingText = editingRow?.chunkId === msg._chunkId && editingRow?.idx === msg._idx && editingRow?.field === 'text';
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
return (
|
||||||
{/* Speaker */}
|
<div key={uniqueIdx} className="p-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors flex gap-4 group relative">
|
||||||
<div className="mb-1 flex items-center gap-2">
|
{/* Time */}
|
||||||
{isEditingSpeaker ? (
|
<div className="w-16 pt-1 flex flex-col items-end gap-1 flex-shrink-0">
|
||||||
<input
|
<span className="text-xs text-slate-400 font-mono">{msg.display_time || "00:00"}</span>
|
||||||
autoFocus
|
</div>
|
||||||
className="text-sm font-bold text-blue-600 dark:text-blue-400 bg-slate-100 dark:bg-slate-800 border rounded px-2 py-0.5 outline-none w-48"
|
|
||||||
value={editValue}
|
|
||||||
onChange={e => setEditValue(e.target.value)}
|
|
||||||
onBlur={() => handleUpdateRow(msg._chunkId!, msg._idx!, 'speaker', editValue)}
|
|
||||||
onKeyDown={e => e.key === 'Enter' && handleUpdateRow(msg._chunkId!, msg._idx!, 'speaker', editValue)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className="font-bold text-sm text-blue-600 dark:text-blue-400 cursor-pointer hover:underline flex items-center gap-1 w-fit"
|
|
||||||
onClick={() => { setEditingRow({chunkId: msg._chunkId!, idx: msg._idx!, field: 'speaker'}); setEditValue(msg.speaker); }}
|
|
||||||
>
|
|
||||||
{msg.speaker}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Text */}
|
<div className="flex-1 min-w-0">
|
||||||
<div>
|
{/* Speaker */}
|
||||||
{isEditingText ? (
|
<div className="mb-1 flex items-center gap-2">
|
||||||
<textarea
|
{isEditingSpeaker ? (
|
||||||
autoFocus
|
<input
|
||||||
className="w-full text-slate-800 dark:text-slate-200 bg-slate-100 dark:bg-slate-800 border rounded px-2 py-1 outline-none min-h-[60px]"
|
autoFocus
|
||||||
value={editValue}
|
className="text-sm font-bold text-blue-600 dark:text-blue-400 bg-slate-100 dark:bg-slate-800 border rounded px-2 py-0.5 outline-none w-48"
|
||||||
onChange={e => setEditValue(e.target.value)}
|
value={editValue}
|
||||||
onBlur={() => handleUpdateRow(msg._chunkId!, msg._idx!, 'text', editValue)}
|
onChange={e => setEditValue(e.target.value)}
|
||||||
/>
|
onBlur={() => handleUpdateRow(msg._chunkId!, msg._idx!, 'speaker', editValue)}
|
||||||
) : (
|
onKeyDown={e => e.key === 'Enter' && handleUpdateRow(msg._chunkId!, msg._idx!, 'speaker', editValue)}
|
||||||
<div
|
/>
|
||||||
className="text-slate-800 dark:text-slate-200 leading-relaxed whitespace-pre-wrap cursor-text hover:bg-slate-100/50 dark:hover:bg-slate-800/30 rounded p-1 -ml-1 transition-colors"
|
) : (
|
||||||
onClick={() => { setEditingRow({chunkId: msg._chunkId!, idx: msg._idx!, field: 'text'}); setEditValue(msg.text); }}
|
<div
|
||||||
>
|
className="font-bold text-sm text-blue-600 dark:text-blue-400 cursor-pointer hover:underline flex items-center gap-1 w-fit"
|
||||||
{msg.text}
|
onClick={() => { setEditingRow({chunkId: msg._chunkId!, idx: msg._idx!, field: 'speaker'}); setEditValue(msg.speaker); }}
|
||||||
</div>
|
>
|
||||||
)}
|
{msg.speaker}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Hover Actions (Trim / Delete) */}
|
{/* Text */}
|
||||||
<div className="absolute right-2 top-2 opacity-0 group-hover:opacity-100 flex gap-1 bg-white dark:bg-slate-900 border dark:border-slate-700 shadow-sm rounded-lg p-1 transition-all">
|
<div>
|
||||||
<button
|
{isEditingText ? (
|
||||||
onClick={() => handleTrim(msg, 'start')}
|
<textarea
|
||||||
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded"
|
autoFocus
|
||||||
title="Trim Start: Delete everything BEFORE this line"
|
className="w-full text-slate-800 dark:text-slate-200 bg-slate-100 dark:bg-slate-800 border rounded px-2 py-1 outline-none min-h-[60px]"
|
||||||
>
|
value={editValue}
|
||||||
<Scissors className="h-3.5 w-3.5 rotate-180" />
|
onChange={e => setEditValue(e.target.value)}
|
||||||
</button>
|
onBlur={() => handleUpdateRow(msg._chunkId!, msg._idx!, 'text', editValue)}
|
||||||
<button
|
/>
|
||||||
onClick={() => handleTrim(msg, 'end')}
|
) : (
|
||||||
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded"
|
<div
|
||||||
title="Trim End: Delete everything AFTER this line"
|
className="text-slate-800 dark:text-slate-200 leading-relaxed whitespace-pre-wrap cursor-text hover:bg-slate-100/50 dark:hover:bg-slate-800/30 rounded p-1 -ml-1 transition-colors"
|
||||||
>
|
onClick={() => { setEditingRow({chunkId: msg._chunkId!, idx: msg._idx!, field: 'text'}); setEditValue(msg.text); }}
|
||||||
<Scissors className="h-3.5 w-3.5" />
|
>
|
||||||
</button>
|
{msg.text}
|
||||||
<div className="w-px bg-slate-200 dark:bg-slate-700 mx-1"></div>
|
</div>
|
||||||
<button
|
)}
|
||||||
onClick={() => {
|
</div>
|
||||||
// Single delete logic is redundant if we have trims, but nice to keep
|
</div>
|
||||||
// Reuse update logic but filter out index
|
|
||||||
// Simplified: Just use handleTrim but that's overkill
|
{/* Hover Actions (Trim / Delete) */}
|
||||||
// Let's implement quick single delete
|
<div className="absolute right-2 top-2 opacity-0 group-hover:opacity-100 flex gap-1 bg-white dark:bg-slate-900 border dark:border-slate-700 shadow-sm rounded-lg p-1 transition-all">
|
||||||
const cId = msg._chunkId!;
|
<button
|
||||||
const idx = msg._idx!;
|
onClick={() => handleTrim(msg, 'start')}
|
||||||
const newChunks = detailMeeting?.chunks?.map(c => {
|
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded"
|
||||||
if (c.id === cId && c.json_content) {
|
title="Trim Start: Delete everything BEFORE this line"
|
||||||
return { ...c, json_content: c.json_content.filter((_, i) => i !== idx) }
|
>
|
||||||
}
|
<Scissors className="h-3.5 w-3.5 rotate-180" />
|
||||||
return c
|
</button>
|
||||||
}) || [];
|
<button
|
||||||
setDetailMeeting(prev => prev ? ({ ...prev, chunks: newChunks }) : null);
|
onClick={() => handleTrim(msg, 'end')}
|
||||||
const updatedC = newChunks.find(c => c.id === cId);
|
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded"
|
||||||
if(updatedC?.json_content) saveChunkUpdate(cId, updatedC.json_content);
|
title="Trim End: Delete everything AFTER this line"
|
||||||
}}
|
>
|
||||||
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded"
|
<Scissors className="h-3.5 w-3.5" />
|
||||||
title="Delete this line"
|
</button>
|
||||||
>
|
<div className="w-px bg-slate-200 dark:bg-slate-700 mx-1"></div>
|
||||||
<X className="h-3.5 w-3.5" />
|
<button
|
||||||
</button>
|
onClick={() => {
|
||||||
</div>
|
// Single delete logic is redundant if we have trims, but nice to keep
|
||||||
|
// Reuse update logic but filter out index
|
||||||
|
// Simplified: Just use handleTrim but that's overkill
|
||||||
|
// Let's implement quick single delete
|
||||||
|
const cId = msg._chunkId!;
|
||||||
|
const idx = msg._idx!;
|
||||||
|
const newChunks = detailMeeting?.chunks?.map(c => {
|
||||||
|
if (c.id === cId && c.json_content) {
|
||||||
|
return { ...c, json_content: c.json_content.filter((_, i) => i !== idx) }
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}) || [];
|
||||||
|
setDetailMeeting(prev => prev ? ({ ...prev, chunks: newChunks }) : null);
|
||||||
|
const updatedC = newChunks.find(c => c.id === cId);
|
||||||
|
if(updatedC?.json_content) saveChunkUpdate(cId, updatedC.json_content);
|
||||||
|
}}
|
||||||
|
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded"
|
||||||
|
title="Delete this line"
|
||||||
|
>
|
||||||
|
<X className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)})}
|
||||||
</div>
|
</div>
|
||||||
)})}
|
) : (
|
||||||
</div>
|
<div className="text-center py-12">
|
||||||
) : (
|
{detailMeeting.chunks && detailMeeting.chunks.length > 0 && detailMeeting.chunks[0].raw_text ? (
|
||||||
<div className="text-center py-12">
|
<div className="p-8">
|
||||||
{detailMeeting.chunks && detailMeeting.chunks.length > 0 && detailMeeting.chunks[0].raw_text ? (
|
<p className="text-yellow-600 mb-2 font-medium">Legacy Format</p>
|
||||||
<div className="p-8">
|
<p className="text-slate-500 text-sm mb-4">Re-upload file to enable editing.</p>
|
||||||
<p className="text-yellow-600 mb-2 font-medium">Legacy Format</p>
|
<div className="text-left font-mono text-xs overflow-auto max-h-96">{detailMeeting.chunks[0].raw_text}</div>
|
||||||
<p className="text-slate-500 text-sm mb-4">Re-upload file to enable editing.</p>
|
</div>
|
||||||
<div className="text-left font-mono text-xs overflow-auto max-h-96">{detailMeeting.chunks[0].raw_text}</div>
|
) : (<p className="text-slate-500">Processing...</p>)}
|
||||||
</div>
|
</div>
|
||||||
) : (<p className="text-slate-500">Processing...</p>)}
|
)
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'translation' && (
|
||||||
|
<div className="p-6">
|
||||||
|
<pre className="whitespace-pre-wrap font-sans text-sm leading-relaxed">{translationResult?.result_text || "Loading translation..."}</pre>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
92
update_notion_task.py
Normal file
92
update_notion_task.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Use the same token file as the other scripts
|
||||||
|
TOKEN_FILE = 'notion_token.txt'
|
||||||
|
|
||||||
|
def get_notion_token():
|
||||||
|
"""Reads the Notion API token from the specified file."""
|
||||||
|
try:
|
||||||
|
with open(TOKEN_FILE, 'r') as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Token file not found at '{TOKEN_FILE}'")
|
||||||
|
print("Please create this file and place your Notion Integration Token inside.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def parse_markdown_to_blocks(md_content):
|
||||||
|
"""
|
||||||
|
Parses a simple markdown string into Notion API block objects.
|
||||||
|
This is a simplified parser for this specific task.
|
||||||
|
"""
|
||||||
|
blocks = []
|
||||||
|
lines = md_content.split('\n')
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.strip()
|
||||||
|
|
||||||
|
if line.startswith("# "):
|
||||||
|
blocks.append({ "object": "block", "type": "heading_1", "heading_1": {"rich_text": [{"type": "text", "text": {"content": line[2:]}}]}})
|
||||||
|
elif line.startswith("## "):
|
||||||
|
blocks.append({ "object": "block", "type": "heading_2", "heading_2": {"rich_text": [{"type": "text", "text": {"content": line[3:]}}]}})
|
||||||
|
elif stripped.startswith("* ") or stripped.startswith("- "):
|
||||||
|
blocks.append({ "object": "block", "type": "bulleted_list_item", "bulleted_list_item": {"rich_text": [{"type": "text", "text": {"content": stripped[2:]}}]}})
|
||||||
|
elif stripped: # Any non-empty line becomes a paragraph
|
||||||
|
blocks.append({ "object": "block", "type": "paragraph", "paragraph": {"rich_text": [{"type": "text", "text": {"content": line}}]}})
|
||||||
|
|
||||||
|
# Add a divider for visual separation
|
||||||
|
blocks.insert(0, {"type": "divider", "divider": {}})
|
||||||
|
blocks.insert(0, {
|
||||||
|
"object": "block", "type": "heading_2", "heading_2": {
|
||||||
|
"rich_text": [{"type": "text", "text": {"content": "Gemini Task-Update:"}}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
|
||||||
|
def append_blocks_to_page(token, page_id, blocks):
|
||||||
|
"""
|
||||||
|
Appends a list of block objects to a Notion page.
|
||||||
|
"""
|
||||||
|
# In Notion, the page ID is the block ID for appending content
|
||||||
|
url = f"https://api.notion.com/v1/blocks/{page_id}/children"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Notion-Version": "2022-06-28",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
payload = {"children": blocks}
|
||||||
|
|
||||||
|
print(f"Appending {len(blocks)} blocks to Notion Page ID: {page_id}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.patch(url, headers=headers, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
print("SUCCESS: Content appended to Notion task.")
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
print(f"ERROR: Failed to update Notion page. Response: {e.response.text}")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: An unexpected error occurred: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("Usage: python update_notion_task.py <page_id> \"<content_string>\"")
|
||||||
|
print("Example: python update_notion_task.py 12345-abc... \"- Task 1\n- Task 2\"")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
page_id = sys.argv[1]
|
||||||
|
content_to_append = sys.argv[2]
|
||||||
|
|
||||||
|
# Basic validation for page_id
|
||||||
|
if not isinstance(page_id, str) or len(page_id) < 32:
|
||||||
|
print(f"Error: Invalid Page ID provided: '{page_id}'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
notion_token = get_notion_token()
|
||||||
|
content_blocks = parse_markdown_to_blocks(content_to_append)
|
||||||
|
append_blocks_to_page(notion_token, page_id, content_blocks)
|
||||||
269
v3_roboplanet-gtm-strategy-2026-01-20.md
Normal file
269
v3_roboplanet-gtm-strategy-2026-01-20.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
# GTM Strategy
|
||||||
|
|
||||||
|
**Recherche-URL:** https://www.inmotionrobotic.com/de/puma
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# GTM STRATEGY REPORT v3
|
||||||
|
|
||||||
|
## 1. Strategic Core
|
||||||
|
|
||||||
|
* **Category Definition:** Der PUMA M20 fällt in die Kategorie der **Sicherheitsroboter**. Diese Kategorie umfasst autonome oder ferngesteuerte Roboter, die primär für Sicherheits-, Überwachungs- und Patrouillenaufgaben in verschiedenen Umgebungen konzipiert sind.
|
||||||
|
|
||||||
|
* **Dynamic Service Logic:** Die Wertschöpfung des PUMA M20 als Sicherheitsroboter basiert auf der Kombination einer "Machine Layer" (die Fähigkeiten des Roboters selbst) und einer "Human Service Layer" (die Dienstleistungen von Wackler Security).
|
||||||
|
|
||||||
|
* **Machine Layer (PUMA M20):** Der Roboter übernimmt die kontinuierliche, unermüdliche Überwachung. Dies umfasst:
|
||||||
|
* **Autonome Patrouillen:** Der Roboter führt selbstständig Patrouillen auf vordefinierten Routen durch, auch in unwegsamem Gelände.
|
||||||
|
* **360°-Umgebungserfassung:** Durch LiDAR und Kameras erfasst der Roboter seine Umgebung umfassend und erkennt potenzielle Gefahren.
|
||||||
|
* **Nacht- und Wärmebildfähigkeit:** Ermöglicht die Überwachung auch bei Dunkelheit und die Detektion von Personen oder Objekten anhand ihrer Wärmesignatur.
|
||||||
|
* **Früherkennung von Anomalien:** Der Roboter ist darauf programmiert, ungewöhnliche Aktivitäten oder Abweichungen von der Norm zu erkennen.
|
||||||
|
* **Alarmierung:** Bei Erkennung einer Gefahr oder Anomalie sendet der Roboter sofort eine Alarmmeldung an die Wackler-Leitstelle.
|
||||||
|
|
||||||
|
* **Human Service Layer (Wackler Security):** Wackler Security ergänzt die Fähigkeiten des Roboters durch menschliche Expertise und Intervention:
|
||||||
|
* **Risikobewertung:** Die Mitarbeiter in der Leitstelle bewerten die vom Roboter gemeldeten Alarme und entscheiden über die nächsten Schritte.
|
||||||
|
* **Intervention:** Bei Bedarf werden Interventionskräfte zum Einsatzort geschickt, um die Situation zu klären, Maßnahmen zu ergreifen und die Sicherheit wiederherzustellen.
|
||||||
|
* **Reporting:** Wackler Security erstellt detaillierte Berichte über alle Vorfälle und Alarme, um die Sicherheitsprozesse kontinuierlich zu verbessern.
|
||||||
|
* **Anpassung und Optimierung:** Wackler Security passt die Routen, Sensoreinstellungen und Alarmparameter des Roboters an die spezifischen Bedürfnisse des Kunden an.
|
||||||
|
|
||||||
|
Diese Kombination aus robotergestützter Überwachung und menschlicher Expertise bietet einen umfassenden Schutz vor Einbruch, Vandalismus, Diebstahl und anderen Sicherheitsrisiken. Der Roboter ist die "Augen und Ohren" vor Ort, während Wackler Security die "Gehirn und Muskeln" für die Reaktion auf Bedrohungen liefert.
|
||||||
|
|
||||||
|
## 2. Executive Summary
|
||||||
|
|
||||||
|
Der Markt für Sicherheitslösungen steht vor der Herausforderung, steigende Sicherheitsanforderungen mit begrenzten Ressourcen und steigenden Personalkosten zu bewältigen. Der PUMA M20 bietet als geländegängiger Sicherheitsroboter eine innovative Lösung, die autonome Überwachung, Früherkennung von Gefahren und die Entlastung von Sicherheitspersonal kombiniert. Durch den Einsatz des PUMA M20 können Unternehmen ihre Sicherheitskosten senken, die Reaktionszeiten verbessern und das Risiko von Einbrüchen, Vandalismus und Produktionsausfällen minimieren. Die Kombination aus robotergestützter Überwachung und menschlicher Expertise von Wackler Security bietet einen umfassenden Schutz, der über herkömmliche Sicherheitslösungen hinausgeht. Der PUMA M20 ist ideal für Unternehmen in der Öl- und Gasindustrie, Industrieparks und Logistikzentren, die nach einer effizienten und zuverlässigen Möglichkeit suchen, ihre Sicherheit zu erhöhen und ihre Betriebsabläufe zu optimieren. Der Return on Investment ergibt sich aus reduzierten Personalkosten, geringeren Verlusten durch Diebstahl und Beschädigung sowie der Vermeidung von Produktionsausfällen.
|
||||||
|
|
||||||
|
## 3. Product Reality Check
|
||||||
|
|
||||||
|
* **Core Capabilities:**
|
||||||
|
* **Geländegängigkeit:** Bewältigt unebenes Gelände, Treppen und Steigungen bis zu 45°.
|
||||||
|
* **Autonome Navigation:** SLAM-basierte Navigation für autonome Missionen und Rückkehr zur Basis.
|
||||||
|
* **360°-Umgebungserfassung:** Duale LiDAR-Systeme und Weitwinkelkameras für umfassende Umgebungswahrnehmung.
|
||||||
|
* **Hohe Zuladung:** Kann bis zu 50 kg tragen.
|
||||||
|
* **Robuste Bauweise:** IP66-Schutzart für Staub- und Wasserbeständigkeit.
|
||||||
|
|
||||||
|
* **Technical Constraints:**
|
||||||
|
|
||||||
|
| Feature | Value | Implication |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Abmessungen (Breite) | 50 cm | Passt durch schmale Gänge und enge Bereiche. |
|
||||||
|
| Gewicht | 33 kg | Kann von zwei Personen getragen werden, beeinflusst aber die Akkulaufzeit. |
|
||||||
|
| Akkulaufzeit | Bis zu 3 Stunden | Erfordert regelmäßiges Aufladen oder Batteriewechsel für kontinuierlichen Betrieb. |
|
||||||
|
| Schritt-Höhe | 25 cm | Begrenzt die Fähigkeit, hohe Hindernisse zu überwinden. |
|
||||||
|
| Maximale Geschwindigkeit | 5 m/s | Schnell genug für Patrouillen, aber nicht für Verfolgungsjagden. |
|
||||||
|
| Betriebstemperatur | -20°C bis 55°C | Geeignet für die meisten Umgebungen, aber nicht für extreme Temperaturen. |
|
||||||
|
|
||||||
|
## 4. Target Architecture (ICPs)
|
||||||
|
|
||||||
|
* **Öl- und Gasindustrie:** Die Öl- und Gasindustrie steht unter enormem Druck, die Sicherheit ihrer Anlagen und Pipelines zu erhöhen, um Unfälle, Umweltschäden und Diebstahl zu verhindern. Die abgelegenen Standorte und die oft gefährlichen Arbeitsbedingungen machen den Einsatz von Sicherheitspersonal teuer und riskant. Der PUMA M20 bietet eine kosteneffiziente und sichere Möglichkeit, Anlagen und Pipelines autonom zu überwachen und potenzielle Gefahren frühzeitig zu erkennen. Der strategische Fit ergibt sich aus der Notwendigkeit, die Sicherheit zu erhöhen, die Betriebskosten zu senken und das Risiko für menschliche Arbeitskräfte zu minimieren.
|
||||||
|
* **Whale Accounts:** Wintershall Dea, BASF, Linde, OMV Deutschland
|
||||||
|
|
||||||
|
* **Industrieparks und große Produktionsstätten:** Industrieparks und Produktionsstätten sind oft weitläufig und schwer zu überwachen. Einbrüche, Vandalismus und Sabotage können zu erheblichen Produktionsausfällen und finanziellen Verlusten führen. Der PUMA M20 bietet eine 24/7-Überwachung großer Areale, detektiert unbefugten Zutritt und entlastet das Sicherheitspersonal, das sich auf kritische Situationen konzentrieren kann. Der strategische Fit ergibt sich aus der Notwendigkeit, die Sicherheit zu erhöhen, die Betriebskosten zu senken und Produktionsausfälle zu vermeiden.
|
||||||
|
* **Whale Accounts:** thyssenkrupp, Robert Bosch, Siemens, Volkswagen, BMW
|
||||||
|
|
||||||
|
* **Logistikzentren und Lagerhäuser:** Logistikzentren und Lagerhäuser sind anfällig für Diebstahl, Beschädigung von Waren und unbefugtes Betreten. Die Überwachung großer Lagerflächen ist personalintensiv und teuer. Der PUMA M20 patrouilliert autonom in Lagerhäusern, erkennt verdächtige Aktivitäten und alarmiert sofort das Sicherheitspersonal, wodurch Diebstahl und Beschädigung reduziert und die Sicherheit erhöht werden. Der strategische Fit ergibt sich aus der Notwendigkeit, Verluste zu minimieren, die Sicherheit zu erhöhen und die Betriebsabläufe zu optimieren.
|
||||||
|
* **Whale Accounts:** DHL, DB Schenker, Amazon, Kühne + Nagel, Rhenus Logistics
|
||||||
|
|
||||||
|
## 5. Strategy Matrix
|
||||||
|
|
||||||
|
| Target Segment | The Pain (Operational) | The Angle (Story) | Differentiation (Service Gap) |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| Öl- und Gasindustrie | Unbefugtes Betreten von Anlagen und Pipelines führt zu Sicherheitsrisiken, Diebstahl und möglichen Umweltschäden, was hohe Überwachungskosten verursacht. | Der geländegängige Security Roboter patrouilliert autonom und zuverlässig abgelegene Anlagen, detektiert Eindringlinge frühzeitig per Wärmebild und reduziert so Sicherheitsrisiken und Kosten. | Wackler Security bietet mit dem Security Roboter eine 24/7 Überwachung (Detektion & Präsenz) und kombiniert diese mit der sofortigen Intervention unserer Sicherheitskräfte (Bewertung & Intervention). Der Roboter sieht die Gefahr, Wackler beseitigt sie. |
|
||||||
|
| Industrieparks und große Produktionsstätten | Hohe Kosten für die Bewachung großer Gelände und Gebäude, insbesondere in der Nacht oder an Wochenenden, sowie das Risiko von Produktionsausfällen durch Einbruch oder Sabotage. | Der Security Roboter übernimmt die lästigen Routinepatrouillen, erkennt Gefahren frühzeitig durch 360°-Umgebungserfassung und entlastet so das Sicherheitspersonal, das sich auf kritische Situationen konzentrieren kann. | Wackler Security kombiniert die unermüdliche Überwachung des Security Roboters mit der professionellen Risikobewertung und Intervention unserer NSL-geschulten Mitarbeiter. So gewährleisten wir umfassenden Schutz vor Einbruch, Vandalismus und Produktionsausfällen. |
|
||||||
|
| Logistikzentren und Lagerhäuser | Diebstahl, Beschädigung von Waren und unbefugtes Betreten führen zu hohen Verlusten und Versicherungsprämien. Die Überwachung großer Lagerflächen ist personalintensiv und teuer. | Der Security Roboter patrouilliert autonom in Lagerhäusern, erkennt verdächtige Aktivitäten und alarmiert sofort das Sicherheitspersonal, wodurch Diebstahl und Beschädigung reduziert und die Sicherheit erhöht werden. | Wackler Security bietet eine Kombination aus robotergestützter Überwachung und menschlicher Expertise. Der Roboter detektiert Unregelmäßigkeiten, unsere Interventionskräfte bewerten die Situation und leiten die notwendigen Maßnahmen ein, um Verluste zu minimieren und die Sicherheit zu gewährleisten. |
|
||||||
|
|
||||||
|
## 6. Operational GTM Roadmap
|
||||||
|
|
||||||
|
* **Step 1: Lead Gen:**
|
||||||
|
* **Inbound:**
|
||||||
|
* **Content Marketing:** Erstellung von Blogartikeln, Whitepapers und Fallstudien, die die Vorteile des PUMA M20 für die jeweiligen ICPs hervorheben. Themen könnten sein: "Wie Sicherheitsroboter die Öl- und Gasindustrie sicherer machen", "Reduzierung von Diebstahl in Logistikzentren durch autonome Überwachung" oder "Effiziente Geländeüberwachung in Industrieparks".
|
||||||
|
* **SEO:** Optimierung der Website und der Inhalte für relevante Suchbegriffe wie "Sicherheitsroboter", "autonome Überwachung", "Industriesicherheit" usw.
|
||||||
|
* **Webinare:** Durchführung von Webinaren zu Themen wie "Die Zukunft der Sicherheit: Robotik und KI" oder "Best Practices für die Implementierung von Sicherheitsrobotern".
|
||||||
|
* **Outbound:**
|
||||||
|
* **Direktansprache:** Identifizierung von Entscheidungsträgern (z.B. Sicherheitsleiter, Betriebsleiter, Logistikmanager) in den Zielunternehmen und direkte Kontaktaufnahme per E-Mail oder Telefon.
|
||||||
|
* **Branchenveranstaltungen:** Teilnahme an relevanten Messen und Konferenzen (z.B. Sicherheitsmessen, Logistikmessen) und Präsentation des PUMA M20.
|
||||||
|
* **LinkedIn:** Nutzung von LinkedIn für die Leadgenerierung und den Aufbau von Beziehungen zu potenziellen Kunden.
|
||||||
|
|
||||||
|
* **Step 2: Consultative Sales:**
|
||||||
|
* **Site-Check:** Vor der Angebotserstellung ist ein Site-Check unerlässlich. Dabei werden folgende Aspekte geprüft:
|
||||||
|
* **Gelände:** Beschaffenheit des Geländes (Steigungen, Unebenheiten, Hindernisse), um die Geländegängigkeit des Roboters zu gewährleisten.
|
||||||
|
* **Umgebung:** Lichtverhältnisse, Temperaturbereich, Staub- und Feuchtigkeitsbelastung, um die Eignung des Roboters für die Einsatzumgebung zu beurteilen.
|
||||||
|
* **Infrastruktur:** Verfügbarkeit von Stromanschlüssen für das Aufladen des Roboters.
|
||||||
|
* **Sicherheitsanforderungen:** Spezifische Sicherheitsanforderungen des Kunden (z.B. Überwachung bestimmter Bereiche, Erkennung bestimmter Gefahren).
|
||||||
|
* **Integration:** Kompatibilität mit bestehenden Sicherheitssystemen (z.B. Alarmanlagen, Überwachungskameras).
|
||||||
|
* **Constraints:** Die Ergebnisse des Site-Checks werden genutzt, um die technischen Constraints des Roboters mit den Anforderungen des Kunden abzugleichen und sicherzustellen, dass der PUMA M20 die gestellten Aufgaben erfüllen kann.
|
||||||
|
|
||||||
|
* **Step 3: Proof of Value:**
|
||||||
|
* **Pilot Phase:** Um das Vertrauen des Kunden zu gewinnen und die Leistungsfähigkeit des PUMA M20 unter Beweis zu stellen, wird eine Pilotphase empfohlen.
|
||||||
|
* **Paid Pilot:** Der Kunde zahlt für den Einsatz des Roboters während der Pilotphase. Dies signalisiert ein starkes Interesse und Engagement des Kunden.
|
||||||
|
* **Free PoC (Proof of Concept):** Der Einsatz des Roboters ist während der Pilotphase kostenlos. Dies kann sinnvoll sein, um Kunden zu gewinnen, die noch unsicher sind oder ein begrenztes Budget haben.
|
||||||
|
* **Ziele der Pilotphase:**
|
||||||
|
* **Demonstration der Leistungsfähigkeit:** Der Roboter soll zeigen, dass er die gestellten Aufgaben (z.B. Patrouillen, Überwachung, Detektion von Gefahren) zuverlässig erfüllen kann.
|
||||||
|
* **Validierung der ROI:** Der Kunde soll erkennen, dass der Einsatz des Roboters zu Kosteneinsparungen und einer Erhöhung der Sicherheit führt.
|
||||||
|
* **Identifizierung von Optimierungspotenzial:** Während der Pilotphase können Optimierungspotenziale identifiziert werden (z.B. Anpassung der Routen, Sensoreinstellungen).
|
||||||
|
|
||||||
|
* **Step 4: Expansion:**
|
||||||
|
* **RaaS (Robot as a Service):** Der PUMA M20 wird als Service angeboten, wobei der Kunde eine monatliche Gebühr für den Einsatz des Roboters zahlt. Dies beinhaltet die Wartung, Reparatur und Software-Updates des Roboters.
|
||||||
|
* **Serviceverträge:** Zusätzlich zum RaaS-Modell können Serviceverträge angeboten werden, die zusätzliche Dienstleistungen wie die Risikobewertung, die Intervention und das Reporting umfassen.
|
||||||
|
* **Upselling:** Nach erfolgreicher Implementierung des PUMA M20 können weitere Roboter oder zusätzliche Dienstleistungen angeboten werden, um den Schutz des Kunden zu erweitern.
|
||||||
|
|
||||||
|
## 7. Commercial Logic (ROI Framework)
|
||||||
|
|
||||||
|
* **ROI Calculation Logic:** Der Return on Investment (ROI) des PUMA M20 ergibt sich aus den Kosteneinsparungen und den zusätzlichen Einnahmen, die durch den Einsatz des Roboters erzielt werden.
|
||||||
|
|
||||||
|
* **The Formula:**
|
||||||
|
|
||||||
|
**Net Value = (Kosteneinsparungen + Zusätzliche Einnahmen) - Investitionskosten**
|
||||||
|
|
||||||
|
**ROI = (Net Value / Investitionskosten) * 100%**
|
||||||
|
|
||||||
|
* **Input Variables:**
|
||||||
|
|
||||||
|
* **Kosteneinsparungen:**
|
||||||
|
* **Personalkosten:** Kosten pro Stunde für Sicherheitspersonal, Anzahl der eingesparten Personalstunden pro Monat.
|
||||||
|
* **Verluste durch Diebstahl und Beschädigung:** Durchschnittliche Verluste pro Monat vor dem Einsatz des Roboters, erwartete Reduzierung der Verluste durch den Einsatz des Roboters.
|
||||||
|
* **Versicherungsprämien:** Höhe der Versicherungsprämien vor dem Einsatz des Roboters, erwartete Reduzierung der Prämien durch den Einsatz des Roboters.
|
||||||
|
* **Energiekosten:** Stromkosten für den Betrieb des Roboters.
|
||||||
|
* **Zusätzliche Einnahmen:**
|
||||||
|
* **Vermeidung von Produktionsausfällen:** Wert der vermiedenen Produktionsausfälle pro Monat durch den Einsatz des Roboters.
|
||||||
|
* **Investitionskosten:**
|
||||||
|
* **Kaufpreis oder monatliche Gebühr für den Roboter (RaaS).**
|
||||||
|
* **Kosten für die Implementierung und Integration des Roboters.**
|
||||||
|
* **Kosten für die Schulung des Personals.**
|
||||||
|
* **Wartungs- und Reparaturkosten.**
|
||||||
|
|
||||||
|
Der Kunde muss diese Variablen bereitstellen, um eine genaue ROI-Berechnung durchführen zu können. Die ROI-Berechnung sollte auf realistischen Annahmen basieren und die spezifischen Bedürfnisse und Gegebenheiten des Kunden berücksichtigen.
|
||||||
|
|
||||||
|
# SALES ENABLEMENT & VISUALS (PHASE 6)
|
||||||
|
|
||||||
|
## Kill-Critique Battlecards
|
||||||
|
|
||||||
|
### Persona: Operativer Entscheider (Anlagenleiter)
|
||||||
|
> **Objection:** "Der Roboter ist zu unzuverlässig und kann die menschliche Arbeitskraft nicht vollständig ersetzen. Was passiert bei technischen Problemen oder in unvorhergesehenen Situationen?"
|
||||||
|
|
||||||
|
**Response:** Unser Security Roboter ist darauf ausgelegt, Routineaufgaben zuverlässig zu erledigen und das Sicherheitspersonal zu entlasten. Durch die Kombination mit der Wackler Interventionskraft ist eine schnelle Reaktion bei Problemen sichergestellt. Der Roboter sieht die Gefahr, Wackler beseitigt sie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Infrastruktur Verantwortlicher (IT-Sicherheitsbeauftragter)
|
||||||
|
> **Objection:** "Die Integration des Roboters in unsere bestehenden Sicherheitssysteme ist zu kompliziert und birgt potenzielle Sicherheitslücken. Wie gewährleisten Sie den Datenschutz und die Datensicherheit?"
|
||||||
|
|
||||||
|
**Response:** Unser Security Roboter bietet flexible API-Integration und entspricht höchsten Sicherheitsstandards. Wir arbeiten eng mit Ihrem IT-Team zusammen, um eine nahtlose und sichere Integration zu gewährleisten. Datenschutz hat höchste Priorität.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Wirtschaftlicher Entscheider (CFO)
|
||||||
|
> **Objection:** "Die Anschaffungskosten des Roboters sind zu hoch und der ROI ist unklar. Können Sie die langfristigen Kosteneinsparungen und den Mehrwert quantifizieren?"
|
||||||
|
|
||||||
|
**Response:** Unser Security Roboter reduziert langfristig die Kosten für Bewachung und Sicherheitsdienste. Durch die Automatisierung von Routinepatrouillen und die frühzeitige Erkennung von Gefahren minimieren wir Risiken und Verluste. Gerne erstellen wir eine detaillierte ROI-Analyse für Ihre spezifischen Anforderungen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Innovations-Treiber (CDO)
|
||||||
|
> **Objection:** "Der Roboter ist eine Insellösung und passt nicht in unsere bestehende Digitalisierungsstrategie. Wie unterstützt der Roboter unsere langfristigen Innovationsziele?"
|
||||||
|
|
||||||
|
**Response:** Unser Security Roboter ist mehr als nur eine Insellösung. Er ist eine innovative Ergänzung zu Ihren bestehenden Sicherheitssystemen und ermöglicht die Automatisierung von Prozessen, die Datenerfassung und die Integration in Ihre Digitalisierungsstrategie. Wir unterstützen Sie bei der Umsetzung Ihrer Innovationsziele.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visual Briefings (Prompts)
|
||||||
|
|
||||||
|
### Öl- und Gasindustrie Überwachung
|
||||||
|
*Context: Ein Security Roboter patrouilliert autonom eine abgelegene Öl-Pipeline bei Nacht.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Photorealistisches Bild eines geländegängigen Security Roboters mit Wärmebildkamera, der autonom eine Öl-Pipeline in einer abgelegenen, nächtlichen Umgebung patrouilliert. Der Roboter ist robust gebaut und mit Sensoren ausgestattet. Im Hintergrund sind schwach beleuchtete Industrieanlagen und eine sternenklare Nacht zu sehen. Der Fokus liegt auf der fortschrittlichen Technologie und der Fähigkeit des Roboters, in schwierigen Umgebungen zu arbeiten.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Industriepark Sicherheit
|
||||||
|
*Context: Ein Security Roboter überwacht ein großes Industriegelände während einer Nachtschicht.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Photorealistisches Bild eines Security Roboters mit 360°-Umgebungserfassung, der ein großes Industriegelände bei Nacht überwacht. Der Roboter ist mit Kameras und Sensoren ausgestattet und patrouilliert autonom zwischen Lagerhallen und Produktionsstätten. Im Hintergrund sind schwach beleuchtete Gebäude und Sicherheitszäune zu sehen. Der Fokus liegt auf der umfassenden Überwachung und der Fähigkeit des Roboters, Gefahren frühzeitig zu erkennen.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logistikzentrum Überwachung
|
||||||
|
*Context: Ein Security Roboter patrouilliert autonom in einem Lagerhaus, um Diebstahl zu verhindern.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Photorealistisches Bild eines Security Roboters, der autonom in einem Lagerhaus patrouilliert. Der Roboter ist mit Kameras und Sensoren ausgestattet und überwacht die Lagerregale und Gänge. Im Hintergrund sind Waren und Mitarbeiter zu sehen. Der Fokus liegt auf der Fähigkeit des Roboters, verdächtige Aktivitäten zu erkennen und Diebstahl zu verhindern.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# VERTICAL LANDING PAGES (PHASE 7)
|
||||||
|
|
||||||
|
## Öl- und Gasindustrie
|
||||||
|
**Headline:** Sichern Sie Ihre Anlagen und Pipelines rund um die Uhr – mit unserer robotergestützten Sicherheitslösung.
|
||||||
|
|
||||||
|
**Subline:** Unbefugtes Betreten, Diebstahl und Umweltschäden bedrohen Ihre Anlagen. Unser geländegängiger Security Roboter patrouilliert autonom, erkennt Eindringlinge frühzeitig und wird durch unsere Interventionskräfte ergänzt – für maximale Sicherheit und Kosteneffizienz.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Reduzieren Sie Sicherheitsrisiken und Überwachungskosten durch autonome 24/7-Patrouillen.
|
||||||
|
- Frühzeitige Detektion von Eindringlingen durch modernste Wärmebildtechnologie.
|
||||||
|
- Minimieren Sie das Risiko für Ihre Mitarbeiter in gefährlichen Umgebungen.
|
||||||
|
- Profitieren Sie von der Kombination aus robotergestützter Überwachung und sofortiger Intervention durch unsere Sicherheitskräfte.
|
||||||
|
|
||||||
|
**CTA:** Pilotprojekt anfragen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Industrieparks und große Produktionsstätten
|
||||||
|
**Headline:** Senken Sie Ihre Bewachungskosten und schützen Sie Ihr Gelände vor Einbruch und Sabotage – mit unserer intelligenten Sicherheitslösung.
|
||||||
|
|
||||||
|
**Subline:** Hohe Bewachungskosten und das Risiko von Produktionsausfällen belasten Ihr Unternehmen. Unser Security Roboter übernimmt Routinepatrouillen, erkennt Gefahren frühzeitig und wird durch unsere NSL-geschulten Mitarbeiter ergänzt – für umfassenden Schutz und maximale Effizienz.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Reduzieren Sie Ihre Bewachungskosten durch den Einsatz von Security Robotern.
|
||||||
|
- Entlasten Sie Ihr Sicherheitspersonal, damit es sich auf kritische Situationen konzentrieren kann.
|
||||||
|
- Erkennen Sie Gefahren frühzeitig durch 360°-Umgebungserfassung.
|
||||||
|
- Profitieren Sie von der Kombination aus unermüdlicher robotergestützter Überwachung und professioneller Risikobewertung durch unsere Experten.
|
||||||
|
|
||||||
|
**CTA:** Pilotprojekt anfragen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BUSINESS CASE & ROI (PHASE 8)
|
||||||
|
|
||||||
|
## Öl- und Gasindustrie
|
||||||
|
**Cost Driver:** Kosten pro Sicherheitsinspektion
|
||||||
|
|
||||||
|
**Efficiency Gain:** undefined
|
||||||
|
|
||||||
|
**Risk Argument:** Die Kosten eines unentdeckten Lecks oder einer Pipeline-Beschädigung (Umweltschäden, Produktionsausfälle, Strafzahlungen, Imageschaden).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Industrieparks und große Produktionsstätten
|
||||||
|
**Cost Driver:** Kosten pro Wachmannstunde
|
||||||
|
|
||||||
|
**Efficiency Gain:** undefined
|
||||||
|
|
||||||
|
**Risk Argument:** Die Kosten eines Einbruchs, Vandalismus oder eines Produktionsstillstands aufgrund mangelnder Überwachung (Sachschäden, Produktionsausfall, Verlust von geistigem Eigentum).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logistikzentren und Lagerhäuser
|
||||||
|
**Cost Driver:** Kosten pro Inventurdurchlauf
|
||||||
|
|
||||||
|
**Efficiency Gain:** undefined
|
||||||
|
|
||||||
|
**Risk Argument:** Die Kosten von Inventurdifferenzen, Diebstahl oder Unfällen im Lager (Fehlbestände, Versicherungsprämien, Arbeitsausfälle).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# FEATURE-TO-VALUE TRANSLATOR (PHASE 9)
|
||||||
|
|
||||||
|
| Feature | The Story (Benefit) | Headline |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Geländegängigkeit: Bewältigt unebenes Gelände, Treppen und Steigungen bis zu 45°. | Der Roboter kann sich in anspruchsvollem Gelände bewegen. Das bedeutet, dass er auch schwer zugängliche Bereiche überwachen kann. Dies führt zu einer umfassenderen Überwachung und Reduzierung von Sicherheitslücken. | Überwindet jedes Hindernis für lückenlose Sicherheit. |
|
||||||
|
| Autonome Navigation: SLAM-basierte Navigation für autonome Missionen und Rückkehr zur Basis. | Der Roboter navigiert selbstständig und kehrt automatisch zur Basis zurück. Das bedeutet, dass keine ständige manuelle Steuerung erforderlich ist. Dies führt zu einer Reduzierung des Personalaufwands und einer kontinuierlichen Überwachung. | Autonom unterwegs für Rund-um-die-Uhr-Überwachung. |
|
||||||
|
| 360°-Umgebungserfassung: Duale LiDAR-Systeme und Weitwinkelkameras für umfassende Umgebungswahrnehmung. | Der Roboter erfasst die gesamte Umgebung ohne tote Winkel. Das bedeutet, dass keine potenziellen Gefahren übersehen werden. Dies führt zu einer frühzeitigen Erkennung von Bedrohungen und einer verbesserten Reaktionsfähigkeit. | Sieht alles, damit Ihnen nichts entgeht. |
|
||||||
|
| Nacht- und Wärmebildfähigkeit: Optionale Nacht- und Wärmebildkameras für den Einsatz bei Dunkelheit. | Der Roboter kann auch bei Dunkelheit und schlechten Sichtverhältnissen eingesetzt werden. Das bedeutet, dass die Überwachung rund um die Uhr gewährleistet ist. Dies führt zu einer erhöhten Sicherheit und Abschreckung von potenziellen Eindringlingen. | Sicherheit, die auch im Dunkeln nicht im Stich lässt. |
|
||||||
|
| Robuste Bauweise: IP66-Schutzart für Staub- und Wasserbeständigkeit, Betriebstemperaturbereich von -20°C bis 55°C. | Der Roboter ist widerstandsfähig gegen extreme Umweltbedingungen. Das bedeutet, dass er zuverlässig im Innen- und Außenbereich eingesetzt werden kann. Dies führt zu einer langen Lebensdauer und geringen Wartungskosten. | Robust und zuverlässig – für den Einsatz unter härtesten Bedingungen. |
|
||||||
|
| Hohe Rechenleistung: Duale Octa-Core 64-Bit Industrieprozessoren für anspruchsvolle Anwendungen. | Der Roboter verfügt über eine hohe Rechenleistung. Das bedeutet, dass er komplexe Aufgaben wie Bildverarbeitung und Mustererkennung schnell und effizient ausführen kann. Dies führt zu einer schnelleren Reaktion auf potenzielle Gefahren und einer verbesserten Entscheidungsfindung. | Intelligente Technologie für schnelle Reaktion und präzise Analyse. |
|
||||||
|
| Flexible Payload-Optionen: Vielseitige Montagesysteme und Stromversorgung für verschiedene Sensoren und Geräte. | Der Roboter kann mit verschiedenen Sensoren und Geräten ausgestattet werden. Das bedeutet, dass er an spezifische Sicherheitsanforderungen angepasst werden kann. Dies führt zu einer flexiblen und maßgeschneiderten Sicherheitslösung. | Anpassbar an Ihre individuellen Sicherheitsbedürfnisse. |
|
||||||
|
| Flottenmanagement und API-Integration: Ermöglicht die Integration in bestehende Systeme und die Datenexport. | Der Roboter kann in bestehende Sicherheitssysteme integriert werden und Daten exportieren. Das bedeutet, dass eine zentrale Überwachung und Steuerung möglich ist. Dies führt zu einer effizienteren Sicherheitsverwaltung und besseren Datenanalyse. | Nahtlose Integration für eine zentrale Sicherheitsüberwachung. |
|
||||||
|
| Hohe Zuladung: Kann bis zu 50kg tragen. | Der Roboter kann schwere Lasten tragen. Das bedeutet, dass er zusätzliche Ausrüstung wie Kameras, Sensoren oder sogar Werkzeuge transportieren kann. Dies führt zu einer erweiterten Funktionalität und Flexibilität im Einsatz. | Trägt schwer, damit Sie sich sicher fühlen. |
|
||||||
291
v4_roboplanet-gtm-strategy-2026-01-20.md
Normal file
291
v4_roboplanet-gtm-strategy-2026-01-20.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# GTM Strategy
|
||||||
|
|
||||||
|
**Recherche-URL:** https://www.inmotionrobotic.com/de/puma
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# GTM STRATEGY REPORT v3.1
|
||||||
|
|
||||||
|
## 1. Strategic Core
|
||||||
|
|
||||||
|
* **Category Definition:** Der PUMA M20 fällt in die Kategorie der **Sicherheitsroboter**. Diese Kategorie umfasst autonome oder ferngesteuerte Roboter, die primär für Sicherheits-, Überwachungs- und Inspektionsaufgaben in verschiedenen Umgebungen konzipiert sind.
|
||||||
|
|
||||||
|
* **Dynamic Service Logic:** Die Wertschöpfung des PUMA M20 als Sicherheitsroboter basiert auf einer intelligenten Kombination aus der "Machine Layer" (den Fähigkeiten des Roboters) und der "Human Service Layer" (den Dienstleistungen von Wackler Security).
|
||||||
|
|
||||||
|
* **Machine Layer (Was der Roboter tut):** Der PUMA M20 bietet eine robuste Plattform für autonome Patrouillen, Überwachung und Inspektion. Seine Geländegängigkeit, 360°-Umgebungserfassung, Nachtsichtfähigkeit und die Möglichkeit zur Integration verschiedener Sensoren (Gas, Temperatur, etc.) ermöglichen es ihm, Gefahren zu erkennen, Daten zu sammeln und in Echtzeit zu übertragen. Er ist das "Auge" und der "Sensor" vor Ort.
|
||||||
|
|
||||||
|
* **Human Service Layer (Was Wackler tut):** Die von Wackler Security bereitgestellte Dienstleistungsebene umfasst die Überwachung der vom Roboter gesammelten Daten durch eine Notruf- und Serviceleitstelle (NSL), die Analyse der Daten durch Sicherheitsexperten und die Entsendung von Interventionskräften im Falle einer Bedrohung oder eines Alarms. Wackler stellt sicher, dass auf die vom Roboter erkannten Gefahren angemessen reagiert wird. Die Kombination aus Roboter-Präsenz und menschlicher Intervention schafft eine umfassende Sicherheitslösung, die über reine Detektion hinausgeht. Der Roboter sieht die Gefahr, Wackler beseitigt sie.
|
||||||
|
|
||||||
|
## 2. Executive Summary
|
||||||
|
|
||||||
|
Der Markt für Sicherheitsroboter bietet erhebliche Wachstumschancen, insbesondere in Branchen wie Öl & Gas, Chemie und Bergbau, in denen Sicherheitsrisiken und die Notwendigkeit effizienter Überwachung hoch sind. Der PUMA M20 adressiert diese Herausforderungen mit seiner robusten Bauweise, Geländegängigkeit und autonomen Navigationsfähigkeiten. Er ermöglicht es Unternehmen, gefährliche Umgebungen sicher zu überwachen, die Sicherheit der Mitarbeiter zu erhöhen und gleichzeitig Betriebskosten zu senken.
|
||||||
|
|
||||||
|
Die Kernwertversprechen des PUMA M20 liegen in der Kombination aus fortschrittlicher Robotertechnologie und dem Know-how von Wackler Security. Während der Roboter kontinuierlich Daten sammelt und Gefahren erkennt, sorgt Wackler für eine professionelle Analyse und Intervention im Alarmfall. Diese "Detektion & Intervention"-Strategie bietet eine umfassende Sicherheitslösung, die über herkömmliche Überwachungssysteme hinausgeht. Durch die Integration des PUMA M20 in bestehende Sicherheitsprotokolle können Unternehmen ihre Reaktionszeiten verbessern, Risiken minimieren und die Effizienz ihrer Sicherheitsmaßnahmen steigern.
|
||||||
|
|
||||||
|
## 3. Product Reality Check (Technical Deep Dive)
|
||||||
|
|
||||||
|
* **Core Capabilities:**
|
||||||
|
1. **Autonome Navigation:** SLAM-basierte Navigation ermöglicht den autonomen Betrieb und die Rückkehr zur Basis, wodurch repetitive Inspektionsaufgaben automatisiert werden können.
|
||||||
|
2. **Geländegängigkeit:** Bewältigt unebenes Gelände, Treppen und Steigungen bis zu 45°, was den Einsatz in anspruchsvollen Umgebungen ermöglicht.
|
||||||
|
3. **360°-Umgebungserfassung:** Duale LiDAR-Systeme und Weitwinkelkameras sorgen für eine umfassende Umgebungswahrnehmung und ermöglichen die Erkennung von Gefahren und Anomalien.
|
||||||
|
4. **Robuste Bauweise:** IP66-Schutz und ein breiter Betriebstemperaturbereich gewährleisten den zuverlässigen Einsatz unter widrigen Bedingungen.
|
||||||
|
5. **Flexible Integration:** Flottenmanagement und API-Integrationen ermöglichen die nahtlose Einbindung in bestehende Systeme und die Nutzung der gesammelten Daten.
|
||||||
|
|
||||||
|
* **Technical Constraints:**
|
||||||
|
|
||||||
|
| Feature | Value | Implication |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Produkt-ID | puma-m20 | Eindeutige Kennzeichnung des Produkts. |
|
||||||
|
| Marke | PUMA | Hersteller des Roboters. |
|
||||||
|
| Modellname | M20 | Spezifische Modellbezeichnung. |
|
||||||
|
| Beschreibung | Kompakter, geländegängiger Quadruped für Inspektionen, Logistik und Missionen, bei denen Menschen nicht hingehen sollten. | Definiert den primären Anwendungsbereich. |
|
||||||
|
| Kategorie | Sicherheitsroboter | Klassifizierung des Produkts. |
|
||||||
|
| Hersteller-URL | https://www.inmotionrobotic.com/de/puma | Link zur Herstellerseite für weitere Informationen. |
|
||||||
|
| Akkulaufzeit | 180 min | Maximale Betriebsdauer mit einer Akkuladung. |
|
||||||
|
| Ladezeit | N/A | Information nicht verfügbar. |
|
||||||
|
| Gewicht | 33 kg | Beeinflusst die Transportierbarkeit und Einsatzmöglichkeiten. |
|
||||||
|
| Abmessungen (Breite) | 50 cm | Wichtig für die Durchfahrt durch enge Bereiche. |
|
||||||
|
| Maximale Steigung | 45° | Begrenzt die Fähigkeit, Steigungen zu überwinden (oberflächenabhängig). |
|
||||||
|
| IP-Schutzart | IP66 | Staub- und Wasserbeständigkeit. |
|
||||||
|
| Kletterhöhe | 25 cm | Maximale Höhe, die der Roboter überwinden kann. |
|
||||||
|
| Navigation | SLAM, LiDAR | Art der verwendeten Navigationstechnologie. |
|
||||||
|
| Konnektivität | Gigabit Ethernet, USB 3.0 | Ermöglicht die Datenübertragung und Integration. |
|
||||||
|
| Maximale Zuladung | 50 kg | Maximale Last, die der Roboter tragen kann. |
|
||||||
|
| Nennlast | 12 kg | Empfohlene Last für optimale Leistung. |
|
||||||
|
| Kamera-Typen | Weitwinkel | Art der verbauten Kameras. |
|
||||||
|
| Nachtsicht | Ja | Fähigkeit, im Dunkeln zu sehen. |
|
||||||
|
| Gasdetektion | N/A | Information nicht verfügbar. |
|
||||||
|
| Schnittstelle für Anbauteile | N/A | Information nicht verfügbar. |
|
||||||
|
| Maximale Geschwindigkeit | 5 m/s | Höchstgeschwindigkeit des Roboters. |
|
||||||
|
| Kontinuierliche Geschwindigkeit | 3 m/s | Geschwindigkeit für längere Einsätze. |
|
||||||
|
| Betriebstemperatur | -20 bis 55 °C | Temperaturbereich für den Betrieb. |
|
||||||
|
| LiDAR Linien | 96 Linien | Auflösung des LiDAR-Systems. |
|
||||||
|
| Externe Stromausgabe | 300 W | Leistung für externe Geräte. |
|
||||||
|
|
||||||
|
## 4. Target Architecture (ICPs)
|
||||||
|
|
||||||
|
* **Öl- und Gasindustrie:** Die Öl- und Gasindustrie steht unter ständigem Druck, die Sicherheit ihrer Anlagen zu erhöhen und gleichzeitig die Betriebskosten zu senken. Die Inspektion von Pipelines und Anlagen in abgelegenen oder gefährlichen Gebieten ist kostspielig und birgt Risiken für das Personal. Der PUMA M20 bietet eine Lösung für diese Herausforderungen, indem er autonome Patrouillen ermöglicht, Leckagen erkennt und die Sicherheit erhöht, ohne Mitarbeiter zu gefährden.
|
||||||
|
* **Whale Accounts:** Wintershall Dea, BASF (als Betreiber von Gasanlagen), OMV Deutschland, DEA Deutsche Erdoel AG
|
||||||
|
|
||||||
|
* **Chemieindustrie:** In der Chemieindustrie sind Gefahrstoffaustritte und Brände eine ständige Bedrohung. Die Überwachung von Anlagen und die frühzeitige Erkennung von Gefahren sind entscheidend, um Umweltverschmutzung, Gesundheitsschäden und finanzielle Verluste zu vermeiden. Der PUMA M20 kann mit verschiedenen Sensoren ausgestattet werden, um Gaskonzentrationen zu messen, Temperaturerhöhungen zu erkennen und frühzeitig Warnungen auszugeben.
|
||||||
|
* **Whale Accounts:** BASF, Bayer, Evonik, Merck, Wacker Chemie
|
||||||
|
|
||||||
|
* **Bergbau:** Der Bergbau ist eine der gefährlichsten Industrien. Einsturzgefahr, unzureichende Belüftung und das Vorhandensein gefährlicher Gase führen zu Arbeitsunfällen und Gesundheitsschäden. Der PUMA M20 kann in unzugänglichen Bereichen eingesetzt werden, um Daten zu sammeln, die Luftqualität zu überwachen und potenzielle Gefahren zu identifizieren. Dies erhöht die Sicherheit der Bergleute und minimiert Ausfallzeiten.
|
||||||
|
* **Whale Accounts:** K+S Aktiengesellschaft, Deutsche Steinkohle AG (in Abwicklung, aber relevant für Nachsorge), LSR Rohstoff GmbH, Deutsches Bergbau-Museum Bochum (als Forschungseinrichtung)
|
||||||
|
|
||||||
|
## 5. Strategy Matrix
|
||||||
|
|
||||||
|
| Target Segment | The Pain (Operational) | The Angle (Story) | Differentiation (Service Gap) |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| Öl- und Gasindustrie | Unbefugter Zutritt zu Anlagen und Pipelines führt zu Produktionsausfällen, Diebstahl und Sicherheitsrisiken. | Unsere geländegängigen Sicherheitsroboter patrouillieren autonom in schwer zugänglichen Gebieten, erkennen Bedrohungen frühzeitig und ermöglichen eine sofortige Reaktion durch unsere Interventionskräfte. | Während andere Anbieter nur die Detektion bieten, gewährleistet unsere 'Detektion & Intervention'-Strategie durch die Kombination aus Roboter-Präsenz und Wackler Security-Einsatzkräften eine umfassende Sicherheitslösung. Der Roboter sieht die Gefahr, Wackler beseitigt sie. |
|
||||||
|
| Chemieindustrie | Gefahrstoffaustritte und Brände stellen eine ständige Bedrohung dar, die zu Umweltverschmutzung, Gesundheitsschäden und hohen finanziellen Verlusten führen können. | Unsere mit Sensorik ausgestatteten Roboter überwachen kontinuierlich die Anlagen auf Gasaustritte, Temperaturerhöhungen und andere Anomalien, um frühzeitig vor potenziellen Gefahren zu warnen und Schlimmeres zu verhindern. | Im Gegensatz zu statischen Überwachungssystemen bieten unsere mobilen Roboter eine flächendeckende und lückenlose Überwachung. Im Alarmfall bewertet die Wackler-Notruf- und Serviceleitstelle (NSL) die Situation und entsendet bei Bedarf umgehend Interventionskräfte. |
|
||||||
|
| Bergbau | Einsturzgefahr, unzureichende Belüftung und das Vorhandensein gefährlicher Gase führen zu Arbeitsunfällen und Gesundheitsschäden der Bergleute. | Unsere robusten und geländegängigen Roboter erkunden unzugängliche Bereiche, überwachen die Luftqualität und erkennen Gefahrenquellen, um die Sicherheit der Bergleute zu gewährleisten und Ausfallzeiten zu minimieren. | Unsere Roboter ermöglichen eine sichere Inspektion und Überwachung von kritischen Bereichen, ohne Mitarbeiter zu gefährden. Die gesammelten Daten werden von Wackler-Sicherheitsexperten analysiert, um präventive Maßnahmen abzuleiten und die Sicherheit kontinuierlich zu verbessern. |
|
||||||
|
|
||||||
|
## 6. Operational GTM Roadmap
|
||||||
|
|
||||||
|
* **Step 1: Lead Gen:**
|
||||||
|
* **Inbound:**
|
||||||
|
* Erstellung von zielgerichteten Inhalten (Whitepapers, Fallstudien, Blog-Artikel) zu den spezifischen Sicherheitsherausforderungen der Zielindustrien (Öl & Gas, Chemie, Bergbau).
|
||||||
|
* Suchmaschinenoptimierung (SEO) für relevante Keywords (z.B. "autonome Anlagenüberwachung", "Gefahrstoffdetektion Roboter", "Sicherheit im Bergbau").
|
||||||
|
* Durchführung von Webinaren und Online-Demonstrationen, die die Vorteile des PUMA M20 und der Wackler-Dienstleistungen hervorheben.
|
||||||
|
* **Outbound:**
|
||||||
|
* Direkte Ansprache von Sicherheitsverantwortlichen und Betriebsleitern in den Zielunternehmen (Identifizierung über LinkedIn, Fachmessen, Branchenverbände).
|
||||||
|
* Teilnahme an relevanten Fachmessen und Konferenzen mit Fokus auf Sicherheit, Robotik und Automatisierung.
|
||||||
|
* Entwicklung von personalisierten E-Mail-Kampagnen, die auf die spezifischen Pain Points der jeweiligen Industrie eingehen.
|
||||||
|
|
||||||
|
* **Step 2: Consultative Sales:**
|
||||||
|
* Durchführung einer detaillierten Standortanalyse (Site-Check) durch Wackler-Sicherheitsexperten, um die spezifischen Sicherheitsanforderungen und Herausforderungen des Kunden zu verstehen.
|
||||||
|
* Überprüfung der technischen Machbarkeit des Einsatzes des PUMA M20 unter Berücksichtigung der Umgebungsbedingungen (Gelände, Temperatur, Staub, Wasser).
|
||||||
|
* Identifizierung potenzieller Hindernisse und Anpassung der Lösung an die spezifischen Bedürfnisse des Kunden (z.B. Integration in bestehende Sicherheitssysteme, Anpassung der Sensorik).
|
||||||
|
* **Wichtige Constraints für den Site-Check:**
|
||||||
|
* **Gelände:** Beschaffenheit des Geländes (Steigungen, Unebenheiten, Treppen)
|
||||||
|
* **Umgebungsbedingungen:** Temperatur, Staub, Wasser, chemische Belastung
|
||||||
|
* **Abmessungen:** Zugänglichkeit von Bereichen (Durchgangsbreiten, Höhe)
|
||||||
|
* **Infrastruktur:** Verfügbarkeit von Stromversorgung, Netzwerkverbindung
|
||||||
|
|
||||||
|
* **Step 3: Proof of Value:**
|
||||||
|
* **Paid Pilot:** Durchführung eines zeitlich begrenzten, bezahlten Pilotprojekts in einer realen Einsatzumgebung des Kunden.
|
||||||
|
* **Definition der Pilotziele:** Klare Definition der zu erreichenden Ziele (z.B. Reduzierung von Sicherheitsvorfällen, Verbesserung der Reaktionszeiten, Kosteneinsparungen).
|
||||||
|
* **Messung der Ergebnisse:** Kontinuierliche Messung und Dokumentation der Ergebnisse während des Pilotprojekts.
|
||||||
|
* **Bewertung des Erfolgs:** Abschließende Bewertung des Pilotprojekts und Präsentation der Ergebnisse an den Kunden.
|
||||||
|
* **Vorteil des Paid Pilot:** Höheres Commitment des Kunden, realistische Testumgebung, Möglichkeit zur Generierung von Referenzen.
|
||||||
|
|
||||||
|
* **Step 4: Expansion:**
|
||||||
|
* **RaaS/Service Contracts:** Umwandlung des Pilotprojekts in einen langfristigen RaaS-Vertrag (Robot as a Service) oder Servicevertrag.
|
||||||
|
* **Skalierung der Lösung:** Ausweitung des Einsatzes des PUMA M20 auf weitere Bereiche oder Standorte des Kunden.
|
||||||
|
* **Kontinuierliche Verbesserung:** Kontinuierliche Überwachung und Optimierung der Lösung, um die Effizienz und Effektivität zu steigern.
|
||||||
|
* **Zusatzleistungen:** Angebot von zusätzlichen Dienstleistungen wie Schulungen, Wartung und Support.
|
||||||
|
|
||||||
|
## 7. Commercial Logic (ROI Framework)
|
||||||
|
|
||||||
|
* **ROI Calculation Logic:** Die Investition in den PUMA M20 und die Wackler-Dienstleistungen generiert einen Mehrwert durch die Reduzierung von Sicherheitsrisiken, die Verbesserung der Effizienz und die Senkung der Betriebskosten.
|
||||||
|
|
||||||
|
* **The Formula:**
|
||||||
|
|
||||||
|
**Net Value = (Reduzierte Risikokosten + Effizienzgewinne + Kosteneinsparungen) - Investitionskosten**
|
||||||
|
|
||||||
|
* **Reduzierte Risikokosten:** Wert der vermiedenen Schäden durch Sicherheitsvorfälle (z.B. Diebstahl, Vandalismus, Unfälle, Umweltschäden).
|
||||||
|
* **Effizienzgewinne:** Wert der gesteigerten Produktivität durch Automatisierung von Inspektions- und Überwachungsaufgaben.
|
||||||
|
* **Kosteneinsparungen:** Reduzierung von Personalkosten, Energiekosten und Wartungskosten.
|
||||||
|
* **Investitionskosten:** Kosten für den PUMA M20, die Wackler-Dienstleistungen und die Implementierung der Lösung.
|
||||||
|
|
||||||
|
* **Input Variables:**
|
||||||
|
|
||||||
|
* **Risikokosten pro Vorfall:** Durchschnittliche Kosten eines Sicherheitsvorfalls (Diebstahl, Vandalismus, Unfall, Umweltschaden).
|
||||||
|
* **Häufigkeit von Sicherheitsvorfällen:** Anzahl der Sicherheitsvorfälle pro Jahr vor und nach der Implementierung der Lösung.
|
||||||
|
* **Personalkosten für Überwachung:** Kosten für das Sicherheitspersonal, das für die Überwachung der Anlagen zuständig ist.
|
||||||
|
* **Zeitaufwand für Inspektionen:** Zeitaufwand für manuelle Inspektionen pro Jahr.
|
||||||
|
* **Kosten für Energie und Wartung:** Kosten für Energie und Wartung der bestehenden Überwachungssysteme.
|
||||||
|
* **Kosten für den PUMA M20 und die Wackler-Dienstleistungen:** Angebotspreis für die Lösung.
|
||||||
|
|
||||||
|
* **Example Calculation:**
|
||||||
|
|
||||||
|
Angenommen, ein Chemieunternehmen hat durchschnittliche Risikokosten von 50.000 € pro Sicherheitsvorfall und verzeichnete bisher 5 Vorfälle pro Jahr. Durch den Einsatz des PUMA M20 und der Wackler-Dienstleistungen kann die Häufigkeit der Vorfälle um 60% reduziert werden. Gleichzeitig werden 20% der Personalkosten für die Überwachung eingespart.
|
||||||
|
|
||||||
|
* **Reduzierte Risikokosten:** 5 Vorfälle * 50.000 € * 60% = 150.000 €
|
||||||
|
* **Effizienzgewinne:** (Annahme: 20% Effizienzsteigerung bei Inspektionen) – schwer zu quantifizieren ohne spezifische Daten.
|
||||||
|
* **Kosteneinsparungen:** (Annahme: 20% Einsparung bei Personalkosten) – muss kundenspezifisch ermittelt werden.
|
||||||
|
* **Investitionskosten:** (Annahme: 100.000 € pro Jahr für PUMA M20 und Wackler-Dienstleistungen)
|
||||||
|
|
||||||
|
**Net Value = (150.000 € + Effizienzgewinne + Kosteneinsparungen) - 100.000 €**
|
||||||
|
|
||||||
|
In diesem Beispiel würde die Investition in den PUMA M20 und die Wackler-Dienstleistungen bereits im ersten Jahr einen positiven Net Value generieren, wenn die Effizienzgewinne und Kosteneinsparungen die Investitionskosten übersteigen. Eine detaillierte ROI-Berechnung sollte jedoch immer auf den spezifischen Daten und Annahmen des Kunden basieren.
|
||||||
|
|
||||||
|
# SALES ENABLEMENT & VISUALS (PHASE 6)
|
||||||
|
|
||||||
|
## Kill-Critique Battlecards
|
||||||
|
|
||||||
|
### Persona: Operativer Entscheider (Anlagenleiter)
|
||||||
|
> **Objection:** "Der Roboter ist zu langsam und unzuverlässig für unsere anspruchsvollen Aufgaben. Wir brauchen sofortige Ergebnisse."
|
||||||
|
|
||||||
|
**Response:** Unsere Roboter sind für den 24/7-Einsatz konzipiert und arbeiten kontinuierlich ohne Ermüdung. Durch die Kombination aus Roboter-Patrouille und der schnellen Reaktionsfähigkeit unserer Wackler-Interventionskräfte garantieren wir eine deutliche Reduzierung von Reaktionszeiten. Unsere Roboter sind mit modernster SLAM-Navigation ausgestattet, um auch in komplexen Umgebungen zuverlässig zu navigieren. Wir bieten eine umfassende Leistungsgarantie und Support, um Ihre Erwartungen zu erfüllen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Infrastruktur Verantwortlicher (IT-Sicherheitsbeauftragter)
|
||||||
|
> **Objection:** "Der Einsatz von Robotern gefährdet unsere Datensicherheit und Compliance-Anforderungen (DSGVO, DGUV V3). Wer ist verantwortlich bei Unfällen?"
|
||||||
|
|
||||||
|
**Response:** Unsere Roboter sind mit modernsten Sicherheitsfunktionen ausgestattet, um Ihre Daten zu schützen. Alle Daten werden verschlüsselt und in Deutschland gehostet, um die DSGVO-Konformität zu gewährleisten. Unsere Lösungen sind DGUV V3-zertifiziert und erfüllen höchste Sicherheitsstandards. Wackler übernimmt die volle Verantwortung und Haftung für den Betrieb der Roboter, einschließlich der Unfallhaftung. Wir bieten umfassende Versicherungsdeckung und ein detailliertes Sicherheitskonzept, das alle relevanten Aspekte abdeckt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Wirtschaftlicher Entscheider (CFO)
|
||||||
|
> **Objection:** "Die Investition in Roboter ist zu teuer und der ROI ist unklar. Wir müssen Kosten senken und die Effizienz steigern."
|
||||||
|
|
||||||
|
**Response:** Unsere Roboterlösungen bieten eine deutliche Reduzierung der Betriebskosten durch die Automatisierung von Routineaufgaben und die Minimierung von Ausfallzeiten. Durch die Kombination aus Roboter-Technologie und den Dienstleistungen von Wackler erzielen Sie eine höhere Effizienz und eine verbesserte Ressourcennutzung. Wir bieten flexible Finanzierungsmodelle und eine detaillierte ROI-Analyse, um den wirtschaftlichen Nutzen unserer Lösung transparent darzustellen. Unsere Kunden berichten von einer durchschnittlichen Amortisationszeit von 12-18 Monaten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Persona: Innovations-Treiber (Leiter Digitalisierung)
|
||||||
|
> **Objection:** "Die Integration von Robotern in unsere bestehende Infrastruktur ist zu komplex und zeitaufwendig. Wir brauchen eine einfache und skalierbare Lösung."
|
||||||
|
|
||||||
|
**Response:** Unsere Roboter sind mit offenen APIs ausgestattet und lassen sich nahtlos in Ihre bestehenden Systeme integrieren. Wir bieten umfassende Integrationsunterstützung und Schulungen, um einen reibungslosen Übergang zu gewährleisten. Unsere Flottenmanagement-Software ermöglicht eine einfache Steuerung und Überwachung aller Roboter. Die Lösung ist modular aufgebaut und kann flexibel an Ihre individuellen Anforderungen angepasst werden. Wir arbeiten eng mit Ihnen zusammen, um eine maßgeschneiderte Lösung zu entwickeln, die Ihre Innovationsziele unterstützt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visual Briefings (Prompts)
|
||||||
|
|
||||||
|
### Öl- und Gasindustrie: Autonome Patrouille
|
||||||
|
*Context: Ein geländegängiger Sicherheitsroboter patrouilliert autonom entlang einer Pipeline in einem abgelegenen Ölfeld. Im Hintergrund sind Raffinerieanlagen zu sehen.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Fotorealistisches Bild eines robusten, geländegängigen Sicherheitsroboters, der autonom entlang einer Pipeline in einem Ölfeld patrouilliert. Der Roboter ist mit Kameras, Sensoren und einem Wackler Security Logo ausgestattet. Im Hintergrund sind Raffinerieanlagen und ein bewölkter Himmel zu sehen. Die Szene ist in der Abenddämmerung aufgenommen, um die Nachtsichtfähigkeiten des Roboters hervorzuheben. Der Fokus liegt auf der autonomen Navigation und der robusten Bauweise des Roboters.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chemieindustrie: Gefahrstoffüberwachung
|
||||||
|
*Context: Ein mit Gassensoren ausgestatteter Roboter überwacht einen Chemiepark auf Gasaustritte. Im Hintergrund sind Tanks und Produktionsanlagen zu sehen.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Fotorealistisches Bild eines Sicherheitsroboters, der mit Gassensoren ausgestattet ist und einen Chemiepark auf Gasaustritte überwacht. Der Roboter ist in der Nähe von Tanks und Produktionsanlagen positioniert. Die Szene ist hell und sonnig, um die Überwachungstechnologie des Roboters hervorzuheben. Im Hintergrund sind Dampfschwaden und ein blauer Himmel zu sehen. Der Fokus liegt auf der präzisen Sensorik und der kontinuierlichen Überwachung des Roboters.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bergbau: Sicherheitsinspektion
|
||||||
|
*Context: Ein robuster Roboter erkundet einen unzugänglichen Bereich in einem Bergwerk und überwacht die Luftqualität. Im Hintergrund sind Bergleute zu sehen.*
|
||||||
|
|
||||||
|
```
|
||||||
|
Fotorealistisches Bild eines robusten und geländegängigen Roboters, der einen unzugänglichen Bereich in einem Bergwerk erkundet. Der Roboter ist mit Sensoren zur Überwachung der Luftqualität ausgestattet. Im Hintergrund sind Bergleute mit Helmen und Lampen zu sehen. Die Szene ist dunkel und staubig, um die schwierigen Bedingungen im Bergwerk darzustellen. Der Fokus liegt auf der robusten Bauweise, der Geländegängigkeit und den Sicherheitsfunktionen des Roboters.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# VERTICAL LANDING PAGES (PHASE 7)
|
||||||
|
|
||||||
|
## Öl- und Gasindustrie
|
||||||
|
**Headline:** Maximale Anlagensicherheit und Produktionsverfügbarkeit für Ihre Öl- und Gasförderung.
|
||||||
|
|
||||||
|
**Subline:** Unbefugter Zutritt, Diebstahl und Sicherheitsrisiken gefährden Ihre Anlagen? Unsere geländegängigen Sicherheitsroboter in Kombination mit den Wackler Security-Experten gewährleisten eine umfassende Überwachung und sofortige Intervention.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Autonome Patrouillen in schwer zugänglichen Gebieten reduzieren das Risiko von Produktionsausfällen und minimieren Inspektionskosten.
|
||||||
|
- Frühzeitige Erkennung von Bedrohungen durch modernste Sensorik und KI-gestützte Analyse ermöglichen eine proaktive Reaktion und verhindern Schäden.
|
||||||
|
- Die Kombination aus Roboter-Präsenz und Wackler Security-Einsatzkräften garantiert eine lückenlose Sicherheitslösung: Der Roboter sieht die Gefahr, Wackler beseitigt sie.
|
||||||
|
- 24/7 Überwachung ohne Ermüdung oder Ablenkung sorgt für kontinuierliche Sicherheit und schützt Ihre Mitarbeiter und Anlagen.
|
||||||
|
- Detaillierte Berichterstattung und Echtzeit-Datenanalyse liefern wertvolle Einblicke zur Optimierung Ihrer Sicherheitsstrategie und zur Einhaltung von Compliance-Anforderungen.
|
||||||
|
|
||||||
|
**CTA:** Fordern Sie jetzt eine kostenlose Sicherheitsanalyse an!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chemieindustrie
|
||||||
|
**Headline:** Schutz vor Gefahrstoffaustritten und Bränden: Sichern Sie Ihre Chemieanlage mit unserer innovativen Robotik-Lösung.
|
||||||
|
|
||||||
|
**Subline:** Gefahrstoffaustritte und Brände bedrohen Ihre Anlagen und Mitarbeiter? Unsere mit Sensorik ausgestatteten Roboter in Kombination mit der Wackler-Notrufzentrale überwachen kontinuierlich Ihre Anlagen und ermöglichen eine schnelle Reaktion im Notfall.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Kontinuierliche Überwachung auf Gasaustritte, Temperaturerhöhungen und andere Anomalien ermöglicht eine frühzeitige Warnung und verhindert Schlimmeres.
|
||||||
|
- Mobile Roboter sorgen für eine flächendeckende und lückenlose Überwachung, im Gegensatz zu statischen Systemen.
|
||||||
|
- Die Wackler-Notruf- und Serviceleitstelle (NSL) bewertet im Alarmfall die Situation und entsendet bei Bedarf umgehend Interventionskräfte.
|
||||||
|
- Reduzierung des Risikos für Mitarbeiter durch Übernahme von Routineinspektionen und Überwachungsaufgaben in potenziell gefährlichen Bereichen.
|
||||||
|
- Erfüllung höchster Sicherheitsstandards und Compliance-Anforderungen durch umfassende Dokumentation und lückenlose Überwachung.
|
||||||
|
|
||||||
|
**CTA:** Vereinbaren Sie jetzt eine Demo unserer Sicherheitslösung!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BUSINESS CASE & ROI (PHASE 8)
|
||||||
|
|
||||||
|
## Öl- und Gasindustrie
|
||||||
|
**Cost Driver:** Stunden für manuelle Inspektionen und Sicherheitsüberwachung von Pipelines und Anlagen.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Schätzung: 40-60% Reduktion der manuellen Inspektionszeit durch autonome Roboterpatrouillen.
|
||||||
|
|
||||||
|
**Risk Argument:** Die Kosten eines Pipeline-Lecks oder einer Anlagenexplosion können sich auf Millionen Euro belaufen, einschliesslich Umweltschäden, Produktionsausfall und Strafen. Ein Security Roboter kann Lecks frühzeitig erkennen und so katastrophale Schäden verhindern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chemieindustrie
|
||||||
|
**Cost Driver:** Stunden für manuelle Überwachung von Gaskonzentrationen und Anlagenzustand in potenziell gefährlichen Bereichen.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Schätzung: 30-50% Reduktion der manuellen Überwachungszeit und verbesserte Datengenauigkeit.
|
||||||
|
|
||||||
|
**Risk Argument:** Ein Chemieunfall kann zu erheblichen Personenschäden, Umweltschäden und Produktionsausfällen führen. Die finanziellen Folgen können sich auf Millionen Euro belaufen, zuzüglich Reputationsschäden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bergbau
|
||||||
|
**Cost Driver:** Stunden für manuelle Inspektion und Überwachung von Minen, insbesondere in unzugänglichen oder gefährlichen Bereichen.
|
||||||
|
|
||||||
|
**Efficiency Gain:** Schätzung: 50-70% Reduktion der manuellen Inspektionszeit und verbesserte Datenerfassung in schwer zugänglichen Bereichen.
|
||||||
|
|
||||||
|
**Risk Argument:** Minenunfälle können zu schweren Verletzungen oder Todesfällen von Bergleuten führen. Die Stilllegung einer Mine nach einem Unfall verursacht erhebliche finanzielle Verluste und Reputationsschäden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# FEATURE-TO-VALUE TRANSLATOR (PHASE 9)
|
||||||
|
|
||||||
|
| Feature | The Story (Benefit) | Headline |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Geländegängigkeit: Bewältigt unebenes Gelände, Treppen und Steigungen bis zu 45°. | Der Roboter kann sich in anspruchsvollem Gelände bewegen. Das bedeutet, er kann Bereiche erreichen, die für Menschen schwer zugänglich sind. Das führt zu einer umfassenderen Überwachung und Inspektion von Anlagen, unabhängig von der Topographie. | Überwindet jedes Hindernis für lückenlose Sicherheit. |
|
||||||
|
| Autonome Navigation: SLAM-basierte Navigation für autonome Missionen und Rückkehr zur Basis. | Der Roboter navigiert selbstständig und kehrt automatisch zur Basis zurück. Das bedeutet, dass keine ständige Fernsteuerung erforderlich ist. Das führt zu einer Reduzierung des Personalaufwands und ermöglicht einen 24/7-Betrieb ohne menschliches Eingreifen. | Autonom im Einsatz – Rund um die Uhr, ohne Pause. |
|
||||||
|
| 360°-Umgebungserfassung: Duale LiDAR-Systeme und Weitwinkelkameras für umfassende Umgebungswahrnehmung. | Der Roboter erfasst die Umgebung vollständig in 360 Grad. Das bedeutet, dass keine toten Winkel entstehen und alle potenziellen Gefahren erkannt werden. Das führt zu einer verbesserten Situationserkennung und schnelleren Reaktionszeiten auf Bedrohungen. | Lückenlose Überwachung – Keine Gefahr bleibt unentdeckt. |
|
||||||
|
| Nachtsichtfähigkeit: Optionale Nacht- und Wärmebildkamera. | Der Roboter kann auch bei Dunkelheit und schlechten Sichtverhältnissen operieren. Das bedeutet, dass die Sicherheit auch nachts gewährleistet ist. Das führt zu einer kontinuierlichen Überwachung, unabhängig von den Lichtverhältnissen. | Sicherheit kennt keine Dunkelheit – Überwachung rund um die Uhr. |
|
||||||
|
| Robuste Bauweise: IP66-Schutz für Staub- und Wasserbeständigkeit, Betriebstemperaturbereich von -20°C bis 55°C. | Der Roboter ist widerstandsfähig gegen extreme Umweltbedingungen. Das bedeutet, dass er auch in rauen Umgebungen zuverlässig funktioniert. Das führt zu einer Minimierung von Ausfallzeiten und einer längeren Lebensdauer. | Trotzt jedem Wetter – Zuverlässigkeit unter Extrembedingungen. |
|
||||||
|
| Hohe Zuladung: Bis zu 50 kg maximale Zuladung. | Der Roboter kann schwere Lasten transportieren. Das bedeutet, dass er Ausrüstung und Sensoren mitführen kann. Das führt zu einer flexibleren Einsatzmöglichkeit und erweitert das Spektrum der Aufgaben, die er übernehmen kann. | Starke Leistung – Transportiert alles, was Sie brauchen. |
|
||||||
|
| Lange Betriebsdauer: Bis zu 3 Stunden Betriebsdauer, erweiterbar durch Hot-Swap-Batterien. | Der Roboter hat eine lange Betriebsdauer und kann bei Bedarf schnell mit neuen Batterien ausgestattet werden. Das bedeutet, dass er lange Einsätze ohne Unterbrechung durchführen kann. Das führt zu einer höheren Effizienz und weniger Ausfallzeiten. | Dauerläufer – Lange Einsatzzeiten für maximale Effizienz. |
|
||||||
|
| Erweiterte Rechenleistung: Duale Octa-Core 64-Bit Industrieprozessoren. | Der Roboter verfügt über eine hohe Rechenleistung. Das bedeutet, dass er komplexe Aufgaben schnell und effizient bearbeiten kann. Das führt zu einer schnelleren Datenverarbeitung und präziseren Entscheidungsfindung. | Intelligente Analyse – Schnelle Datenverarbeitung für präzise Ergebnisse. |
|
||||||
|
| Flexible Integration: Flottenmanagement und API-Integrationen für Datenaustausch. | Der Roboter lässt sich einfach in bestehende Systeme integrieren. Das bedeutet, dass ein nahtloser Datenaustausch möglich ist. Das führt zu einer besseren Übersicht und Kontrolle über die Roboterflotte. | Nahtlose Integration – Einfache Einbindung in Ihre bestehende Infrastruktur. |
|
||||||
|
| Anpassbare Nutzlasten: Unterstützung für LiDAR, Thermal-, PTZ-, Gassensoren und Beacons. | Der Roboter kann mit verschiedenen Sensoren ausgestattet werden. Das bedeutet, dass er für unterschiedliche Aufgaben angepasst werden kann. Das führt zu einer hohen Flexibilität und erweitert das Einsatzspektrum. | Vielseitig einsetzbar – Konfigurierbar für Ihre spezifischen Anforderungen. |
|
||||||
Reference in New Issue
Block a user