Intial commit
هذا الالتزام موجود في:
3
.gitignore
مباع
Normal file
3
.gitignore
مباع
Normal file
@@ -0,0 +1,3 @@
|
||||
jobs.csv
|
||||
.venv
|
||||
__pycache__
|
68
ai.py
Normal file
68
ai.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# To run this code you need to install the following dependencies:
|
||||
# pip install google-genai
|
||||
|
||||
import base64
|
||||
import os
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def generate(description, instruction):
|
||||
client = genai.Client(
|
||||
api_key=""
|
||||
)
|
||||
|
||||
model = "gemini-2.5-flash"
|
||||
contents = [
|
||||
types.Content(
|
||||
role="user",
|
||||
parts=[
|
||||
types.Part.from_text(text=description),
|
||||
],
|
||||
),
|
||||
]
|
||||
generate_content_config = types.GenerateContentConfig(
|
||||
thinking_config=types.ThinkingConfig(
|
||||
thinking_budget=0,
|
||||
),
|
||||
response_mime_type="application/json",
|
||||
response_schema=genai.types.Schema(
|
||||
type=genai.types.Type.OBJECT,
|
||||
required=[
|
||||
"percentage",
|
||||
"why I'm I a good fit",
|
||||
"what I'm I missing",
|
||||
],
|
||||
properties={
|
||||
"percentage": genai.types.Schema(
|
||||
type=genai.types.Type.INTEGER,
|
||||
),
|
||||
"why I'm I a good fit": genai.types.Schema(
|
||||
type=genai.types.Type.STRING,
|
||||
),
|
||||
"what I'm I missing": genai.types.Schema(
|
||||
type=genai.types.Type.STRING,
|
||||
),
|
||||
},
|
||||
),
|
||||
system_instruction=[
|
||||
types.Part.from_text(text=instruction),
|
||||
],
|
||||
)
|
||||
|
||||
# for chunk in client.models.generate_content_stream(
|
||||
# model=model,
|
||||
# contents=contents,
|
||||
# config=generate_content_config,
|
||||
# ):
|
||||
# print(chunk.text, end="")
|
||||
response = client.models.generate_content(
|
||||
model=model,
|
||||
contents=contents,
|
||||
config=generate_content_config,
|
||||
)
|
||||
return response.text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate()
|
48
alert.py
Normal file
48
alert.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
|
||||
def send_email(sender, receiver, password, good_fit_jobs):
|
||||
print("preparing message")
|
||||
html_body = """<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6;">
|
||||
<h2 style="color: #2c3e50;">My Job Alert</h2>
|
||||
<p><b>{count} jobs</b> have been found that match your profile:</p>
|
||||
<ul style="padding-left: 20px;">
|
||||
""".format(
|
||||
count=len(good_fit_jobs)
|
||||
)
|
||||
|
||||
for job in good_fit_jobs:
|
||||
html_body += f"""
|
||||
<li style="margin-bottom: 15px;">
|
||||
<p><b>Title:</b> {job["title"]}</p>
|
||||
<p><b>URL:</b> <a href="{job["url"]}">{job["url"]}</a></p>
|
||||
<p><b>Percentage:</b> {job["percentage"]}</p>
|
||||
<p><b>Why I'm a good fit:</b> {job["why I'm I a good fit"]}</p>
|
||||
<p><b>What I'm missing:</b> {job["what I'm I missing"]}</p>
|
||||
</li>
|
||||
"""
|
||||
|
||||
html_body += """
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
message = MIMEText(html_body, "html")
|
||||
message["Subject"] = f"MY JOB ALERT: {len(good_fit_jobs)} jobs has been found"
|
||||
message["From"] = sender
|
||||
message["To"] = receiver
|
||||
|
||||
print(f"Sending email with {len(good_fit_jobs)} jobs")
|
||||
with smtplib.SMTP("smtp.gmail.com", timeout=60, port=587) as connection:
|
||||
connection.starttls()
|
||||
connection.login(user=sender, password=password)
|
||||
connection.sendmail(
|
||||
from_addr=sender,
|
||||
to_addrs=receiver,
|
||||
msg=message.as_string(),
|
||||
)
|
||||
connection.close()
|
42
instruction.txt
Normal file
42
instruction.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
i will give you job descriptions and you tell me if i'm a good fit or not in a percentage, why I'm I a good fit and what I'm I missing
|
||||
this is my cv:
|
||||
Ahmed Hesham
|
||||
Ahmed.hesham.farag@gmail.com
|
||||
Cairo, Egypt
|
||||
LinkedIn:https://www.linkedin.com/in/ahmed-hesham0/
|
||||
GitHub:https://github.com/ahmedhesham301
|
||||
PROFILE
|
||||
DevOps Engineer with hands-on experience in automating CI/CD pipelines, containerizing
|
||||
applications, and deploying scalable infrastructure on AWS using Docker, Terraform, and
|
||||
Ansible. Skilled in backend development with Python and Go, building RESTful APIs and
|
||||
automating system workflows.
|
||||
EDUCATION
|
||||
Sadat Academy | Cairo, Egypt
|
||||
Computer science
|
||||
Predicted Grade: Very good
|
||||
Oct 2022 – June 2026
|
||||
TECHNICAL SKILLS
|
||||
-DevOps Tools: Jenkins, Docker, Ansible, Terraform.
|
||||
-Programming & Scripting: Python, GO, Bash, YAML, JSON.
|
||||
-Monitoring & Observability: Prometheus, Grafana.
|
||||
-Cloud Platforms & OS: Linux, AWS, Ubuntu.
|
||||
-Database: SQL, PostgreSQL.
|
||||
-Version control: Git, GitHub.
|
||||
Projects
|
||||
•Built CI/CD pipeline using Jenkins to test and deploy Flask app to EC2 on AWS. The
|
||||
pipeline included building a Docker image, pushing it to Docker Hub, provisioning
|
||||
infrastructure with Terraform, and configuring the server using Ansible.
|
||||
GitHub Repo: weather-app
|
||||
•A Docker image for a Minecraft server that allows you to specify the desired version as
|
||||
an argument. The image automatically downloads the specified version from Mojang's
|
||||
website and launches the server with a single command. GitHub repo
|
||||
•An Amazon price tracker that monitors the price of a specified item. It scrapes the current
|
||||
price and sends you an email notification whenever the price drops below your desired
|
||||
price.
|
||||
EXPERIENCE
|
||||
Orange Digital Centre
|
||||
Jan 2025 – Feb 2025
|
||||
DevOps internship
|
||||
Gained hands-on experience in CI/CD pipelines using Jenkins, Ansible, and Terraform.
|
||||
ADDITIONAL SKILLS
|
||||
Languages: Arabic (Native), English (Fluent)Microsoft Office: Proficient in Word and PowerPoint
|
30
jobs.py
Normal file
30
jobs.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from jobspy import scrape_jobs
|
||||
|
||||
|
||||
def getJobs(jobTitle, results_wanted, hours_old):
|
||||
jobs = scrape_jobs(
|
||||
site_name=[
|
||||
"indeed",
|
||||
"linkedin",
|
||||
# "zip_recruiter",
|
||||
"google",
|
||||
# "glassdoor",
|
||||
# "bayt",
|
||||
# "naukri",
|
||||
# "bdjobs",
|
||||
],
|
||||
search_term=jobTitle,
|
||||
location="Egypt",
|
||||
results_wanted=results_wanted,
|
||||
google_search_term=f"{jobTitle} jobs near Cairo since {hours_old} hours",
|
||||
hours_old=hours_old,
|
||||
country_indeed="Egypt",
|
||||
linkedin_fetch_description=True, # gets more info such as description, direct job url (slower)
|
||||
# proxies=["208.195.175.46:65095", "208.195.175.45:65095", "localhost"],
|
||||
)
|
||||
print(f"Found {len(jobs)} jobs")
|
||||
# print(jobs)
|
||||
return jobs
|
||||
# jobs.to_csv(
|
||||
# "jobs.csv", quoting=csv.QUOTE_NONNUMERIC, escapechar="\\", index=False
|
||||
# ) # to_excel
|
68
main.py
Normal file
68
main.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from jobs import getJobs
|
||||
from ai import generate
|
||||
from alert import send_email
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
SENDER = ""
|
||||
PASSWORD = ""
|
||||
RECEIVER = ""
|
||||
|
||||
good_fit_jobs = []
|
||||
|
||||
|
||||
with open("instruction.txt", "r") as f:
|
||||
CV = f.read()
|
||||
|
||||
|
||||
def get_jobs(job_title, cv, results_wanted, hours_old):
|
||||
jobs = getJobs(job_title, results_wanted, hours_old)
|
||||
for i, job in jobs.iterrows():
|
||||
# print(job["description"])
|
||||
# print("_______________")
|
||||
print("index is :", i)
|
||||
|
||||
if (i + 1) % 10 == 0 and i != 0:
|
||||
print("Sleeping to avoid API rate limits")
|
||||
time.sleep(60)
|
||||
try_count = 3
|
||||
while try_count > 0:
|
||||
try:
|
||||
cleaned_description = "\n".join(
|
||||
[line for line in job["description"].splitlines() if line.strip()]
|
||||
)
|
||||
ai_response = generate(cleaned_description, cv)
|
||||
ai_response_dict = json.loads(ai_response)
|
||||
break
|
||||
except json.JSONDecodeError as e:
|
||||
try_count -= 1
|
||||
print("_______________")
|
||||
print(cleaned_description)
|
||||
print("_______________")
|
||||
print(ai_response)
|
||||
print(e)
|
||||
print("Sleeping after fail to avoid API rate limits")
|
||||
time.sleep(6)
|
||||
else:
|
||||
print("All attempts failed.")
|
||||
continue
|
||||
# print(ai_response_dict)
|
||||
if ai_response_dict["percentage"] > 0:
|
||||
# print("adding job to good_fit_jobs")
|
||||
good_fit_jobs.append(
|
||||
{
|
||||
"title": job["title"],
|
||||
"url": job["job_url"],
|
||||
"percentage": ai_response_dict["percentage"],
|
||||
"why I'm I a good fit": ai_response_dict["why I'm I a good fit"],
|
||||
"what I'm I missing": ai_response_dict["what I'm I missing"],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_jobs("devops", CV, results_wanted=2, hours_old=4)
|
||||
# get_jobs("backend", CV, results_wanted=30, hours_old=2)
|
||||
if len(good_fit_jobs) > 0:
|
||||
send_email(SENDER, RECEIVER, PASSWORD, good_fit_jobs)
|
35
requirements.txt
Normal file
35
requirements.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
annotated-types==0.7.0
|
||||
anyio==4.11.0
|
||||
beautifulsoup4==4.13.5
|
||||
cachetools==5.5.2
|
||||
certifi==2025.8.3
|
||||
charset-normalizer==3.4.3
|
||||
google-auth==2.40.3
|
||||
google-genai==1.38.0
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
idna==3.10
|
||||
markdownify==0.13.1
|
||||
numpy==1.26.3
|
||||
pandas==2.3.2
|
||||
pyasn1==0.6.1
|
||||
pyasn1_modules==0.4.2
|
||||
pydantic==2.11.9
|
||||
pydantic_core==2.33.2
|
||||
python-dateutil==2.9.0.post0
|
||||
python-jobspy==1.1.82
|
||||
pytz==2025.2
|
||||
regex==2024.11.6
|
||||
requests==2.32.5
|
||||
rsa==4.9.1
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
soupsieve==2.8
|
||||
tenacity==9.1.2
|
||||
tls-client==1.0.1
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions==4.15.0
|
||||
tzdata==2025.2
|
||||
urllib3==2.5.0
|
||||
websockets==15.0.1
|
المرجع في مشكلة جديدة
حظر مستخدم