Spaces:
Sleeping
Sleeping
shaileshjadhavSS
commited on
Commit
·
53f9c43
1
Parent(s):
f7c75d0
Added local and AWS functionality for questions
Browse files- .gitattributes +0 -35
- .gitignore +1 -0
- __init__.py +0 -0
- app.py +25 -140
- core/__init__.py +0 -0
- core/questions_loader_aws.py +58 -0
- core/questions_loader_dummy.py +53 -0
- core/questions_loader_local.py +49 -0
- core/slack_notifier.py +50 -0
- core/utils.py +0 -0
- presentation/__init__.py +0 -0
- presentation/layout.py +134 -0
- questions/ai/questions.csv +36 -0
- questions/common/questions.csv +36 -0
- questions/python/questions.csv +16 -0
- settings.py +7 -0
.gitattributes
DELETED
@@ -1,35 +0,0 @@
|
|
1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__pycache__/
|
__init__.py
ADDED
File without changes
|
app.py
CHANGED
@@ -1,174 +1,59 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
import requests
|
3 |
import os
|
4 |
-
|
|
|
|
|
|
|
|
|
5 |
|
6 |
-
import
|
7 |
-
|
8 |
|
9 |
# Slack Webhook
|
10 |
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
|
11 |
-
|
12 |
-
|
13 |
-
def send_candidate_info(name, email, years_of_experience, skills, performance_score=None):
|
14 |
-
# Prepare the candidate details message
|
15 |
-
candidate_message = {
|
16 |
-
"text": f"Round - 1\nCandidate Name - {name}\nEmail: {email}\nTotal experience - {years_of_experience} years\nSkills: {skills}\nPerformance Score: {performance_score}"
|
17 |
-
}
|
18 |
-
|
19 |
-
# Send the candidate details message
|
20 |
-
response = requests.post(SLACK_WEBHOOK_URL, data=json.dumps(candidate_message), headers={'Content-Type': 'application/json'})
|
21 |
-
|
22 |
-
if response.status_code == 200:
|
23 |
-
print("Candidate info sent successfully!")
|
24 |
-
else:
|
25 |
-
print(f"Failed to send candidate info: {response.status_code}, {response.text}")
|
26 |
-
|
27 |
|
28 |
def call():
|
29 |
-
# Store the start time if it's the first time
|
30 |
-
if 'start_time' not in st.session_state:
|
31 |
-
st.session_state['start_time'] = datetime.now()
|
32 |
-
|
33 |
# Define questions
|
34 |
-
questions = [
|
35 |
-
|
36 |
-
|
37 |
-
"option1": "3",
|
38 |
-
"option2": "4",
|
39 |
-
"option3": "5",
|
40 |
-
"option4": "6",
|
41 |
-
"answer": "4",
|
42 |
-
"importance": "high"
|
43 |
-
},
|
44 |
-
{
|
45 |
-
"question": "What is 3*6?",
|
46 |
-
"option1": "12",
|
47 |
-
"option2": "18",
|
48 |
-
"option3": "21",
|
49 |
-
"option4": "24",
|
50 |
-
"answer": "18",
|
51 |
-
"importance": "low"
|
52 |
-
},
|
53 |
-
{
|
54 |
-
"question": "What is 8/2?",
|
55 |
-
"option1": "2",
|
56 |
-
"option2": "3",
|
57 |
-
"option3": "4",
|
58 |
-
"option4": "5",
|
59 |
-
"answer": "4",
|
60 |
-
"importance": "medium"
|
61 |
-
},
|
62 |
-
{
|
63 |
-
"question": "What is 5-3?",
|
64 |
-
"option1": "1",
|
65 |
-
"option2": "2",
|
66 |
-
"option3": "3",
|
67 |
-
"option4": "4",
|
68 |
-
"answer": "2",
|
69 |
-
"importance": "high"
|
70 |
-
}
|
71 |
-
]
|
72 |
-
|
73 |
-
marks = {
|
74 |
-
"high": 3,
|
75 |
-
"medium": 2,
|
76 |
-
"low": 1
|
77 |
-
}
|
78 |
-
|
79 |
score = 0
|
80 |
total_questions = len(questions)
|
81 |
|
82 |
-
# Create a progress bar
|
83 |
-
progress_bar = st.progress(0)
|
84 |
-
|
85 |
-
# Time logic for countdown
|
86 |
-
start_time = st.session_state['start_time']
|
87 |
-
elapsed_time = datetime.now() - start_time
|
88 |
-
remaining_time = max(0, 300 - int(elapsed_time.total_seconds())) # 5 minutes = 300 seconds
|
89 |
-
minutes, seconds = divmod(remaining_time, 60)
|
90 |
-
st.text(f"Time Remaining: {minutes:02}:{seconds:02}")
|
91 |
-
|
92 |
for idx, question in enumerate(questions):
|
93 |
# Section for each question with styling
|
94 |
-
|
95 |
-
selected_option = st.radio(
|
96 |
-
"",
|
97 |
-
[question['option1'], question['option2'], question['option3'], question['option4']],
|
98 |
-
key=f"q{idx}",
|
99 |
-
label_visibility="collapsed",
|
100 |
-
help="Choose the correct answer"
|
101 |
-
)
|
102 |
|
103 |
# Checking for correct answer and assigning points based on importance
|
104 |
if selected_option == question['answer']:
|
105 |
score += 1
|
106 |
|
107 |
-
# Update the progress bar after each question
|
108 |
-
progress_bar.progress((idx + 1) / total_questions)
|
109 |
|
110 |
-
if st.button("Submit Test", use_container_width=True):
|
111 |
st.session_state['test_started'] = False
|
112 |
-
|
113 |
-
send_candidate_info(st.session_state['name'], st.session_state['email'], st.session_state['experience'], st.session_state['technology'], (score/total_questions)*100)
|
114 |
|
115 |
|
116 |
def main():
|
117 |
# Set page config with custom title and layout
|
118 |
st.set_page_config(page_title="Candidate MCQ Platform", layout="wide")
|
119 |
-
|
120 |
-
# Display logo and header in the top section
|
121 |
-
st.markdown("""
|
122 |
-
<style>
|
123 |
-
.header {
|
124 |
-
text-align: center;
|
125 |
-
font-size: 36px;
|
126 |
-
font-weight: bold;
|
127 |
-
margin-top: 20px;
|
128 |
-
color: #059D66;
|
129 |
-
}
|
130 |
-
.logo {
|
131 |
-
display: block;
|
132 |
-
margin: 0 auto;
|
133 |
-
max-width: 100px;
|
134 |
-
height: auto;
|
135 |
-
}
|
136 |
-
</style>
|
137 |
-
<div class="header">
|
138 |
-
<img src="https://framerusercontent.com/images/QNgS2NOAyBozR6eFdgzSFhJRbY.png?scale-down-to=512" class="logo" />
|
139 |
-
Candidate Assessment Platform
|
140 |
-
</div>
|
141 |
-
""", unsafe_allow_html=True)
|
142 |
|
143 |
if 'test_started' not in st.session_state:
|
144 |
st.session_state['test_started'] = False
|
145 |
|
146 |
if not st.session_state['test_started']:
|
147 |
st.title("Welcome to the Candidate Assessment Platform")
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
if signup_submit:
|
158 |
-
if not name or not email:
|
159 |
-
st.error("Name and Email are required! Please fill both fields.")
|
160 |
-
else:
|
161 |
-
st.session_state['test_started'] = True
|
162 |
-
st.session_state['name'] = name
|
163 |
-
st.session_state['email'] = email
|
164 |
-
st.session_state['experience'] = experience
|
165 |
-
st.session_state['technology'] = technology
|
166 |
-
st.header("Instructions")
|
167 |
-
st.info("""
|
168 |
-
1. You have 30 minutes to complete the test.
|
169 |
-
2. Each question is multiple-choice.
|
170 |
-
3. Your results will be shared upon completion.
|
171 |
-
""")
|
172 |
else:
|
173 |
call()
|
174 |
|
|
|
|
|
|
|
1 |
import os
|
2 |
+
import streamlit as st
|
3 |
+
from settings import BASE_DIR, NUMBER_OF_TECHNICAL_QUESTIONS, NUMBER_OF_COMMON_QUESTIONS
|
4 |
+
from core.slack_notifier import SlackNotifier
|
5 |
+
from core.questions_loader_local import QuestionLoaderLocal
|
6 |
+
from presentation.layout import Layout
|
7 |
|
8 |
+
import streamlit as st
|
9 |
+
|
10 |
|
11 |
# Slack Webhook
|
12 |
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
|
13 |
+
layout = Layout()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
def call():
|
|
|
|
|
|
|
|
|
16 |
# Define questions
|
17 |
+
questions = QuestionLoaderLocal(os.path.join(BASE_DIR, "questions", st.session_state['technology'].lower(), "questions.csv"), NUMBER_OF_TECHNICAL_QUESTIONS).fetch_questions()
|
18 |
+
common_questions = QuestionLoaderLocal(os.path.join(BASE_DIR, "questions", "common", "questions.csv"), NUMBER_OF_COMMON_QUESTIONS).fetch_questions()
|
19 |
+
questions.extend(common_questions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
score = 0
|
21 |
total_questions = len(questions)
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
for idx, question in enumerate(questions):
|
24 |
# Section for each question with styling
|
25 |
+
selected_option = layout.render_test_question(question, idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
# Checking for correct answer and assigning points based on importance
|
28 |
if selected_option == question['answer']:
|
29 |
score += 1
|
30 |
|
|
|
|
|
31 |
|
32 |
+
if st.button("Submit Test", use_container_width=True, type="primary"):
|
33 |
st.session_state['test_started'] = False
|
34 |
+
layout.render_completion_message(score, total_questions)
|
35 |
+
SlackNotifier(SLACK_WEBHOOK_URL).send_candidate_info(st.session_state['name'], st.session_state['email'], st.session_state['experience'], st.session_state['technology'], (score/total_questions)*100)
|
36 |
|
37 |
|
38 |
def main():
|
39 |
# Set page config with custom title and layout
|
40 |
st.set_page_config(page_title="Candidate MCQ Platform", layout="wide")
|
41 |
+
layout.render_header()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
if 'test_started' not in st.session_state:
|
44 |
st.session_state['test_started'] = False
|
45 |
|
46 |
if not st.session_state['test_started']:
|
47 |
st.title("Welcome to the Candidate Assessment Platform")
|
48 |
+
name, email, experience, technology, submit = layout.render_signup_form()
|
49 |
+
if name and email:
|
50 |
+
st.session_state['name'] = name
|
51 |
+
st.session_state['email'] = email
|
52 |
+
st.session_state['experience'] = experience
|
53 |
+
st.session_state['technology'] = technology
|
54 |
+
layout.render_instructions()
|
55 |
+
if submit:
|
56 |
+
st.session_state['test_started'] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
else:
|
58 |
call()
|
59 |
|
core/__init__.py
ADDED
File without changes
|
core/questions_loader_aws.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import boto3
|
2 |
+
import csv
|
3 |
+
from botocore.exceptions import NoCredentialsError, PartialCredentialsError
|
4 |
+
|
5 |
+
class QuestionLoaderAWS:
|
6 |
+
def __init__(self, bucket_name, aws_access_key, aws_secret_key):
|
7 |
+
"""
|
8 |
+
Initializes the QuestionLoader with AWS credentials and bucket name.
|
9 |
+
|
10 |
+
:param bucket_name: The name of the S3 bucket.
|
11 |
+
:param aws_access_key: AWS access key ID.
|
12 |
+
:param aws_secret_key: AWS secret access key.
|
13 |
+
"""
|
14 |
+
self.bucket_name = bucket_name
|
15 |
+
self.s3_client = boto3.client(
|
16 |
+
's3',
|
17 |
+
aws_access_key_id=aws_access_key,
|
18 |
+
aws_secret_access_key=aws_secret_key
|
19 |
+
)
|
20 |
+
|
21 |
+
def fetch_questions(self, technology):
|
22 |
+
"""
|
23 |
+
Fetches the questions for the given technology from the S3 bucket.
|
24 |
+
|
25 |
+
:param technology: The technology (e.g., Python, Django) to fetch questions for.
|
26 |
+
:return: A list of dictionaries, where each dictionary represents a question.
|
27 |
+
:raises: Exception if the file cannot be fetched or read.
|
28 |
+
"""
|
29 |
+
file_key = f"questions/{technology}/questions.csv"
|
30 |
+
|
31 |
+
try:
|
32 |
+
response = self.s3_client.get_object(Bucket=self.bucket_name, Key=file_key)
|
33 |
+
questions = []
|
34 |
+
|
35 |
+
# Decode and parse the CSV content
|
36 |
+
lines = response['Body'].read().decode('utf-8').splitlines()
|
37 |
+
csv_reader = csv.DictReader(lines)
|
38 |
+
for row in csv_reader:
|
39 |
+
questions.append({
|
40 |
+
"question": row["question"],
|
41 |
+
"option1": row["option1"],
|
42 |
+
"option2": row["option2"],
|
43 |
+
"option3": row["option3"],
|
44 |
+
"option4": row["option4"],
|
45 |
+
"answer": row["answer"],
|
46 |
+
"importance": row["importance"].lower()
|
47 |
+
})
|
48 |
+
|
49 |
+
return questions
|
50 |
+
|
51 |
+
except self.s3_client.exceptions.NoSuchKey:
|
52 |
+
raise FileNotFoundError(f"No questions found for technology: {technology}")
|
53 |
+
|
54 |
+
except (NoCredentialsError, PartialCredentialsError):
|
55 |
+
raise PermissionError("Invalid AWS credentials. Please check your .env file.")
|
56 |
+
|
57 |
+
except Exception as e:
|
58 |
+
raise RuntimeError(f"Failed to fetch questions: {str(e)}")
|
core/questions_loader_dummy.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class QuestionLoaderDummy:
|
2 |
+
def __init__(self):
|
3 |
+
"""
|
4 |
+
Initializes the QuestionLoader with Dummy data.
|
5 |
+
"""
|
6 |
+
pass
|
7 |
+
|
8 |
+
def fetch_questions(self, technology=None):
|
9 |
+
"""
|
10 |
+
Fetches the questions for the given technology from the S3 bucket.
|
11 |
+
|
12 |
+
:param technology: The technology (e.g., Python, Django) to fetch questions for.
|
13 |
+
:return: A list of dictionaries, where each dictionary represents a question.
|
14 |
+
:raises: Exception if the file cannot be fetched or read.
|
15 |
+
"""
|
16 |
+
return [
|
17 |
+
{
|
18 |
+
"question": "What is 2+2?",
|
19 |
+
"option1": "3",
|
20 |
+
"option2": "4",
|
21 |
+
"option3": "5",
|
22 |
+
"option4": "6",
|
23 |
+
"answer": "4",
|
24 |
+
"importance": "high"
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"question": "What is 3*6?",
|
28 |
+
"option1": "12",
|
29 |
+
"option2": "18",
|
30 |
+
"option3": "21",
|
31 |
+
"option4": "24",
|
32 |
+
"answer": "18",
|
33 |
+
"importance": "low"
|
34 |
+
},
|
35 |
+
{
|
36 |
+
"question": "What is 8/2?",
|
37 |
+
"option1": "2",
|
38 |
+
"option2": "3",
|
39 |
+
"option3": "4",
|
40 |
+
"option4": "5",
|
41 |
+
"answer": "4",
|
42 |
+
"importance": "medium"
|
43 |
+
},
|
44 |
+
{
|
45 |
+
"question": "What is 5-3?",
|
46 |
+
"option1": "1",
|
47 |
+
"option2": "2",
|
48 |
+
"option3": "3",
|
49 |
+
"option4": "4",
|
50 |
+
"answer": "2",
|
51 |
+
"importance": "high"
|
52 |
+
}
|
53 |
+
]
|
core/questions_loader_local.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import csv
|
2 |
+
import os
|
3 |
+
import random
|
4 |
+
|
5 |
+
class QuestionLoaderLocal:
|
6 |
+
def __init__(self, file_path, question_count):
|
7 |
+
"""
|
8 |
+
Initializes the QuestionLoader with a base path for local files.
|
9 |
+
|
10 |
+
:param base_path: The base path where the question files are located.
|
11 |
+
"""
|
12 |
+
self.base_path = "candidate_assesment/data"
|
13 |
+
self.question_count = question_count
|
14 |
+
self.file_path = file_path
|
15 |
+
|
16 |
+
def fetch_questions(self):
|
17 |
+
"""
|
18 |
+
Fetches the questions for the given technology from the local file system.
|
19 |
+
|
20 |
+
:param technology: The technology (e.g., Python, Django) to fetch questions for.
|
21 |
+
:return: A list of dictionaries, where each dictionary represents a question.
|
22 |
+
:raises: Exception if the file cannot be fetched or read.
|
23 |
+
"""
|
24 |
+
# file_path = os.path.join(BASE_DIR, "questions", technology, "questions.csv")
|
25 |
+
if not os.path.exists(self.file_path):
|
26 |
+
raise FileNotFoundError(f"No questions found for technology")
|
27 |
+
|
28 |
+
try:
|
29 |
+
questions = []
|
30 |
+
|
31 |
+
# Read and parse the CSV file
|
32 |
+
with open(self.file_path, mode="r", encoding="utf-8") as file:
|
33 |
+
csv_reader = csv.DictReader(file)
|
34 |
+
for row in csv_reader:
|
35 |
+
questions.append({
|
36 |
+
"question": row["question"],
|
37 |
+
"option1": row["option1"],
|
38 |
+
"option2": row["option2"],
|
39 |
+
"option3": row["option3"],
|
40 |
+
"option4": row["option4"],
|
41 |
+
"answer": row["answer"],
|
42 |
+
"importance": row["importance"].lower()
|
43 |
+
})
|
44 |
+
# Randomly select 20 questions
|
45 |
+
sampled_questions = random.sample(questions, min(self.question_count, len(questions)))
|
46 |
+
return sampled_questions
|
47 |
+
|
48 |
+
except Exception as e:
|
49 |
+
raise RuntimeError(f"Failed to fetch questions: {str(e)}")
|
core/slack_notifier.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import requests
|
3 |
+
|
4 |
+
class SlackNotifier:
|
5 |
+
def __init__(self, webhook_url):
|
6 |
+
"""
|
7 |
+
Initializes the SlackNotifier with the webhook URL.
|
8 |
+
|
9 |
+
:param webhook_url: The Slack webhook URL for sending messages.
|
10 |
+
"""
|
11 |
+
self.webhook_url = webhook_url
|
12 |
+
|
13 |
+
def send_message(self, text):
|
14 |
+
"""
|
15 |
+
Sends a plain text message to Slack.
|
16 |
+
|
17 |
+
:param text: The message to send.
|
18 |
+
:raises: Exception if the message fails to send.
|
19 |
+
"""
|
20 |
+
payload = {"text": text}
|
21 |
+
|
22 |
+
response = requests.post(
|
23 |
+
self.webhook_url,
|
24 |
+
data=json.dumps(payload),
|
25 |
+
headers={"Content-Type": "application/json"}
|
26 |
+
)
|
27 |
+
|
28 |
+
if response.status_code != 200:
|
29 |
+
raise RuntimeError(f"Failed to send message to Slack: {response.status_code}, {response.text}")
|
30 |
+
|
31 |
+
def send_candidate_info(self, name, email, years_of_experience, skills, performance_score=None):
|
32 |
+
"""
|
33 |
+
Sends a formatted message with candidate information to Slack.
|
34 |
+
|
35 |
+
:param name: Candidate's name.
|
36 |
+
:param email: Candidate's email.
|
37 |
+
:param years_of_experience: Total years of experience.
|
38 |
+
:param skills: Candidate's skills.
|
39 |
+
:param performance_score: Candidate's performance score (optional).
|
40 |
+
"""
|
41 |
+
message = (
|
42 |
+
f"*Candidate Assessment Report*\n\n"
|
43 |
+
f"*Name:* {name}\n"
|
44 |
+
f"*Email:* {email}\n"
|
45 |
+
f"*Experience:* {years_of_experience} years\n"
|
46 |
+
f"*Skills:* {skills}\n"
|
47 |
+
f"*Performance Score:* {performance_score if performance_score is not None else 'N/A'}\n"
|
48 |
+
)
|
49 |
+
|
50 |
+
self.send_message(message)
|
core/utils.py
ADDED
File without changes
|
presentation/__init__.py
ADDED
File without changes
|
presentation/layout.py
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from settings import TECHNOLOGIES_LIST
|
3 |
+
class Layout:
|
4 |
+
@staticmethod
|
5 |
+
def render_header():
|
6 |
+
"""
|
7 |
+
Renders the header of the application with a logo and title.
|
8 |
+
"""
|
9 |
+
st.markdown(
|
10 |
+
"""
|
11 |
+
<style>
|
12 |
+
.header {
|
13 |
+
text-align: center;
|
14 |
+
font-size: 28px; /* Reduced size */
|
15 |
+
font-weight: 600; /* Slightly lighter font weight */
|
16 |
+
color: #034732; /* Darker green for a more professional look */
|
17 |
+
margin-top: 15px;
|
18 |
+
margin-bottom: 5px;
|
19 |
+
}
|
20 |
+
.sub-header {
|
21 |
+
text-align: center;
|
22 |
+
font-size: 14px; /* Subtle and smaller font */
|
23 |
+
color: #5E6472; /* Muted grayish-blue for contrast */
|
24 |
+
margin-bottom: 20px;
|
25 |
+
}
|
26 |
+
.logo {
|
27 |
+
display: block;
|
28 |
+
margin: 0 auto;
|
29 |
+
max-width: 60px; /* Smaller logo size */
|
30 |
+
height: auto;
|
31 |
+
margin-bottom: 10px;
|
32 |
+
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); /* Added subtle shadow */
|
33 |
+
}
|
34 |
+
.divider {
|
35 |
+
height: 1px;
|
36 |
+
margin: 20px 0;
|
37 |
+
background-color: #D0D0D0; /* Light gray for a clean divider */
|
38 |
+
border: none;
|
39 |
+
}
|
40 |
+
</style>
|
41 |
+
<div>
|
42 |
+
<img src="https://framerusercontent.com/images/QNgS2NOAyBozR6eFdgzSFhJRbY.png?scale-down-to=512" class="logo" />
|
43 |
+
<div class="header">NonStop Assessments</div>
|
44 |
+
<div class="sub-header">Empowering talent, one question at a time.</div>
|
45 |
+
<hr class="divider">
|
46 |
+
</div>
|
47 |
+
""",
|
48 |
+
unsafe_allow_html=True
|
49 |
+
)
|
50 |
+
|
51 |
+
|
52 |
+
@staticmethod
|
53 |
+
def render_signup_form():
|
54 |
+
"""
|
55 |
+
Renders the signup form for candidate details.
|
56 |
+
|
57 |
+
:return: Tuple containing name, email, experience, and selected technology.
|
58 |
+
"""
|
59 |
+
with st.form("signup_form"):
|
60 |
+
st.header("Sign Up")
|
61 |
+
|
62 |
+
name = st.text_input("Full Name", help="Please enter your full name")
|
63 |
+
email = st.text_input("Email", help="Please enter your email")
|
64 |
+
experience = st.slider("Total Years of Experience", 0, 20, 0, help="Select your years of experience")
|
65 |
+
technology = st.selectbox("Technology", TECHNOLOGIES_LIST, help="Select your primary technology")
|
66 |
+
submit = st.form_submit_button("Start Test")
|
67 |
+
|
68 |
+
if submit:
|
69 |
+
if not name or not email:
|
70 |
+
st.error("Name and Email are required! Please fill both fields.")
|
71 |
+
return None, None, None, None, None
|
72 |
+
return name, email, experience, technology, submit
|
73 |
+
|
74 |
+
return None, None, None, None, None
|
75 |
+
|
76 |
+
@staticmethod
|
77 |
+
def render_instructions():
|
78 |
+
"""
|
79 |
+
Renders test instructions for the candidates.
|
80 |
+
"""
|
81 |
+
st.header("Instructions")
|
82 |
+
st.info(
|
83 |
+
"""
|
84 |
+
1. You have 30 minutes to complete the test.
|
85 |
+
2. Each question is multiple-choice.
|
86 |
+
3. Your results will be shared upon completion.
|
87 |
+
"""
|
88 |
+
)
|
89 |
+
|
90 |
+
@staticmethod
|
91 |
+
def render_progress_bar(current, total):
|
92 |
+
"""
|
93 |
+
Renders a progress bar for the test.
|
94 |
+
|
95 |
+
:param current: Current progress (integer).
|
96 |
+
:param total: Total progress (integer).
|
97 |
+
"""
|
98 |
+
st.progress(current / total)
|
99 |
+
|
100 |
+
@staticmethod
|
101 |
+
def render_test_question(question, idx):
|
102 |
+
"""
|
103 |
+
Renders a single test question with multiple-choice options.
|
104 |
+
|
105 |
+
:param question: Dictionary containing question details.
|
106 |
+
:param idx: Index of the question for unique Streamlit keys.
|
107 |
+
:return: Selected option from the user.
|
108 |
+
"""
|
109 |
+
st.markdown(f"""
|
110 |
+
<div class="question-card">
|
111 |
+
<b>Question {idx + 1}:</b> {question['question']}
|
112 |
+
</div>
|
113 |
+
""",
|
114 |
+
unsafe_allow_html=True,
|
115 |
+
)
|
116 |
+
selected_option = st.radio(
|
117 |
+
"",
|
118 |
+
[question['option1'], question['option2'], question['option3'], question['option4']],
|
119 |
+
key=f"q{idx}",
|
120 |
+
label_visibility="collapsed",
|
121 |
+
help="Choose the correct answer"
|
122 |
+
)
|
123 |
+
st.markdown("---")
|
124 |
+
return selected_option
|
125 |
+
|
126 |
+
@staticmethod
|
127 |
+
def render_completion_message(score, total):
|
128 |
+
"""
|
129 |
+
Displays a completion message after the test is submitted.
|
130 |
+
|
131 |
+
:param score: Total score obtained by the candidate.
|
132 |
+
:param total: Total questions in the test.
|
133 |
+
"""
|
134 |
+
st.success(f"Test completed! Your score: {score}/{total}")
|
questions/ai/questions.csv
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
question,option1,option2,option3,option4,answer,importance
|
2 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
3 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
4 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
5 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
6 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
7 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
8 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
9 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
10 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
11 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
12 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
13 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
14 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
15 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
16 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
17 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
18 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
19 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
20 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
21 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
22 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
23 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
24 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
25 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
26 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
27 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
28 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
29 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
30 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
31 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
32 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
33 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
34 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
35 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
36 |
+
Question X,Option A,Option B,Option C,Option D,Answer,medium
|
questions/common/questions.csv
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
question,option1,option2,option3,option4,answer,importance
|
2 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
3 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
4 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
5 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
6 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
7 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
8 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
9 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
10 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
11 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
12 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
13 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
14 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
15 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
16 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
17 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
18 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
19 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
20 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
21 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
22 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
23 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
24 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
25 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
26 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
27 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
28 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
29 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
30 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
31 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
32 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
33 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
34 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
35 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
36 |
+
Common Question,Option A,Option B,Option C,Option D,Answer,medium
|
questions/python/questions.csv
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
question,option1,option2,option3,option4,answer,importance
|
2 |
+
What is the output of: print(type([]))?,list,tuple,dict,set,list,low
|
3 |
+
Which keyword is used to define a function in Python?,fun,function,def,define,def,low
|
4 |
+
What does the len() function do?,Returns length of a sequence,Returns a list,Converts string to list,Creates a dictionary,Returns length of a sequence,low
|
5 |
+
How do you declare a variable in Python?,var x = 10,x = 10,int x = 10,declare x = 10,x = 10,low
|
6 |
+
Which of the following is a valid Python data type?,integer,float,string,All of the above,All of the above,low
|
7 |
+
What is the output of: print(3 * 'Python')?,PythonPythonPython,Error,Python3,None,PythonPythonPython,medium
|
8 |
+
Which method is used to add an element to a set?,add(),append(),insert(),extend(),add(),medium
|
9 |
+
What is the output of: print(5 // 2)?,2.5,2,3,Error,2,medium
|
10 |
+
What is the correct syntax to create a dictionary?,{key: value},[key: value],(key: value),key: value,{key: value},medium
|
11 |
+
How do you handle exceptions in Python?,try-catch,try-finally,try-except,try-else,try-except,medium
|
12 |
+
What is a Python decorator?,A function returning another function,A type of module,A Python class,A data structure,A function returning another function,high
|
13 |
+
What is the purpose of the 'with' statement?,Memory management,Simplify exception handling,File handling and cleanup,All of the above,All of the above,high
|
14 |
+
What is the time complexity of accessing an element in a dictionary?,O(1),O(n),O(log n),O(n^2),O(1),high
|
15 |
+
Which module is used for multithreading in Python?,multiprocessing,threading,os,subprocess,threading,high
|
16 |
+
What is the output of: print([i**2 for i in range(3)])?,"[0, 1, 4]","[1, 4, 9]","[0, 2, 4]","[1, 2, 3]","[0, 1, 4]",high
|
settings.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from pathlib import Path
|
3 |
+
BASE_DIR = Path(__file__).resolve().parent
|
4 |
+
NUMBER_OF_TECHNICAL_QUESTIONS = os.environ.get("NUMBER_OF_QUESTIONS", 20)
|
5 |
+
NUMBER_OF_COMMON_QUESTIONS = os.environ.get("NUMBER_OF_COMMON_QUESTIONS", 10)
|
6 |
+
TECHNOLOGIES = os.environ.get("TECHNOLOGIES", "Python,Django,RoR,NodeJS,React,Flutter,QA,Java,Data Engineering,AI")
|
7 |
+
TECHNOLOGIES_LIST = TECHNOLOGIES.split(",")
|