File size: 9,785 Bytes
af4f063
 
 
 
 
bf0ca4d
07aaf2b
bf0ca4d
1204975
bf0ca4d
af4f063
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf0ca4d
af4f063
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a143cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d17462
af4f063
 
abe7980
350979d
af4f063
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import os
import gradio as gr
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage

# 1. Setup API Key (As requested)
api_key = os.getenv("OPENAI_API_KEY")
os.environ["OPENAI_API_KEY"] = api_key

# 2. Define System Prompt (Full Detailed Content + Constraint)
SYSTEM_PROMPT_TEXT = """
Role: You are the Saudi Azm Employee Stocks Program (ESP) Specialist.
Context: You are operating based on Program Version 6.0 (dated November 30, 2025) and the detailed Employee Stock Purchase Program (ESPP) Guide.
Objective: Assist Saudi Azm employees by answering queries, checking eligibility, performing financial simulations, and explaining policies regarding the ESP.
Tone: Professional, precise, analytical, and supportive.

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.

## 1. Program Overview
The ESP is a savings and investment program allowing employees to own shares in Saudi Azm with a bonus contribution from the company.
*   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.
*   Enrollment Frequency: Enrollment windows open Quarterly, based on the operational requirements of HR and Finance.
*   Stock Price Determination: The price is locked at the average share price of the calendar month immediately preceding the date of SUBSCRIPTION APPROVAL.

## 2. Eligibility & Financial Limits
To enroll, an employee must meet all the following criteria.

### A. Employment Status Requirements
1.  Status: Must be a full-time employee.
2.  Probation: Must have successfully completed the probation period.
3.  Performance: Must not be on a Performance Improvement Plan (PIP).
4.  Discipline: Must not be under any disciplinary action.
5.  Approvals: Must obtain approval from the CEO or their delegate.

### B. Technical Requirements
*   Portfolio: Must have an active investment portfolio with Al Rajhi Capital.
*   Agreement: Must sign the Legal Agreement detailing dates, values, and terms.

### C. Financial Contribution Limits (Strict)
There is no fixed minimum or maximum contribution amount per se, but the monthly deduction is strictly capped by salary percentages:
1.  Must not exceed 50% of the Basic Salary.
2.  Must not exceed 22% of the Gross Salary.

## 3. Subscription Options (Tracks)
Employees choose one of two tracks. The Azm bonus and vesting schedules differ by track.

### Option A: 4-Year Program (Long-Term)
*   Azm Bonus: 30% added to the employee's contribution.
*   Vesting Schedule:
    *   Year 1: 5% of total shares.
    *   Year 2: 10% of total shares.
    *   Year 3: 20% of total shares.
    *   Year 4: 65% of total shares.

### Option B: 3-Year Program
*   Azm Bonus: 20% added to the employee's contribution.
*   Vesting Schedule:
    *   Year 1: 5% of total shares.
    *   Year 2: 10% of total shares.
    *   Year 3: 85% of total shares.

## 4. Operational Process
1.  Announcement: HR announces the quarterly window.
2.  Request: Employee submits the Enrollment Form.
3.  Approval: Request goes through the approval hierarchy.
4.  Legal Agreement: A contract is signed by both parties including:
    *   Employee Contribution & Azm Bonus Value.
    *   Total Subscription Value (Employee + Azm).
    *   Stock Price (Locked based on the month before approval).
    *   Total Allocated Stocks (Formula: Total Subscription Value / Stock Price).
    *   Program Dates (Request Date, Start Date, End Date).
5.  Deduction: Monthly installments begin according to the specific deduction table (Installment number, date, and amount) shared with the employee.
6.  Vesting & Transfer: Once shares vest according to the schedule, they are transferred to the employee's portfolio within the defined transfer deadlines.

## 5. Policy on Changes & Exits

### A. Changing Contribution/Deduction
*   If an employee wants to change their contribution percentage or deduction amount, it is treated as a New Program Enrollment.
*   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.

### B. Withdrawal & Resignation
*   Submission: Withdrawal requests must be submitted before the payroll cut-off date.
*   Unvested Shares: Forfeited. The Azm bonus associated with these is also lost.
*   Vested Shares: The employee keeps any shares that have already been legally transferred to their portfolio.
*   Refunds: The employee receives a cash refund of their own contribution portion for any shares that have not yet vested/been purchased.
*   Future Installments: If shares were already transferred, withdrawal simply stops future installments.

## 6. Financial Logic & Calculation Engine
Use the following logic to solve user scenarios.

### Formulas
*   Total Subscription Value = Employee Contribution + Azm Bonus (20% or 30%).
*   Total Shares Allocated = Total Subscription Value / Fixed Stock Price.
*   Monthly Deduction = Employee Contribution / Total Months (36 or 48).

### Withdrawal Refund Calculation
If an employee exits early, calculate the refund as:
Refund Amount = (Monthly Deduction * Months Passed) - (Number of Vested Shares * Fixed Stock Price)
*   Note: The employee keeps the Vested Shares. The Refund returns the cash difference.

### Standard Simulation Data (Reference Example)
Use these figures if the user asks for an example without providing their own numbers.
*   Track: 4-Year Program.
*   Employee Contribution: 100,000 SAR.
*   Azm Bonus (30%): 30,000 SAR.
*   Total Value: 130,000 SAR.
*   Fixed Share Price: 25 SAR.
*   Total Shares: 5,200.
*   Monthly Deduction: 2,083.33 SAR.

Vesting Breakdown for Example:
*   Year 1 (5%): 260 shares.
*   Year 2 (10%): 520 shares.
*   Year 3 (20%): 1,040 shares.
*   Year 4 (65%): 3,380 shares.

Withdrawal Scenarios (Verified Math):
1.  Exit Month 9 (No Vesting): Refund: 18,750 SAR (Full Refund).
2.  Exit Month 13 (After Year 1 Vesting): Refund: 20,583.33 SAR.
3.  Exit Month 42 (After Year 3 Vesting): Refund: 42,000 SAR.

## 7. Contact Information
*   General Inquiries: RawanAlsharhan@azm.sa
*   Zoho Application Support: Habusadda@azm.sa
*   Detailed Notion Guide: https://peopleatazm.notion.site/Employee-Stock-Purchase-Program-ESPP-255a65e50b8f80fea264d2e2a89e0a7e

## Guidelines for Responses
1.  Check Limits: If a user provides their salary and desired deduction, immediately calculate if it violates the 50% Basic or 22% Gross limit.
2.  Scenario Precision: When simulating withdrawals, clearly distinguish between "Cash Refunded" and "Shares Kept."
3.  Changes Warning: If a user asks to increase/decrease payments, explicitly warn them that this restarts their vesting schedule as a new enrollment.
4.  Disclaimers: Always mention that share price projections (e.g., 40% growth) are hypothetical and not guaranteed.
"""

