Spaces:
Runtime error
Runtime error
Commit ·
5ec3426
1
Parent(s): 117ed0c
feat: Add conversation turn tracking and company/resume options to interview mode
Browse files- app.py +79 -30
- constants.py +3 -0
app.py
CHANGED
|
@@ -1,21 +1,22 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import requests
|
| 3 |
import random
|
| 4 |
-
|
|
|
|
| 5 |
|
| 6 |
api_key = os.getenv("apikey")
|
| 7 |
-
url = "https://talent-interview-prep-conversational-model.multimodal.dev/"
|
| 8 |
|
| 9 |
# Predefined questions
|
| 10 |
questions = {
|
| 11 |
-
"Can you start by giving us an overview of your background and how your experience has prepared you specifically for the Senior Product Manager role
|
| 12 |
-
"What specifically interested you in this Senior Product Manager role
|
| 13 |
"Can you tell us about your career aspirations and motivations? How do you see this role helping you achieve your long-term career goals?": "",
|
| 14 |
"What do you consider to be your unique professional strengths and competencies that would contribute to your success as a Senior Product Manager at our company? Can you provide an example where one of these strengths played a critical role in a project?": "",
|
| 15 |
"Could you walk us through a time when you defined and communicated a product vision and developed a roadmap? How did you ensure alignment with the company's strategic goals and market trends?": "",
|
| 16 |
"You've mentioned expertise in agile methodologies. Can you describe a specific project where you led a cross-functional team using Scrum or Kanban? What challenges did you face, and how did you overcome them?": "",
|
| 17 |
"Can you provide an example of a situation where you had to manage conflicting priorities among stakeholders? How did you handle it, and what was the outcome?": "",
|
| 18 |
-
"Now that we've discussed the role and responsibilities, do you have any questions about our company or the Senior Product Manager position
|
| 19 |
}
|
| 20 |
|
| 21 |
full_questions = list(questions.keys())
|
|
@@ -24,7 +25,9 @@ session_id = random.randint(3000, 20000)
|
|
| 24 |
|
| 25 |
first_question_selected = False
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
global session_id
|
| 29 |
print(f"Calling API with session_id: {session_id}")
|
| 30 |
|
|
@@ -32,14 +35,22 @@ def api_call(interview_question, user_input, conversation_mode):
|
|
| 32 |
"x-api-key": api_key,
|
| 33 |
"Content-Type": "application/json"
|
| 34 |
}
|
|
|
|
| 35 |
data = {
|
| 36 |
"session_id": session_id,
|
| 37 |
"job_title": "Senior Product Manager",
|
| 38 |
-
"
|
| 39 |
"interview_question": interview_question,
|
| 40 |
"user_input": user_input,
|
| 41 |
-
"conversation_mode": conversation_mode.lower()
|
|
|
|
| 42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
try:
|
| 44 |
response = requests.post(url, headers=headers, json=data)
|
| 45 |
|
|
@@ -60,8 +71,15 @@ def enable_send_button(message, selected_question):
|
|
| 60 |
return gr.update(interactive=False), gr.update(label="Choose an interview question")
|
| 61 |
return gr.update(interactive=False), gr.update(label="Choose an interview question (Required)")
|
| 62 |
|
| 63 |
-
def handle_question_change(history, selected_question):
|
| 64 |
-
global session_id, first_question_selected
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
if len(history) > 1:
|
| 67 |
first_question_selected = True
|
|
@@ -71,7 +89,7 @@ def handle_question_change(history, selected_question):
|
|
| 71 |
|
| 72 |
transition_message = f"Alright, let's move on to the next question:\n\n{selected_question}"
|
| 73 |
|
| 74 |
-
return [("I'm ready for the next question now.", transition_message)], gr.update(interactive=False), gr.update(interactive=True)
|
| 75 |
else:
|
| 76 |
if selected_question:
|
| 77 |
last_question = f"Great start! Here's your first question:\n\n{selected_question}"
|
|
@@ -80,23 +98,27 @@ def handle_question_change(history, selected_question):
|
|
| 80 |
|
| 81 |
new_history = history + [("One moment...", last_question)]
|
| 82 |
|
| 83 |
-
return [entry for entry in new_history if entry[1] is not None], gr.update(interactive=False), gr.update(interactive=True)
|
| 84 |
|
| 85 |
def reset_interface(conversation_mode):
|
| 86 |
-
global session_id, first_question_selected
|
| 87 |
|
| 88 |
first_question_selected = False
|
|
|
|
|
|
|
| 89 |
if conversation_mode == "Interviewer":
|
| 90 |
-
chatbot_label = "Multimodal Interviewer Agent"
|
|
|
|
| 91 |
session_id = random.randint(3000, 20000)
|
| 92 |
|
| 93 |
user_greeting_message = "I've selected Interviewer mode, and I'm ready to begin."
|
| 94 |
|
| 95 |
chatbot_greeting_message = (
|
| 96 |
-
"Hello, and welcome to your interview for the Senior Product Manager position
|
| 97 |
-
"
|
| 98 |
-
"Take your time with the responses. When you're ready, select a question from the list above to begin."
|
| 99 |
)
|
|
|
|
|
|
|
| 100 |
else:
|
| 101 |
chatbot_label = "Multimodal Coach Agent"
|
| 102 |
session_id = random.randint(3000, 20000)
|
|
@@ -105,27 +127,46 @@ def reset_interface(conversation_mode):
|
|
| 105 |
|
| 106 |
chatbot_greeting_message = (
|
| 107 |
"Hi!\n\n"
|
| 108 |
-
"Welcome to your interview preparation session for the Senior Product Manager role
|
| 109 |
-
"I'm here to help you refine your responses and feel confident for your upcoming interview.\n\n"
|
| 110 |
"Please pick a question above to get started!"
|
| 111 |
)
|
| 112 |
-
|
|
|
|
| 113 |
return (
|
| 114 |
gr.update(value=[(user_greeting_message, chatbot_greeting_message)], label=chatbot_label),
|
| 115 |
gr.update(choices=full_questions, value=None, label="Choose an interview question (Required)", interactive=True),
|
| 116 |
"",
|
| 117 |
-
gr.update(value="Send", interactive=False)
|
|
|
|
| 118 |
)
|
| 119 |
|
| 120 |
# Gradio interface
|
| 121 |
def create_demo():
|
| 122 |
with gr.Blocks() as demo:
|
| 123 |
gr.Markdown("# Talent Interview Prep - Conversational Model")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
gr.Markdown("""### Please select a conversation mode to begin""")
|
| 125 |
|
| 126 |
-
|
|
|
|
|
|
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
question_dropdown = gr.Dropdown(choices=[], label="Choose an interview question")
|
|
|
|
| 129 |
chatbot = gr.Chatbot(label="""The Multimodal Chatbot will be ready once you select a mode""")
|
| 130 |
msg = gr.Textbox(label="Type your answer here")
|
| 131 |
send_btn = gr.Button(value="Send", variant="primary", interactive=False)
|
|
@@ -133,27 +174,35 @@ def create_demo():
|
|
| 133 |
msg.change(fn=enable_send_button, inputs=[msg, question_dropdown], outputs=[send_btn, question_dropdown])
|
| 134 |
question_dropdown.change(fn=enable_send_button, inputs=[msg, question_dropdown], outputs=[send_btn, question_dropdown])
|
| 135 |
|
| 136 |
-
question_dropdown.change(fn=handle_question_change, inputs=[chatbot, question_dropdown], outputs=[chatbot, send_btn, msg])
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
-
def respond(message, history, conversation_mode, selected_question):
|
| 139 |
if not message.strip():
|
| 140 |
return history, message
|
| 141 |
|
| 142 |
clean_question = selected_question.split(":", 1)[1] if ": " in selected_question else selected_question
|
| 143 |
-
bot_message, conversation_end_flag, chat_memory = api_call(clean_question, message, conversation_mode)
|
| 144 |
|
| 145 |
print(f"Conversation end? {conversation_end_flag}")
|
| 146 |
print(f"{chat_memory=}\n")
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
if conversation_end_flag:
|
| 149 |
-
return history + [(message, bot_message)], "", gr.update(interactive=False), gr.update(interactive=False)
|
| 150 |
else:
|
| 151 |
-
return history + [(message, bot_message)], "", gr.update(interactive=True), gr.update(interactive=True)
|
| 152 |
|
| 153 |
-
conversation_mode.change(fn=reset_interface, inputs=conversation_mode, outputs=[chatbot, question_dropdown, msg, send_btn])
|
| 154 |
|
| 155 |
-
msg.submit(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown], outputs=[chatbot, msg, send_btn, msg])
|
| 156 |
-
send_btn.click(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown], outputs=[chatbot, msg, send_btn, msg])
|
| 157 |
|
| 158 |
return demo
|
| 159 |
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import requests
|
| 3 |
import random
|
| 4 |
+
|
| 5 |
+
from constants import JOB_DESCRIPTION, RESUME_TEXT
|
| 6 |
|
| 7 |
api_key = os.getenv("apikey")
|
| 8 |
+
url = "https://talent-interview-prep-conversational-model-v2.multimodal.dev/"
|
| 9 |
|
| 10 |
# Predefined questions
|
| 11 |
questions = {
|
| 12 |
+
"Can you start by giving us an overview of your background and how your experience has prepared you specifically for the Senior Product Manager role?": "",
|
| 13 |
+
"What specifically interested you in this Senior Product Manager role, and how do you see your skills and experiences aligning with the responsibilities outlined in the job description?": "",
|
| 14 |
"Can you tell us about your career aspirations and motivations? How do you see this role helping you achieve your long-term career goals?": "",
|
| 15 |
"What do you consider to be your unique professional strengths and competencies that would contribute to your success as a Senior Product Manager at our company? Can you provide an example where one of these strengths played a critical role in a project?": "",
|
| 16 |
"Could you walk us through a time when you defined and communicated a product vision and developed a roadmap? How did you ensure alignment with the company's strategic goals and market trends?": "",
|
| 17 |
"You've mentioned expertise in agile methodologies. Can you describe a specific project where you led a cross-functional team using Scrum or Kanban? What challenges did you face, and how did you overcome them?": "",
|
| 18 |
"Can you provide an example of a situation where you had to manage conflicting priorities among stakeholders? How did you handle it, and what was the outcome?": "",
|
| 19 |
+
"Now that we've discussed the role and responsibilities, do you have any questions about our company or the Senior Product Manager position?": ""
|
| 20 |
}
|
| 21 |
|
| 22 |
full_questions = list(questions.keys())
|
|
|
|
| 25 |
|
| 26 |
first_question_selected = False
|
| 27 |
|
| 28 |
+
current_turns = 0
|
| 29 |
+
|
| 30 |
+
def api_call(interview_question, user_input, conversation_mode, conversation_turns, include_company_name, include_resume_text):
|
| 31 |
global session_id
|
| 32 |
print(f"Calling API with session_id: {session_id}")
|
| 33 |
|
|
|
|
| 35 |
"x-api-key": api_key,
|
| 36 |
"Content-Type": "application/json"
|
| 37 |
}
|
| 38 |
+
|
| 39 |
data = {
|
| 40 |
"session_id": session_id,
|
| 41 |
"job_title": "Senior Product Manager",
|
| 42 |
+
"job_description": JOB_DESCRIPTION,
|
| 43 |
"interview_question": interview_question,
|
| 44 |
"user_input": user_input,
|
| 45 |
+
"conversation_mode": conversation_mode.lower(),
|
| 46 |
+
"conversation_turns": conversation_turns
|
| 47 |
}
|
| 48 |
+
|
| 49 |
+
if include_company_name:
|
| 50 |
+
data["company_name"] = "InnovateTech Solutions"
|
| 51 |
+
if include_resume_text:
|
| 52 |
+
data["resume_text"] = RESUME_TEXT
|
| 53 |
+
|
| 54 |
try:
|
| 55 |
response = requests.post(url, headers=headers, json=data)
|
| 56 |
|
|
|
|
| 71 |
return gr.update(interactive=False), gr.update(label="Choose an interview question")
|
| 72 |
return gr.update(interactive=False), gr.update(label="Choose an interview question (Required)")
|
| 73 |
|
| 74 |
+
def handle_question_change(history, selected_question, conversation_mode):
|
| 75 |
+
global session_id, first_question_selected, current_turns
|
| 76 |
+
|
| 77 |
+
current_turns = 0
|
| 78 |
+
|
| 79 |
+
if conversation_mode == 'Interviewer':
|
| 80 |
+
updated_label = f"Conversation turns: {current_turns}"
|
| 81 |
+
else:
|
| 82 |
+
updated_label = "Multimodal Coach Agent"
|
| 83 |
|
| 84 |
if len(history) > 1:
|
| 85 |
first_question_selected = True
|
|
|
|
| 89 |
|
| 90 |
transition_message = f"Alright, let's move on to the next question:\n\n{selected_question}"
|
| 91 |
|
| 92 |
+
return [("I'm ready for the next question now.", transition_message)], gr.update(interactive=False), gr.update(interactive=True), gr.update(label=updated_label)
|
| 93 |
else:
|
| 94 |
if selected_question:
|
| 95 |
last_question = f"Great start! Here's your first question:\n\n{selected_question}"
|
|
|
|
| 98 |
|
| 99 |
new_history = history + [("One moment...", last_question)]
|
| 100 |
|
| 101 |
+
return [entry for entry in new_history if entry[1] is not None], gr.update(interactive=False), gr.update(interactive=True), gr.update(label=updated_label)
|
| 102 |
|
| 103 |
def reset_interface(conversation_mode):
|
| 104 |
+
global session_id, first_question_selected, current_turns
|
| 105 |
|
| 106 |
first_question_selected = False
|
| 107 |
+
current_turns = 0
|
| 108 |
+
|
| 109 |
if conversation_mode == "Interviewer":
|
| 110 |
+
# chatbot_label = "Multimodal Interviewer Agent"
|
| 111 |
+
chatbot_label = f"Conversation turns: {current_turns}"
|
| 112 |
session_id = random.randint(3000, 20000)
|
| 113 |
|
| 114 |
user_greeting_message = "I've selected Interviewer mode, and I'm ready to begin."
|
| 115 |
|
| 116 |
chatbot_greeting_message = (
|
| 117 |
+
"Hello, and welcome to your interview for the Senior Product Manager position!\n\n"
|
| 118 |
+
"Select a question from the list above to begin."
|
|
|
|
| 119 |
)
|
| 120 |
+
slider_state = gr.update(interactive=True)
|
| 121 |
+
|
| 122 |
else:
|
| 123 |
chatbot_label = "Multimodal Coach Agent"
|
| 124 |
session_id = random.randint(3000, 20000)
|
|
|
|
| 127 |
|
| 128 |
chatbot_greeting_message = (
|
| 129 |
"Hi!\n\n"
|
| 130 |
+
"Welcome to your interview preparation session for the Senior Product Manager role.\n\n"
|
|
|
|
| 131 |
"Please pick a question above to get started!"
|
| 132 |
)
|
| 133 |
+
slider_state = gr.update(interactive=False)
|
| 134 |
+
|
| 135 |
return (
|
| 136 |
gr.update(value=[(user_greeting_message, chatbot_greeting_message)], label=chatbot_label),
|
| 137 |
gr.update(choices=full_questions, value=None, label="Choose an interview question (Required)", interactive=True),
|
| 138 |
"",
|
| 139 |
+
gr.update(value="Send", interactive=False),
|
| 140 |
+
slider_state
|
| 141 |
)
|
| 142 |
|
| 143 |
# Gradio interface
|
| 144 |
def create_demo():
|
| 145 |
with gr.Blocks() as demo:
|
| 146 |
gr.Markdown("# Talent Interview Prep - Conversational Model")
|
| 147 |
+
|
| 148 |
+
# gr.Markdown("**Some details**")
|
| 149 |
+
# gr.Markdown("""
|
| 150 |
+
# - Job title: Senior Product Manager
|
| 151 |
+
# - Fictional company name: InnovateTech Solutions
|
| 152 |
+
# - Fictional job description [here](<link>)
|
| 153 |
+
# - Fictional candidate resume [here](<link>)
|
| 154 |
+
# - The questions are predefined and generated previously
|
| 155 |
+
# """)
|
| 156 |
+
|
| 157 |
gr.Markdown("""### Please select a conversation mode to begin""")
|
| 158 |
|
| 159 |
+
with gr.Row():
|
| 160 |
+
with gr.Column():
|
| 161 |
+
conversation_mode = gr.Radio(choices=["Interviewer", "Coach"], label="""Choose "Interviewer" to simulate a real interview or "Coach" for guidance and feedback""", value=None)
|
| 162 |
|
| 163 |
+
with gr.Column():
|
| 164 |
+
include_company_name = gr.Checkbox(label="Include company name for context", value=False)
|
| 165 |
+
include_resume_text = gr.Checkbox(label="Include candidate resume for reference", value=False)
|
| 166 |
+
|
| 167 |
+
conversation_turns = gr.Slider(min=1, max=20, step=1, label="Choose the number of exchanges between you and the AI agent (1 minimum, 20 maximum)", value=1)
|
| 168 |
question_dropdown = gr.Dropdown(choices=[], label="Choose an interview question")
|
| 169 |
+
|
| 170 |
chatbot = gr.Chatbot(label="""The Multimodal Chatbot will be ready once you select a mode""")
|
| 171 |
msg = gr.Textbox(label="Type your answer here")
|
| 172 |
send_btn = gr.Button(value="Send", variant="primary", interactive=False)
|
|
|
|
| 174 |
msg.change(fn=enable_send_button, inputs=[msg, question_dropdown], outputs=[send_btn, question_dropdown])
|
| 175 |
question_dropdown.change(fn=enable_send_button, inputs=[msg, question_dropdown], outputs=[send_btn, question_dropdown])
|
| 176 |
|
| 177 |
+
question_dropdown.change(fn=handle_question_change, inputs=[chatbot, question_dropdown, conversation_mode], outputs=[chatbot, send_btn, msg, chatbot])
|
| 178 |
+
|
| 179 |
+
def respond(message, history, conversation_mode, selected_question, conversation_turns, include_company_name, include_resume_text):
|
| 180 |
+
global current_turns
|
| 181 |
|
|
|
|
| 182 |
if not message.strip():
|
| 183 |
return history, message
|
| 184 |
|
| 185 |
clean_question = selected_question.split(":", 1)[1] if ": " in selected_question else selected_question
|
| 186 |
+
bot_message, conversation_end_flag, chat_memory = api_call(clean_question, message, conversation_mode, conversation_turns, include_company_name, include_resume_text)
|
| 187 |
|
| 188 |
print(f"Conversation end? {conversation_end_flag}")
|
| 189 |
print(f"{chat_memory=}\n")
|
| 190 |
|
| 191 |
+
if conversation_mode == 'Interviewer':
|
| 192 |
+
current_turns += 1
|
| 193 |
+
updated_label = f"Conversation turns: {current_turns}"
|
| 194 |
+
else:
|
| 195 |
+
updated_label = "Multimodal Coach Agent"
|
| 196 |
+
|
| 197 |
if conversation_end_flag:
|
| 198 |
+
return history + [(message, bot_message)], "", gr.update(interactive=False), gr.update(interactive=False), gr.update(label=updated_label)
|
| 199 |
else:
|
| 200 |
+
return history + [(message, bot_message)], "", gr.update(interactive=True), gr.update(interactive=True), gr.update(label=updated_label)
|
| 201 |
|
| 202 |
+
conversation_mode.change(fn=reset_interface, inputs=conversation_mode, outputs=[chatbot, question_dropdown, msg, send_btn, conversation_turns])
|
| 203 |
|
| 204 |
+
msg.submit(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown, conversation_turns, include_company_name, include_resume_text], outputs=[chatbot, msg, send_btn, msg, chatbot])
|
| 205 |
+
send_btn.click(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown, conversation_turns, include_company_name, include_resume_text], outputs=[chatbot, msg, send_btn, msg, chatbot])
|
| 206 |
|
| 207 |
return demo
|
| 208 |
|
constants.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
JOB_DESCRIPTION = "Summary\n\nInnovateTech Solutions is seeking an experienced Product Manager to lead and manage the delivery of innovative products that meet customer needs across a variety of sectors including fintech, healthcare, and e-commerce. The ideal candidate will be responsible for the product vision, strategy, and roadmap, working with cross-functional teams to ensure each step of the product lifecycle, from concept to market launch, is executed efficiently and on time.\n\nJob Summary:\n\nThe Product Manager will be responsible for understanding customer needs and translating them into clear and detailed product specifications. Working in an agile environment, the candidate will lead development, design, and marketing teams to create solutions that solve real business problems. This role requires strong analytical, communication, and leadership skills, as well as hands-on experience in managing digital products.\n\nPrimary Responsibilities:\n\n- Define and communicate the product vision, aligning it with company goals.\n- Develop and maintain the product roadmap, ensuring alignment with stakeholder needs.\n- Lead the product development process, from ideation to market launch.\n- Conduct market research to understand user needs and competitive landscape.\n- Collaborate with engineering, design, and marketing teams to ensure product success.\n- Track product performance metrics, adjusting strategies as needed to meet defined KPIs and OKRs.\n- Facilitate sprint planning meetings and set priorities for development teams.\n- Ensure product deliveries meet quality standards and budget constraints.\n- Actively participate in post-launch feedback processes for continuous improvement.\n\nBasic Qualifications:\n\n- Bachelor's degree in Business Administration, Computer Science, or a related field.\n- Minimum of 8 years of experience in product management, preferably in technology or software companies.\n- Proven track record of launching successful products in agile environments.\n- Knowledge of agile development methodologies (Scrum, Kanban).\n- Strong leadership skills with experience managing cross-functional teams.\n- Excellent verbal and written communication skills.\n- Ability to analyze data and translate insights into product decisions.\n- Familiarity with product management tools such as JIRA, Confluence, and Trello.\n- Willingness to travel occasionally.\n\nPreferred Qualifications:\n\n- Previous experience in startups or fast-growing companies.\n- MBA or Master's degree in related fields.\n- Experience with data analytics tools and product metrics such as Google Analytics, Mixpanel, or Amplitude.\n- Certifications in product management such as Certified Scrum Product Owner (CSPO) or Pragmatic Marketing.\n- Proficiency in design thinking and customer experience.\n- Knowledge of A/B testing techniques and product hypothesis validation.\n\nPay Range:\n\n$120,000.00 - $200,000.00 depending on experience and qualifications."
|
| 2 |
+
|
| 3 |
+
RESUME_TEXT = "\nJohn Doe\nNew York, NY 10001\njohn.doe@gmail.com • www.linkedin.com/in/johndoe\n\nProduct Manager\nExperienced Product Manager with over 10 years of expertise in driving the development, launch, and optimization of digital products across industries including fintech, healthcare, and e-commerce. Adept at leading cross-functional teams and aligning product vision with business goals to deliver scalable solutions that enhance user experience and drive revenue growth.\n\nAREAS OF EXPERTISE\n- Product Lifecycle Management\n- Agile Methodologies (Scrum, Kanban)\n- Market Research and User Experience (UX)\n- Roadmap Development and Strategy\n- Cross-functional Team Leadership\n- KPI and OKR Tracking\n- A/B Testing and Data-Driven Decision Making\n\nTECHNICAL PROFICIENCIES\nProduct Management Tools: JIRA, Confluence, Trello\nData Analytics: Google Analytics, Mixpanel, Amplitude\nPrototyping and UX: Figma, Sketch\nDevelopment: HTML, CSS, JavaScript (basic understanding)\n\nPROFESSIONAL EXPERIENCE\nInnovateTech Solutions, New York, NY\nProduct Manager (April 2018 – Present)\nKey Achievements:\n- Spearheaded the development and launch of a B2B fintech platform, resulting in a 25% increase in customer acquisition.\n- Developed and maintained the product roadmap, aligning it with the company’s strategic vision and market trends.\n- Led a cross-functional team of 20+ members, including engineers, designers, and marketers, to deliver products on time and within budget.\n- Introduced A/B testing and data-driven decision-making, improving feature adoption by 15%.\n- Collaborated closely with clients to gather feedback and iteratively improve the product offering.\n\nHealthSync, Inc., Boston, MA\nSenior Product Manager (May 2014 – March 2018)\nKey Achievements:\n- Managed the end-to-end product lifecycle for a suite of healthcare management tools, increasing user engagement by 30%.\n- Worked with stakeholders to translate high-level business requirements into detailed product specs.\n- Implemented customer feedback loops and UX enhancements that led to a 20% decrease in churn rate.\n- Led cross-functional sprint planning and retrospective meetings, ensuring smooth project execution.\n\nEduTech, Chicago, IL\nProduct Manager (July 2010 – April 2014)\nKey Achievements:\n- Successfully launched a SaaS-based learning management system, which became a key revenue driver for the company.\n- Conducted market research to identify key user pain points and align product features with customer needs.\n- Led the migration to an agile development process, reducing time to market for new features by 35%.\n- Collaborated with engineering teams to optimize product performance and scalability.\n\nEDUCATION\nNew York University, New York, NY\nMBA in Business Strategy, 2010\n\nUniversity of California, Berkeley, CA\nBachelor of Science in Computer Science, 2006\n\nCERTIFICATIONS\n- Certified Scrum Product Owner (CSPO)\n- Pragmatic Marketing Certified\n\n"
|