Files
bewerb/.claude/skills/projekt-anlegen/SKILL.md
tlg 16156db5c8 Erste Bewerbungs-Session: SÜ2-K.o.-Regel, Agentur-Schreibregeln, Entwurf FindYou
- rahmenbedingungen.md + Skill projekt-anlegen: Sicherheitsüberprüfung (SÜ/SÜ2)
  immer  (Dauer ≥ 3 Monate, K.-o.-Kriterium)
- gedaechtnis/stil.md: Agentur-Anschreiben-Regeln (Teaser, kein Techno-Blabla,
  keine falschen Behauptungen, Lücken nicht erwähnen, Absatz-Aufbau, Header,
  feste Grußformel)
- gedaechtnis/schwerpunkte.md: Fachthemen + Querschnitts-Schlagworte ergänzt
- gedaechtnis/inhalte.md: geprüfte Fakten und Nicht-Behauptungen
- bewerbungen/: Entwurf FindYou (Data Scientist/MLOps) + Register-Eintrag
- gedaechtnis/log.md: datierter Eintrag

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 18:14:13 +02:00

13 KiB
Raw Blame History

name, description
name description
projekt-anlegen Bewertet Freiberufler-Projektausschreibungen gegen den Lebenslauf von Dr.-Ing. Thomas Langer und trägt das Ergebnis als Verkaufschance (Opportunity) ins EspoCRM ein: Anforderungen extrahieren, in Must/Nice/Misc einordnen, Abdeckung mit // bewerten, Must-have-Match und Nice-to-have-Match berechnen, nach Review im Chat ins CRM schreiben. Verwende diesen Skill IMMER, wenn der Nutzer ein Projektangebot, eine Projektausschreibung oder ein Stellenangebot für Freiberufler bewerten oder ins CRM eintragen will (z. B. von freelancermap, GULP/Randstad, Hays, SThree, etwa „Bewerte das Projekt", „Projekt-Match", „Passt dieses Projekt zu mir?", „Anforderungen abgleichen", „Projekt anlegen", „Projekt ins CRM eintragen").

Projekt anlegen: Projektangebote bewerten und ins EspoCRM eintragen

Pipeline: Ausschreibung beschaffen → Anforderungen + Käufer/Firma/Kontakt extrahieren → einordnen → gegen Lebenslauf bewerten → Rahmenbedingungen gegen Vorgaben bewerten → Matches berechnen → Review im Chat (Pflicht) → Firma → Kontakt → Verkaufschance ins EspoCRM schreiben (per Shell) → verifizieren.

Voraussetzungen und feste Pfade

Zweck Pfad
Lebenslauf (Markdown) vorgaben/Lebenslauf_Dr-Ing_Thomas_Langer.md
Rahmenbedingungen (Misc-Bewertung) vorgaben/rahmenbedingungen.md
EspoCRM-Zugangsdaten .secrets/espocrm-api.md

Alle drei mit dem Read-Tool lesen. Ohne Lebenslauf keine fachliche Bewertung, ohne Rahmenbedingungen keine automatische Misc-Bewertung, ohne Zugangsdaten kein CRM-Eintrag.

Die Zugangsdaten-Datei enthält Base-URL, Auth-Header-Schema, API-Key sowie verifizierte Entitäts- und Feldnamen. Der API-Key darf niemals im Chat angezeigt, in Dateien geschrieben oder in Commits/Doku übernommen werden — er wird nur zur Laufzeit in eine Shell-Variable gelesen und direkt in curl-Aufrufe eingesetzt.

Schritt 1: Ausschreibung beschaffen und Eckdaten extrahieren

Die Projektausschreibung kommt auf zwei Wegen:

  • URL: Nennt der Nutzer eine URL, hole den Seitentext mit dem WebFetch-Tool.
  • Text: Fügt der Nutzer den Ausschreibungstext direkt in den Chat ein, nutze diesen.

Bei Login-geschützten Portalen ist das Einfügen von Text nötig. Ist unklar, welche Quelle gilt, nachfragen.

