Skip to content

Commit e120a06

Browse files
Merge branch 'main' into metrics-extension-dbm
2 parents 338021b + 2aafd63 commit e120a06

File tree

80 files changed

+16632
-331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+16632
-331
lines changed

.DS_Store

0 Bytes
Binary file not shown.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
**/__pycache__/
2+
venv/
3+
files/venv/
4+
**/config.py
5+
.DS_Store
6+
data/
7+
*.log
8+
*.txt
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Copyright (c) 2025 Oracle and/or its affiliates.
2+
3+
The Universal Permissive License (UPL), Version 1.0
4+
5+
Subject to the condition set forth below, permission is hereby granted to any
6+
person obtaining a copy of this software, associated documentation and/or data
7+
(collectively the "Software"), free of charge and under any and all copyright
8+
rights in the Software, and any and all patent rights owned or freely
9+
licensable by each licensor hereunder covering either (i) the unmodified
10+
Software as contributed to or provided by such licensor, or (ii) the Larger
11+
Works (as defined below), to deal in both
12+
13+
(a) the Software, and
14+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
15+
one is included with the Software (each a "Larger Work" to which the Software
16+
is contributed by such licensors),
17+
18+
without restriction, including without limitation the rights to copy, create
19+
derivative works of, display, perform, and distribute the Software and make,
20+
use, sell, offer for sale, import, export, have made, and have sold the
21+
Software and the Larger Work(s), and to sublicense the foregoing rights on
22+
either these or other terms.
23+
24+
This license is subject to the following condition:
25+
The above copyright notice and either this complete permission notice or at
26+
a minimum a reference to the UPL must be included in all copies or
27+
substantial portions of the Software.
28+
29+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35+
SOFTWARE.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# HR Goal Alignment Chatbot
2+
3+
This project provides an AI-powered HR assistant suite built using Oracle Cloud Infrastructure's Generative AI, Oracle Database, and Streamlit.
4+
5+
This asset lives under: `ai/generative-ai-service/hr-goal-alignment`
6+
7+
---
8+
9+
## When to use this asset?
10+
11+
This chatbot system is designed to support employee–manager goal alignment, self-assessment reflection, course recommendations, and meeting preparation through conversational AI. It’s especially useful during performance review cycles and organizational alignment phases.
12+
13+
---
14+
15+
## How to use this asset?
16+
17+
See the full setup and usage guide in [`files/README.md`](./files/README.md).
18+
19+
---
20+
21+
## License
22+
23+
Copyright (c) 2025 Oracle and/or its affiliates.
24+
25+
Licensed under the Universal Permissive License (UPL), Version 1.0.
26+
27+
See [LICENSE](./LICENSE) for more details.
28+
29+
---
30+
31+
32+
33+
## Disclaimer
34+
35+
ORACLE AND ITS AFFILIATES DO NOT PROVIDE ANY WARRANTY WHATSOEVER, EXPRESS OR IMPLIED, FOR ANY SOFTWARE, MATERIAL OR CONTENT OF ANY KIND CONTAINED OR PRODUCED WITHIN THIS REPOSITORY, AND IN PARTICULAR SPECIFICALLY DISCLAIM ANY AND ALL IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. FURTHERMORE, ORACLE AND ITS AFFILIATES DO NOT REPRESENT THAT ANY CUSTOMARY SECURITY REVIEW HAS BEEN PERFORMED WITH RESPECT TO ANY SOFTWARE, MATERIAL OR CONTENT CONTAINED OR PRODUCED WITHIN THIS REPOSITORY. IN ADDITION, AND WITHOUT LIMITING THE FOREGOING, THIRD PARTIES MAY HAVE POSTED SOFTWARE, MATERIAL OR CONTENT TO THIS REPOSITORY WITHOUT ANY REVIEW. USE AT YOUR OWN RISK.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Copyright (c) 2025 Oracle and/or its affiliates.
2+
import streamlit as st
3+
from urllib.parse import quote, unquote
4+
5+
from org_chart_backend import check_goal_alignment, fetch_goals_from_emp, mapping_all_employees
6+
7+
try:
8+
import graphviz
9+
except ImportError:
10+
st.error("Please install graphviz package: pip install graphviz")
11+
st.stop()
12+
13+
st.title("Organization Chart")
14+
st.markdown("Use the sidebar to adjust settings and select employees for details.")
15+
16+
# Get org chart data
17+
mapping, employees_df = mapping_all_employees()
18+
if not mapping:
19+
st.warning("No data available to display.")
20+
st.stop()
21+
22+
# Get all employee names
23+
all_employees = sorted(set(mapping.keys()) | {emp for values in mapping.values() for emp in values})
24+
25+
# Sidebar controls
26+
with st.sidebar:
27+
st.header("Chart Settings")
28+
orientation = st.selectbox("Layout", ["Top-Down", "Left-Right"], index=0)
29+
zoom_level = st.slider("Zoom Level (%)", 50, 200, 50, step=10)
30+
31+
# Direct employee selection
32+
st.markdown("---")
33+
st.header("Select Employee")
34+
35+
# Preloaded selectbox with all employees
36+
selected_employee = st.selectbox(
37+
"Choose an employee:",
38+
[""] + all_employees, # Add empty option at the top
39+
index=0
40+
)
41+
42+
if selected_employee:
43+
if st.button("View Details"):
44+
st.session_state.selected_employee = selected_employee
45+
# Update URL parameter
46+
st.query_params["emp"] = quote(selected_employee)
47+
st.rerun()
48+
49+
# Store selected employee in session state
50+
if "selected_employee" not in st.session_state:
51+
# Try to get from URL params first
52+
params = st.query_params
53+
emp_param = params.get("emp")
54+
if emp_param:
55+
if isinstance(emp_param, list):
56+
emp_param = emp_param[0]
57+
st.session_state.selected_employee = unquote(emp_param)
58+
else:
59+
st.session_state.selected_employee = None
60+
61+
# Create the graphviz chart
62+
def create_org_chart():
63+
dot = graphviz.Digraph(format="svg")
64+
65+
# Set orientation
66+
if orientation == "Left-Right":
67+
dot.attr(rankdir="LR")
68+
69+
# Set global attributes
70+
dot.attr("graph")
71+
dot.attr("node", shape="box", style="rounded")
72+
73+
# Add all nodes
74+
for node in all_employees:
75+
if node == st.session_state.selected_employee:
76+
dot.node(node, style="filled,rounded", fillcolor="#ffd966")
77+
else:
78+
dot.node(node)
79+
80+
# Add all edges
81+
for manager, subordinates in mapping.items():
82+
for subordinate in subordinates:
83+
dot.edge(manager, subordinate)
84+
85+
return dot
86+
87+
# Create chart
88+
chart = create_org_chart()
89+
90+
# Generate SVG for zoomable display
91+
svg_code = chart.pipe(format='svg').decode('utf-8')
92+
93+
# Create HTML with zoom and pan capabilities
94+
html_chart = f"""
95+
<div style="border:1px solid #ddd; width:100%; height:500px; overflow:auto;">
96+
<div style="transform:scale({zoom_level/100}); transform-origin:top left;">
97+
{svg_code}
98+
</div>
99+
</div>
100+
"""
101+
102+
# Display the chart with HTML component
103+
st.components.v1.html(html_chart, height=520, scrolling=True) # type: ignore
104+
105+
# Show details section for selected employee
106+
# ------------------------------------------------------------
107+
# 📋 Employee profile & goal alignment
108+
# ------------------------------------------------------------
109+
if st.session_state.selected_employee:
110+
emp = st.session_state.selected_employee
111+
with st.spinner("Loading employee details...", show_time=True):
112+
# ── Fetch data ────────────────────────────────────────────
113+
manager = next((m for m, emps in mapping.items() if emp in emps), None)
114+
reports = mapping.get(emp, [])
115+
v_align, h_align = check_goal_alignment(employees_df, emp, manager)
116+
goals_df = fetch_goals_from_emp(employees_df, emp)
117+
118+
# ── PROFILE HEADER ───────────────────────────────────────
119+
with st.container(border=True):
120+
head, metrics = st.columns([3, 2])
121+
122+
with head:
123+
st.header(f"👤 {emp.splitlines()[0]}")
124+
title = emp.splitlines()[1][1:-1] if len(emp.splitlines()) > 1 else ""
125+
st.caption(title)
126+
127+
rel_emp = ""
128+
if manager:
129+
m_name = manager.split('(')[0]
130+
rel_emp = rel_emp + f"**Manager:** {m_name} "
131+
if len(reports) > 0:
132+
rel_emp = rel_emp + f"&emsp;|&emsp; **Direct reports:** {len(reports)}"
133+
134+
st.write(rel_emp, unsafe_allow_html=True)
135+
136+
with metrics:
137+
# side-by-side animated metrics
138+
if v_align:
139+
st.metric("Vertical alignment", f"{v_align}% ", f"{100-v_align}%")
140+
if h_align:
141+
st.metric("Horizontal alignment", f"{h_align}% ", f"{100-h_align}%")
142+
143+
# animated progress bars look slick and let the user
144+
if v_align:
145+
st.progress(v_align / 100.0, text="Vertical goal alignment")
146+
if h_align:
147+
st.progress(h_align / 100.0, text="Horizontal goal alignment")
148+
149+
# ------------------------------------------------------------
150+
# 🎯 Goal quality & list (robust, no KeyErrors)
151+
# ------------------------------------------------------------
152+
SMART_COL = "smart" # original column
153+
LABEL_COL = "Quality" # new column for the emoji chip
154+
155+
# --- 1. create a Boolean mask -------------------------------
156+
mask = goals_df[SMART_COL].astype(str).str.lower().isin({"yes", "true", "smart"})
157+
goals_pretty = goals_df.copy()
158+
goals_pretty[LABEL_COL] = mask.map({True: "✅ SMART", False: "⚪️ Not SMART"})
159+
goals_pretty.drop(columns=[SMART_COL], inplace=True)
160+
161+
# move the chip to the front
162+
first = goals_pretty.pop(LABEL_COL)
163+
goals_pretty.insert(0, LABEL_COL, first)
164+
165+
# --- 2. quick KPI -------------------------------------------
166+
total, good = len(goals_pretty), mask.sum()
167+
pct_good = int(good / total * 100) if total else 0
168+
169+
c_num, c_bar = st.columns([1, 4])
170+
with c_num:
171+
st.metric("SMART goals", f"{good}/{total}", f"{pct_good}%")
172+
with c_bar:
173+
st.progress(pct_good / 100, text=f"{pct_good}% SMART")
174+
175+
st.divider()
176+
177+
# --- 3. style just the chip column --------------------------
178+
def chip_style(val):
179+
return (
180+
"background-color:#d4edda;font-weight:bold"
181+
if "✅" in val
182+
else "background-color:#f8d7da;font-weight:bold"
183+
)
184+
185+
styled = goals_pretty.style.applymap(chip_style, subset=[LABEL_COL]) # type: ignore
186+
st.dataframe(styled, use_container_width=True, hide_index=True)

0 commit comments

Comments
 (0)