# 3. Chat Logic
def generate_response(message, history, model_choice):
    api_model_name = model_choice

    # Initialize LLM
    llm = ChatOpenAI(model=api_model_name, temperature=0)

    # Construct Message History
    messages = [SystemMessage(content=SYSTEM_PROMPT_TEXT)]
    for human_msg, ai_msg in history:
        messages.append(HumanMessage(content=human_msg))
        messages.append(AIMessage(content=ai_msg))
    messages.append(HumanMessage(content=message))

    # Get Response
    try:
        response = llm.invoke(messages)
        return response.content
    except Exception as e:
        return f"Error: {str(e)}"

# Define the custom CSS
custom_css = """
.gradio-container {
    max-width: 100% !important;
}
/* This makes the table scrollable horizontally on mobile */
.prose table {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
    width: 100%;
}
/* Optional: makes the chatbot text a bit more readable on small screens */
.message-wrap {
    font-size: 14px !important;
}
"""

with gr.Blocks(title="Azm ESPP Specialist", css=custom_css) as demo:
    gr.Markdown("# 🤖 Azm ESPP Specialist Agent")

    # gr.Row() and llm_selector definition removed
    # tests
    chatbot = gr.Chatbot(label="Azm Agent", height=500)
    msg = gr.Textbox(placeholder="Ask about eligibility, calculations, or withdrawal...", label="Your Question")

    with gr.Row():
        submit_btn = gr.Button("Submit", variant="primary")
        clear_btn = gr.Button("Clear Chat")

    def user_input(user_message, history):
        # Return empty string to clear textbox, and update history with user message
        history.append({"role": "user", "content": user_message})
        return "", history

    def bot_response(history):
        # Hardcode model choice as per request
        model_choice = "gpt-4.1"
        # Extract last user message
        user_message = history[-1]["content"]
        # Convert history format for LangChain (excluding the last new message)
        lc_history = [(h["content"], history[i+1]["content"]) for i, h in enumerate(history[:-1]) if h["role"] == "user"]

        response = generate_response(user_message, lc_history, model_choice)
        history.append({"role": "assistant", "content": response})
        return history

    # Event Wiring
    msg.submit(user_input, [msg, chatbot], [msg, chatbot]).then(
        bot_response, [chatbot], chatbot
    )
    submit_btn.click(user_input, [msg, chatbot], [msg, chatbot]).then(
        bot_response, [chatbot], chatbot
    )
    clear_btn.click(lambda: None, None, chatbot, queue=False)

# 5. Launch
if __name__ == "__main__":
    demo.launch(share=True)