commit 871c2763428c87edbe8ec5879ebf210791493386 Author: Sebastian Egli Date: Thu Aug 28 17:19:54 2025 +0200 initial commit with first prototype diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c780f7d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +.env \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bb7b8bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# 1. Use a lightweight Python image +FROM python:3.12-slim + +# 2. Environment settings +ENV PYTHONUNBUFFERED=1 +ENV PIP_NO_CACHE_DIR=1 + +# 3. Set working directory inside the container +WORKDIR /app + +# 4. Copy and install dependencies first (better caching) +COPY requirements.txt . +RUN pip install -r requirements.txt + +# 5. Copy all your app files into the container +COPY . . + +# 6. Set the default command to run your script +ENTRYPOINT ["python", "job_suggestor.py"] \ No newline at end of file diff --git a/already_suggested_companies.txt b/already_suggested_companies.txt new file mode 100644 index 0000000..091424b --- /dev/null +++ b/already_suggested_companies.txt @@ -0,0 +1 @@ +Bereits vorgeschlagene Unternehmen \ No newline at end of file diff --git a/cv_sebastian_egli.txt b/cv_sebastian_egli.txt new file mode 100644 index 0000000..5bcc76d --- /dev/null +++ b/cv_sebastian_egli.txt @@ -0,0 +1,127 @@ +Dr. SebastianEgli + +Geoinformatiker + +Person +Geb: 18.01.1986 +Nat: Deutsch, Schweiz + +Adresse +A. d. Schülerhecke 13 +35037 Marburg +Deutschland + +Tel & E-Mail ++49 174 2465052 +seb.egli@gmail.com + +Schwerpunkte +Business Intelligence +Datenanalyse +Erdbeobachtung (EO) +Maschinelles Lernen +Agrarmodellierung +Klimatologie + +Werkzeuge +QGIS, GDAL, OGR +Pandas, Numpy +Xarray, Satpy +Scikit-learn +Tensorflow, Mxnet +Open Data Cube +STAC, ERPNext +airtable, metabase +n8n, Kobo, Python +Java, R, Kotlin + +Sprachen +Deutsch +Englisch +Französisch + +Berufserfahrung +01/14 - heute Mitgründer & Entwickler +Bridgesoft GbR, Marburg, Deutschland +Entwicklung mobiler Anwendungen für mittelständische Unternehmen. Part- +ner: PC Gärtner GmbH, Ökobox-Online, Bosshammersch Hof, Infralytic +GmbH + +09/22 - 07/25 Chief Data Scientist +agriBORA GmbH, Deutschland/Kenia +Teamleitung, Projekt- und Produktkonzeption, Landwirtschaftliches Moni- +toring und Modellierung, satellitengestützte Feldflächenerkennung, +Ertragsprognose, Kreditbewertung mittels maschinellem Lernen + +11/19 - 08/22 Wissenschaftlicher Mitarbeiter LCRS, Philipps-Universität Marburg, Deutschland +Atmosphärische Fernerkundung, Softwareentwicklung für Lehrzwecke in +der Atmosphärenwissenschaft, Entwicklung von Anwendungen für das +Waldmanagement mittels UAV-Einsatz, Maschinelles Lernen + +06/15 - 05/19 Wissenschaftlicher Mitarbeiter LCRS, Philipps-Universität Marburg, Deutschland +Erkennung von Wolken und Nebel in geostationären Satellitendaten, Ent- +wicklung von Fernerkundungsalgorithmen, Integration von Anwendungen +des maschinellen Lernens + +03/13 - 01/14 Wissenschaftlicher Mitarbeiter LCRS, Philipps-Universität Marburg, Deutschland +Untersuchung der vertikalen Verteilung mikrophysikalischer Eigenschaften +in Strahlungsnebeln mittels Radarprofilierung und ballongetragenen Profil- +messungen. + +05/11 - 01/13 Studentische Hilfskraft +LCRS, Philipps-Universität Marburg, Deutschland +Satellitendatenakquise, Verwaltung operationeller Stationsdaten der Mar- +burger Ground Truth- und Profiling-Station, Entwicklung von Werkzeugen +zur automatischen Datenkonvertierung verschiedener atmosphärischer +Messinstrumente. + +02/11 - 04/11 Praktikant +Deutscher Wetterdienst (DWD), Offenbach, Deutschland +Erfassung verfügbarer In-situ-Hageldaten beim DWD, Durchführbarkeits- +analyse einer Hagelklimatologie, Analyse räumlich-zeitlicher Variationen +der Hagelhäufigkeit und Erstellung von Hagelrisikokarten. + +02/09 - 04/09 Praktikant +Bundesamt für Kartographie und Geodäsie (BKG), Frankfurt, Deutschland +Entwicklung eines digitalen Landschaftsmodells (Maßstab: 1:1.000.000) +auf Grundlage der beim BKG verwendeten digitalen topografischen Kar- +tenbasis. + +Preise +10/20 Wissenschaftspreis 2019 +Industrie- und Handelskammer Kassel-Marburg +Der Preis wird zur Würdigung herausragender Dissertationen verliehen, die +eine hohe Relevanz für wirtschaftliche Fragestellungen aufweisen. +Verliehen für die Dissertation: “Satellite-Based Fog Detection: A Dynamic +Retrieval Method for Europe Based on Machine Learning“ + +Ausbildung +06/15 - 05/19 Promotion in Geographie +LCRS, Philipps-Universität Marburg, Deutschland +Hauptthemen: Nebelerkennung, satellitengestützte Fernerkundung, +maschinelles Lernen +Titel der Dissertation: “Satellite-Based Fog Detection: A Dynamic Retrieval +Method for Europe Based on Machine Learning“ (Note: 1,0) + +10/10 - 01/13 M.Sc. Umweltgeographie +Philipps-Universität Marburg, Deutschland +Hauptfach: Physische Geographie +Nebenfach: Informatik +Titel der Masterarbeit: “The influence of drop size distributions on the rela- +tionship between liquid water content and radar reflectivity“ (Note: 15,0) + +10/08 - 09/10 B.Sc. Geographie +Philipps-Universität Marburg, Deutschland +Hauptfach: Physische Geographie +Nebenfächer: Geologie & Informatik +Zusätzliches Modul: Friedens- und Konfliktforschung +Titel der Bachelorarbeit: “Validation of the simulation results of the winter +storm Kyrill“ (Note: 13,0) + +08/06 - 07/08 Bachelorstudium in Geographie Universität Zürich, Schweiz +Hauptfächer: Kartographie, Klimatologie & GIS + +09/96 - 07/05 Weiterführende Schule Klettgau-Gymnasium Tiengen, Deutschland +Abschluss: Abitur (Note: 11,0) + +09/92 - 07/96 Grundschule Grundschule Kadelburg, Deutschland \ No newline at end of file diff --git a/job_suggestor.ipynb b/job_suggestor.ipynb new file mode 100644 index 0000000..a380500 --- /dev/null +++ b/job_suggestor.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "cc3d4ef7", + "metadata": {}, + "outputs": [], + "source": [ + "from google import genai\n", + "from google.genai import types\n", + "from datetime import datetime\n", + "from pydantic import BaseModel\n", + "import os\n", + "\n", + "class JobSuggestion(BaseModel):\n", + " company_name: str\n", + " company_description: str\n", + " job_title: str\n", + " application_text: str\n", + "\n", + "# Load CV from file\n", + "with open(\"cv_sebastian_egli.txt\", \"r\", encoding=\"utf-8\") as f:\n", + " cv = f.read()\n", + "\n", + "# Load already suggested companies from file (to avoid duplicates)\n", + "with open(\"already_suggested_companies.txt\", \"r\", encoding=\"utf-8\") as f:\n", + " already_suggested_companies = f.read()\n", + "\n", + "# Define boolean for even-numbered days (I want to switch un/solicited every day)\n", + "today = datetime.today()\n", + "unsolicited_application = today.day % 2 == 0\n", + "\n", + "if unsolicited_application:\n", + " query = [\"\"\"Analysiere meinen Lebenslauf (cv) und die Liste bereits vorgeschlagener Unternehmen (already_suggested_companies). \n", + "Schlage mir genau ein Unternehmen in der Nähe von Marburg vor, das noch nicht in already_suggested_companies enthalten ist \n", + "und für eine Initiativbewerbung besonders gut zu meinem Profil passt. \n", + "\n", + "Erkläre in maximal 150 Wörtern prägnant, warum dieses Unternehmen eine besonders gute Wahl für mich wäre. \n", + "Formuliere die Antwort als kurze, professionelle Email mit dem Titel: 'Neuer Jobvorschlag'. \n", + "Begründe die Eignung anhand meiner Qualifikationen und Interessen. \n", + "Füge außerdem einen einzelnen Satz hinzu, der direkt in einem Bewerbungsschreiben genutzt werden könnte, \n", + "um die Übereinstimmung zwischen mir und dem Unternehmen hervorzuheben. \n", + "\n", + "Liefere nur die Email, keine zusätzliche Erklärung.\"\"\",already_suggested_companies,cv]\n", + "else:\n", + " query = [\"\"\"Analysiere meinen Lebenslauf (cv) und die Liste bereits vorgeschlagener Unternehmen (already_suggested_companies). \n", + "Schlage mir genau eine aktuell offene Stellenausschreibung in der Nähe von Marburg vor für ein Unternehmen, das noch nicht in already_suggested_companies enthalten ist \n", + "und die besonders gut zu meinem Profil passt. \n", + "\n", + "Erkläre in maximal 150 Wörtern prägnant, warum diese Stelle eine besonders gute Wahl für mich wäre. \n", + "Formuliere die Antwort als kurze, professionelle Email mit dem Titel: 'Neuer Jobvorschlag'. \n", + "Begründe die Eignung anhand meiner Qualifikationen und Interessen. \n", + "Füge außerdem einen einzelnen Satz hinzu, der direkt in einem Bewerbungsschreiben genutzt werden könnte, \n", + "um die Übereinstimmung zwischen mir und der ausgeschriebenen Stelle hervorzuheben. \n", + "\n", + "Liefere nur die Email, keine zusätzliche Erklärung.\"\"\",already_suggested_companies,cv]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7c68f66d", + "metadata": {}, + "outputs": [], + "source": [ + "api_key = os.environ.get(\"GEMINI_API_KEY\")\n", + "\n", + "client = genai.Client(api_key=api_key)\n", + "\n", + "# Step 1: grounded search (necessary for live web search)\n", + "resp_grounded = client.models.generate_content(\n", + " model=\"gemini-2.5-flash\",\n", + " contents=query,\n", + " config=types.GenerateContentConfig(\n", + " tools=[types.Tool(google_search=types.GoogleSearch())]\n", + " ),\n", + ")\n", + "\n", + "grounded_answer = resp_grounded.text # freeform answer" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "fe54c9a2", + "metadata": {}, + "outputs": [], + "source": [ + "# Step 2: convert to JSON\n", + "resp_json = client.models.generate_content(\n", + " model=\"gemini-2.5-flash\",\n", + " contents=f\"Turn this answer into JSON: {grounded_answer}\",\n", + " config=types.GenerateContentConfig(\n", + " response_mime_type=\"application/json\",\n", + " response_schema=JobSuggestion,\n", + " ),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "476ae4e4", + "metadata": {}, + "outputs": [], + "source": [ + "# Append the response to already_suggested_companies.txt\n", + "with open(\"already_suggested_companies.txt\", \"a\", encoding=\"utf-8\") as f:\n", + " f.write(\"\\n\" + resp_json.parsed.company_name)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c3d72c4f", + "metadata": {}, + "outputs": [], + "source": [ + "import smtplib\n", + "from email.mime.text import MIMEText\n", + "\n", + "gmail_key = os.environ.get(\"GMAIL_PW\")\n", + "\n", + "# Account details\n", + "sender = \"seb.egli@gmail.com\"\n", + "recipient = \"seb.egli@gmail.com\"\n", + "password = gmail_key\n", + "\n", + "# Create email\n", + "msg = MIMEText(grounded_answer)\n", + "msg[\"From\"] = sender\n", + "msg[\"To\"] = recipient\n", + "msg[\"Subject\"] = \"Neuer Jobvorschlag\"\n", + "\n", + "# Send email using Gmail's SMTP\n", + "with smtplib.SMTP_SSL(\"smtp.gmail.com\", 465) as server:\n", + " server.login(sender, password)\n", + " server.sendmail(sender, recipient, msg.as_string())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "odc-landsat", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/job_suggestor.py b/job_suggestor.py new file mode 100644 index 0000000..b989aa8 --- /dev/null +++ b/job_suggestor.py @@ -0,0 +1,100 @@ +from google import genai +from google.genai import types +from datetime import datetime +from pydantic import BaseModel +import smtplib +from email.mime.text import MIMEText +import os + +# Load keys from environment variables +api_key = os.environ.get("GEMINI_API_KEY") +gmail_key = os.environ.get("GMAIL_PW") + +class JobSuggestion(BaseModel): + company_name: str + company_description: str + job_title: str + application_text: str + +# Load CV from file +with open("cv_sebastian_egli.txt", "r", encoding="utf-8") as f: + cv = f.read() + +# Load already suggested companies from file (to avoid duplicates) +with open("already_suggested_companies.txt", "r", encoding="utf-8") as f: + already_suggested_companies = f.read() + +# Define boolean for even-numbered days (I want to switch un/solicited every day) +today = datetime.today() +unsolicited_application = today.day % 2 == 0 + +if unsolicited_application: + query = ["""Analysiere meinen Lebenslauf (cv) und die Liste bereits vorgeschlagener Unternehmen (already_suggested_companies). +Schlage mir genau ein Unternehmen in der Nähe von Marburg vor, das noch nicht in already_suggested_companies enthalten ist +und für eine Initiativbewerbung besonders gut zu meinem Profil passt. + +Erkläre in maximal 150 Wörtern prägnant, warum dieses Unternehmen eine besonders gute Wahl für mich wäre. +Formuliere die Antwort als kurze, professionelle Email mit dem Titel: 'Neuer Jobvorschlag'. +Begründe die Eignung anhand meiner Qualifikationen und Interessen. +Füge außerdem einen einzelnen Satz hinzu, der direkt in einem Bewerbungsschreiben genutzt werden könnte, +um die Übereinstimmung zwischen mir und dem Unternehmen hervorzuheben. + +Liefere nur die Email, keine zusätzliche Erklärung.""",already_suggested_companies,cv] +else: + query = ["""Analysiere meinen Lebenslauf (cv) und die Liste bereits vorgeschlagener Unternehmen (already_suggested_companies). +Schlage mir genau eine aktuell offene Stellenausschreibung in der Nähe von Marburg vor für ein Unternehmen, das noch nicht in already_suggested_companies enthalten ist +und die besonders gut zu meinem Profil passt. + +Erkläre in maximal 150 Wörtern prägnant, warum diese Stelle eine besonders gute Wahl für mich wäre. +Formuliere die Antwort als kurze, professionelle Email mit dem Titel: 'Neuer Jobvorschlag'. +Begründe die Eignung anhand meiner Qualifikationen und Interessen. +Füge außerdem einen einzelnen Satz hinzu, der direkt in einem Bewerbungsschreiben genutzt werden könnte, +um die Übereinstimmung zwischen mir und der ausgeschriebenen Stelle hervorzuheben. + +Liefere nur die Email, keine zusätzliche Erklärung.""",already_suggested_companies,cv] + +# Get response from Gemini +# Initialize Gemini client +client = genai.Client(api_key=api_key) + +# Step 1: grounded search (necessary for live web search) +resp_grounded = client.models.generate_content( + model="gemini-2.5-flash", + contents=query, + config=types.GenerateContentConfig( + tools=[types.Tool(google_search=types.GoogleSearch())] + ), +) + +grounded_answer = resp_grounded.text # freeform answer + +# Step 2: convert to JSON +resp_json = client.models.generate_content( + model="gemini-2.5-flash", + contents=f"Turn this answer into JSON: {grounded_answer}", + config=types.GenerateContentConfig( + response_mime_type="application/json", + response_schema=JobSuggestion, + ), +) + +# Append the response to already_suggested_companies.txt +with open("already_suggested_companies.txt", "a", encoding="utf-8") as f: + f.write("\n" + resp_json.parsed.company_name) + +# Send email with the suggestion +# Account details +sender = "seb.egli@gmail.com" +recipient = "seb.egli@gmail.com" +password = gmail_key + +# Create email +msg = MIMEText(grounded_answer) +msg["From"] = sender +msg["To"] = recipient +msg["Subject"] = "Neuer Jobvorschlag" + +# Send email using Gmail's SMTP +with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server: + server.login(sender, password) + server.sendmail(sender, recipient, msg.as_string()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..59b67a7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +google-genai +pydantic \ No newline at end of file