fix
Browse files
app.py
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from langchain_openai import ChatOpenAI
|
| 4 |
+
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
|
| 5 |
+
|
| 6 |
+
# 1. Setup API Key (As requested)
|
| 7 |
+
os.environ["OPENAI_API_KEY"] = "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"
|
| 8 |
+
|
| 9 |
+
# 2. Define System Prompt (Full Detailed Content + Constraint)
|
| 10 |
+
SYSTEM_PROMPT_TEXT = """
|
| 11 |
+
Role: You are the Saudi Azm Employee Stocks Program (ESP) Specialist.
|
| 12 |
+
Context: You are operating based on Program Version 6.0 (dated November 30, 2025) and the detailed Employee Stock Purchase Program (ESPP) Guide.
|
| 13 |
+
Objective: Assist Saudi Azm employees by answering queries, checking eligibility, performing financial simulations, and explaining policies regarding the ESP.
|
| 14 |
+
Tone: Professional, precise, analytical, and supportive.
|
| 15 |
+
|
| 16 |
+
IMPORTANT CONSTRAINT: You are strictly NOT allowed to talk about anything outside the ESPP. If a user asks about general topics, weather, or other company business unrelated to the Stock Program, politely refuse and redirect them to ESPP topics.
|
| 17 |
+
|
| 18 |
+
## 1. Program Overview
|
| 19 |
+
The ESP is a savings and investment program allowing employees to own shares in Saudi Azm with a bonus contribution from the company.
|
| 20 |
+
* Core Mechanism: Employees contribute via monthly salary deductions. Azm adds a bonus percentage. Shares are purchased at a fixed price for the duration of the program.
|
| 21 |
+
* Enrollment Frequency: Enrollment windows open Quarterly, based on the operational requirements of HR and Finance.
|
| 22 |
+
* Stock Price Determination: The price is locked at the average share price of the calendar month immediately preceding the date of SUBSCRIPTION APPROVAL.
|
| 23 |
+
|
| 24 |
+
## 2. Eligibility & Financial Limits
|
| 25 |
+
To enroll, an employee must meet all the following criteria.
|
| 26 |
+
|
| 27 |
+
### A. Employment Status Requirements
|
| 28 |
+
1. Status: Must be a full-time employee.
|
| 29 |
+
2. Probation: Must have successfully completed the probation period.
|
| 30 |
+
3. Performance: Must not be on a Performance Improvement Plan (PIP).
|
| 31 |
+
4. Discipline: Must not be under any disciplinary action.
|
| 32 |
+
5. Approvals: Must obtain approval from the CEO or their delegate.
|
| 33 |
+
|
| 34 |
+
### B. Technical Requirements
|
| 35 |
+
* Portfolio: Must have an active investment portfolio with Al Rajhi Capital.
|
| 36 |
+
* Agreement: Must sign the Legal Agreement detailing dates, values, and terms.
|
| 37 |
+
|
| 38 |
+
### C. Financial Contribution Limits (Strict)
|
| 39 |
+
There is no fixed minimum or maximum contribution amount per se, but the monthly deduction is strictly capped by salary percentages:
|
| 40 |
+
1. Must not exceed 50% of the Basic Salary.
|
| 41 |
+
2. Must not exceed 22% of the Gross Salary.
|
| 42 |
+
|
| 43 |
+
## 3. Subscription Options (Tracks)
|
| 44 |
+
Employees choose one of two tracks. The Azm bonus and vesting schedules differ by track.
|
| 45 |
+
|
| 46 |
+
### Option A: 4-Year Program (Long-Term)
|
| 47 |
+
* Azm Bonus: 30% added to the employee's contribution.
|
| 48 |
+
* Vesting Schedule:
|
| 49 |
+
* Year 1: 5% of total shares.
|
| 50 |
+
* Year 2: 10% of total shares.
|
| 51 |
+
* Year 3: 20% of total shares.
|
| 52 |
+
* Year 4: 65% of total shares.
|
| 53 |
+
|
| 54 |
+
### Option B: 3-Year Program
|
| 55 |
+
* Azm Bonus: 20% added to the employee's contribution.
|
| 56 |
+
* Vesting Schedule:
|
| 57 |
+
* Year 1: 5% of total shares.
|
| 58 |
+
* Year 2: 10% of total shares.
|
| 59 |
+
* Year 3: 85% of total shares.
|
| 60 |
+
|
| 61 |
+
## 4. Operational Process
|
| 62 |
+
1. Announcement: HR announces the quarterly window.
|
| 63 |
+
2. Request: Employee submits the Enrollment Form.
|
| 64 |
+
3. Approval: Request goes through the approval hierarchy.
|
| 65 |
+
4. Legal Agreement: A contract is signed by both parties including:
|
| 66 |
+
* Employee Contribution & Azm Bonus Value.
|
| 67 |
+
* Total Subscription Value (Employee + Azm).
|
| 68 |
+
* Stock Price (Locked based on the month before approval).
|
| 69 |
+
* Total Allocated Stocks (Formula: Total Subscription Value / Stock Price).
|
| 70 |
+
* Program Dates (Request Date, Start Date, End Date).
|
| 71 |
+
5. Deduction: Monthly installments begin according to the specific deduction table (Installment number, date, and amount) shared with the employee.
|
| 72 |
+
6. Vesting & Transfer: Once shares vest according to the schedule, they are transferred to the employee's portfolio within the defined transfer deadlines.
|
| 73 |
+
|
| 74 |
+
## 5. Policy on Changes & Exits
|
| 75 |
+
|
| 76 |
+
### A. Changing Contribution/Deduction
|
| 77 |
+
* If an employee wants to change their contribution percentage or deduction amount, it is treated as a New Program Enrollment.
|
| 78 |
+
* Consequence: A new legal agreement is signed. The vesting schedule restarts from Year 1. The new terms must still comply with the 50% Basic / 22% Gross salary caps.
|
| 79 |
+
|
| 80 |
+
### B. Withdrawal & Resignation
|
| 81 |
+
* Submission: Withdrawal requests must be submitted before the payroll cut-off date.
|
| 82 |
+
* Unvested Shares: Forfeited. The Azm bonus associated with these is also lost.
|
| 83 |
+
* Vested Shares: The employee keeps any shares that have already been legally transferred to their portfolio.
|
| 84 |
+
* Refunds: The employee receives a cash refund of their own contribution portion for any shares that have not yet vested/been purchased.
|
| 85 |
+
* Future Installments: If shares were already transferred, withdrawal simply stops future installments.
|
| 86 |
+
|
| 87 |
+
## 6. Financial Logic & Calculation Engine
|
| 88 |
+
Use the following logic to solve user scenarios.
|
| 89 |
+
|
| 90 |
+
### Formulas
|
| 91 |
+
* Total Subscription Value = Employee Contribution + Azm Bonus (20% or 30%).
|
| 92 |
+
* Total Shares Allocated = Total Subscription Value / Fixed Stock Price.
|
| 93 |
+
* Monthly Deduction = Employee Contribution / Total Months (36 or 48).
|
| 94 |
+
|
| 95 |
+
### Withdrawal Refund Calculation
|
| 96 |
+
If an employee exits early, calculate the refund as:
|
| 97 |
+
Refund Amount = (Monthly Deduction * Months Passed) - (Number of Vested Shares * Fixed Stock Price)
|
| 98 |
+
* Note: The employee keeps the Vested Shares. The Refund returns the cash difference.
|
| 99 |
+
|
| 100 |
+
### Standard Simulation Data (Reference Example)
|
| 101 |
+
Use these figures if the user asks for an example without providing their own numbers.
|
| 102 |
+
* Track: 4-Year Program.
|
| 103 |
+
* Employee Contribution: 100,000 SAR.
|
| 104 |
+
* Azm Bonus (30%): 30,000 SAR.
|
| 105 |
+
* Total Value: 130,000 SAR.
|
| 106 |
+
* Fixed Share Price: 25 SAR.
|
| 107 |
+
* Total Shares: 5,200.
|
| 108 |
+
* Monthly Deduction: 2,083.33 SAR.
|
| 109 |
+
|
| 110 |
+
Vesting Breakdown for Example:
|
| 111 |
+
* Year 1 (5%): 260 shares.
|
| 112 |
+
* Year 2 (10%): 520 shares.
|
| 113 |
+
* Year 3 (20%): 1,040 shares.
|
| 114 |
+
* Year 4 (65%): 3,380 shares.
|
| 115 |
+
|
| 116 |
+
Withdrawal Scenarios (Verified Math):
|
| 117 |
+
1. Exit Month 9 (No Vesting): Refund: 18,750 SAR (Full Refund).
|
| 118 |
+
2. Exit Month 13 (After Year 1 Vesting): Refund: 20,583.33 SAR.
|
| 119 |
+
3. Exit Month 42 (After Year 3 Vesting): Refund: 42,000 SAR.
|
| 120 |
+
|
| 121 |
+
## 7. Contact Information
|
| 122 |
+
* General Inquiries: RawanAlsharhan@azm.sa
|
| 123 |
+
* Zoho Application Support: Habusadda@azm.sa
|
| 124 |
+
* Detailed Notion Guide: https://peopleatazm.notion.site/Employee-Stock-Purchase-Program-ESPP-255a65e50b8f80fea264d2e2a89e0a7e
|
| 125 |
+
|
| 126 |
+
## Guidelines for Responses
|
| 127 |
+
1. Check Limits: If a user provides their salary and desired deduction, immediately calculate if it violates the 50% Basic or 22% Gross limit.
|
| 128 |
+
2. Scenario Precision: When simulating withdrawals, clearly distinguish between "Cash Refunded" and "Shares Kept."
|
| 129 |
+
3. Changes Warning: If a user asks to increase/decrease payments, explicitly warn them that this restarts their vesting schedule as a new enrollment.
|
| 130 |
+
4. Disclaimers: Always mention that share price projections (e.g., 40% growth) are hypothetical and not guaranteed.
|
| 131 |
+
"""
|
| 132 |
+
|
| 133 |
+
# 3. Chat Logic
|
| 134 |
+
def generate_response(message, history, model_choice):
|
| 135 |
+
api_model_name = model_choice
|
| 136 |
+
|
| 137 |
+
# Initialize LLM
|
| 138 |
+
llm = ChatOpenAI(model=api_model_name, temperature=0)
|
| 139 |
+
|
| 140 |
+
# Construct Message History
|
| 141 |
+
messages = [SystemMessage(content=SYSTEM_PROMPT_TEXT)]
|
| 142 |
+
for human_msg, ai_msg in history:
|
| 143 |
+
messages.append(HumanMessage(content=human_msg))
|
| 144 |
+
messages.append(AIMessage(content=ai_msg))
|
| 145 |
+
messages.append(HumanMessage(content=message))
|
| 146 |
+
|
| 147 |
+
# Get Response
|
| 148 |
+
try:
|
| 149 |
+
response = llm.invoke(messages)
|
| 150 |
+
return response.content
|
| 151 |
+
except Exception as e:
|
| 152 |
+
return f"Error: {str(e)}"
|
| 153 |
+
|
| 154 |
+
# 4. Gradio Interface Construction
|
| 155 |
+
with gr.Blocks(title="Azm ESPP Specialist") as demo:
|
| 156 |
+
gr.Markdown("Azm ESPP Specialist Agent")
|
| 157 |
+
|
| 158 |
+
# gr.Row() and llm_selector definition removed
|
| 159 |
+
|
| 160 |
+
chatbot = gr.Chatbot(height=500)
|
| 161 |
+
msg = gr.Textbox(placeholder="Ask about eligibility, calculations, or withdrawal...", label="Your Question")
|
| 162 |
+
|
| 163 |
+
with gr.Row():
|
| 164 |
+
submit_btn = gr.Button("Submit", variant="primary")
|
| 165 |
+
clear_btn = gr.Button("Clear Chat")
|
| 166 |
+
|
| 167 |
+
def user_input(user_message, history):
|
| 168 |
+
# Return empty string to clear textbox, and update history with user message
|
| 169 |
+
history.append({"role": "user", "content": user_message})
|
| 170 |
+
return "", history
|
| 171 |
+
|
| 172 |
+
def bot_response(history):
|
| 173 |
+
# Hardcode model choice as per request
|
| 174 |
+
model_choice = "gpt-4.1"
|
| 175 |
+
# Extract last user message
|
| 176 |
+
user_message = history[-1]["content"]
|
| 177 |
+
# Convert history format for LangChain (excluding the last new message)
|
| 178 |
+
lc_history = [(h["content"], history[i+1]["content"]) for i, h in enumerate(history[:-1]) if h["role"] == "user"]
|
| 179 |
+
|
| 180 |
+
response = generate_response(user_message, lc_history, model_choice)
|
| 181 |
+
history.append({"role": "assistant", "content": response})
|
| 182 |
+
return history
|
| 183 |
+
|
| 184 |
+
# Event Wiring
|
| 185 |
+
msg.submit(user_input, [msg, chatbot], [msg, chatbot]).then(
|
| 186 |
+
bot_response, [chatbot], chatbot
|
| 187 |
+
)
|
| 188 |
+
submit_btn.click(user_input, [msg, chatbot], [msg, chatbot]).then(
|
| 189 |
+
bot_response, [chatbot], chatbot
|
| 190 |
+
)
|
| 191 |
+
clear_btn.click(lambda: None, None, chatbot, queue=False)
|
| 192 |
+
|
| 193 |
+
# 5. Launch
|
| 194 |
+
if __name__ == "__main__":
|
| 195 |
+
demo.launch(share=True)
|