Daraus entnehmen:

  • Projektname: der Titel der Ausschreibung, ohne Portal-Zusatz (z. B. ohne „auf www.freelancermap.de").
  • Projekt-URL: die tatsächliche URL der Seite, falls vorhanden.
  • Beschreibung und Aufgaben: nur Kontext für die Bewertung, keine Tabellenzeilen.
  • Anforderungen: jede Anforderung einzeln, im Originalwortlaut (behutsam kürzen ist erlaubt, Bedeutung nie verändern). Rahmenbedingungen (Start, Einsatzort/Remote-Anteil, Auslastung, Laufzeit) zählen als Anforderungen der Kategorie Misc.
  • Käufer-Typ: Agentur (Wiederverkäufer) ODER Endkunde (Direktauftrag).
    • Agentur-Signale: bekannte Personaldienstleister/Vermittler (Hays, GULP/Randstad, SThree/Computer Futures, Aristo …), Formulierungen wie „im Auftrag unseres Kunden", „für unseren Kunden", Vermittler-Kontext des Portals.
    • Nicht eindeutig bestimmbar → im Review nachfragen.
  • Firmenname: Name der Agentur bzw. des Endkunden.
  • Ansprechperson: vollständiger Name, falls genannt.

Schritt 2: Einordnung (Kat.)

Jede Anforderung bekommt genau eine Kategorie:

  • Must — zwingend. Signale: eigener Abschnitt „Must-haves"/„Anforderungen", Wörter wie „zwingend", „erforderlich", „vorausgesetzt", „sehr gute Kenntnisse".
  • Nice — optional. Signale: Abschnitt „Nice-to-haves", Wörter wie „von Vorteil", „wünschenswert", „idealerweise", „plus".
  • Misc — Rahmenbedingungen (Start, Ort, Auslastung, Laufzeit) und alles, was weder Muss noch Wunsch-Qualifikation ist.

Explizite Abschnittsüberschriften der Ausschreibung haben Vorrang vor Signalwörtern. Steht „idealerweise" innerhalb einer Must-Anforderung (z. B. „… und idealerweise Kubernetes"), bleibt die Zeile Must — der idealerweise-Teil ist dann für die Bewertung nicht ausschlaggebend.

Schritt 3: Bewertung gegen den Lebenslauf

Lebenslauf vollständig lesen und jede Anforderung nüchtern bewerten:

  • nur bei klarer Evidenz im Lebenslauf.
  • wenn der Lebenslauf nichts Belastbares hergibt. Streng bleiben — eine geschönte Bewertung macht die Match-Zahlen wertlos. Kalibrierung aus der Praxis: Proof-of-Concept-Erfahrung deckt „produktiven Betrieb" nicht ab; „mehrjährig" wörtlich nehmen; ein ALM-Produktname (z. B. „Azure DevOps Server") belegt keine Cloud-Plattform-Erfahrung.
  • nur bei echter Teilevidenz, wenn die Entscheidung von Wissen abhängt, das nur Thomas hat — solche Fälle klärt das Review.
  • „wie z. B."-Aufzählungen: gleichwertige Alternativen zählen als Abdeckung (Beispiel: Ollama/llama.cpp/Transformers decken „LLM-Inference-Stacks wie z. B. vLLM, TGI, Triton" ab).
  • Rahmenbedingungen (Misc) werden NICHT hier, sondern in Schritt 3b automatisch gegen vorgaben/rahmenbedingungen.md bewertet.

Schritt 3b: Rahmenbedingungen automatisch bewerten (Misc)

