#include <iostream>
#include <limits>
#include <iomanip>
#include <fstream>
#include "StudentProfile.hpp"
#include "JobParser.hpp"
#include "Sorter.hpp"
using namespace std;
// Function to clear input buffer
void clearInputBuffer() {
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
// Function to get yes/no input
bool getYesNo(const string& prompt) {
string input;
while (true) {
cout << prompt << " (y/n): ";
getline(cin, input);
if (input == "y" || input == "Y") return true;
if (input == "n" || input == "N") return false;
cout << "Please enter 'y' or 'n'.\n";
}
}
// Function to get multiple entries from user
vector<string> getMultipleEntries(const string& itemName) {
vector<string> entries;
string input;
cout << "\nEnter " << itemName << " (one per line). Press Enter on empty line to finish:\n";
while (true) {
cout << "> ";
getline(cin, input);
if (input.empty()) {
if (entries.empty()) {
cout << "No " << itemName << " entered. Try again or press Enter to skip.\n";
continue;
}
break;
}
entries.push_back(input);
}
return entries;
}
// Function to build student profile interactively
StudentProfile buildStudentProfile() {
StudentProfile student;
string input;
cout << "\n" << string(60, '=') << "\n";
cout << " STUDENT PROFILE CREATION\n";
cout << string(60, '=') << "\n\n";
// Basic Information
cout << "Let's start with your basic information:\n";
cout << "Enter your full name: ";
getline(cin, input);
student.setName(input);
cout << "Enter your major: ";
getline(cin, input);
student.setMajor(input);
// Remote preference (the only preference we keep)
student.setWantRemote(getYesNo("\nAre you willing to work remotely?"));
// Skills
cout << "\nNow, let's add your skills.\n";
student.setSkills(getMultipleEntries("skills"));
// Experiences
bool addExperiences = getYesNo("\nWould you like to add your work experiences?");
if (addExperiences) {
student.setExperiences(getMultipleEntries("experiences"));
}
// Interests
bool addInterests = getYesNo("\nWould you like to add your interests?");
if (addInterests) {
student.setInterests(getMultipleEntries("interests"));
}
// Projects
bool addProjects = getYesNo("\nWould you like to add your projects?");
if (addProjects) {
student.setProjects(getMultipleEntries("projects"));
}
// Courses
bool addCourses = getYesNo("\nWould you like to add relevant courses?");
if (addCourses) {
student.setCourses(getMultipleEntries("courses"));
}
// Display the completed profile
student.displayProfile();
// Ask if profile is correct
bool correct = getYesNo("\nIs this profile correct?");
if (!correct) {
cout << "\nLet's try again. Starting over...\n";
return buildStudentProfile();
}
return student;
}
// Function to load and parse job files
vector<Job> loadAllJobFiles(const vector<string>& filenames) {
vector<Job> allJobs;
cout << "\nLOADING JOB DESCRIPTIONS\n";
for (const auto& filename : filenames) {
cout << "Processing " << filename << "...\n";
JobParser parser(filename);
vector<Job> jobs = parser.loadAllJobs();
// Add all jobs from this file to the master list
allJobs.insert(allJobs.end(), jobs.begin(), jobs.end());
cout << "Found " << jobs.size() << " job(s) in " << filename << "\n\n";
}
cout << "Total jobs loaded: " << allJobs.size() << "\n";
return allJobs;
}
// Function to display ranking summary
void displayRankingSummary(const vector<pair<Job, double>>& rankedJobs) {
cout << "\nQUICK RANKING SUMMARY\n";
for (size_t i = 0; i < min(3, (int)rankedJobs.size()); ++i) {
const auto& job = rankedJobs[i].first;
double score = rankedJobs[i].second;
cout << i+1 << ". " << job.getTitle()
<< " at " << job.getCompany()
<< " - " << fixed << setprecision(1) << score << "%\n";
}
if (rankedJobs.size() > 3) {
cout << "... and " << rankedJobs.size() - 3 << " more jobs\n";
}
}
int main() {
cout << "\n";
cout << "** JOB RANKING SYSTEM FOR STUDENTS **\n\n";
// Step 1: Build student profile
cout << "First, let's create your student profile.\n";
StudentProfile student = buildStudentProfile();
// Step 2: Load job files
cout << "\nNow, let's load the job descriptions.\n";
vector<string> jobFiles;
// Check if Job1.txt exists and add it
ifstream file1("Job1.txt");
if (file1.good()) {
jobFiles.push_back("Job1.txt");
file1.close();
} else {
cout << "Warning: Job1.txt not found. Creating a sample file...\n";
ofstream sample("Job1.txt");
sample << "Software Engineering Intern\n";
sample << "Tech Company\n\n";
sample << "We are looking for a Python developer with experience in JavaScript and SQL.\n";
sample << "This is a remote position. Salary: $30/hr.\n";
sample << "Undergraduate students welcome to apply.\n";
sample << "---\n";
sample.close();
jobFiles.push_back("Job1.txt");
}
ifstream file2("Job2.txt");
if (file2.good()) {
jobFiles.push_back("Job2.txt");
file2.close();
} else {
cout << "Warning: Job2.txt not found. Creating a sample file...\n";
ofstream sample("Job2.txt");
sample << "GIS Co-op Student\n";
sample << "YVR Airport\n\n";
sample << "Experience with ArcGIS Pro, FME, and ESRI Field Maps required.\n";
sample << "Salary range: $24.80-$27.33/hr. On-site position in Vancouver.\n";
sample << "Undergraduate students in GIS or related field.\n";
sample << "---\n";
sample.close();
jobFiles.push_back("Job2.txt");
}
// Step 3: Parse all job files
vector<Job> allJobs = loadAllJobFiles(jobFiles);
if (allJobs.empty()) {
cout << "\nNo jobs found to rank. Exiting...\n";
return 1;
}
// Step 4: Rank the jobs
cout << "\nRANKING JOBS...\n";
Sorter sorter;
// Ask user if they want to adjust weights
bool adjustWeights = getYesNo("\nWould you like to adjust the ranking weights?");
if (adjustWeights) {
cout << "\nEnter weights (should sum to 1.0):\n";
cout << "(Skills, Interests, Remote, Industry Experience)\n";
double skills, interests, remote, industry;
cout << "Skills weight (default 0.50): ";
cin >> skills;
cout << "Interests weight (default 0.15): ";
cin >> interests;
cout << "Remote weight (default 0.10): ";
cin >> remote;
cout << "Industry experience weight (default 0.25): ";
cin >> industry;
clearInputBuffer(); // Clear the input buffer
sorter.setWeights(skills, interests, remote, industry);
}
// Sort the jobs
auto rankedJobs = sorter.sortJobs(allJobs, student);
// Step 5: Display rankings
cout << "\n";
sorter.displayRankings(rankedJobs, student);
// Step 6: Show summary
displayRankingSummary(rankedJobs);
// Step 7: Option to save rankings
bool saveRankings = getYesNo("\nWould you like to save the rankings to a file?");
if (saveRankings) {
string filename;
cout << "Enter filename to save (e.g., rankings.txt): ";
getline(cin, filename);
ofstream file(filename);
if (file.is_open()) {
// Save the summary
file << "JOB RANKINGS FOR " << student.getName() << "\n";
file << string(50, '=') << "\n\n";
for (size_t i = 0; i < rankedJobs.size(); ++i) {
const auto& job = rankedJobs[i].first;
double score = rankedJobs[i].second;
file << i+1 << ". " << job.getTitle()
<< " at " << job.getCompany()
<< " - " << fixed << setprecision(1) << score << "%\n";
}
file.close();
cout << "Rankings saved to " << filename << "\n";
} else {
cout << "Error saving rankings.\n";
}
}
cout << "\nThank you for using the Job Ranking System!\n";
return 0;
}
#include "Job.hpp"
#include <iostream>
#include <iomanip>
#include <sstream>
using namespace std;
// Constructor
Job::Job() : isRemote(false), salary(0.0) {}
// Setters
void Job::setTitle(const string& newTitle) {
title = newTitle;
}
void Job::setCompany(const string& newCompany) {
company = newCompany;
}
void Job::setJobSkills(const vector<string>& newSkills) {
jobSkills = newSkills;
}
void Job::setExperienceLevel(const string& newLevel) {
experienceLevel = newLevel;
}
void Job::setRemote(bool remote) {
isRemote = remote;
}
void Job::setSalary(double newSalary) {
salary = newSalary;
}
void Job::setIndustries(const vector<string>& newIndustries) {
industries = newIndustries;
}
// Getters
string Job::getTitle() const {
return title;
}
string Job::getCompany() const {
return company;
}
const vector<string>& Job::getRequiredSkills() const { // Changed name
return jobSkills;
}
string Job::getExperienceLevel() const {
return experienceLevel;
}
bool Job::getRemote() const {
return isRemote;
}
double Job::getSalary() const {
return salary;
}
const vector<string>& Job::getIndustries() const {
return industries;
}
// Display function
void Job::displayJob() const {
cout << "\n" << string(50, '-') << "\n";
cout << "Job: " << title << " at " << company << "\n";
cout << string(50, '-') << "\n";
if (isRemote) cout << " (Remote)";
cout << "\n";
cout << "Experience: " << experienceLevel << "\n";
cout << "Salary: $" << fixed << setprecision(2) << salary << "\n";
cout << "Required Skills:\n";
for (const auto& skill : jobSkills) {
cout << " • " << skill << "\n";
}
cout << "Industries:\n";
for (const auto& industry : industries) {
cout << " • " << industry << "\n";
}
}
// To string function for file parsing
string Job::toString() const {
stringstream ss;
ss << "TITLE:" << title << "\n";
ss << "COMPANY:" << company << "\n";
ss << "REMOTE:" << (isRemote ? "yes" : "no") << "\n"; // Removed location
ss << "EXPERIENCE:" << experienceLevel << "\n";
ss << "SALARY:" << salary << "\n";
ss << "SKILLS:";
for (size_t i = 0; i < jobSkills.size(); ++i) {
if (i > 0) ss << ",";
ss << jobSkills[i];
}
ss << "\n";
ss << "INDUSTRIES:";
for (size_t i = 0; i < industries.size(); ++i) {
if (i > 0) ss << ",";
ss << industries[i];
}
ss << "\n";
return ss.str(); // Removed description reference
}
// Defines Job class
#ifndef JOB_HPP
#define JOB_HPP
#include <string>
#include <vector>
using namespace std;
class Job {
private:
string title;
string company;
vector<string> jobSkills;
string experienceLevel; //undergraduate vs. masters vs. phd
bool isRemote;
double salary;
vector<string> industries;
public:
// Constructor
Job();
// Setters
void setTitle(const string& newTitle);
void setCompany(const string& newCompany);
void setJobSkills(const vector<string>& newSkills);
void setExperienceLevel(const string& newLevel);
void setRemote(bool remote);
void setSalary(double newSalary);
void setIndustries(const vector<string>& newIndustries);
// Getters (const because they don't modify)
string getTitle() const;
string getCompany() const;
const vector<string>& getRequiredSkills() const;
string getExperienceLevel() const;
bool getRemote() const;
double getSalary() const;
const vector<string>& getIndustries() const;
// Other
void displayJob() const;
string toString() const; // For saving to file
};
#endif
// Student profile class
#include "StudentProfile.hpp"
#include <iostream>
#include <algorithm>
#include <sstream>
#include <iomanip>
using namespace std;
//Constructor
StudentProfile::StudentProfile() : wantRemote(false) {
}
//Set functions
void StudentProfile::setName(const string& newName) {
name = newName;
}
void StudentProfile::setMajor(const string& newMajor) {
major = newMajor;
}
void StudentProfile::setWantRemote(bool remote) {
wantRemote = remote;
}
void StudentProfile::setSkills(const vector<string>& newSkills) {
skills = newSkills;
}
void StudentProfile::setExperiences(const vector<string>& newExperiences) {
experiences = newExperiences;
}
void StudentProfile::setInterests(const vector<string>& newInterests) {
interests = newInterests;
}
void StudentProfile::setProjects(const vector<string>& newProjects) {
projects = newProjects;
}
void StudentProfile::setCourses(const vector<string>& newCourses) {
courses = newCourses;
}
//Get functions
string StudentProfile::getName() const {
return name;
}
string StudentProfile::getMajor() const {
return major;
}
bool StudentProfile::getWantRemote() const {
return wantRemote;
}
const vector<string>& StudentProfile::getSkills() const {
return skills;
}
const vector<string>& StudentProfile::getExperiences() const {
return experiences;
}
const vector<string>& StudentProfile::getInterests() const {
return interests;
}
const vector<string>& StudentProfile::getProjects() const {
return projects;
}
const vector<string>& StudentProfile::getCourses() const {
return courses;
}
// File parsing
string StudentProfile::toString() const {
stringstream ss;
// Basic info
ss << "NAME:" << name << "\n";
ss << "MAJOR:" << major << "\n";
ss << "REMOTE:" << (wantRemote ? "yes" : "no") << "\n";
// Skills
ss << "SKILLS:";
for (size_t i = 0; i < skills.size(); ++i) {
if (i > 0) ss << ",";
ss << skills[i];
}
ss << "\n";
// Experiences
ss << "EXPERIENCES:";
for (size_t i = 0; i < experiences.size(); ++i) {
if (i > 0) ss << ",";
ss << experiences[i];
}
ss << "\n";
// Interests
ss << "INTERESTS:";
for (size_t i = 0; i < interests.size(); ++i) {
if (i > 0) ss << ",";
ss << interests[i];
}
ss << "\n";
// Projects
ss << "PROJECTS:";
for (size_t i = 0; i < projects.size(); ++i) {
if (i > 0) ss << ",";
ss << projects[i];
}
ss << "\n";
// Courses
ss << "COURSES:";
for (size_t i = 0; i < courses.size(); ++i) {
if (i > 0) ss << ",";
ss << courses[i];
}
ss << "\n";
return ss.str();
}
void StudentProfile::fromString(const string& data) {
stringstream ss(data);
string line;
while (getline(ss, line)) {
size_t colonPos = line.find(':');
if (colonPos == string::npos) continue;
string key = line.substr(0, colonPos);
string value = line.substr(colonPos + 1);
if (key == "NAME") {
name = value;
}
else if (key == "MAJOR") {
major = value;
}
else if (key == "REMOTE") {
wantRemote = (value == "yes");
}
else if (key == "SKILLS") {
skills.clear();
stringstream skillSS(value);
string skill;
while (getline(skillSS, skill, ',')) {
if (!skill.empty()) skills.push_back(skill);
}
}
else if (key == "EXPERIENCES") {
experiences.clear();
stringstream expSS(value);
string exp;
while (getline(expSS, exp, ',')) {
if (!exp.empty()) experiences.push_back(exp);
}
}
else if (key == "INTERESTS") {
interests.clear();
stringstream interestSS(value);
string interest;
while (getline(interestSS, interest, ',')) {
if (!interest.empty()) interests.push_back(interest);
}
}
else if (key == "PROJECTS") {
projects.clear();
stringstream projectSS(value);
string project;
while (getline(projectSS, project, ',')) {
if (!project.empty()) projects.push_back(project);
}
}
else if (key == "COURSES") {
courses.clear();
stringstream courseSS(value);
string course;
while (getline(courseSS, course, ',')) {
if (!course.empty()) courses.push_back(course);
}
}
}
}
// Display function
void StudentProfile::displayProfile() const {
cout << "\n" << string(50, '=') << "\n";
cout << " STUDENT PROFILE\n";
cout << string(50, '=') << "\n\n";
// Basic Information
cout << "BASIC INFORMATION:\n";
cout << " Name: " << name << "\n";
cout << " Major: " << major << "\n";
cout << " Remote: " << (wantRemote ? "Yes" : "No") << "\n\n";
// Skills
cout << "SKILLS (" << skills.size() << "):\n";
if (skills.empty()) {
cout << " None listed\n";
} else {
for (const auto& skill : skills) {
cout << " • " << skill << "\n";
}
}
cout << "\n";
// Experiences
cout << "EXPERIENCES (" << experiences.size() << "):\n";
if (experiences.empty()) {
cout << " None listed\n";
} else {
for (const auto& exp : experiences) {
cout << " • " << exp << "\n";
}
}
cout << "\n";
// Interests
cout << "INTERESTS (" << interests.size() << "):\n";
if (interests.empty()) {
cout << " None listed\n";
} else {
for (const auto& interest : interests) {
cout << " • " << interest << "\n";
}
}
cout << "\n";
// Projects
cout << "PROJECTS (" << projects.size() << "):\n";
if (projects.empty()) {
cout << " None listed\n";
} else {
for (const auto& project : projects) {
cout << " • " << project << "\n";
}
}
cout << "\n";
// Courses
cout << "COURSES (" << courses.size() << "):\n";
if (courses.empty()) {
cout << " None listed\n";
} else {
for (const auto& course : courses) {
cout << " • " << course << "\n";
}
}
cout << "\n";
cout << "\n" << string(50, '=') << "\n";
}
// Clear function
void StudentProfile::clearAll() {
name.clear();
major.clear();
wantRemote = false;
skills.clear();
experiences.clear();
interests.clear();
projects.clear();
courses.clear();
}
// Defines student profile class
#ifndef STUDENT_PROFILE_HPP
#define STUDENT_PROFILE_HPP
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
class StudentProfile {
private:
// Basic info
string name;
string major;
// Lists of qualifications
vector<string> skills;
vector<string> experiences;
vector<string> interests;
vector<string> projects;
vector<string> courses;
// Preferences
bool wantRemote; // Only preference left!
public:
//constructor
StudentProfile();
//set functions
void setName(const string& newName);
void setMajor(const string& newMajor);
void setWantRemote(bool remote);
void setSkills(const vector<string>& newSkills);
void setExperiences(const vector<string>& newExperiences);
void setInterests(const vector<string>& newInterests);
void setProjects(const vector<string>& newProjects);
void setCourses(const vector<string>& newCourses);
//get functions
string getName() const;
string getMajor() const;
bool getWantRemote() const;
const vector<string>& getSkills() const;
const vector<string>& getExperiences() const;
const vector<string>& getInterests() const;
const vector<string>& getProjects() const;
const vector<string>& getCourses() const;
//file parsing
string toString() const; // For saving to file
void fromString(const string& data); // For loading from file
//displaying
void displayProfile() const;
//clearing
void clearAll();
};
#endif
Title: Co‑op Student - Engineering Services (GIS)
Company: Vancouver Airport Authority
YVR is Canada’s Pacific gateway to Asia and beyond supporting global connectivity, and getting more of Canada to the world. From supporting tourism and trade, to connecting friends and family, to investing in people and communities. Our work has an impact far beyond the terminal.
Behind every flight is a team of talented professionals spanning a wide range of skillsets and experience:
• Engineering, infrastructure, and asset management
• Technology, analytics, and digital systems
• Sustainability, climate, and environmental stewardship
• Operations, safety, and emergency readiness
• Commercial strategy, finance, and data commercialization
• Communications, experience design, and people systems
If you’re interested in a chance to be part of a people-first organization with a chance to make a real impact, this may be the opportunity for you.
Your Coop Experience at YVR
This isn’t just a summer job. It’s an experience you’ll carry forward.
As a YVR coop student, you won’t just observe — you’ll have a chance to be hands on, contributing to significant projects with our YVR team. Depending on the role, you may:
• Work on live operational, digital, or infrastructure initiatives
• Analyze data that informs decision making at scale
• Contribute to climate, safety, or sustainability outcomes
• Support tools, models, or processes used by frontline teams
• Help design better employee, passenger, or system experiences
• Collaborate across departments in a fastmoving operational environment
You’ll see how Canada’s second busiest airport moves 27-million passengers a year while driving safety, service, innovation, and accountability, with a clear view on how your work makes an impact.
Your Specific Project
In this role, you will use tools including ArcGIS Pro, Vertigis Studio and workflows, ArcGIS Portal, FME, and ESRI Field Maps to develop a more comprehensive tool to track planning, inspection, and maintenance of drainage infrastructure (ditches, culverts, piping, manholes, catch basins, and related assets). You will work with multiple departments to iterate on the tool, integrate feedback, and may expand the approach to track other construction and maintenance projects, activities, and assets.
We’re looking for students who:
• Are enrolled in a recognized postsecondary program
• Are curious about how large systems actually work
• Enjoy learning new tools, technologies, or ways of thinking
• Ask thoughtful questions and follow them through
• Care about doing meaningful work with real outcomes
• Want experience that translates across industries
You don’t need to know everything on day one —just the motivation to learn, contribute, and figure things out.
Why a Co-Op at YVR?
• Work at one of Canada’s busiest and most innovative airports
• Contribute to projects tied directly to YVR’s 2026 Business Plan
• Gain exposure to complex, multidisciplinary operations
• Build experience that’s transferable across sectors
• Learn alongside people who are invested in developing early talent
• See how your work supports the province, the economy, and the public
Ready for Takeoff?
If you’re energized by big systems, real responsibility, and meaningful impact, we’d love to hear from you. Apply and help shape the future of aviation, infrastructure, and service at YVR.
Additional Information:
• Application deadline: March 20, 2026
• Start and End Date: May 4, 2026 – August 28, 2026 (4-month Term)
• Hours of Work: 37.5 hours
• Dress Code: Westcoast Business Casual
• Number of Openings: 1
Salary Range : $24.80/hr + Living Wage top up to $27.33/hr
Who We Are
YVR is more than just an airport. We connect our beautiful province and all it has to offer to the world. We are all leaders and trailblazers for change and innovation, so no matter the department or team you’re a part of, the work you do matters.
At YVR, we are flexible in everything we do. We will work together to find ways to deliver customer excellence that helps us all thrive.
Whatever your background and wherever you’re from, you belong at YVR. If you have any questions about accessibility or require any assistance applying, please reach out at
[email protected].
---
Title: Electronics Co-Op
Company: EnerSys
EnerSys is a global leader in stored energy solutions for industrial applications. We have over thirty manufacturing and assembly plants worldwide servicing over 10,000 customers in more than 100 countries. Worldwide headquarters are located in Reading, PA, USA with regional headquarters in Europe and Asia. We complement our extensive line of Motive Power and Energy Systems with a full range of integrated services and systems. With sales and service locations throughout the world, and over 100 years of battery experience, EnerSys is the power/full solution for stored DC power products.
What We’re Offering
• Culture: We value and strive for excellence in all that we do through innovative technology by creating long lasting relationships with our stakeholders, co-workers, and customers. We continually strive to foster teamwork, engagement and enhance our employee’s skills and competence by providing appropriate training.
Compensation Range: 22.00-25.00 per hour
Compensation may vary based on applicant's work experience, education level, skill set, and/or location.
Job Purpose
Alpha Technologies Ltd. is looking for an Electronics Co-Op to join the Engineering team in our Burnaby head office. We are looking for a co-op student to help define a test strategy, and then perform the tests, for some exciting new analog and digital products we are developing. The position involves working within the engineering team to fully understand the products and then working on the verification test plan for the prototype products. The candidate will perform the testing and troubleshooting, if necessary, using a variety of lab test equipment, and may also need to construct test jigs or other equipment as part of the test process. The candidate will also be responsible to create the test report. If problems are found, the student will work with the designers to help identify and implement solutions. The ideal candidate shall have a good working knowledge and experience with microprocessors, peripheral circuits, and switching power supplies.
Essential Duties and Responsibilities
• Design and develop hardware circuits for various test jigs, involving both analog and digital circuitry.
• Familiarity with LTSPICE or similar would be desirable
• Familiarity with basic op-amp circuits, transistor circuits, etc.
• Familiarity with PCB CAD package like Altium would be an asset
• Create design calculations and develop detailed design verification plans
• Execute test plans, document and analyze results
• Propose and implement design changes to the product based on testing
• Prepare and maintain technical documentation
• Work with the firmware and software team to develop solutions
• Contribute to continuous improvements of existing systems.
• Participate in the development of the department's strategic goals and direction
Qualifications
• Enrolled in a 4 year university pursuing an engineering degree. Electronics Engineering degree preferred.
• Familiarity with a variety of test equipment, including oscilloscopes, spectrum analyzers, environmental chambers, etc.
• Experience soldering SMT components.
• Any experience with analog circuits, micro-processor based circuitry, switch-mode power supplies.
• Creating test plans, executing testing and documenting results.
• Requires strong written and verbal communication skills.
• Ability to collaborate with team and stakeholders to derive options and solutions to problems.
• Excellent analysis, problem solving, design and troubleshooting skills.
• Thrives in a highly collaborative cross-functional environment.
• Ability to work a full-time 40 hour per week schedule.
• Ability to commute to the Burnaby location reliably.
• Available to work between May 4 to August 28 , 2026.
General Job Requirements
• This position will work in an office setting, expect minimal physical demands.
EnerSys provides equal employment opportunities to all employees and applicants for employment and prohibits discrimination and harassment of any type without regard to race, color, religion, age, sex, national origin, disability status, genetics, protected veteran status, sexual orientation, gender identity or expression, or any other characteristic protected by federal, state or local laws.
Know Your Rights
Know Your Rights (Spanish)
We use artificial intelligence to screen, assess and select applicants for open positions, including for the purposes of reviewing and ranking application materials and scoring answers to application questions. Accordingly, decisions about your application and eligibility for employment with EnerSys may be made based exclusively on the automated processing of the personal information that you submit in your application materials
Title: Software Co-op
Company: Cellula Robotics
Job description
Cellula is looking to add a Software Co-op to its Engineering team to support its growing list of engineering-to-order projects. This position will begin in May 2026. We are open to both 4 or 8 month co-op terms, but with a preference for 8 months.
Cellula is an innovative engineering company that specializes in automated and tele-robotics systems; primarily for the offshore and subsea market space. Our solutions are designed, built, and tested in-house by a skilled team of engineers, technicians, and support staff. We take pride in what we do and work hard to bring the best value to our clients. We want to learn from the field so we can continually improve how we deliver solutions to our clients. To that end, we encourage the same team members who helped design & build our solutions to assist the client in the field with installation, operational trials, and service.
As the Software Co-op, you will be responsible for working in the engineering team on the design, testing and commissioning of AUV (Autonomous Underwater Vehicle). General duties include supporting engineers in the testing, development and production of subsea robotic technology. Good communication skills and a willingness to learn are required, and students with an eager attitude and excellent work ethic will gain the most benefit. You will be exposed to aspects of the key disciplines involved: Electrical, Mechanical, Software and Project Management particularly from an software engineering point of view.
As a company, we offer team members a chance to grow professionally with ever-changing projects. Our office is a casual, collaborative environment where you will be able to learn about our state-of-the-art systems from technical experts. In addition, we offer flexible work hours, benefits, and competitive compensation. This position is primarily an on-site position with some limited flexibility for remote work.
Duties and Responsibilities
• AUV software development and testing
• Writing/reviewing software documentation, including design documents, test plans, and reports
• System testing, including software tests, bench tests, and subsystem tests
• Assisting with testing, troubleshooting, and commissioning AUV system
• Assisting with operational objectives
• Data review & analysis
• Assist in system assembly, integration, and troubleshooting
Required Skills & Experience
• Currently enrolled in a Software Engineering, Mechatronics, or Computer Engineering program leading to a Bachelor’s degree or higher
• Proficient communication skills, both written and oral
• Problem solving, analytical thinking, and research skills
• Aptitude for detail with the ability to work in a team environment
• Ability to identify tasks and take initiative with minimal direction
• Experience with Windows O/S, Microsoft office (Word, Excel, Powerpoint, Visio)
• Experience with Linux
• Experience with C++ and Python programming languages
Desirable Skills & Experience
• Experience in the subsea industry
• Experience with robotic systems
• Control Systems: theory & practice
• Labview and Arduino programming
• MATLAB and Simulink experience
• ISO9001 quality control
• Extracurricular club/team involvement
Please include a cover letter as part of your application.
Reports to: Software Engineering Manager
Application advice: Well written cover letters carry a lot of weight, as do select photos of projects and/or experience. What makes you unique, special & interesting to us? Don’t tell us – show us!
The above job requirements will be further developed during the term. All requirements are not needed to apply, but some experience will be helpful.
#include "Sorter.hpp"
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <regex>
using namespace std;
// Constructor
Sorter::Sorter() {
initializeRelatedSkills();
}
// Initialize related skills mapping
void Sorter::initializeRelatedSkills() const {
// Programming languages family
relatedSkills["Python"] = {"python", "python3", "python programming", "django", "flask"};
relatedSkills["Java"] = {"java", "java8", "java11", "spring", "maven", "gradle"};
relatedSkills["C++"] = {"cpp", "c plus plus", "c/c++", "embedded"};
relatedSkills["JavaScript"] = {"js", "javascript", "node.js", "react", "vue", "angular", "typescript"};
relatedSkills["SQL"] = {"sql", "mysql", "postgresql", "database", "query"};
// Web frameworks
relatedSkills["React"] = {"react", "reactjs", "react.js", "jsx"};
relatedSkills["Node.js"] = {"node", "nodejs", "node.js", "express"};
// GIS family
relatedSkills["ArcGIS"] = {"arcgis", "esri", "gis", "mapping", "arcmap"};
relatedSkills["GIS"] = {"gis", "mapping", "spatial", "geographic"};
// Data science
relatedSkills["Machine Learning"] = {"ml", "machine learning", "ai", "artificial intelligence", "deep learning"};
relatedSkills["Data Analysis"] = {"data analysis", "analytics", "statistics", "visualization"};
}
// Convert string to lowercase
string Sorter::toLower(const string& str) const {
string lower = str;
transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
return lower;
}
// Check for exact word match
bool Sorter::isExactMatch(const string& text, const string& keyword) const {
string lowerText = toLower(text);
string lowerKeyword = toLower(keyword);
regex wordRegex("\\b" + lowerKeyword + "\\b");
return regex_search(lowerText, wordRegex);
}
// Check for partial match
bool Sorter::isPartialMatch(const string& text, const string& keyword) const {
string lowerText = toLower(text);
string lowerKeyword = toLower(keyword);
return lowerText.find(lowerKeyword) != string::npos;
}
// Calculate similarity between two strings (0-1)
double Sorter::calculateSimilarity(const string& s1, const string& s2) const {
string lower1 = toLower(s1);
string lower2 = toLower(s2);
if (lower1 == lower2) return 1.0;
// Check if one contains the other
if (lower1.find(lower2) != string::npos || lower2.find(lower1) != string::npos) {
return 0.8;
}
// Simple character overlap (very basic)
int common = 0;
for (char c : lower1) {
if (lower2.find(c) != string::npos) {
common++;
}
}
double maxLen = max(lower1.length(), lower2.length());
return (maxLen > 0) ? (common / maxLen) * 0.5 : 0.0;
}
// Check if two skills are related
bool Sorter::areSkillsRelated(const string& skill1, const string& skill2) const {
string lower1 = toLower(skill1);
string lower2 = toLower(skill2);
// Direct relationship
for (const auto& [baseSkill, relatedList] : relatedSkills) {
string lowerBase = toLower(baseSkill);
// Check if either skill is the base
if (lower1 == lowerBase || lower2 == lowerBase) {
for (const auto& related : relatedList) {
string lowerRelated = toLower(related);
if (lower1 == lowerRelated || lower2 == lowerRelated) {
return true;
}
}
}
// Check if both are in the same related list
bool skill1InList = false;
bool skill2InList = false;
for (const auto& related : relatedList) {
string lowerRelated = toLower(related);
if (lower1 == lowerRelated) skill1InList = true;
if (lower2 == lowerRelated) skill2InList = true;
}
if (skill1InList && skill2InList) {
return true;
}
}
// Partial match as fallback
if (lower1.find(lower2) != string::npos || lower2.find(lower1) != string::npos) {
return true;
}
return false;
}
// Get match value between student skill and job skill (0-1)
double Sorter::getSkillMatchValue(const string& studentSkill, const string& jobSkill) const {
string lowerStudent = toLower(studentSkill);
string lowerJob = toLower(jobSkill);
// Exact match
if (lowerStudent == lowerJob) {
return 1.0;
}
// Related skills
if (areSkillsRelated(studentSkill, jobSkill)) {
return 0.7;
}
// Partial match
if (lowerStudent.find(lowerJob) != string::npos || lowerJob.find(lowerStudent) != string::npos) {
return 0.5;
}
return 0.0;
}
// Helper function to extract potential skills from text descriptions
vector<string> Sorter::extractSkillsFromText(const string& text, const vector<string>& jobSkills) const {
vector<string> foundSkills;
string lowerText = toLower(text);
for (const auto& jobSkill : jobSkills) {
string lowerSkill = toLower(jobSkill);
// Check if the job skill appears in the text
if (lowerText.find(lowerSkill) != string::npos) {
foundSkills.push_back(jobSkill);
}
// Also check related skills
else {
for (const auto& [baseSkill, relatedList] : relatedSkills) {
if (lowerSkill == toLower(baseSkill)) {
for (const auto& related : relatedList) {
if (lowerText.find(toLower(related)) != string::npos) {
foundSkills.push_back(jobSkill);
break;
}
}
}
}
}
}
return foundSkills;
}
// Updated calculateSkillScore function
double Sorter::calculateSkillScore(const Job& job, const StudentProfile& student) const {
const auto& jobSkills = job.getRequiredSkills();
const auto& studentSkills = student.getSkills();
const auto& studentProjects = student.getProjects();
const auto& studentCourses = student.getCourses();
if (jobSkills.empty()) {
return 0.5; // Neutral score if no skills listed
}
// Step 1: Create a map to track which job skills are covered
map<string, double> skillCoverage;
for (const auto& jobSkill : jobSkills) {
skillCoverage[jobSkill] = 0.0; // Initialize with no coverage
}
// Step 2: Check explicitly listed skills (highest confidence)
for (const auto& studentSkill : studentSkills) {
for (const auto& jobSkill : jobSkills) {
double matchValue = getSkillMatchValue(studentSkill, jobSkill);
if (matchValue > skillCoverage[jobSkill]) {
skillCoverage[jobSkill] = max(skillCoverage[jobSkill], matchValue);
}
}
}
// Step 3: Extract skills from projects (medium confidence)
for (const auto& project : studentProjects) {
vector<string> extractedSkills = extractSkillsFromText(project, jobSkills);
for (const auto& skill : extractedSkills) {
// Project evidence gives 0.8 confidence (slightly less than explicit skill)
skillCoverage[skill] = max(skillCoverage[skill], 0.8);
}
}
// Step 4: Extract skills from courses (medium-high confidence)
for (const auto& course : studentCourses) {
vector<string> extractedSkills = extractSkillsFromText(course, jobSkills);
for (const auto& skill : extractedSkills) {
// Course evidence gives 0.9 confidence (almost as good as explicit skill)
skillCoverage[skill] = max(skillCoverage[skill], 0.9);
}
}
// Step 5: Calculate overall skill score
double totalCoverage = 0.0;
for (const auto& [skill, coverage] : skillCoverage) {
totalCoverage += coverage;
}
// Base score is average coverage of required skills
double baseScore = totalCoverage / jobSkills.size();
// Bonus for having multiple ways to demonstrate skills
int skillsCovered = 0;
for (const auto& [skill, coverage] : skillCoverage) {
if (coverage > 0.5) skillsCovered++;
}
double coverageRatio = skillsCovered / (double)jobSkills.size();
// Final score combines depth (coverage) and breadth (how many skills covered)
double finalScore = (baseScore * 0.7) + (coverageRatio * 0.3);
return min(1.0, finalScore); // Cap at 1.0
}
// Calculate interest score
double Sorter::calculateInterestScore(const Job& job, const StudentProfile& student) const {
const auto& interests = student.getInterests();
const auto& jobSkills = job.getRequiredSkills();
const auto& industries = job.getIndustries();
const string& major = student.getMajor(); // GET MAJOR
if (interests.empty() && major.empty()) {
return 0.5;
}
double totalScore = 0.0;
int matchedItems = 0;
// Check interests (as before)
for (const auto& interest : interests) {
double bestMatch = 0.0;
// ... existing interest matching code ...
if (bestMatch > 0) {
totalScore += bestMatch;
matchedItems++;
}
}
// NEW: Check major against job title and industry
if (!major.empty()) {
double majorMatch = 0.0;
// Check against job title
if (isPartialMatch(job.getTitle(), major)) {
majorMatch = max(majorMatch, 0.7);
}
// Check against industries
for (const auto& industry : industries) {
if (isPartialMatch(industry, major)) {
majorMatch = max(majorMatch, 0.8);
}
}
// Check against skills
for (const auto& skill : jobSkills) {
if (isPartialMatch(skill, major)) {
majorMatch = max(majorMatch, 0.6);
}
}
if (majorMatch > 0) {
totalScore += majorMatch;
matchedItems++;
}
}
return (matchedItems > 0) ? totalScore / matchedItems : 0.0;
}
// Calculate industry match score
double Sorter::calculateIndustryScore(const Job& job, const StudentProfile& student) const {
const auto& industries = job.getIndustries();
const auto& interests = student.getInterests();
const auto& experiences = student.getExperiences();
if (industries.empty()) {
return 0.5; // Neutral if no industries listed
}
double totalScore = 0.0;
// Check if student's experiences match industries
for (const auto& industry : industries) {
double bestMatch = 0.0;
// Match with experiences
for (const auto& exp : experiences) {
if (isPartialMatch(exp, industry)) {
bestMatch = max(bestMatch, 1.0);
}
}
// Also consider interests as secondary indicator
for (const auto& interest : interests) {
if (isPartialMatch(industry, interest)) {
bestMatch = max(bestMatch, 0.6); // Interest alone is worth less than actual experience
}
}
totalScore += bestMatch;
}
return totalScore / industries.size();
}
// Calculate remote match score
double Sorter::calculateRemoteScore(const Job& job, const StudentProfile& student) const {
bool studentWantsRemote = student.getWantRemote();
bool jobIsRemote = job.getRemote();
// Student wants remote AND job is remote = PERFECT match
if (studentWantsRemote && jobIsRemote) {
return 1.0; // 100% - exactly what they want
}
// Student wants remote but job is onsite = POOR match
if (studentWantsRemote && !jobIsRemote) {
return 0.2; // 20% - not what they wanted
}
// Student wants onsite but job is remote = POOR match
if (!studentWantsRemote && jobIsRemote) {
return 0.2; // 20% - not what they wanted
}
// Student wants onsite AND job is onsite = PERFECT match
if (!studentWantsRemote && !jobIsRemote) {
return 1.0; // 100% - exactly what they want
}
return 0.5; // Neutral fallback
}
// Calculate total score for a job
double Sorter::calculateTotalScore(const Job& job, const StudentProfile& student) const {
// Check degree level FIRST - GATEKEEPER
string jobExp = toLower(job.getExperienceLevel());
if (jobExp == "phd" || jobExp == "masters") {
return 0.0; // Not qualified - score 0
}
// Only calculate detailed scores for undergraduate positions
double skillScore = calculateSkillScore(job, student);
double interestScore = calculateInterestScore(job, student);
double remoteScore = calculateRemoteScore(job, student);
double industryScore = calculateIndustryScore(job, student);
// Apply weights (now with 4 components)
double total = (skillScore * weights.skills) +
(interestScore * weights.interests) +
(remoteScore * weights.remote) +
(industryScore * weights.industry);
// Normalize to percentage
double maxPossible = weights.skills + weights.interests +
weights.remote + weights.industry;
return (total / maxPossible) * 100.0;
}
// Sort jobs by score
vector<pair<Job, double>> Sorter::sortJobs(const vector<Job>& jobs,
const StudentProfile& student) const {
vector<pair<Job, double>> ranked;
for (const auto& job : jobs) {
double score = calculateTotalScore(job, student);
ranked.push_back({job, score});
}
// Sort by score (highest first)
sort(ranked.begin(), ranked.end(),
[](const pair<Job, double>& a, const pair<Job, double>& b) {
return a.second > b.second;
});
return ranked;
}
// Get detailed breakdown of match scores
map<string, double> Sorter::getMatchBreakdown(const Job& job,
const StudentProfile& student) const {
map<string, double> breakdown;
breakdown["Skills"] = calculateSkillScore(job, student) * 100;
breakdown["Interests"] = calculateInterestScore(job, student) * 100;
breakdown["Remote"] = calculateRemoteScore(job, student) * 100;
breakdown["Industry"] = calculateIndustryScore(job, student) * 100;
return breakdown;
}
// Helper function for getting matched skills
vector<string> Sorter::getMatchedSkills(const Job& job, const StudentProfile& student) const {
const auto& jobSkills = job.getRequiredSkills();
const auto& studentSkills = student.getSkills();
vector<string> matchedSkills;
for (const auto& jobSkill : jobSkills) {
for (const auto& studentSkill : studentSkills) {
if (getSkillMatchValue(studentSkill, jobSkill) >= 0.5) {
matchedSkills.push_back(jobSkill);
break;
}
}
}
return matchedSkills;
}
void Sorter::displayRankings(const vector<pair<Job, double>>& rankedJobs,
const StudentProfile& student) const {
cout << "\n" << string(80, '=') << "\n";
cout << " JOB RANKINGS FOR " << student.getName() << "\n";
cout << string(80, '=') << "\n\n";
if (rankedJobs.empty()) {
cout << "No jobs to rank.\n";
return;
}
// Separate jobs by qualification
vector<pair<Job, double>> qualified;
vector<pair<Job, double>> unqualified;
for (const auto& jobPair : rankedJobs) {
string jobExp = toLower(jobPair.first.getExperienceLevel());
if (jobExp == "phd" || jobExp == "masters") {
unqualified.push_back(jobPair);
} else {
qualified.push_back(jobPair);
}
}
// Display qualified jobs first
if (!qualified.empty()) {
cout << "JOBS YOU QUALIFY FOR:\n";
cout << string(80, '-') << "\n";
for (size_t i = 0; i < qualified.size(); ++i) {
const auto& job = qualified[i].first;
double score = qualified[i].second;
auto breakdown = getMatchBreakdown(job, student);
cout << "+" << string(78, '-') << "+\n";
cout << "| RANK #" << left << setw(3) << (i + 1)
<< " - " << setw(50) << job.getTitle()
<< " Score: " << fixed << setprecision(1) << score << "% |\n";
cout << "|" << string(78, '-') << "|\n";
cout << "| Company: " << left << setw(68) << job.getCompany() << "|\n";
cout << "| Type (Remote/Onsite): " << left << setw(27) << (job.getRemote() ? "Yes" : "No")
<< " Salary: $" << fixed << setprecision(2) << job.getSalary() << " |\n";
cout << "|" << string(78, '-') << "|\n";
// Show match breakdown
cout << "| MATCH BREAKDOWN: |\n";
cout << "| Skills: " << fixed << setprecision(1) << setw(6)
<< breakdown["Skills"] << "% match"
<< string(44 - to_string((int)breakdown["Skills"]).length(), ' ') << "|\n";
cout << "| Interests: " << fixed << setprecision(1) << setw(6)
<< breakdown["Interests"] << "% match"
<< string(44 - to_string((int)breakdown["Interests"]).length(), ' ') << "|\n";
cout << "| Industry: " << fixed << setprecision(1) << setw(6)
<< breakdown["Industry"] << "% match"
<< string(44 - to_string((int)breakdown["Industry"]).length(), ' ') << "|\n";
cout << "| Type (Remote/Onsite): " << fixed << setprecision(1) << setw(6)
<< breakdown["Remote"] << "% match"
<< string(44 - to_string((int)breakdown["Remote"]).length(), ' ') << "|\n";
cout << "|" << string(78, '-') << "|\n";
// Get all matches
vector<string> matchedSkills = getMatchedSkills(job, student);
vector<string> matchedIndustries;
vector<string> matchedInterests;
// Find matched industries
const auto& jobIndustries = job.getIndustries();
const auto& studentExperiences = student.getExperiences();
const auto& studentInterests = student.getInterests();
// Check which industries match student experiences or interests
for (const auto& industry : jobIndustries) {
// Check experiences
for (const auto& exp : studentExperiences) {
if (isPartialMatch(exp, industry)) {
matchedIndustries.push_back(industry + " (experience)");
break;
}
}
// Check interests (if not already matched by experience)
if (find(matchedIndustries.begin(), matchedIndustries.end(), industry + " (experience)") == matchedIndustries.end()) {
for (const auto& interest : studentInterests) {
if (isPartialMatch(interest, industry)) {
matchedIndustries.push_back(industry + " (interest)");
break;
}
}
}
}
// Find which interests matched
for (const auto& interest : studentInterests) {
// Check against job title
if (isPartialMatch(job.getTitle(), interest)) {
matchedInterests.push_back(interest + " (title)");
}
// Check against job skills
else {
for (const auto& skill : job.getRequiredSkills()) {
if (isPartialMatch(skill, interest) || isPartialMatch(interest, skill)) {
matchedInterests.push_back(interest + " (skill)");
break;
}
}
}
}
// Display Matched Skills
if (!matchedSkills.empty()) {
cout << "| MATCHED SKILLS: |\n";
string line = "| ";
for (const auto& skill : matchedSkills) {
if (line.length() + skill.length() + 3 > 79) {
cout << line << string(79 - line.length(), ' ') << "|\n";
line = "| " + skill;
} else {
if (line != "| ") line += ", ";
line += skill;
}
}
if (!line.empty()) {
cout << line << string(79 - line.length(), ' ') << "|\n";
}
}
// Display Matched Industries
if (!matchedIndustries.empty()) {
cout << "| MATCHED INDUSTRIES: |\n";
string line = "| ";
for (const auto& industry : matchedIndustries) {
if (line.length() + industry.length() + 3 > 79) {
cout << line << string(79 - line.length(), ' ') << "|\n";
line = "| " + industry;
} else {
if (line != "| ") line += ", ";
line += industry;
}
}
if (!line.empty()) {
cout << line << string(79 - line.length(), ' ') << "|\n";
}
}
// Display Matched Interests
if (!matchedInterests.empty()) {
cout << "| MATCHED INTERESTS: |\n";
string line = "| ";
for (const auto& interest : matchedInterests) {
if (line.length() + interest.length() + 3 > 79) {
cout << line << string(79 - line.length(), ' ') << "|\n";
line = "| " + interest;
} else {
if (line != "| ") line += ", ";
line += interest;
}
}
if (!line.empty()) {
cout << line << string(79 - line.length(), ' ') << "|\n";
}
}
// Show skills evidence from projects/courses (if any)
const auto& projects = student.getProjects();
const auto& courses = student.getCourses();
if (!projects.empty() || !courses.empty()) {
cout << "|" << string(78, '-') << "|\n";
cout << "| SKILLS EVIDENCE: |\n";
if (!projects.empty()) {
cout << "| • From " << projects.size() << " project(s) |\n";
}
if (!courses.empty()) {
cout << "| • From " << courses.size() << " course(s) |\n";
}
}
cout << "+" << string(78, '-') << "+\n\n";
}
}
// Display unqualified jobs with warning
if (!unqualified.empty()) {
cout << "\nJOBS YOU ARE NOT QUALIFIED FOR (Advanced Degree Required):\n";
cout << string(80, '-') << "\n";
for (const auto& jobPair : unqualified) {
const auto& job = jobPair.first;
cout << "+" << string(78, '-') << "+\n";
cout << "| NOT QUALIFIED: " << left << setw(57) << job.getTitle() << " |\n";
cout << "|" << string(78, '-') << "|\n";
cout << "| Company: " << left << setw(68) << job.getCompany() << "|\n";
cout << "| This position requires a " << job.getExperienceLevel()
<< " degree. |\n";
cout << "| You are an undergraduate student. |\n";
// Still show what the score WOULD have been (for curiosity)
auto breakdown = getMatchBreakdown(job, student);
double potentialScore = (breakdown["Skills"] * weights.skills +
breakdown["Interests"] * weights.interests +
breakdown["Industry"] * weights.industry +
breakdown["Remote"] * weights.remote) /
(weights.skills + weights.interests +
weights.industry + weights.remote);
cout << "| Score would have been: " << fixed << setprecision(1)
<< setw(6) << potentialScore << "% (if qualified) |\n";
cout << "+" << string(78, '-') << "+\n\n";
}
}
}
// Set custom weights
void Sorter::setWeights(double skills, double interests, double remote, double industry) {
double total = skills + interests + remote + industry;
if (abs(total - 1.0) > 0.01) {
cout << "Warning: Weights sum to " << total << ". Normalizing to 1.0\n";
weights.skills = skills / total;
weights.interests = interests / total;
weights.remote = remote / total;
weights.industry = industry / total;
} else {
weights.skills = skills;
weights.interests = interests;
weights.remote = remote;
weights.industry = industry;
}
}
// Reset to default weights
void Sorter::resetWeights() {
weights.skills = 0.50;
weights.interests = 0.15;
weights.remote = 0.10;
weights.industry = 0.25;
}
#include "JobParser.hpp"
#include <iostream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <regex>
#include <set>
using namespace std;
// Constructor
JobParser::JobParser(const string& file) : filename(file) {}
// Helper: trim whitespace
string JobParser::trim(const string& str) {
size_t first = str.find_first_not_of(" \t\n\r");
if (first == string::npos) return "";
size_t last = str.find_last_not_of(" \t\n\r");
return str.substr(first, last - first + 1);
}
// Helper: split string by delimiter
vector<string> JobParser::splitString(const string& str, char delimiter) {
vector<string> tokens;
stringstream ss(str);
string token;
while (getline(ss, token, delimiter)) {
tokens.push_back(trim(token));
}
return tokens;
}
// Helper: check if text contains a word (case-insensitive, whole word)
bool JobParser::containsWord(const string& text, const string& word) {
string lowerText = text;
string lowerWord = word;
transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower);
transform(lowerWord.begin(), lowerWord.end(), lowerWord.begin(), ::tolower);
regex wordRegex("\\b" + lowerWord + "\\b");
return regex_search(lowerText, wordRegex);
}
// Helper: extract salary from description
double JobParser::extractSalary(const string& text) {
// Pattern for salary ranges: $24.80-$27.33/hr or $24.80 per hour
regex rangePattern(R"(\$\s*(\d+(?:\.\d+)?)\s*-\s*\$\s*(\d+(?:\.\d+)?)\s*(?:\/hr|per hour|/hour|/year|annually)?)");
regex singlePattern(R"(\$\s*(\d+(?:\.\d+)?)\s*(?:\/hr|per hour|/hour|/year|annually))");
regex numberPattern(R"((\d+(?:\.\d+)?)\s*(?:\/hr|per hour|/hour))");
smatch matches;
string lowerText = text;
transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower);
if (regex_search(lowerText, matches, rangePattern)) {
return stod(matches[1].str());
}
if (regex_search(lowerText, matches, singlePattern)) {
return stod(matches[1].str());
}
if (regex_search(lowerText, matches, numberPattern)) {
return stod(matches[1].str());
}
return 0.0;
}
// Helper: determine experience level from description
string JobParser::determineExperienceLevel(const string& text) {
string lowerText = text;
transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower);
if (containsWord(lowerText, "phd") ||
containsWord(lowerText, "doctoral") ||
containsWord(lowerText, "doctorate") ||
containsWord(lowerText, "ph.d")) {
return "phd";
}
if (containsWord(lowerText, "masters") ||
containsWord(lowerText, "master's") ||
containsWord(lowerText, "graduate degree") ||
containsWord(lowerText, "graduate student") ||
containsWord(lowerText, "msc") ||
containsWord(lowerText, "m.s.")) {
return "masters";
}
if (containsWord(lowerText, "undergraduate") ||
containsWord(lowerText, "bachelor") ||
containsWord(lowerText, "bsc") ||
containsWord(lowerText, "b.s.") ||
containsWord(lowerText, "b.a.") ||
containsWord(lowerText, "co-op") ||
containsWord(lowerText, "coop") ||
containsWord(lowerText, "intern") ||
containsWord(lowerText, "internship")) {
return "undergraduate";
}
return "undergraduate";
}
// Helper: extract ALL skills from description (comprehensive list)
vector<string> JobParser::extractAllSkills(const string& text) {
set<string> uniqueSkills;
string lowerText = text;
transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower);
// ===== PROGRAMMING LANGUAGES =====
vector<pair<string, string>> programmingLanguages = {
{"python", "Python"},
{"java", "Java"},
{"c\\+\\+", "C++"},
{"c#", "C#"},
{"javascript", "JavaScript"},
{"typescript", "TypeScript"},
{"html", "HTML"},
{"css", "CSS"},
{"sql", "SQL"},
{"php", "PHP"},
{"ruby", "Ruby"},
{"swift", "Swift"},
{"kotlin", "Kotlin"},
{"go", "Go"},
{"rust", "Rust"},
{"scala", "Scala"},
{"perl", "Perl"},
{"r\\b", "R"},
{"matlab", "MATLAB"},
{"julia", "Julia"},
{"dart", "Dart"},
{"assembly", "Assembly"},
{"bash", "Bash"},
{"powershell", "PowerShell"}
};
// ===== WEB DEVELOPMENT =====
vector<pair<string, string>> webTechnologies = {
{"react", "React"},
{"angular", "Angular"},
{"vue", "Vue.js"},
{"node.js", "Node.js"},
{"nodejs", "Node.js"},
{"express", "Express.js"},
{"django", "Django"},
{"flask", "Flask"},
{"spring", "Spring"},
{"asp.net", "ASP.NET"},
{"jquery", "jQuery"},
{"bootstrap", "Bootstrap"},
{"tailwind", "Tailwind CSS"},
{"rest", "REST API"},
{"graphql", "GraphQL"},
{"ajax", "AJAX"},
{"json", "JSON"},
{"xml", "XML"},
{"webpack", "Webpack"},
{"babel", "Babel"},
{"npm", "npm"},
{"yarn", "Yarn"}
};
// ===== DATABASES =====
vector<pair<string, string>> databases = {
{"mysql", "MySQL"},
{"postgresql", "PostgreSQL"},
{"postgres", "PostgreSQL"},
{"mongodb", "MongoDB"},
{"nosql", "NoSQL"},
{"redis", "Redis"},
{"elasticsearch", "Elasticsearch"},
{"cassandra", "Cassandra"},
{"oracle", "Oracle"},
{"sqlite", "SQLite"},
{"mariadb", "MariaDB"},
{"dynamodb", "DynamoDB"},
{"firebase", "Firebase"},
{"supabase", "Supabase"}
};
// ===== CLOUD & DEVOPS =====
vector<pair<string, string>> cloudDevOps = {
{"aws", "AWS"},
{"amazon web services", "AWS"},
{"azure", "Azure"},
{"gcp", "Google Cloud"},
{"google cloud", "Google Cloud"},
{"cloud computing", "Cloud Computing"},
{"docker", "Docker"},
{"kubernetes", "Kubernetes"},
{"k8s", "Kubernetes"},
{"jenkins", "Jenkins"},
{"gitlab ci", "GitLab CI"},
{"github actions", "GitHub Actions"},
{"terraform", "Terraform"},
{"ansible", "Ansible"},
{"puppet", "Puppet"},
{"chef", "Chef"},
{"linux", "Linux"},
{"unix", "Unix"},
{"windows server", "Windows Server"}
};
// ===== GIS & ENGINEERING =====
vector<pair<string, string>> gisEngineering = {
{"arcgis", "ArcGIS"},
{"arcgis pro", "ArcGIS Pro"},
{"vertigis", "Vertigis Studio"},
{"fme", "FME"},
{"esri", "ESRI"},
{"gis", "GIS"},
{"qgis", "QGIS"},
{"mapbox", "Mapbox"},
{"google earth", "Google Earth"},
{"remote sensing", "Remote Sensing"},
{"autocad", "AutoCAD"},
{"solidworks", "SolidWorks"},
{"catia", "CATIA"},
{"ansys", "ANSYS"},
{"matlab", "MATLAB"},
{"simulink", "Simulink"},
{"labview", "LabVIEW"},
{"plc", "PLC"},
{"scada", "SCADA"},
{"drainage", "Drainage Engineering"},
{"infrastructure", "Infrastructure"},
{"transportation", "Transportation Engineering"}
};
// ===== DATA SCIENCE & ML =====
vector<pair<string, string>> dataScience = {
{"machine learning", "Machine Learning"},
{"deep learning", "Deep Learning"},
{"ai", "Artificial Intelligence"},
{"artificial intelligence", "Artificial Intelligence"},
{"data science", "Data Science"},
{"data analysis", "Data Analysis"},
{"data visualization", "Data Visualization"},
{"statistics", "Statistics"},
{"pandas", "Pandas"},
{"numpy", "NumPy"},
{"scikit-learn", "Scikit-learn"},
{"tensorflow", "TensorFlow"},
{"pytorch", "PyTorch"},
{"keras", "Keras"},
{"opencv", "OpenCV"},
{"nlp", "Natural Language Processing"},
{"llm", "Large Language Models"},
{"chatgpt", "ChatGPT"},
{"prompt engineering", "Prompt Engineering"},
{"tableau", "Tableau"},
{"power bi", "Power BI"},
{"excel", "Excel"},
{"spss", "SPSS"},
{"sas", "SAS"}
};
// ===== SOFT SKILLS =====
vector<pair<string, string>> softSkills = {
{"leadership", "Leadership"},
{"communication", "Communication"},
{"teamwork", "Teamwork"},
{"collaboration", "Collaboration"},
{"problem solving", "Problem Solving"},
{"critical thinking", "Critical Thinking"},
{"time management", "Time Management"},
{"project management", "Project Management"},
{"agile", "Agile"},
{"scrum", "Scrum"},
{"kanban", "Kanban"},
{"presentation", "Presentation Skills"},
{"writing", "Technical Writing"},
{"documentation", "Documentation"}
};
// Combine all skill categories
vector<pair<string, string>> allSkills;
allSkills.insert(allSkills.end(), programmingLanguages.begin(), programmingLanguages.end());
allSkills.insert(allSkills.end(), webTechnologies.begin(), webTechnologies.end());
allSkills.insert(allSkills.end(), databases.begin(), databases.end());
allSkills.insert(allSkills.end(), cloudDevOps.begin(), cloudDevOps.end());
allSkills.insert(allSkills.end(), gisEngineering.begin(), gisEngineering.end());
allSkills.insert(allSkills.end(), dataScience.begin(), dataScience.end());
allSkills.insert(allSkills.end(), softSkills.begin(), softSkills.end());
for (const auto& skillPair : allSkills) {
const string& pattern = skillPair.first;
const string& displayName = skillPair.second;
regex wordRegex("\\b" + pattern + "\\b", regex::icase);
if (regex_search(lowerText, wordRegex)) {
uniqueSkills.insert(displayName);
}
}
vector<string> skills(uniqueSkills.begin(), uniqueSkills.end());
return skills;
}
// Helper: extract industries from description
vector<string> JobParser::extractIndustries(const string& text) {
set<string> uniqueIndustries;
string lowerText = text;
transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower);
vector<pair<string, string>> industryKeywords = {
{"technology", "Technology"},
{"software", "Software"},
{"hardware", "Hardware"},
{"healthcare", "Healthcare"},
{"medical", "Healthcare"},
{"pharma", "Pharmaceutical"},
{"biotech", "Biotechnology"},
{"finance", "Finance"},
{"banking", "Finance"},
{"insurance", "Insurance"},
{"aerospace", "Aerospace"},
{"aviation", "Aviation"},
{"automotive", "Automotive"},
{"energy", "Energy"},
{"oil and gas", "Oil & Gas"},
{"renewable energy", "Renewable Energy"},
{"consulting", "Consulting"},
{"education", "Education"},
{"university", "Education"},
{"government", "Government"},
{"public sector", "Government"},
{"non-profit", "Non-profit"},
{"ngo", "Non-profit"},
{"retail", "Retail"},
{"e-commerce", "E-commerce"},
{"manufacturing", "Manufacturing"},
{"telecommunications", "Telecommunications"},
{"telecom", "Telecommunications"},
{"transportation", "Transportation"},
{"logistics", "Logistics"},
{"infrastructure", "Infrastructure"},
{"construction", "Construction"},
{"real estate", "Real Estate"},
{"sustainability", "Sustainability"},
{"climate", "Climate Tech"},
{"environmental", "Environmental"},
{"mining", "Mining"},
{"agriculture", "Agriculture"},
{"food", "Food & Beverage"},
{"hospitality", "Hospitality"},
{"tourism", "Tourism"},
{"media", "Media"},
{"entertainment", "Entertainment"},
{"gaming", "Gaming"},
{"cybersecurity", "Cybersecurity"},
{"defense", "Defense"}
};
for (const auto& industryPair : industryKeywords) {
const string& pattern = industryPair.first;
const string& displayName = industryPair.second;
regex wordRegex("\\b" + pattern + "\\b", regex::icase);
if (regex_search(lowerText, wordRegex)) {
uniqueIndustries.insert(displayName);
}
}
vector<string> industries(uniqueIndustries.begin(), uniqueIndustries.end());
return industries;
}
// NEW: Extract title from "Title:" prefix
string JobParser::extractTitle(const string& text) {
stringstream ss(text);
string line;
while (getline(ss, line)) {
if (line.find("Title:") == 0) {
return trim(line.substr(6)); // Return everything after "Title:"
}
}
return "Unknown Title"; // Default if not found
}
// NEW: Extract company from "Company:" prefix
string JobParser::extractCompany(const string& text) {
stringstream ss(text);
string line;
while (getline(ss, line)) {
if (line.find("Company:") == 0) {
return trim(line.substr(8)); // Return everything after "Company:"
}
}
return "Unknown Company"; // Default if not found
}
// Main parsing method
vector<Job> JobParser::loadAllJobs() {
vector<Job> jobs;
ifstream file(filename);
if (!file.is_open()) {
cout << "Error: Could not open " << filename << endl;
return jobs;
}
string line;
string currentDescription;
bool readingJob = false;
int jobCount = 0;
while (getline(file, line)) {
// Check for job separator (---)
if (line.find("---") == 0) {
if (readingJob && !currentDescription.empty()) {
jobCount++;
// Create and populate job
Job job;
// Extract title and company from description using prefixes
job.setTitle(extractTitle(currentDescription));
job.setCompany(extractCompany(currentDescription));
// Extract ALL skills from description
job.setJobSkills(extractAllSkills(currentDescription));
// Extract other fields
job.setExperienceLevel(determineExperienceLevel(currentDescription));
job.setRemote(containsWord(currentDescription, "remote"));
job.setSalary(extractSalary(currentDescription));
job.setIndustries(extractIndustries(currentDescription));
jobs.push_back(job);
cout << "Parsed job #" << jobCount << ": " << job.getTitle()
<< " (" << job.getRequiredSkills().size() << " skills found)\n";
}
// Reset for next job
currentDescription.clear();
readingJob = false;
}
else {
// Add line to current job description
if (!readingJob && !line.empty()) {
readingJob = true;
}
if (readingJob) {
currentDescription += line + "\n";
}
}
}
// Don't forget the last job
if (readingJob && !currentDescription.empty()) {
jobCount++;
Job job;
job.setTitle(extractTitle(currentDescription));
job.setCompany(extractCompany(currentDescription));
job.setJobSkills(extractAllSkills(currentDescription));
job.setExperienceLevel(determineExperienceLevel(currentDescription));
job.setRemote(containsWord(currentDescription, "remote"));
job.setSalary(extractSalary(currentDescription));
job.setIndustries(extractIndustries(currentDescription));
jobs.push_back(job);
cout << "Parsed job #" << jobCount << ": " << job.getTitle()
<< " (" << job.getRequiredSkills().size() << " skills found)\n";
}
file.close();
cout << "\nLoaded " << jobs.size() << " jobs from " << filename << endl;
return jobs;
}
// Display parsed jobs for debugging
void JobParser::displayParsedJobs(const vector<Job>& jobs) {
for (size_t i = 0; i < jobs.size(); ++i) {
const auto& job = jobs[i];
cout << "\n" << string(60, '=') << "\n";
cout << "JOB #" << (i + 1) << "\n";
cout << string(60, '=') << "\n";
cout << "Title: " << job.getTitle() << "\n";
cout << "Company: " << job.getCompany() << "\n";
cout << "Experience Level: " << job.getExperienceLevel() << "\n";
cout << "Remote: " << (job.getRemote() ? "Yes" : "No") << "\n";
cout << "Salary: $" << job.getSalary() << "\n";
cout << "Skills (" << job.getRequiredSkills().size() << " found):\n";
if (job.getRequiredSkills().empty()) {
cout << " None detected\n";
} else {
for (const auto& skill : job.getRequiredSkills()) {
cout << " • " << skill << "\n";
}
}
cout << "Industries (" << job.getIndustries().size() << "):\n";
if (job.getIndustries().empty()) {
cout << " None detected\n";
} else {
for (const auto& industry : job.getIndustries()) {
cout << " • " << industry << "\n";
}
}
cout << string(60, '-') << "\n";
}
}
#ifndef JOB_PARSER_HPP
#define JOB_PARSER_HPP
#include <string>
#include <vector>
#include <fstream>
#include "Job.hpp"
using namespace std;
class JobParser {
private:
string filename;
// Helper methods
vector<string> splitString(const string& str, char delimiter);
string trim(const string& str);
bool containsWord(const string& text, const string& word);
double extractSalary(const string& text);
string determineExperienceLevel(const string& text);
vector<string> extractAllSkills(const string& text); // Extracts ALL skills found
vector<string> extractIndustries(const string& text);
string extractTitle(const string& text);
string extractCompany(const string& text);
public:
// Constructor
JobParser(const string& file);
// Main parsing method
vector<Job> loadAllJobs();
// For debugging
void displayParsedJobs(const vector<Job>& jobs);
};
#endif
#ifndef SORTER_HPP
#define SORTER_HPP
#include <vector>
#include <utility>
#include <string>
#include <map>
#include "Job.hpp"
#include "StudentProfile.hpp"
using namespace std;
class Sorter {
private:
// Weights for different factors
struct Weights {
double skills = 0.50; // 50% - technical match
double interests = 0.15; // 15% - interest alignment
double remote = 0.10; // 10% - remote preference
double industry = 0.25; // 25% - industry experience match
} weights;
// Helper methods for string matching
string toLower(const string& str) const;
bool isExactMatch(const string& text, const string& keyword) const;
bool isPartialMatch(const string& text, const string& keyword) const;
double calculateSimilarity(const string& s1, const string& s2) const;
// Scoring helpers
double calculateSkillScore(const Job& job, const StudentProfile& student) const;
double calculateInterestScore(const Job& job, const StudentProfile& student) const;
double calculateRemoteScore(const Job& job, const StudentProfile& student) const;
double calculateIndustryScore(const Job& job, const StudentProfile& student) const;
// Relationship mapping for related skills
bool areSkillsRelated(const string& skill1, const string& skill2) const;
double getSkillMatchValue(const string& studentSkill, const string& jobSkill) const;
// Predefined skill relationships
mutable map<string, vector<string>> relatedSkills;
void initializeRelatedSkills() const;
// Helper to get matched skills
vector<string> getMatchedSkills(const Job& job, const StudentProfile& student) const;
// Helper to extract skills from text descriptions
vector<string> extractSkillsFromText(const string& text, const vector<string>& jobSkills) const;
public:
// Constructor
Sorter();
// Main scoring method
double calculateTotalScore(const Job& job, const StudentProfile& student) const;
// Ranking methods
vector<pair<Job, double>> sortJobs(const vector<Job>& jobs, const StudentProfile& student) const;
// Display methods
void displayRankings(const vector<pair<Job, double>>& rankedJobs,
const StudentProfile& student) const;
// Weight adjustment
void setWeights(double skills, double interests, double remote, double industry);
void resetWeights();
// Get match breakdown for a single job
map<string, double> getMatchBreakdown(const Job& job, const StudentProfile& student) const;
};
#endif