Build an Internship Skill Matcher
Build a Python app that ranks internship roles using skill matching and TF-IDF.
Introduction
30 Second Summary
Internship titles can sound promising while hiding very different skill expectations. A clear comparison helps you decide where your current coursework fits and what to study next.
In this project, you will build a browser-based Internship Skill Match Analyzer with Python and Streamlit. You will compare exact keyword overlap with TF-IDF cosine similarity across five sample internship profiles.
What You'll Build
Enter your skills in the finished app and watch your browser rank five internship roles while revealing the skills behind every result.
By the end of this project, you'll have:
- An interactive role ranking where you enter a comma-separated skill list and see five internship profiles scored in your browser.
- An explainable baseline-versus-TF-IDF comparison that reveals how wider role descriptions can improve a simple keyword ranking.
- A public GitHub repository that lets other people inspect your code and reproduce the analyzer from its setup instructions.
- Secret Mission: normalize abbreviations such as js and ml before the analyzer calculates its rankings.
Are there any prerequisites?
You need basic familiarity with Python syntax, functions, lists, and dictionaries. You also need a Windows computer with Visual Studio Code plus a GitHub account.
The project uses local open-source packages with GitHub Free. The main path costs $0.
Before We Start
Before the hands-on work begins, take a moment to define who your analyzer helps and why it exists. This keeps the project focused on exploring internship-role skill alignment as learning guidance, not making hiring decisions.
Set Up the Windows Project Workspace
Your analyzer needs a stable home before feature work begins. A virtual environment keeps this project's packages separate from your other projects.
A verified Git installation gives you the version-control workflow this project needs. The version check confirms that Python can run the pinned Streamlit and scikit-learn packages.
In this step, get ready to:
- Prepare supported Git and Python versions.
- Create the project folder with an active virtual environment.
- Install the dependencies and launch the Streamlit Hello app.
Install Git and verify Python
Git records changes as you build the analyzer. This project uses Git for Windows 2.56.0 so your later repository commands behave consistently.
- Visit the official Git for Windows installation page in your browser.
- Download the official x64 installer for Git for Windows 2.56.0.
- Run the downloaded installer.
- Approve the Windows permission request if one appears.
- Keep the installer's default selections.
- Complete the installation.
- Press the Windows key to open Windows search.
- Type Command Prompt and press Enter.
- Check the installed Git version by running:
git --version
What does this command do?
- The git command asks the installed Git program to print its version.
Your output should include version 2.56.0.
✔️ I see the required Git version
Great. Git for Windows 2.56.0 is installed and responding in Command Prompt.
ⓧ I see an older Git version
Your existing Git installation needs an in-place upgrade before you continue.
- Return to the official Git for Windows installation page.
- Download the x64 installer for version 2.56.0.
- Run the installer with its default selections.
- Close Command Prompt after the installation finishes.
- Reopen Command Prompt through Windows search.
- Repeat the Git version check above.
ⓧ Git is not recognized
Command Prompt may have opened before the installer updated your system path.
- Close Command Prompt.
- Press the Windows key to reopen Windows search.
- Type Command Prompt and press Enter.
- Repeat the Git version check above.
- Run the official x64 installer again if Git is still unavailable.
Streamlit 1.66.1 supports Python versions from 3.11 through 3.15. The next check confirms that your existing Python installation falls inside that range.
- Check your Python version in Command Prompt by running:
python --version
What does this command do?
- The python command prints the version that Command Prompt uses for this project.
✔️ I see a supported Python version
Python is ready. Continue if the version starts with 3.11, 3.12, 3.13, 3.14, or 3.15.
ⓧ I see an unsupported version
This project needs Python 3.11 through 3.15 for its pinned packages.
- Visit the official Python downloads page for Windows.
- Download the current 64-bit stable Python release.
- Install Python 3.15.2 through the official Windows installation flow.
- Close Command Prompt after the installation finishes.
- Reopen Command Prompt through Windows search.
- Repeat the Python version check above.
ⓧ Python is not recognized
Command Prompt cannot find a usable Python installation on your system path.
- Visit the official Python downloads page for Windows.
- Download the current 64-bit stable Python release.
- Install Python 3.15.2 through the official Windows installation flow.
- Close Command Prompt after the installation finishes.
- Reopen Command Prompt through Windows search.
- Repeat the Python version check above.
Still seeing the wrong version?
Restart Command Prompt after installing either tool because an older window may still use the previous system path.
If the version still looks wrong, help me verify the Git and Python installations on Windows.
Create and activate the project environment
The project folder gives every local file one predictable location. The .venv environment keeps the analyzer's package versions contained inside that folder.
- Return to the Command Prompt window from the version checks.
- Move to your Desktop by running this command:
cd "%USERPROFILE%\Desktop"
What does this command do?
- The cd command moves Command Prompt to the Desktop inside your Windows user profile.
Your Command Prompt path should now end with Desktop.
- Create the internship-skill-match-analyzer folder and enter it by running these commands:
mkdir internship-skill-match-analyzer
cd internship-skill-match-analyzer
What do these commands do?
- The mkdir command creates your project folder on the Desktop.
- The cd command moves Command Prompt inside that new folder.
Your prompt should now end with Desktop\internship-skill-match-analyzer.
- Create the project virtual environment by running:
python -m venv .venv
What does this command do?
- Python creates an isolated environment named .venv inside internship-skill-match-analyzer.
- Packages installed after activation stay associated with this project.
- Confirm that the environment folder exists by running:
dir
What does this command do?
- The dir command lists the contents of your current project folder.
You should see .venv in the directory listing.
- Activate the virtual environment by running:
.venv\Scripts\activate.bat
What does this command do?
- The activation script updates this Command Prompt session to use the Python tools inside .venv.
You should see (.venv) at the start of your Command Prompt line. That prefix confirms the isolated environment is active.
Environment not activating?
- Confirm that Command Prompt is inside the internship-skill-match-analyzer folder.
- Confirm that the directory listing includes .venv.
- Recreate the environment if the folder is missing.
If activation still fails, help me activate my Windows virtual environment.
Install the packages and launch Streamlit
The active environment is ready to receive this project's dependencies. Streamlit provides the browser interface while scikit-learn provides the text-ranking tools used later.
Expect the package download to take a few minutes.
- Install Streamlit 1.66.1 and scikit-learn 1.9.1 by running:
python -m pip install streamlit==1.66.1 scikit-learn==1.9.1
What does this command do?
- Python runs pip from the active virtual environment.
- The version pins keep every learner on the same Streamlit and scikit-learn releases.
Once the Command Prompt line returns without package errors, both dependencies are installed inside .venv.
Package installation failing?
- Confirm that your Command Prompt line starts with (.venv).
- Restore your internet connection if the download was interrupted.
- Repeat the installation command after the connection is stable.
If installation still fails, help me debug the package setup.
The setup check starts a local Streamlit server. It opens a browser tab while Command Prompt keeps the server running.
Before you launch the check, do you think the browser will open a working app from the packages inside .venv?
- Launch the Streamlit setup check by running:
python -m streamlit hello
What does this command do?
- Python starts Streamlit from the active virtual environment.
- Streamlit serves its built-in Hello app locally and opens it in your browser.
You should see the Streamlit Hello app in a new browser tab. Your Command Prompt should remain busy while the app is running.
Hello app not opening?
- Keep the Streamlit process running while you check the browser.
- Visit the local address printed in Command Prompt if no browser tab opens.
- Confirm that the Command Prompt line showed (.venv) before you launched Streamlit.
If the app still does not load, help me debug the Streamlit Hello check.
- Stop the Hello app by pressing Ctrl+C in Command Prompt.
Your Windows workspace is ready. Next, you will use VS Code to turn this verified Streamlit setup into the first working keyword matcher.
Build the Keyword Matcher
Your Streamlit workspace can already serve an app in the browser. Now you'll turn that working setup into your first internship ranking tool.
The first ranking counts exact skill matches with set intersection. You'll test whether that simple score can separate all five roles.
In this step, get ready to:
- Define five internship role profiles with required skills.
- Calculate exact keyword-overlap scores from a learner's skill set.
- Run the browser interface to examine the baseline ranking.
Define the internship role profiles
Each role profile is a Python dictionary containing a title. It also holds a summary plus the skills used by the baseline.
- Switch back to VS Code.
- Click the new file icon in the Explorer sidebar.
- Type app.py as the file name.
- Press Enter to create the file.
- Define the five role profiles by pasting this code into app.py:
import streamlit as st
ROLE_PROFILES = [
{"title": "Data Analyst Intern", "summary": "Analyze structured data, write queries, find trends, and explain results with clear visual reports.", "required_skills": ["python", "sql", "dbms", "statistics", "pandas", "data visualization"]},
{"title": "Machine Learning Intern", "summary": "Prepare datasets, train models, evaluate predictions, and communicate experiment results.", "required_skills": ["python", "statistics", "scikit-learn", "machine learning", "data preprocessing", "model evaluation"]},
{"title": "Python Developer Intern", "summary": "Build readable Python programs, organize reusable logic, work with APIs, and test application behavior.", "required_skills": ["python", "oop", "git", "algorithms", "api", "testing"]},
{"title": "Frontend Developer Intern", "summary": "Build responsive browser interfaces, handle user interactions, and turn designs into usable web pages.", "required_skills": ["html", "css", "javascript", "responsive design", "git", "react"]},
{"title": "Applied AI Intern", "summary": "Combine Python applications with AI services, structured prompts, data handling, and reliable developer workflows.", "required_skills": ["python", "api integration", "prompt engineering", "llm", "git", "data structures"]},
]
What does this code do?
- The import makes Streamlit's interface functions available through st.
- ROLE_PROFILES stores the five sample internship profiles in one list.
- required_skills gives the baseline a consistent skill list for each role.
- summary stores wider role context that you can compare with the baseline later.
- Save app.py by pressing Ctrl+S.
- Confirm app.py appears inside the internship-skill-match-analyzer folder in the Explorer sidebar.
Role data showing an error?
Check that every role starts with an opening brace. Check that every role ends with a closing brace plus a comma.
Confirm the final closing bracket belongs to ROLE_PROFILES.
Help me check my role profile syntax.
Build the exact-match ranking
A set intersection keeps only skills that appear in both sets. The baseline converts the number of matched skills into a percentage for each role.
- Add the parsing helpers plus the ranking function below ROLE_PROFILES in app.py:
def parse_skills(text):
return {skill.strip().lower() for skill in text.split(",") if skill.strip()}
def build_result(role, score, learner_skills):
role_skills = set(role["required_skills"])
return {"title": role["title"], "score": score, "matched": sorted(learner_skills & role_skills), "missing": sorted(role_skills - learner_skills)}
def rank_by_keywords(learner_skills):
results = []
for role in ROLE_PROFILES:
role_skills = set(role["required_skills"])
score = round(100 * len(learner_skills & role_skills) / len(role_skills))
results.append(build_result(role, score, learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
How does the matcher work?
- parse_skills() splits the input at each comma. It removes extra spaces plus duplicate skills.
- build_result() collects the title plus score. It also prepares matched skills plus missing skills for later display.
- rank_by_keywords() calculates the percentage of required skills found in learner_skills.
- sorted() places the highest score first by using reverse=True.
- Save app.py by pressing Ctrl+S.
- Switch back to the active Command Prompt from the previous step.
- Start the app server by running this command:
python -m streamlit run app.py
What does this command do?
This command asks Python to launch app.py through Streamlit. Streamlit keeps the process running so it can serve the app in your browser.
Your browser should open the Streamlit app shell. The Command Prompt should remain free of a Python traceback.
Good, your role data plus scoring functions now load as part of a running Streamlit app.
- Keep the Command Prompt process running while you continue editing app.py.
- Switch back to app.py in VS Code.
- Add the progress-bar display function below rank_by_keywords():
def display_results(results, show_details):
for result in results:
st.progress(result["score"], text=f"{result['title']}: {result['score']}%")
What does this display function do?
- display_results() loops through the sorted results in ranking order.
- st.progress() turns each percentage into a progress bar with a role label.
- show_details is accepted by the shared display function. This baseline only draws the scores.
- Save app.py by pressing Ctrl+S.
- Refresh the browser tab running the app.
The Streamlit app shell should load without an error page. This confirms the display function has valid Python syntax.
App server not loading?
Confirm the Command Prompt still shows the running Streamlit process. Start the app again if that process was stopped.
Check that display_results() is aligned at the left edge of app.py.
Help me debug the matcher functions.
Add the interface and test the baseline
The interface collects a comma-separated skill list. Its button passes the cleaned set through the keyword ranking before displaying all five scores.
- Add the Streamlit interface below display_results() in app.py:
st.write("# Internship Skill Match Analyzer")
st.write("Compare an exact keyword baseline with TF-IDF cosine similarity. Use the result to plan learning, not to predict hiring decisions.")
skills_text = st.text_area("Enter your skills separated by commas", value="python, dbms, html, css, javascript, oop", height=120)
if st.button("Analyze my skills", type="primary"):
learner_skills = parse_skills(skills_text)
if not learner_skills:
st.write("Enter at least one skill before running the analysis.")
else:
st.write("## Baseline: exact keyword overlap")
display_results(rank_by_keywords(learner_skills), show_details=False)
st.write("The baseline treats every exact skill equally and ignores the wider role descriptions.")
How does the interface work?
- skills_text stores the comma-separated text from the input area.
- learner_skills receives the cleaned set returned by parse_skills().
- rank_by_keywords() returns the five sorted result dictionaries.
- display_results() renders those results after the learner selects the analysis button.
- Save app.py by pressing Ctrl+S.
- Refresh the browser tab running the app.
You should see the analyzer title plus a text area containing the default skills. You should also see the Analyze my skills button.
Interface missing from the browser?
Confirm the interface code starts at the left edge of app.py. Code accidentally placed inside display_results() only runs when that function is called.
Check the Command Prompt for a traceback. Use its final file line to find the corresponding line in VS Code.
Help me fix the missing Streamlit interface.
✔️ Awesome, I've got everything!
Great. Double check that app.py is saved before testing the matcher.
ⓧ I'd like to double check the full code
Compare your complete app.py file with this version:
import streamlit as st
ROLE_PROFILES = [
{"title": "Data Analyst Intern", "summary": "Analyze structured data, write queries, find trends, and explain results with clear visual reports.", "required_skills": ["python", "sql", "dbms", "statistics", "pandas", "data visualization"]},
{"title": "Machine Learning Intern", "summary": "Prepare datasets, train models, evaluate predictions, and communicate experiment results.", "required_skills": ["python", "statistics", "scikit-learn", "machine learning", "data preprocessing", "model evaluation"]},
{"title": "Python Developer Intern", "summary": "Build readable Python programs, organize reusable logic, work with APIs, and test application behavior.", "required_skills": ["python", "oop", "git", "algorithms", "api", "testing"]},
{"title": "Frontend Developer Intern", "summary": "Build responsive browser interfaces, handle user interactions, and turn designs into usable web pages.", "required_skills": ["html", "css", "javascript", "responsive design", "git", "react"]},
{"title": "Applied AI Intern", "summary": "Combine Python applications with AI services, structured prompts, data handling, and reliable developer workflows.", "required_skills": ["python", "api integration", "prompt engineering", "llm", "git", "data structures"]},
]
def parse_skills(text):
return {skill.strip().lower() for skill in text.split(",") if skill.strip()}
def build_result(role, score, learner_skills):
role_skills = set(role["required_skills"])
return {"title": role["title"], "score": score, "matched": sorted(learner_skills & role_skills), "missing": sorted(role_skills - learner_skills)}
def rank_by_keywords(learner_skills):
results = []
for role in ROLE_PROFILES:
role_skills = set(role["required_skills"])
score = round(100 * len(learner_skills & role_skills) / len(role_skills))
results.append(build_result(role, score, learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def display_results(results, show_details):
for result in results:
st.progress(result["score"], text=f"{result['title']}: {result['score']}%")
st.write("# Internship Skill Match Analyzer")
st.write("Compare an exact keyword baseline with TF-IDF cosine similarity. Use the result to plan learning, not to predict hiring decisions.")
skills_text = st.text_area("Enter your skills separated by commas", value="python, dbms, html, css, javascript, oop", height=120)
if st.button("Analyze my skills", type="primary"):
learner_skills = parse_skills(skills_text)
if not learner_skills:
st.write("Enter at least one skill before running the analysis.")
else:
st.write("## Baseline: exact keyword overlap")
display_results(rank_by_keywords(learner_skills), show_details=False)
st.write("The baseline treats every exact skill equally and ignores the wider role descriptions.")
Before you test the baseline, do you think six listed skills can separate all five internship roles clearly?
- Keep the default input as python, dbms, html, css, javascript, oop.
- Click Analyze my skills.
You should see five baseline progress bars. Frontend Developer Intern should rank first at 50%.
Data Analyst Intern plus Python Developer Intern should tie at 33%. Machine Learning Intern plus Applied AI Intern should tie at 17%.
What did the baseline reveal?
The baseline found exact skill overlap successfully. However, each matched skill contributes the same amount to its role score.
The summaries in ROLE_PROFILES never enter the calculation. Those ties expose the exact limitation this first matcher was designed to reveal.
That is your first working internship ranking in the browser. Next, you'll use TF-IDF plus cosine similarity to bring the role descriptions into the comparison.
Improve the Ranking with TF-IDF
The baseline in your Streamlit app now ranks all five internship roles from exact skill matches.
That first result exposed a limitation. Exact overlap gives every skill equal weight. It also ignores the role summaries.
In this step, TF-IDF turns the learner profile and role descriptions into weighted text vectors. Cosine similarity then measures how closely those vectors align.
In this step, get ready to:
- Import the text-vectorization and similarity tools.
- Build a TF-IDF ranking function for the five internship roles.
- Display the improved ranking beneath the keyword baseline.
Why TF-IDF and cosine similarity?
TF-IDF gives more influence to useful terms within the document collection. Bigrams also preserve short phrases such as machine learning.
Cosine similarity compares the direction of the resulting vectors. A closer direction produces a stronger profile-to-role score.
Import the TF-IDF tools
The scikit-learn library supplies one tool for building the weighted vectors. It supplies another tool for comparing them.
- In app.py, find import streamlit as st at the top.
- Add the two ranking imports directly below it by pasting this code:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
What do these imports provide?
- TfidfVectorizer converts the profile and role text into weighted numerical vectors.
- cosine_similarity compares the profile vector with every role vector.
- Save app.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Return to the browser tab from the previous step.
- Click Analyze my skills.
You should still see the baseline heading with five progress bars. This confirms that the new imports load without breaking the existing matcher.
Did the existing app stop working?
A missing dependency message means the browser is using a Python environment without the installed project packages. Confirm that the Command Prompt window from earlier still shows the active virtual environment.
Check that both imports sit directly beneath import streamlit as st in app.py.
Help me fix the new scikit-learn imports in app.py.
Build the TF-IDF ranking function
The improved ranker needs one text document for the learner profile. It also needs one document for each internship role.
- In app.py, find def display_results(results, show_details):.
- Add rank_by_tfidf immediately above it by pasting this code:
def rank_by_tfidf(learner_skills):
documents = [" ".join(sorted(learner_skills))]
for role in ROLE_PROFILES:
documents.append(" ".join([role["title"], role["summary"], " ".join(role["required_skills"])]))
matrix = TfidfVectorizer(ngram_range=(1, 2)).fit_transform(documents)
similarities = cosine_similarity(matrix[0:1], matrix[1:])[0]
results = []
for index, role in enumerate(ROLE_PROFILES):
results.append(build_result(role, round(float(similarities[index]) * 100), learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
What does this function do?
- documents stores the learner profile first. Each role document follows it.
- Each role document contains the title. It also contains the summary and required skills.
- TfidfVectorizer(ngram_range=(1, 2)) captures individual words plus two-word phrases.
- matrix[0:1] selects the learner profile vector. matrix[1:] selects all five role vectors.
- similarities holds the profile-to-role comparisons. The loop converts each value into a percentage.
- build_result keeps the same result structure used by the baseline. The final line sorts the scores from highest to lowest.
Display and verify the improved ranking
The new function can now calculate the stronger ranking. The button branch needs to call it after displaying the baseline limitation.
- In app.py, find the final st.write line about the baseline limitation.
- Add the improved section directly below that line by pasting this code:
st.write("## Improved: TF-IDF and cosine similarity")
display_results(rank_by_tfidf(learner_skills), show_details=False)
What does this interface code do?
- The first line creates a separate heading beneath the baseline explanation.
- The second line sends the TF-IDF results through the existing display_results function.
- show_details=False keeps this step focused on comparing the two rankings.
- Save app.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check that you saved app.py before testing the improved ranking.
ⓧ I'd like to double check the full code
Compare your complete app.py file with this version:
import streamlit as st
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
ROLE_PROFILES = [
{"title": "Data Analyst Intern", "summary": "Analyze structured data, write queries, find trends, and explain results with clear visual reports.", "required_skills": ["python", "sql", "dbms", "statistics", "pandas", "data visualization"]},
{"title": "Machine Learning Intern", "summary": "Prepare datasets, train models, evaluate predictions, and communicate experiment results.", "required_skills": ["python", "statistics", "scikit-learn", "machine learning", "data preprocessing", "model evaluation"]},
{"title": "Python Developer Intern", "summary": "Build readable Python programs, organize reusable logic, work with APIs, and test application behavior.", "required_skills": ["python", "oop", "git", "algorithms", "api", "testing"]},
{"title": "Frontend Developer Intern", "summary": "Build responsive browser interfaces, handle user interactions, and turn designs into usable web pages.", "required_skills": ["html", "css", "javascript", "responsive design", "git", "react"]},
{"title": "Applied AI Intern", "summary": "Combine Python applications with AI services, structured prompts, data handling, and reliable developer workflows.", "required_skills": ["python", "api integration", "prompt engineering", "llm", "git", "data structures"]},
]
def parse_skills(text):
return {skill.strip().lower() for skill in text.split(",") if skill.strip()}
def build_result(role, score, learner_skills):
role_skills = set(role["required_skills"])
return {"title": role["title"], "score": score, "matched": sorted(learner_skills & role_skills), "missing": sorted(role_skills - learner_skills)}
def rank_by_keywords(learner_skills):
results = []
for role in ROLE_PROFILES:
role_skills = set(role["required_skills"])
score = round(100 * len(learner_skills & role_skills) / len(role_skills))
results.append(build_result(role, score, learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def rank_by_tfidf(learner_skills):
documents = [" ".join(sorted(learner_skills))]
for role in ROLE_PROFILES:
documents.append(" ".join([role["title"], role["summary"], " ".join(role["required_skills"])]))
matrix = TfidfVectorizer(ngram_range=(1, 2)).fit_transform(documents)
similarities = cosine_similarity(matrix[0:1], matrix[1:])[0]
results = []
for index, role in enumerate(ROLE_PROFILES):
results.append(build_result(role, round(float(similarities[index]) * 100), learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def display_results(results, show_details):
for result in results:
st.progress(result["score"], text=f"{result['title']}: {result['score']}%")
st.write("# Internship Skill Match Analyzer")
st.write("Compare an exact keyword baseline with TF-IDF cosine similarity. Use the result to plan learning, not to predict hiring decisions.")
skills_text = st.text_area("Enter your skills separated by commas", value="python, dbms, html, css, javascript, oop", height=120)
if st.button("Analyze my skills", type="primary"):
learner_skills = parse_skills(skills_text)
if not learner_skills:
st.write("Enter at least one skill before running the analysis.")
else:
st.write("## Baseline: exact keyword overlap")
display_results(rank_by_keywords(learner_skills), show_details=False)
st.write("The baseline treats every exact skill equally and ignores the wider role descriptions.")
st.write("## Improved: TF-IDF and cosine similarity")
display_results(rank_by_tfidf(learner_skills), show_details=False)
Before you test the updated app, which internship role do you expect to rank near the top for the default skills?
- Switch back to the Command Prompt window where .venv remains active.
- Launch the updated app by running this command:
python -m streamlit run app.py
- Switch to the browser tab for your Streamlit app.
- Keep python, dbms, html, css, javascript, oop in the skills field.
- Click Analyze my skills.
You should see Improved: TF-IDF and cosine similarity beneath the five baseline scores. Frontend Developer Intern should appear near the top of the improved ranking.
Improved ranking missing or failing?
- Confirm that the two improved-section lines sit inside the final else block. Their indentation should match the baseline display lines.
- Compare rank_by_tfidf with the full code above. A misplaced bracket can prevent vectorization.
- Confirm that matrix[0:1] selects the learner profile. Confirm that matrix[1:] selects the role documents.
Help me debug the TF-IDF ranking in app.py.
Strong work. Your analyzer now compares exact keyword overlap with a ranking that uses the wider role text.
Your two ranking methods are now visible side by side. Next, you'll turn those scores into practical study guidance by showing matched skills and missing skills.
Explain Matches and Skill Gaps
Your TF-IDF cosine similarity ranking now uses role descriptions to order the five internships. A score alone still gives you little direction for what to study next.
Each result already carries evidence from set intersection and set difference. In this step, you'll expose that evidence through Streamlit.
In this step, get ready to:
- Reveal matched skills and study gaps for each improved role score.
- Preserve the baseline as a visible comparison.
- Add a non-hiring disclaimer to the completed analysis.
Show skill evidence for improved results
The build_result() function already creates sorted matched and missing lists for each role. The display layer can now turn those lists into visible evidence.
- Switch back to app.py in the editor from earlier.
- Find the existing display_results() function.
- Replace that function with this completed version:
def display_results(results, show_details):
for result in results:
st.progress(result["score"], text=f"{result['title']}: {result['score']}%")
if show_details:
st.write(f"Matched skills: {', '.join(result['matched']) or 'None yet'}")
st.write(f"Skills to learn next: {', '.join(result['missing']) or 'No sample skills missing'}")
What does this function add?
- The st.progress() call keeps the existing percentage bar for every role.
- The show_details switch controls whether the two skill labels appear.
- The fallback text keeps each label readable when a list is empty.
- Scroll to the improved ranking section near the bottom of app.py.
- Find the improved display_results() call where show_details currently uses False.
- Change that argument to True so the line matches:
display_results(rank_by_tfidf(learner_skills), show_details=True)
Why turn on the details here?
Passing True reveals the evidence beneath each improved score. The baseline call still passes False to preserve the comparison.
- Save app.py.
- Switch back to the browser tab from earlier.
Before you click Analyze my skills, which ranking section do you expect to show the skill evidence?
- Click Analyze my skills.
You'll see matched skills and skills to learn next beneath every improved score. You have turned each percentage into an explanation the learner can inspect.
Skill details missing?
- Confirm the improved display_results() call passes show_details=True.
- Check that the two st.write() lines are indented inside the if show_details: block.
Help me debug missing matched and missing skill labels.
Compare both ranking views
The same function now supports two levels of detail. Its show_details argument makes the difference visible without duplicating the display logic.
- Review the Baseline: exact keyword overlap section.
- Confirm the baseline contains five role progress bars without skill labels.
- Review the Improved: TF-IDF and cosine similarity section.
- Confirm every improved role includes a progress bar plus both skill labels.
Why keep the baseline visible?
Keeping the exact-match baseline visible makes its limitation concrete. The improved section provides more context while the baseline shows how little a score explains on its own.
Add the learning-guidance disclaimer
The role profiles are sample learning data. A clear disclaimer keeps the ranking honest about the decision it supports.
- In app.py, find the improved result call at the bottom of the analysis block.
- Locate this line:
display_results(rank_by_tfidf(learner_skills), show_details=True)
What is missing here?
This line displays the explainable ranking. The interface still needs a boundary that describes how the scores should be used.
- Replace that line with these two lines:
display_results(rank_by_tfidf(learner_skills), show_details=True)
st.write("These scores are learning guidance based on sample role profiles. They are not hiring probabilities or employer decisions.")
Why add this disclaimer?
The disclaimer defines the app as a learning tool built from sample profiles. It prevents the ranking from being presented as evidence about hiring outcomes.
- Save app.py.
✔️ Awesome, I've got everything!
Great. Double check that you saved app.py before testing the completed analyzer.
ⓧ I'd like to double check the full code
Compare your completed app.py file with this reference:
import streamlit as st
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
ROLE_PROFILES = [
{"title": "Data Analyst Intern", "summary": "Analyze structured data, write queries, find trends, and explain results with clear visual reports.", "required_skills": ["python", "sql", "dbms", "statistics", "pandas", "data visualization"]},
{"title": "Machine Learning Intern", "summary": "Prepare datasets, train models, evaluate predictions, and communicate experiment results.", "required_skills": ["python", "statistics", "scikit-learn", "machine learning", "data preprocessing", "model evaluation"]},
{"title": "Python Developer Intern", "summary": "Build readable Python programs, organize reusable logic, work with APIs, and test application behavior.", "required_skills": ["python", "oop", "git", "algorithms", "api", "testing"]},
{"title": "Frontend Developer Intern", "summary": "Build responsive browser interfaces, handle user interactions, and turn designs into usable web pages.", "required_skills": ["html", "css", "javascript", "responsive design", "git", "react"]},
{"title": "Applied AI Intern", "summary": "Combine Python applications with AI services, structured prompts, data handling, and reliable developer workflows.", "required_skills": ["python", "api integration", "prompt engineering", "llm", "git", "data structures"]},
]
def parse_skills(text):
return {skill.strip().lower() for skill in text.split(",") if skill.strip()}
def build_result(role, score, learner_skills):
role_skills = set(role["required_skills"])
return {"title": role["title"], "score": score, "matched": sorted(learner_skills & role_skills), "missing": sorted(role_skills - learner_skills)}
def rank_by_keywords(learner_skills):
results = []
for role in ROLE_PROFILES:
role_skills = set(role["required_skills"])
score = round(100 * len(learner_skills & role_skills) / len(role_skills))
results.append(build_result(role, score, learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def rank_by_tfidf(learner_skills):
documents = [" ".join(sorted(learner_skills))]
for role in ROLE_PROFILES:
documents.append(" ".join([role["title"], role["summary"], " ".join(role["required_skills"])]))
matrix = TfidfVectorizer(ngram_range=(1, 2)).fit_transform(documents)
similarities = cosine_similarity(matrix[0:1], matrix[1:])[0]
results = []
for index, role in enumerate(ROLE_PROFILES):
results.append(build_result(role, round(float(similarities[index]) * 100), learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def display_results(results, show_details):
for result in results:
st.progress(result["score"], text=f"{result['title']}: {result['score']}%")
if show_details:
st.write(f"Matched skills: {', '.join(result['matched']) or 'None yet'}")
st.write(f"Skills to learn next: {', '.join(result['missing']) or 'No sample skills missing'}")
st.write("# Internship Skill Match Analyzer")
st.write("Compare an exact keyword baseline with TF-IDF cosine similarity. Use the result to plan learning, not to predict hiring decisions.")
skills_text = st.text_area("Enter your skills separated by commas", value="python, dbms, html, css, javascript, oop", height=120)
if st.button("Analyze my skills", type="primary"):
learner_skills = parse_skills(skills_text)
if not learner_skills:
st.write("Enter at least one skill before running the analysis.")
else:
st.write("## Baseline: exact keyword overlap")
display_results(rank_by_keywords(learner_skills), show_details=False)
st.write("The baseline treats every exact skill equally and ignores the wider role descriptions.")
st.write("## Improved: TF-IDF and cosine similarity")
display_results(rank_by_tfidf(learner_skills), show_details=True)
st.write("These scores are learning guidance based on sample role profiles. They are not hiring probabilities or employer decisions.")
Before you test the finished app, do you expect the baseline and improved sections to explain their scores equally well?
- Switch back to the browser tab from earlier.
- Keep python, dbms, html, css, javascript, oop in the skills field.
- Click Analyze my skills.
You'll see five baseline score bars. Below them, you'll see five improved score bars with matched skills, skills to learn next, and the learning-guidance disclaimer.
That's the explanation layer working: every improved score now shows its evidence and its limits.
Completed output not appearing?
- Confirm the baseline call still uses show_details=False if skill labels appear in both sections.
- Confirm the improved call uses show_details=True if the lower section has no skill labels.
- Check that the disclaimer is indented at the same level as the improved display_results() call.
Help me debug the completed results display.
Your analyzer now turns every improved score into study guidance. Next, you'll publish the finished project so another person can inspect the code and reproduce the result.
Publish the Project to GitHub
Your browser-based Streamlit analyzer now ranks internship roles. It also explains each result with matched skills plus learning gaps.
A working local app becomes stronger evidence when another person can inspect the code. Git records a reproducible snapshot. GitHub gives that snapshot a public home.
In this step, get ready to:
- Add dependency pins, source-control exclusions, and setup documentation.
- Configure your Git identity before committing the finished project.
- Publish the commit to a public GitHub repository.
Add the reproducibility files
Someone cloning your project needs the same package versions plus clear setup instructions. The repository also needs to exclude the local virtual environment from version control.
- Switch back to Visual Studio Code.
- Select the Explorer icon in the left sidebar.
- Click the new file icon beside the internship-skill-match-analyzer folder.
- Enter requirements.txt as the file name.
- Press Enter.
- Add the pinned dependencies by pasting this content:
scikit-learn==1.9.1
streamlit==1.66.1
Why Pin the Packages?
The version pins tell Python to install scikit-learn 1.9.1 plus Streamlit 1.66.1. This keeps future installations aligned with the environment you tested.
- Save requirements.txt by pressing Ctrl+S.
- Confirm the Explorer sidebar lists requirements.txt.
File Missing from the Explorer?
Check that you created requirements.txt inside the internship-skill-match-analyzer folder. Save the file if its editor tab still shows an unsaved-change indicator.
Help me find a new file that is missing from the Visual Studio Code Explorer.
- Click the new file icon beside the internship-skill-match-analyzer folder.
- Enter .gitignore as the file name.
- Press Enter.
- Add the source-control exclusions by pasting this content:
.venv/
__pycache__/
What Does This File Exclude?
The .venv/ rule keeps the project-specific virtual environment out of Git. The __pycache__/ rule excludes generated Python cache files.
- Save .gitignore by pressing Ctrl+S.
- Confirm the Explorer sidebar lists .gitignore.
Virtual Environment Still Included?
Check that the first character in .gitignore is a full stop. Confirm that .venv/ matches the virtual environment folder name exactly.
Help me check why my .gitignore is not excluding .venv from Git.
- Click the new file icon beside the internship-skill-match-analyzer folder.
- Enter README.md as the file name.
- Press Enter.
- Document the project by pasting this content:
# Internship Skill Match Analyzer
A beginner-friendly Python project that compares exact skill overlap with TF-IDF cosine similarity across five sample internship profiles.
## What it demonstrates
- Python functions, lists, dictionaries, sets, and sorting
- A deliberately weak keyword-matching baseline
- TF-IDF text vectorization and cosine similarity
- An interactive Streamlit interface
- Reproducible Git and GitHub workflow
## Run locally on Windows
```bat
python -m venv .venv
.venv\Scripts\activate.bat
python -m pip install streamlit==1.66.1 scikit-learn==1.9.1
python -m streamlit run app.py
```
Enter a comma-separated skill list and compare the two rankings.
## Important limitation
This is a learning tool built from sample role profiles. Its scores are not hiring probabilities and must not be used to make employment decisions.
What Does the README Explain?
The README introduces the analyzer plus the concepts it demonstrates. It also gives another Windows user the commands needed to run the app.
The final limitation keeps the project's purpose clear. The scores support learning decisions based on sample profiles.
- Save README.md by pressing Ctrl+S.
- Confirm the Explorer sidebar lists README.md.
README Formatting Look Wrong?
Check that the heading markers remain at the start of their lines. Confirm that the Windows commands stay between the opening plus closing code fences.
Help me fix the Markdown formatting in my project README.
✔️ Awesome, I've got everything!
Your project now has the application code plus the three files needed for a reproducible repository. Double-check that every editor tab is saved.
ⓧ I'd like to double check the full code
Compare each saved file with the complete project below:
import streamlit as st
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
ROLE_PROFILES = [
{"title": "Data Analyst Intern", "summary": "Analyze structured data, write queries, find trends, and explain results with clear visual reports.", "required_skills": ["python", "sql", "dbms", "statistics", "pandas", "data visualization"]},
{"title": "Machine Learning Intern", "summary": "Prepare datasets, train models, evaluate predictions, and communicate experiment results.", "required_skills": ["python", "statistics", "scikit-learn", "machine learning", "data preprocessing", "model evaluation"]},
{"title": "Python Developer Intern", "summary": "Build readable Python programs, organize reusable logic, work with APIs, and test application behavior.", "required_skills": ["python", "oop", "git", "algorithms", "api", "testing"]},
{"title": "Frontend Developer Intern", "summary": "Build responsive browser interfaces, handle user interactions, and turn designs into usable web pages.", "required_skills": ["html", "css", "javascript", "responsive design", "git", "react"]},
{"title": "Applied AI Intern", "summary": "Combine Python applications with AI services, structured prompts, data handling, and reliable developer workflows.", "required_skills": ["python", "api integration", "prompt engineering", "llm", "git", "data structures"]},
]
def parse_skills(text):
return {skill.strip().lower() for skill in text.split(",") if skill.strip()}
def build_result(role, score, learner_skills):
role_skills = set(role["required_skills"])
return {"title": role["title"], "score": score, "matched": sorted(learner_skills & role_skills), "missing": sorted(role_skills - learner_skills)}
def rank_by_keywords(learner_skills):
results = []
for role in ROLE_PROFILES:
role_skills = set(role["required_skills"])
score = round(100 * len(learner_skills & role_skills) / len(role_skills))
results.append(build_result(role, score, learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def rank_by_tfidf(learner_skills):
documents = [" ".join(sorted(learner_skills))]
for role in ROLE_PROFILES:
documents.append(" ".join([role["title"], role["summary"], " ".join(role["required_skills"])]))
matrix = TfidfVectorizer(ngram_range=(1, 2)).fit_transform(documents)
similarities = cosine_similarity(matrix[0:1], matrix[1:])[0]
results = []
for index, role in enumerate(ROLE_PROFILES):
results.append(build_result(role, round(float(similarities[index]) * 100), learner_skills))
return sorted(results, key=lambda item: item["score"], reverse=True)
def display_results(results, show_details):
for result in results:
st.progress(result["score"], text=f"{result['title']}: {result['score']}%")
if show_details:
st.write(f"Matched skills: {', '.join(result['matched']) or 'None yet'}")
st.write(f"Skills to learn next: {', '.join(result['missing']) or 'No sample skills missing'}")
st.write("# Internship Skill Match Analyzer")
st.write("Compare an exact keyword baseline with TF-IDF cosine similarity. Use the result to plan learning, not to predict hiring decisions.")
skills_text = st.text_area("Enter your skills separated by commas", value="python, dbms, html, css, javascript, oop", height=120)
if st.button("Analyze my skills", type="primary"):
learner_skills = parse_skills(skills_text)
if not learner_skills:
st.write("Enter at least one skill before running the analysis.")
else:
st.write("## Baseline: exact keyword overlap")
display_results(rank_by_keywords(learner_skills), show_details=False)
st.write("The baseline treats every exact skill equally and ignores the wider role descriptions.")
st.write("## Improved: TF-IDF and cosine similarity")
display_results(rank_by_tfidf(learner_skills), show_details=True)
st.write("These scores are learning guidance based on sample role profiles. They are not hiring probabilities or employer decisions.")
What Should app.py Contain?
This file contains the completed keyword baseline plus the TF-IDF ranking. It also renders the score details plus the non-hiring disclaimer.
scikit-learn==1.9.1
streamlit==1.66.1
What Should requirements.txt Contain?
This file contains one exact package pin per line. These are the versions used to build the analyzer.
.venv/
__pycache__/
What Should .gitignore Contain?
This file excludes the virtual environment plus generated Python cache directories. Both entries end with a forward slash.
# Internship Skill Match Analyzer
A beginner-friendly Python project that compares exact skill overlap with TF-IDF cosine similarity across five sample internship profiles.
## What it demonstrates
- Python functions, lists, dictionaries, sets, and sorting
- A deliberately weak keyword-matching baseline
- TF-IDF text vectorization and cosine similarity
- An interactive Streamlit interface
- Reproducible Git and GitHub workflow
## Run locally on Windows
```bat
python -m venv .venv
.venv\Scripts\activate.bat
python -m pip install streamlit==1.66.1 scikit-learn==1.9.1
python -m streamlit run app.py
```
Enter a comma-separated skill list and compare the two rankings.
## Important limitation
This is a learning tool built from sample role profiles. Its scores are not hiring probabilities and must not be used to make employment decisions.
What Should README.md Contain?
This file documents the project's purpose plus its learning value. It also contains the complete Windows setup path plus the responsible-use limitation.
Create the local Git history
A Git commit captures the exact state of your finished project. Your identity connects that commit to the name plus email you choose.
- Switch back to the active Windows Command Prompt for internship-skill-match-analyzer.
- Press Ctrl+C if Streamlit logs are still updating in that terminal.
- Confirm (.venv) still appears at the start of the prompt.
- Replace Your Name with the name you want attached to your commits.
- Replace YOUR_EMAIL with an email associated with your GitHub account.
- Configure your Git identity by running these commands:
git config --global user.name "Your Name"
git config --global user.email "YOUR_EMAIL"
What Do These Commands Configure?
The first command sets the author name for future commits. The second command sets the author email.
The --global setting applies this identity to repositories created with your Windows account.
- Initialize the repository on main by running these commands:
git init -b main
git add .
git commit -m "Build internship skill match analyzer"
What Does This Commit Capture?
- The first command creates a local repository with main as its initial branch.
- The second command stages the project files that are not excluded by .gitignore.
- The third command stores the staged files with the message Build internship skill match analyzer.
- Read the commit summary printed in the terminal.
You should see a successful first-commit summary. The changed-file list should cover app.py, requirements.txt, README.md, and .gitignore.
Commit Did Not Complete?
If Git asks for your identity, rerun both configuration commands with your own values. If the commit reports no files, confirm that the four project files are saved inside internship-skill-match-analyzer.
Help me troubleshoot why my first Git commit did not complete.
Publish through GitHub
The local commit is ready. An empty public repository gives it a destination without creating conflicting starter files.
- Return to GitHub in your signed-in browser.
- Select New repository.
- Enter internship-skill-match-analyzer in the repository name field.
- Select Public as the repository visibility.
- Keep every initialization option unselected.
- Click Create repository.
- Select HTTPS for the repository URL.
- Copy the repository URL here: https://github.com/YOUR_USERNAME/internship-skill-match-analyzer.git.
Why Leave the Repository Empty?
Your local project already contains its README plus source-control settings. An empty repository lets the existing commit become the starting history.
- Replace REMOTE-URL with the repository URL you copied.
- Connect the local repository to GitHub by running this command:
git remote add origin REMOTE-URL
What Does This Command Connect?
The command saves your GitHub repository URL under the remote name origin. Future pushes can use that short name.
Before you check, predict whether the terminal will show one remote entry or separate entries for fetching plus pushing.
- Verify the saved remote by running:
git remote -v
What Should the Remote Check Show?
You should see two origin entries using the GitHub URL you copied. One entry handles fetching. The other handles pushing.
Your first authenticated HTTPS push may open a browser sign-in through Git Credential Manager. Complete that secure browser flow if it appears.
Before you publish the commit, predict which four files will appear in the GitHub file list.
- Push the main branch by running:
git push -u origin main
What Does the Push Do?
The command uploads your local main branch to origin. The upstream setting connects future pushes to the same remote branch.
That is the publishing milestone reached. Your finished analyzer can now be inspected plus reproduced from its public repository.
- Refresh the public repository page in your browser.
You should see app.py, requirements.txt, README.md, and .gitignore in the repository file list. The README should render beneath the files.
Push Not Reaching GitHub?
If browser authentication appears, finish the Git Credential Manager sign-in before returning to the terminal. If the remote check shows an unexpected URL, compare it with the HTTPS URL on your repository page.
Help me troubleshoot why my committed project is not appearing on GitHub.
Secret mission
Normalize Common Skill Aliases
Learners often shorten skills to entries such as js, ml, or db. Add an alias-normalization layer so those abbreviations reach the same rankings as their full skill names.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the analyzer available, pause its public visibility, or remove every copy. The project costs $0 under GitHub Free with no metered cloud or API resources.
Resources you used:
- Local project folder internship-skill-match-analyzer containing app.py, requirements.txt, .gitignore, and README.md.
- Project-local virtual environment stored in .venv/.
- Public GitHub repository named internship-skill-match-analyzer.
Keep everything running
No action needed. Choose this if you want to keep showing the project or continue improving it.
- Your public repository stays available at no cost on GitHub Free.
- Your local app files remain ready for future changes.
- Your .venv/ environment remains available for rerunning the analyzer.
Pause - I'll come back to this later
Stop the local app to free your terminal. Make the repository private to hide it from public view while preserving your work.
- If the Streamlit app is running, press Ctrl+C in Command Prompt.
- Return to the internship-skill-match-analyzer repository on GitHub.
- Select Settings in the repository navigation.
- Scroll to Danger Zone.
- Select Change repository visibility.
- Select Make this repository private.
Your repository is now hidden from public view. That gives you privacy without throwing away your commit history.
Delete - I don't want to use this again
Remove all project resources only when you want a clean break. GitHub says some deleted repositories can be restored within 90 days.
Deleting the local folder clears the only copy stored on your computer.
- If the Streamlit app is running, press Ctrl+C in Command Prompt.
- Return to the internship-skill-match-analyzer repository on GitHub.
- Select Settings in the repository navigation.
- Select General in the settings sidebar.
- Scroll to Danger Zone.
- Select Delete this repository.
- Complete the confirmation shown by GitHub.
- Refresh the former repository page.
The public repository should now be unavailable. That confirms the online copy has been removed.
- Use File Explorer to find the internship-skill-match-analyzer folder.
- Select the internship-skill-match-analyzer folder.
- Use File Explorer's delete option to remove the selected folder.
- Empty the Recycle Bin to erase the deleted folder from storage.
This clears the app files from your computer. The .venv/ environment is removed with the folder.
- Search File Explorer for internship-skill-match-analyzer.
Your cleanup is complete when File Explorer no longer finds the project folder.
Nice Work!
Nice Work!
You made it! Your browser-based internship skill matcher now ranks five roles from a learner's skills. The finished Python app is published in a reproducible public GitHub repository.
What you learned:
- Built an interactive skill-matching app with Streamlit. The app turns comma-separated skills into ranked internship matches.
- Replaced a weak exact-match baseline with TF-IDF and cosine similarity. Explained each improved result through matched skills. Highlighted the missing skills to study next. Kept every score grounded with a learning-tool disclaimer.
- Used Git to preserve the project's history. Published the finished repository on GitHub with reproducible setup instructions.
- Secret Mission: added an alias normalization layer before either ranking algorithm runs. Verified that abbreviated input produces the same canonical skills as full input.
Ready to quiz yourself?