vorgaben/rahmenbedingungen.md lesen und jede Misc-Anforderung danach bewerten:

  • Verfügbar ab / Projektstart: Start ≤ heute + 8 Wochen (56 Tage) → ; > 8 Wochen → ; nicht genannt → . („heute" = aktuelles Datum.)
  • Auslastung: 75100 % → ; < 75 % → ; nicht genannt → .
  • Laufzeit: jede → .
  • Einsatzort/Remote: 100 % Remote → . Bei < 100 % Remote (Hybrid/Onsite) Distanz des Onsite-Orts zu Sauerlach bestimmen (siehe unten): ≤ 50 km → ; 5060 km → ; > 60 km → . Einsatzort/Remote-Anteil unklar oder nicht genannt → .
  • Sicherheitsüberprüfung: Wird eine Sicherheitsüberprüfung gefordert (SÜ, SÜ1/SÜ2/SÜ3, Ü2, Geheimschutz o. Ä.) → immer (Dauer ≥ 3 Monate, K.-o.-Kriterium). Im Review als K.-o. hervorheben.

Distanzbestimmung (Geokodierung, Pflicht bei Onsite/Hybrid): Onsite-Ort per Nominatim auflösen und Luftlinie zu Sauerlach (47,9721 N / 11,6528 O) per Haversine rechnen. Bei Geokodierungs-Fehler/Mehrdeutigkeit (Ausgabe UNBEKANNT) → . Nominatim-Policy: max. 1 Request/s.

python3 - "<ONSITE_ORT>" <<'PY'
import sys, json, urllib.request, urllib.parse
from math import radians, sin, cos, asin, sqrt
ort = sys.argv[1]
UA = 'bewerb-projekt-anlegen/1.0 (Thomas.Langer@destengs.com)'
def geo(q):
    url = 'https://nominatim.openstreetmap.org/search?' + urllib.parse.urlencode(
        {'q': q, 'format': 'json', 'countrycodes': 'de', 'limit': 1})
    d = json.load(urllib.request.urlopen(
        urllib.request.Request(url, headers={'User-Agent': UA}), timeout=20))
    return (float(d[0]['lat']), float(d[0]['lon'])) if d else None
S = (47.9721357, 11.6528398)
g = geo(ort)
if not g:
    print('UNBEKANNT')
else:
    dlat = radians(g[0]-S[0]); dlon = radians(g[1]-S[1])
    h = sin(dlat/2)**2 + cos(radians(S[0]))*cos(radians(g[0]))*sin(dlon/2)**2
    print(f'{2*6371*asin(sqrt(h)):.1f} km')
PY

Schritt 4: Match-Berechnung

  • Must-have-Match: Mittelwert über alle Must-Zeilen mit = 100 %, = 25 %, = 0 %.
  • Nice-to-have-Match: dasselbe über alle Nice-Zeilen.
  • Misc-Zeilen gehen in keinen Match ein.
  • Auf ganze Prozent runden. Ist eine Kategorie leer, „–" statt einer Zahl schreiben.

Schritt 5: Ausgabeformat (exakt einhalten)

Der Inhalt des CRM-Feldes „Beschreibung" — genau dieses Format, Spaltenüberschriften wörtlich (Kat. und halten die Spalten schmal):

Must-have-Match: 50 % · Nice-to-have-Match: 33 %

| Nr. | Kat. | ❔ | Anforderung |
|---|---|---|---|
| 1 | Must | ❌ | Mehrjährige praktische Erfahrung im Aufbau und Betrieb von AI-/LLM-Plattformen |
| 2 | Must | ✅ | Erfahrung mit RAG-Systemen, Vektordatenbanken und Anbindung relevanter Datenquellen |
| 3 | Nice | ✅ | Kenntnisse in LLM-Inference-Stacks (z. B. vLLM, TGI, Triton, Ray) |
| 4 | Misc | ✅ | Hybrid: ca. 40 % onsite München, 60 % remote |
  • Nr. fortlaufend ab 1, Reihenfolge: erst alle Must-, dann Nice-, dann Misc-Zeilen (innerhalb der Kategorie in Reihenfolge der Ausschreibung).
  • Kat. ∈ {Must, Nice, Misc}; Bewertungsspalte ∈ {, , }.
  • Beide Match-Zahlen in einer Zeile, getrennt durch ·.

Schritt 6: Review im Chat — immer vor jedem CRM-Schreibvorgang

Niemals ohne explizite Freigabe ins CRM schreiben. Im Chat zeigen:

  1. Vorgesehener Name der Opportunity (= Projektname) und Projektlink.
  2. Käufer-Typ (Agentur/Direktauftrag) mit Kurzbegründung.
  3. Firma: Name und type (Reseller bei Agentur, Customer bei Direktauftrag); „neu anlegen" oder „bestehende nutzen: <Name/ID>". Bei ähnlichen, nicht identischen Treffern die Kandidatenliste zeigen und entscheiden lassen.
  4. Kontakt: Name; „neu anlegen" oder „bestehend nutzen: <Name/ID>".
  5. Verknüpfungs-Zuordnung der Verkaufschance (account vs. cAccount1, Kontakt).
  6. Den wörtlichen Beschreibungs-Markdown in einem Codeblock.
  7. Kurze Begründungen zu allen - und -Bewertungen (Must/Nice/Misc) außerhalb der Tabelle.

Korrekturen kommen als „Nr. X → //" oder als Korrektur zu Firma/Kontakt/Typ. Danach betroffene Matches neu berechnen und geänderte Werte nennen. Erst nach Freigabe weiter zu Schritt 7.

Schritt 7: CRM-Eintrag per Shell — Reihenfolge Firma → Kontakt → Verkaufschance

Diese Umgebung erreicht die CRM-Domain direkt; der Zugriff läuft per curl. Key/Base sicher laden (Werte nicht ausgeben):

KEY=$(grep -oE '[0-9a-f]{32}' .secrets/espocrm-api.md | head -1)
BASE=$(grep -oE 'https://[^ `]+/api/v1' .secrets/espocrm-api.md | head -1)

7.1 Firma (Account)

Dedup-Suche mit einem Kern-Token des Namens (z. B. „Aristo"):

curl -s -G "$BASE/Account" -H "X-Api-Key: $KEY" \
  --data-urlencode 'where[0][type]=contains' \
  --data-urlencode 'where[0][attribute]=name' \
  --data-urlencode 'where[0][value]=<KERN>' \
  --data-urlencode 'select=name,type' --data-urlencode 'maxSize=50'

Treffer gemäß Review-Entscheidung behandeln. Bestehende Firma → deren id als ACCOUNT_ID merken. Sonst neu anlegen — Payload mit dem Write-Tool nach /tmp/account.json (type: Reseller bei Agentur, Customer bei Direktauftrag):

{"name": "<FIRMENNAME>", "type": "<Reseller|Customer>"}
curl -s -i -X POST "$BASE/Account" -H "X-Api-Key: $KEY" -H 'Content-Type: application/json' --data @/tmp/account.json

id aus der Antwort als ACCOUNT_ID merken.

7.2 Kontakt (Contact)

Dedup-Suche per Nachname:

curl -s -G "$BASE/Contact" -H "X-Api-Key: $KEY" \
  --data-urlencode 'where[0][type]=contains' \
  --data-urlencode 'where[0][attribute]=name' \
  --data-urlencode 'where[0][value]=<NACHNAME>' \
  --data-urlencode 'select=name,accountName' --data-urlencode 'maxSize=50'

Bestehenden Kontakt → dessen id als CONTACT_ID merken. Sonst neu anlegen — Payload nach /tmp/contact.json; die Verknüpfung zur Firma über accountId ist beim Neu-Anlegen zwingend:

{"firstName": "<VORNAME>", "lastName": "<NACHNAME>", "accountId": "<ACCOUNT_ID>"}
curl -s -i -X POST "$BASE/Contact" -H "X-Api-Key: $KEY" -H 'Content-Type: application/json' --data @/tmp/contact.json

id aus der Antwort als CONTACT_ID merken. Namensaufteilung: letztes Token = lastName, der Rest = firstName; ungewöhnliche Fälle im Review klären.

7.3 Verkaufschance (Opportunity)

Duplikat-Prüfung des Namens (bei Treffer Suffix „ (2)", „ (3)", … anhängen):

curl -s -G "$BASE/Opportunity" -H "X-Api-Key: $KEY" \
  --data-urlencode 'where[0][type]=startsWith' \
  --data-urlencode 'where[0][attribute]=name' \
  --data-urlencode 'where[0][value]=<PROJEKTNAME>' \
  --data-urlencode 'select=name' --data-urlencode 'maxSize=100'

Payload nach /tmp/opp.json. Verknüpfungen je Käufer-Typ:

  • Agentur: cAccount1Id = ACCOUNT_ID („Über Agentur"); kein accountId.
  • Direktauftrag: accountId = ACCOUNT_ID; kein cAccount1Id.
{"name": "<EINDEUTIGER_NAME>", "description": "<BESCHREIBUNG_MARKDOWN>", "cProjektlink": "<PROJEKT_URL>", "contactsIds": ["<CONTACT_ID>"], "cAccount1Id": "<ACCOUNT_ID — nur bei Agentur, sonst Feld weglassen und stattdessen accountId setzen>"}
curl -s -i -X POST "$BASE/Opportunity" -H "X-Api-Key: $KEY" -H 'Content-Type: application/json' --data @/tmp/opp.json

Minimaler Payload genügt (Stage/Wahrscheinlichkeit setzt das CRM). Bei HTTP 400 steht die Ursache im X-Status-Reason-Header. Nachträgliche Korrekturen: PUT "$BASE/Opportunity/<id>". Danach /tmp/account.json /tmp/contact.json /tmp/opp.json löschen.

Schritt 8: Verifizieren und berichten

Nach dem POST den Datensatz gegenlesen:

curl -s -G "$BASE/Opportunity/<id>" -H "X-Api-Key: $KEY"

Prüfen: stimmen Name und Projektlink, hat die Beschreibung die Match-Zeile und alle Tabellenzeilen, sind // intakt; sind die Verknüpfungen korrekt — Agentur: cAccount1Name gesetzt und accountName leer; Direktauftrag: accountName gesetzt; contactsIds/contactId enthält den Kontakt. Dann im Chat melden: gewählter Name (inkl. evtl. Duplikat-Suffix), beide Match-Zahlen, Firma und Kontakt (jeweils neu/bestehend) und der Direktlink https://crm.creature-go.com/#Opportunity/view/<id>.