Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- .gitattributes +1 -0
- README.md +2 -1
- app.py +254 -72
- logo.png +3 -0
- quizzes.py +300 -0
- teams.py +147 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* 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
|
|
|
|
|
|
| 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
|
| 36 |
+
logo.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -10,8 +10,9 @@ pinned: false
|
|
| 10 |
|
| 11 |
# BOS AI Training Platform
|
| 12 |
|
| 13 |
-
Six module course on using AI at BOS Security.
|
| 14 |
|
| 15 |
To edit a module, change the matching entry in `MODULES` in `modules.py`. To change the help text shown at the bottom of every module, edit `IT_HELP`.
|
| 16 |
|
| 17 |
To make the Space private, set Visibility to Private in the Space settings.
|
|
|
|
|
|
| 10 |
|
| 11 |
# BOS AI Training Platform
|
| 12 |
|
| 13 |
+
Six module course on using AI at BOS Security. Lessons are in `modules.py`, knowledge checks in `quizzes.py`, the tools by team reference in `teams.py`, and UI and styling in `app.py`.
|
| 14 |
|
| 15 |
To edit a module, change the matching entry in `MODULES` in `modules.py`. To change the help text shown at the bottom of every module, edit `IT_HELP`.
|
| 16 |
|
| 17 |
To make the Space private, set Visibility to Private in the Space settings.
|
| 18 |
+
Quizzes are matched to modules by position, so if you add a module, add a matching list in `quizzes.py`.
|
app.py
CHANGED
|
@@ -1,90 +1,175 @@
|
|
| 1 |
"""
|
| 2 |
BOS AI Training Platform
|
| 3 |
A guided, module based course on using AI at BOS Security, delivered as a
|
| 4 |
-
HuggingFace Gradio Space.
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
|
|
|
|
| 7 |
import html
|
|
|
|
|
|
|
| 8 |
import gradio as gr
|
| 9 |
from modules import MODULES, IT_HELP
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
TEAL = "#13A89E"
|
| 13 |
-
|
| 14 |
-
INK = "#
|
| 15 |
-
MUTED = "#
|
| 16 |
PAPER = "#FFFFFF"
|
| 17 |
-
WASH = "#
|
| 18 |
-
RULE = "#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
CSS = f"""
|
| 21 |
-
:root {{ --
|
| 22 |
-
--muted: {MUTED}; --paper: {PAPER}; --wash: {WASH}; --rule: {RULE}; }}
|
| 23 |
* {{ font-family: Arial, Helvetica, sans-serif !important; }}
|
| 24 |
body, .gradio-container {{ background: var(--wash) !important; color: var(--ink); }}
|
| 25 |
-
.gradio-container {{ max-width:
|
| 26 |
footer {{ display: none !important; }}
|
| 27 |
|
| 28 |
/* Masthead */
|
| 29 |
-
.masthead {{
|
| 30 |
-
|
| 31 |
-
.masthead
|
| 32 |
-
.masthead
|
| 33 |
-
.masthead .
|
| 34 |
-
.masthead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
/* Rail */
|
| 37 |
-
.rail {{ background: var(--paper); border: 1px solid var(--rule); border-
|
| 38 |
-
padding:
|
| 39 |
-
.rail .rail-head {{ padding:
|
| 40 |
-
.rail .
|
| 41 |
-
.rail
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
.rail
|
| 50 |
-
.rail
|
| 51 |
-
.rail .wrap label span {{ white-space: normal !important; }}
|
| 52 |
|
| 53 |
/* Reading column */
|
| 54 |
-
.reader {{ background: var(--paper); border: 1px solid var(--rule); border-radius:
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
.reader
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
.reader h3:first-of-type {{ margin-top: 0; }}
|
| 60 |
-
.reader p {{ font-size:
|
| 61 |
-
.reader p b {{ color: var(--
|
| 62 |
-
.reader .help {{ margin-top:
|
| 63 |
-
.reader .help p {{ margin: 0; max-width: none; }}
|
| 64 |
-
.reader .help p b {{ color: var(--teal-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
/* Nav */
|
| 67 |
-
.navrow {{ margin-top: 14px; }}
|
| 68 |
-
.navrow button {{ border-radius:
|
| 69 |
-
|
| 70 |
-
.navrow
|
| 71 |
-
.navrow .
|
| 72 |
-
.
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
@media (max-width: 900px) {{
|
|
|
|
| 75 |
.reader {{ padding: 28px 22px; }}
|
| 76 |
-
.
|
|
|
|
|
|
|
| 77 |
}}
|
| 78 |
"""
|
| 79 |
|
| 80 |
-
LABELS = [f"{i + 1}. {m['title']}" for i, m in enumerate(MODULES)]
|
| 81 |
|
|
|
|
| 82 |
|
| 83 |
-
def
|
| 84 |
m = MODULES[idx]
|
|
|
|
| 85 |
parts = [
|
| 86 |
-
|
| 87 |
-
f'<
|
|
|
|
| 88 |
f'<h2>{html.escape(m["title"])}</h2>',
|
| 89 |
f'<p class="summary">{html.escape(m["summary"])}</p>',
|
| 90 |
]
|
|
@@ -96,49 +181,146 @@ def render(idx: int) -> str:
|
|
| 96 |
return "\n".join(parts)
|
| 97 |
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def progress_text(idx: int) -> str:
|
| 100 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
|
| 103 |
-
def
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
|
| 108 |
-
def
|
| 109 |
-
idx = max(0, min(
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
|
| 113 |
with gr.Blocks(css=CSS, title="BOS AI Training") as demo:
|
|
|
|
| 114 |
gr.HTML(
|
| 115 |
f"""
|
| 116 |
<div class="masthead">
|
| 117 |
-
<div>
|
| 118 |
-
|
| 119 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
</div>
|
| 121 |
-
<div class="
|
| 122 |
</div>
|
| 123 |
"""
|
| 124 |
)
|
| 125 |
-
|
| 126 |
state = gr.State(0)
|
| 127 |
|
| 128 |
with gr.Row(equal_height=False):
|
| 129 |
-
with gr.Column(scale=3, min_width=
|
| 130 |
gr.HTML('<div class="rail-head">Learning path</div>')
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
with gr.Column(scale=8):
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
with gr.Row(elem_classes=["navrow"]):
|
| 135 |
-
prev_btn = gr.Button("Previous module",
|
| 136 |
progress = gr.HTML(progress_text(0))
|
| 137 |
-
next_btn = gr.Button("Next module",
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
if __name__ == "__main__":
|
| 144 |
demo.launch()
|
|
|
|
| 1 |
"""
|
| 2 |
BOS AI Training Platform
|
| 3 |
A guided, module based course on using AI at BOS Security, delivered as a
|
| 4 |
+
HuggingFace Gradio Space.
|
| 5 |
+
|
| 6 |
+
Content: modules.py (lessons), quizzes.py (knowledge checks), teams.py (tools by team).
|
| 7 |
+
Logo: logo.png next to this file.
|
| 8 |
"""
|
| 9 |
|
| 10 |
+
import base64
|
| 11 |
import html
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
import gradio as gr
|
| 15 |
from modules import MODULES, IT_HELP
|
| 16 |
+
from quizzes import QUIZZES
|
| 17 |
+
from teams import TEAMS, TEAMS_INTRO
|
| 18 |
|
| 19 |
+
# Palette sampled from the BOS logo, teal as the single accent
|
| 20 |
+
BLUE = "#15516B"
|
| 21 |
+
BLUE_DEEP = "#0E3A4D"
|
| 22 |
TEAL = "#13A89E"
|
| 23 |
+
TEAL_DEEP = "#0E7F78"
|
| 24 |
+
INK = "#1B2430"
|
| 25 |
+
MUTED = "#61707E"
|
| 26 |
PAPER = "#FFFFFF"
|
| 27 |
+
WASH = "#F4F7F9"
|
| 28 |
+
RULE = "#E1E8ED"
|
| 29 |
+
MISS = "#C0563A"
|
| 30 |
+
|
| 31 |
+
LOGO_PATH = Path(__file__).with_name("logo.png")
|
| 32 |
+
LOGO_SRC = ""
|
| 33 |
+
if LOGO_PATH.exists():
|
| 34 |
+
LOGO_SRC = "data:image/png;base64," + base64.b64encode(LOGO_PATH.read_bytes()).decode()
|
| 35 |
+
|
| 36 |
+
N = len(MODULES)
|
| 37 |
+
TEAM_PAGE = N # index of the Tools by team page in the rail
|
| 38 |
+
MAXQ = max(len(q) for q in QUIZZES)
|
| 39 |
|
| 40 |
CSS = f"""
|
| 41 |
+
:root {{ --blue: {BLUE}; --blue-deep: {BLUE_DEEP}; --teal: {TEAL}; --teal-deep: {TEAL_DEEP};
|
| 42 |
+
--ink: {INK}; --muted: {MUTED}; --paper: {PAPER}; --wash: {WASH}; --rule: {RULE}; --miss: {MISS}; }}
|
| 43 |
* {{ font-family: Arial, Helvetica, sans-serif !important; }}
|
| 44 |
body, .gradio-container {{ background: var(--wash) !important; color: var(--ink); }}
|
| 45 |
+
.gradio-container {{ max-width: 1280px !important; margin: 0 auto !important; padding: 20px 20px 56px !important; }}
|
| 46 |
footer {{ display: none !important; }}
|
| 47 |
|
| 48 |
/* Masthead */
|
| 49 |
+
.masthead {{ display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap;
|
| 50 |
+
background: var(--paper); border: 1px solid var(--rule); border-radius: 14px; padding: 18px 26px; }}
|
| 51 |
+
.masthead .brand {{ display: flex; align-items: center; gap: 22px; }}
|
| 52 |
+
.masthead img {{ height: 52px; width: auto; display: block; }}
|
| 53 |
+
.masthead .divider {{ width: 1px; height: 44px; background: var(--rule); }}
|
| 54 |
+
.masthead h1 {{ margin: 0; font-size: 22px; font-weight: 700; color: var(--blue); letter-spacing: -0.01em; }}
|
| 55 |
+
.masthead .tag {{ margin: 3px 0 0; font-size: 14px; color: var(--muted); }}
|
| 56 |
+
.masthead .tool {{ font-size: 13px; color: var(--muted); padding: 8px 14px; border: 1px solid var(--rule);
|
| 57 |
+
border-radius: 999px; background: var(--wash); }}
|
| 58 |
+
.masthead .tool b {{ color: var(--blue); }}
|
| 59 |
+
|
| 60 |
+
/* Progress bar */
|
| 61 |
+
.track {{ height: 6px; background: var(--rule); border-radius: 999px; overflow: hidden; margin: 14px 4px 4px; }}
|
| 62 |
+
.track div {{ height: 100%; background: var(--teal); border-radius: 999px; transition: width .35s ease; }}
|
| 63 |
|
| 64 |
/* Rail */
|
| 65 |
+
.rail {{ background: var(--paper) !important; border: 1px solid var(--rule) !important; border-radius: 14px !important;
|
| 66 |
+
padding: 14px 10px !important; position: sticky; top: 16px; align-self: flex-start; gap: 4px !important; }}
|
| 67 |
+
.rail .rail-head {{ padding: 4px 12px 10px; font-size: 13px; color: var(--muted); }}
|
| 68 |
+
.rail .rail-sub {{ padding: 14px 12px 6px; font-size: 13px; color: var(--muted); border-top: 1px solid var(--rule); margin-top: 6px; }}
|
| 69 |
+
.rail button {{ width: 100%; justify-content: flex-start !important; text-align: left !important;
|
| 70 |
+
padding: 12px 14px !important; margin: 0 !important; border-radius: 10px !important;
|
| 71 |
+
font-size: 15px !important; line-height: 1.35 !important; font-weight: 400 !important;
|
| 72 |
+
white-space: normal !important; transition: background .15s ease, color .15s ease, transform .1s ease; }}
|
| 73 |
+
.rail button.secondary {{ background: transparent !important; border: 1px solid transparent !important; color: var(--ink) !important; box-shadow: none !important; }}
|
| 74 |
+
.rail button.secondary:hover {{ background: var(--wash) !important; }}
|
| 75 |
+
.rail button.primary {{ background: #E7F6F4 !important; border: 1px solid #C6EBE7 !important; color: var(--blue) !important;
|
| 76 |
+
font-weight: 700 !important; box-shadow: none !important; }}
|
| 77 |
+
.rail button:active {{ transform: scale(.985); }}
|
| 78 |
+
.rail button:focus-visible {{ outline: 2px solid var(--teal) !important; outline-offset: 2px; }}
|
|
|
|
| 79 |
|
| 80 |
/* Reading column */
|
| 81 |
+
.reader {{ background: var(--paper); border: 1px solid var(--rule); border-radius: 14px; padding: 44px 60px 36px;
|
| 82 |
+
animation: rise .28s ease both; }}
|
| 83 |
+
@keyframes rise {{ from {{ opacity: 0; transform: translateY(6px); }} to {{ opacity: 1; transform: none; }} }}
|
| 84 |
+
@media (prefers-reduced-motion: reduce) {{ .reader, .result {{ animation: none; }} .track div {{ transition: none; }} }}
|
| 85 |
+
.reader .meta {{ display: flex; align-items: center; gap: 12px; margin-bottom: 14px; font-size: 13px; color: var(--muted); }}
|
| 86 |
+
.reader .pill {{ display: inline-block; padding: 4px 11px; border-radius: 999px; font-weight: 700; font-size: 12px;
|
| 87 |
+
background: #E7F6F4; color: var(--teal-deep); }}
|
| 88 |
+
.reader .pill.required {{ background: var(--blue); color: #fff; }}
|
| 89 |
+
.reader h2 {{ font-size: 34px; line-height: 1.12; color: var(--blue); margin: 0 0 10px; letter-spacing: -0.02em; }}
|
| 90 |
+
.reader .summary {{ font-size: 18px; color: var(--muted); margin: 0 0 32px; padding-bottom: 24px;
|
| 91 |
+
border-bottom: 1px solid var(--rule); max-width: 64ch; line-height: 1.45; }}
|
| 92 |
+
.reader h3 {{ font-size: 20px; color: var(--blue); margin: 36px 0 10px; letter-spacing: -0.01em; }}
|
| 93 |
.reader h3:first-of-type {{ margin-top: 0; }}
|
| 94 |
+
.reader p {{ font-size: 16.5px; line-height: 1.65; margin: 0 0 14px; max-width: 70ch; color: var(--ink); }}
|
| 95 |
+
.reader p b {{ color: var(--blue); }}
|
| 96 |
+
.reader .help {{ margin-top: 44px; padding: 20px 24px; background: var(--wash); border-radius: 12px; border-left: 4px solid var(--teal); }}
|
| 97 |
+
.reader .help p {{ margin: 0; max-width: none; font-size: 15.5px; }}
|
| 98 |
+
.reader .help p b {{ color: var(--teal-deep); }}
|
| 99 |
+
|
| 100 |
+
/* Quiz */
|
| 101 |
+
.quiz {{ background: var(--paper) !important; border: 1px solid var(--rule) !important; border-radius: 14px !important;
|
| 102 |
+
padding: 28px 36px 26px !important; margin-top: 16px; gap: 6px !important; }}
|
| 103 |
+
.quiz .qhead h3 {{ font-size: 20px; color: var(--blue); margin: 0 0 4px; }}
|
| 104 |
+
.quiz .qhead p {{ color: var(--muted); font-size: 15px; margin: 0 0 12px; max-width: 70ch; }}
|
| 105 |
+
.quiz fieldset {{ border: none !important; padding: 0 !important; margin: 0 !important; }}
|
| 106 |
+
.quiz .block {{ border: none !important; background: transparent !important; padding: 6px 0 !important; box-shadow: none !important; }}
|
| 107 |
+
.quiz span[data-testid="block-info"] {{ font-size: 16px !important; font-weight: 700 !important; color: var(--ink) !important;
|
| 108 |
+
margin-bottom: 10px !important; display: block; line-height: 1.4; }}
|
| 109 |
+
.quiz .wrap {{ display: flex; flex-direction: column; gap: 6px !important; }}
|
| 110 |
+
.quiz .wrap label {{ display: flex !important; align-items: center; gap: 12px; padding: 11px 14px !important;
|
| 111 |
+
border: 1px solid var(--rule) !important; border-radius: 10px !important; background: var(--paper) !important;
|
| 112 |
+
cursor: pointer; font-size: 15px; box-shadow: none !important; transition: border-color .12s ease, background .12s ease; }}
|
| 113 |
+
.quiz .wrap label:hover {{ border-color: var(--teal) !important; }}
|
| 114 |
+
.quiz .wrap label.selected {{ border-color: var(--teal) !important; background: #E7F6F4 !important; color: var(--blue); }}
|
| 115 |
+
.quiz .wrap label input {{ accent-color: var(--teal); }}
|
| 116 |
+
.quiz .wrap label span {{ white-space: normal !important; }}
|
| 117 |
+
.quiz .check {{ background: var(--blue) !important; border: 1px solid var(--blue) !important; color: #fff !important; box-shadow: none !important;
|
| 118 |
+
border-radius: 10px !important; font-weight: 700 !important; padding: 12px 22px !important; margin-top: 8px; width: auto; }}
|
| 119 |
+
.quiz .check:hover {{ background: var(--blue-deep) !important; }}
|
| 120 |
+
.result {{ margin-top: 12px; animation: rise .25s ease both; }}
|
| 121 |
+
.result .score {{ font-size: 18px; font-weight: 700; color: var(--blue); margin: 0 0 12px; }}
|
| 122 |
+
.result .item {{ padding: 14px 16px; border-radius: 10px; margin-bottom: 10px; font-size: 15px; line-height: 1.5; }}
|
| 123 |
+
.result .item.ok {{ background: #E7F6F4; border-left: 4px solid var(--teal); }}
|
| 124 |
+
.result .item.miss {{ background: #FBEFEA; border-left: 4px solid var(--miss); }}
|
| 125 |
+
.result .item p {{ margin: 0 0 6px; }}
|
| 126 |
+
.result .item p:last-child {{ margin: 0; }}
|
| 127 |
+
.result .item b {{ color: var(--blue); }}
|
| 128 |
+
.result .warn {{ color: var(--miss); font-weight: 700; }}
|
| 129 |
+
|
| 130 |
+
/* Tools by team */
|
| 131 |
+
.teamwrap .tabs {{ margin-top: -14px; }}
|
| 132 |
+
.teamwrap .tab-nav {{ border-bottom: 1px solid var(--rule) !important; gap: 2px; }}
|
| 133 |
+
.teamwrap .tab-nav button {{ font-size: 15px !important; font-weight: 700 !important; color: var(--muted) !important; border: none !important;
|
| 134 |
+
border-bottom: 3px solid transparent !important; border-radius: 0 !important; padding: 12px 18px !important;
|
| 135 |
+
background: transparent !important; box-shadow: none !important; }}
|
| 136 |
+
.teamwrap .tab-nav button.selected {{ color: var(--blue) !important; border-bottom-color: var(--teal) !important; }}
|
| 137 |
+
.teamwrap .tabitem {{ border: none !important; padding: 4px 0 0 !important; background: transparent !important; }}
|
| 138 |
+
.teamwrap .tabitem h3 {{ font-size: 19px; color: var(--blue); margin: 26px 0 8px; }}
|
| 139 |
+
.teamwrap .tabitem h3:first-child {{ margin-top: 10px; }}
|
| 140 |
+
.teamwrap .tabitem p {{ font-size: 16px; line-height: 1.65; margin: 0 0 12px; max-width: 72ch; color: var(--ink); }}
|
| 141 |
+
.teamwrap .tabitem p b {{ color: var(--blue); }}
|
| 142 |
|
| 143 |
/* Nav */
|
| 144 |
+
.navrow {{ margin-top: 14px; align-items: center; }}
|
| 145 |
+
.navrow button {{ border-radius: 10px !important; font-weight: 700 !important; font-size: 14px !important;
|
| 146 |
+
padding: 12px 20px !important; transition: background .15s ease, transform .1s ease; }}
|
| 147 |
+
.navrow button:active {{ transform: scale(.98); }}
|
| 148 |
+
.navrow .primary {{ background: var(--teal) !important; border: 1px solid var(--teal) !important; color: #fff !important; box-shadow: none !important; }}
|
| 149 |
+
.navrow .primary:hover {{ background: var(--teal-deep) !important; }}
|
| 150 |
+
.navrow .secondary {{ background: var(--paper) !important; border: 1px solid var(--rule) !important; color: var(--blue) !important; box-shadow: none !important; }}
|
| 151 |
+
.navrow .secondary:hover {{ background: var(--wash) !important; }}
|
| 152 |
+
.progress {{ text-align: center; color: var(--muted); font-size: 14px; }}
|
| 153 |
|
| 154 |
@media (max-width: 900px) {{
|
| 155 |
+
.rail {{ position: static; }}
|
| 156 |
.reader {{ padding: 28px 22px; }}
|
| 157 |
+
.quiz {{ padding: 22px 20px !important; }}
|
| 158 |
+
.reader h2 {{ font-size: 27px; }}
|
| 159 |
+
.masthead .divider {{ display: none; }}
|
| 160 |
}}
|
| 161 |
"""
|
| 162 |
|
|
|
|
| 163 |
|
| 164 |
+
# Rendering helpers
|
| 165 |
|
| 166 |
+
def render_module(idx: int) -> str:
|
| 167 |
m = MODULES[idx]
|
| 168 |
+
pill_cls = "pill required" if m["depth"].lower() == "required" else "pill"
|
| 169 |
parts = [
|
| 170 |
+
'<div class="reader">',
|
| 171 |
+
f'<div class="meta"><span class="{pill_cls}">{html.escape(m["depth"])}</span>'
|
| 172 |
+
f'<span>Module {idx + 1} of {N}</span></div>',
|
| 173 |
f'<h2>{html.escape(m["title"])}</h2>',
|
| 174 |
f'<p class="summary">{html.escape(m["summary"])}</p>',
|
| 175 |
]
|
|
|
|
| 181 |
return "\n".join(parts)
|
| 182 |
|
| 183 |
|
| 184 |
+
def render_team_header() -> str:
|
| 185 |
+
return (
|
| 186 |
+
'<div class="reader" style="padding-bottom:20px">'
|
| 187 |
+
'<div class="meta"><span class="pill">Reference</span><span>Tools by team</span></div>'
|
| 188 |
+
"<h2>Where Claude fits next to Zoho, Apollo, and Microsoft 365</h2>"
|
| 189 |
+
f"{TEAMS_INTRO}</div>"
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def track(idx: int) -> str:
|
| 194 |
+
pct = 100 if idx >= N else round((idx + 1) / N * 100)
|
| 195 |
+
return f'<div class="track"><div style="width:{pct}%"></div></div>'
|
| 196 |
+
|
| 197 |
+
|
| 198 |
def progress_text(idx: int) -> str:
|
| 199 |
+
return '<div class="progress">Reference</div>' if idx >= N else f'<div class="progress">{idx + 1} of {N}</div>'
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def rail_updates(idx: int):
|
| 203 |
+
return [gr.update(variant="primary" if i == idx else "secondary") for i in range(N + 1)]
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def quiz_updates(idx: int):
|
| 207 |
+
"""Return updates for the MAXQ radio components for module idx."""
|
| 208 |
+
qs = QUIZZES[idx] if idx < N else []
|
| 209 |
+
out = []
|
| 210 |
+
for i in range(MAXQ):
|
| 211 |
+
if i < len(qs):
|
| 212 |
+
out.append(gr.update(label=f"{i + 1}. {qs[i]['q']}", choices=qs[i]["options"], value=None, visible=True))
|
| 213 |
+
else:
|
| 214 |
+
out.append(gr.update(visible=False, value=None))
|
| 215 |
+
return out
|
| 216 |
|
| 217 |
|
| 218 |
+
def grade(idx: int, *answers) -> str:
|
| 219 |
+
qs = QUIZZES[idx]
|
| 220 |
+
unanswered = [i + 1 for i, a in enumerate(answers[: len(qs)]) if a is None]
|
| 221 |
+
if unanswered:
|
| 222 |
+
nums = ", ".join(str(n) for n in unanswered)
|
| 223 |
+
return f'<div class="result"><p class="warn">Answer question {nums} before checking.</p></div>'
|
| 224 |
+
items = []
|
| 225 |
+
correct = 0
|
| 226 |
+
for i, q in enumerate(qs):
|
| 227 |
+
chosen = answers[i]
|
| 228 |
+
right = q["options"][q["answer"]]
|
| 229 |
+
if chosen == right:
|
| 230 |
+
correct += 1
|
| 231 |
+
items.append(
|
| 232 |
+
f'<div class="item ok"><p><b>Question {i + 1}.</b> Correct.</p>'
|
| 233 |
+
f'<p>{html.escape(right)}</p></div>'
|
| 234 |
+
)
|
| 235 |
+
else:
|
| 236 |
+
items.append(
|
| 237 |
+
f'<div class="item miss"><p><b>Question {i + 1}.</b> Not quite. You chose: {html.escape(chosen)}</p>'
|
| 238 |
+
f'<p><b>Correct answer:</b> {html.escape(right)}</p>'
|
| 239 |
+
f'<p>{html.escape(q["why"])}</p></div>'
|
| 240 |
+
)
|
| 241 |
+
score = f'<p class="score">You got {correct} of {len(qs)} right.</p>'
|
| 242 |
+
if correct == len(qs):
|
| 243 |
+
score = f'<p class="score">All {len(qs)} correct. Move on to the next module.</p>'
|
| 244 |
+
return f'<div class="result">{score}{"".join(items)}</div>'
|
| 245 |
|
| 246 |
|
| 247 |
+
def show(idx: int):
|
| 248 |
+
idx = max(0, min(TEAM_PAGE, idx))
|
| 249 |
+
on_module = idx < N
|
| 250 |
+
return [
|
| 251 |
+
idx,
|
| 252 |
+
gr.update(visible=on_module), # module_view
|
| 253 |
+
gr.update(visible=not on_module), # team_view
|
| 254 |
+
render_module(idx if on_module else 0),
|
| 255 |
+
track(idx),
|
| 256 |
+
progress_text(idx),
|
| 257 |
+
gr.update(interactive=idx > 0),
|
| 258 |
+
gr.update(interactive=idx < TEAM_PAGE, value="Next module" if idx < N - 1 else "Tools by team"),
|
| 259 |
+
"", # clear quiz result
|
| 260 |
+
*rail_updates(idx),
|
| 261 |
+
*quiz_updates(idx),
|
| 262 |
+
]
|
| 263 |
|
| 264 |
|
| 265 |
with gr.Blocks(css=CSS, title="BOS AI Training") as demo:
|
| 266 |
+
logo_html = f'<img src="{LOGO_SRC}" alt="BOS">' if LOGO_SRC else ""
|
| 267 |
gr.HTML(
|
| 268 |
f"""
|
| 269 |
<div class="masthead">
|
| 270 |
+
<div class="brand">
|
| 271 |
+
{logo_html}
|
| 272 |
+
<div class="divider"></div>
|
| 273 |
+
<div>
|
| 274 |
+
<h1>AI Training Platform</h1>
|
| 275 |
+
<p class="tag">Six modules with a knowledge check at the end of each, plus a tools by team reference.</p>
|
| 276 |
+
</div>
|
| 277 |
</div>
|
| 278 |
+
<div class="tool">Company standard: <b>Claude Enterprise</b></div>
|
| 279 |
</div>
|
| 280 |
"""
|
| 281 |
)
|
| 282 |
+
bar = gr.HTML(track(0))
|
| 283 |
state = gr.State(0)
|
| 284 |
|
| 285 |
with gr.Row(equal_height=False):
|
| 286 |
+
with gr.Column(scale=3, min_width=250, elem_classes=["rail"]):
|
| 287 |
gr.HTML('<div class="rail-head">Learning path</div>')
|
| 288 |
+
rail_btns = [
|
| 289 |
+
gr.Button(f"{i + 1}. {m['title']}", variant="primary" if i == 0 else "secondary")
|
| 290 |
+
for i, m in enumerate(MODULES)
|
| 291 |
+
]
|
| 292 |
+
gr.HTML('<div class="rail-sub">Reference</div>')
|
| 293 |
+
rail_btns.append(gr.Button("Tools by team", variant="secondary"))
|
| 294 |
+
|
| 295 |
with gr.Column(scale=8):
|
| 296 |
+
with gr.Column(visible=True) as module_view:
|
| 297 |
+
content = gr.HTML(render_module(0))
|
| 298 |
+
with gr.Column(elem_classes=["quiz"]):
|
| 299 |
+
gr.HTML('<div class="qhead"><h3>Knowledge check</h3>'
|
| 300 |
+
"<p>Pick one answer per question, then check. Anything you miss shows the correct answer and why.</p></div>")
|
| 301 |
+
radios = [gr.Radio(choices=[], label="", visible=False) for _ in range(MAXQ)]
|
| 302 |
+
check_btn = gr.Button("Check answers", elem_classes=["check"])
|
| 303 |
+
result = gr.HTML("")
|
| 304 |
+
|
| 305 |
+
with gr.Column(visible=False, elem_classes=["teamwrap"]) as team_view:
|
| 306 |
+
gr.HTML(render_team_header())
|
| 307 |
+
with gr.Tabs():
|
| 308 |
+
for t in TEAMS:
|
| 309 |
+
with gr.Tab(t["name"]):
|
| 310 |
+
gr.HTML(t["html"])
|
| 311 |
+
|
| 312 |
with gr.Row(elem_classes=["navrow"]):
|
| 313 |
+
prev_btn = gr.Button("Previous module", variant="secondary", interactive=False)
|
| 314 |
progress = gr.HTML(progress_text(0))
|
| 315 |
+
next_btn = gr.Button("Next module", variant="primary")
|
| 316 |
|
| 317 |
+
outputs = [state, module_view, team_view, content, bar, progress, prev_btn, next_btn, result, *rail_btns, *radios]
|
| 318 |
+
for i, b in enumerate(rail_btns):
|
| 319 |
+
b.click(lambda i=i: show(i), outputs=outputs)
|
| 320 |
+
prev_btn.click(lambda s: show(s - 1), inputs=state, outputs=outputs)
|
| 321 |
+
next_btn.click(lambda s: show(s + 1), inputs=state, outputs=outputs)
|
| 322 |
+
check_btn.click(grade, inputs=[state, *radios], outputs=result)
|
| 323 |
+
demo.load(lambda: show(0), outputs=outputs)
|
| 324 |
|
| 325 |
if __name__ == "__main__":
|
| 326 |
demo.launch()
|
logo.png
ADDED
|
Git LFS Details
|
quizzes.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Knowledge checks, one list per module, matched by position to MODULES in modules.py.
|
| 3 |
+
Each question: prompt, options, answer (index into options), and why (shown when wrong).
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
QUIZZES = [
|
| 7 |
+
# Module 1: AI at BOS
|
| 8 |
+
[
|
| 9 |
+
{
|
| 10 |
+
"q": "Which AI tool is approved for BOS and client information?",
|
| 11 |
+
"options": [
|
| 12 |
+
"Any tool, as long as you delete the chat afterward",
|
| 13 |
+
"The BOS Claude Enterprise account only",
|
| 14 |
+
"Claude Enterprise or a personal ChatGPT account",
|
| 15 |
+
"Whatever tool your manager prefers",
|
| 16 |
+
],
|
| 17 |
+
"answer": 1,
|
| 18 |
+
"why": "Company and client information goes into the BOS Claude Enterprise account and nowhere else. Personal accounts, free tools, and AI features in other apps have not been reviewed and cannot be defended in an audit.",
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"q": "What does a large language model actually do?",
|
| 22 |
+
"options": [
|
| 23 |
+
"Looks up verified facts in a database",
|
| 24 |
+
"Produces the most plausible next text based on the conversation so far",
|
| 25 |
+
"Connects to BOS systems automatically to find answers",
|
| 26 |
+
"Records everything you type for the company",
|
| 27 |
+
],
|
| 28 |
+
"answer": 1,
|
| 29 |
+
"why": "A language model predicts plausible text from patterns it learned. It does not look up facts unless given a tool for that, and plausible is not the same as true.",
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"q": "Which of these is the AI tool NOT good at without help?",
|
| 33 |
+
"options": [
|
| 34 |
+
"Tightening the wording of a paragraph you wrote",
|
| 35 |
+
"Summarizing a long document you attach",
|
| 36 |
+
"Knowing BOS procedures, clients, and pricing on its own",
|
| 37 |
+
"Laying out pros and cons of two approaches",
|
| 38 |
+
],
|
| 39 |
+
"answer": 2,
|
| 40 |
+
"why": "The model does not know anything about BOS, our clients, or our procedures unless you put that context into the conversation.",
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"q": "Why did BOS standardize on one AI tool?",
|
| 44 |
+
"options": [
|
| 45 |
+
"It was the cheapest option",
|
| 46 |
+
"Data control, consistency, and compliance",
|
| 47 |
+
"So IT could monitor every employee",
|
| 48 |
+
"Because other tools do not work",
|
| 49 |
+
],
|
| 50 |
+
"answer": 1,
|
| 51 |
+
"why": "One approved tool puts our data under negotiated terms, lets IT support and configure it consistently, and gives us something we can defend in an audit.",
|
| 52 |
+
},
|
| 53 |
+
],
|
| 54 |
+
# Module 2: How AI Works
|
| 55 |
+
[
|
| 56 |
+
{
|
| 57 |
+
"q": "The AI confidently gives you a statistic you did not provide. What should you do?",
|
| 58 |
+
"options": [
|
| 59 |
+
"Use it, because the tool sounded confident",
|
| 60 |
+
"Verify it against a real source before using it anywhere",
|
| 61 |
+
"Ask the AI to confirm and then use it",
|
| 62 |
+
"Round it to be safe",
|
| 63 |
+
],
|
| 64 |
+
"answer": 1,
|
| 65 |
+
"why": "Numbers the model produces on its own are the highest hallucination risk. Every figure needs a source you can point to. Asking the AI to confirm its own guess is not verification.",
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"q": "What is the model's context?",
|
| 69 |
+
"options": [
|
| 70 |
+
"Its knowledge of the whole internet",
|
| 71 |
+
"Everything in the current conversation, including attachments",
|
| 72 |
+
"The BOS company handbook",
|
| 73 |
+
"Its memory of your past chats",
|
| 74 |
+
],
|
| 75 |
+
"answer": 1,
|
| 76 |
+
"why": "Context is everything the model can see in this conversation: your messages, its replies, and any files. That is its entire working knowledge for the chat.",
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"q": "A long chat starts forgetting instructions you gave earlier. The most likely cause is:",
|
| 80 |
+
"options": [
|
| 81 |
+
"The AI is broken and IT needs to restart it",
|
| 82 |
+
"The context limit has pushed earlier material out of view",
|
| 83 |
+
"Someone else is using your account",
|
| 84 |
+
"The model updated overnight",
|
| 85 |
+
],
|
| 86 |
+
"answer": 1,
|
| 87 |
+
"why": "Context has a size limit. Long sessions and many attachments can push earlier material out. Start a fresh chat with a clean summary of where you are.",
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"q": "Who is the authority on whether a camera works with the VirtuGuard platform?",
|
| 91 |
+
"options": [
|
| 92 |
+
"The AI, if you describe the camera well",
|
| 93 |
+
"The camera manufacturer's website",
|
| 94 |
+
"The Immix integration matrix",
|
| 95 |
+
"Whoever sold the camera",
|
| 96 |
+
],
|
| 97 |
+
"answer": 2,
|
| 98 |
+
"why": "Compatibility claims come from the Immix integration matrix, not from the AI's opinion or a vendor's marketing page.",
|
| 99 |
+
},
|
| 100 |
+
],
|
| 101 |
+
# Module 3: First Conversations
|
| 102 |
+
[
|
| 103 |
+
{
|
| 104 |
+
"q": "Which request is most likely to produce something you can actually send?",
|
| 105 |
+
"options": [
|
| 106 |
+
"Write an email",
|
| 107 |
+
"Write a short email to a non technical property manager explaining that a scheduled camera check moved from Tuesday to Thursday",
|
| 108 |
+
"Email about the camera thing",
|
| 109 |
+
"Write something professional",
|
| 110 |
+
],
|
| 111 |
+
"answer": 1,
|
| 112 |
+
"why": "Good requests carry context, specifics, and material. Who it is for, what it is about, and how long it should be all change the result.",
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"q": "The first answer is not quite right. What do experienced users do?",
|
| 116 |
+
"options": [
|
| 117 |
+
"Start over in a new chat with the same request",
|
| 118 |
+
"Accept it and fix it by hand",
|
| 119 |
+
"Tell the tool what to change and iterate two or three times",
|
| 120 |
+
"Try a different AI tool",
|
| 121 |
+
],
|
| 122 |
+
"answer": 2,
|
| 123 |
+
"why": "Treat the first answer as a first draft. Shorter, less formal, you dropped the part about the invoice. Two or three rounds is normal.",
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"q": "Before using the tool for work, you should confirm that:",
|
| 127 |
+
"options": [
|
| 128 |
+
"You are signed into the BOS organization workspace, not a personal account",
|
| 129 |
+
"Your manager has approved each request",
|
| 130 |
+
"You have read the full user manual",
|
| 131 |
+
"The chat is set to private mode",
|
| 132 |
+
],
|
| 133 |
+
"answer": 0,
|
| 134 |
+
"why": "Check the workspace shown is the BOS organization. If you are unsure which account you are in, ask BOS IT before you use it for work.",
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"q": "Why start a new chat for each new task?",
|
| 138 |
+
"options": [
|
| 139 |
+
"The company is billed per chat",
|
| 140 |
+
"Mixing unrelated work degrades results and makes things harder to find",
|
| 141 |
+
"Old chats are deleted after an hour",
|
| 142 |
+
"It is required by the security policy",
|
| 143 |
+
],
|
| 144 |
+
"answer": 1,
|
| 145 |
+
"why": "Unrelated work in one long conversation clutters the context and lowers quality. One task per chat keeps results sharp and searchable.",
|
| 146 |
+
},
|
| 147 |
+
],
|
| 148 |
+
# Module 4: Better Results
|
| 149 |
+
[
|
| 150 |
+
{
|
| 151 |
+
"q": "You want consistent output in your own format and tone. The most reliable technique is:",
|
| 152 |
+
"options": [
|
| 153 |
+
"Describe your style in detail",
|
| 154 |
+
"Paste an example of past work you were happy with and ask for the same style",
|
| 155 |
+
"Ask the tool to guess your style",
|
| 156 |
+
"Use very short prompts",
|
| 157 |
+
],
|
| 158 |
+
"answer": 1,
|
| 159 |
+
"why": "One good example teaches format and tone faster than any description. Here is how I wrote the last three, write the next one the same way.",
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"q": "You need a long document. The better approach is:",
|
| 163 |
+
"options": [
|
| 164 |
+
"Ask for the whole thing in one request",
|
| 165 |
+
"Ask for an outline, review it, then write one section at a time",
|
| 166 |
+
"Ask three times and pick the best one",
|
| 167 |
+
"Ask for it twice as long, then cut",
|
| 168 |
+
],
|
| 169 |
+
"answer": 1,
|
| 170 |
+
"why": "Breaking big tasks into steps catches problems early and gives each piece full attention. One shot requests for long documents invite mediocre results.",
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"q": "Which instruction cuts hallucination sharply when working from an attached file?",
|
| 174 |
+
"options": [
|
| 175 |
+
"Be accurate",
|
| 176 |
+
"Answer only from the document and tell me when it does not cover something",
|
| 177 |
+
"Use your best judgment",
|
| 178 |
+
"Do not make mistakes",
|
| 179 |
+
],
|
| 180 |
+
"answer": 1,
|
| 181 |
+
"why": "Telling the model to answer only from the document, and to say when the document is silent, keeps it from filling gaps with plausible guesses.",
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"q": "Before creating a shared project for your team, you should:",
|
| 185 |
+
"options": [
|
| 186 |
+
"Just build it, then tell people",
|
| 187 |
+
"Ask BOS IT which projects already exist",
|
| 188 |
+
"Get sign off from the CEO",
|
| 189 |
+
"Copy one from a personal account",
|
| 190 |
+
],
|
| 191 |
+
"answer": 1,
|
| 192 |
+
"why": "IT can tell you what already exists and help set one up, so we avoid a dozen slightly different versions of the same thing.",
|
| 193 |
+
},
|
| 194 |
+
],
|
| 195 |
+
# Module 5: Security and Data
|
| 196 |
+
[
|
| 197 |
+
{
|
| 198 |
+
"q": "A document may contain Controlled Unclassified Information. You need a summary. What do you do?",
|
| 199 |
+
"options": [
|
| 200 |
+
"Summarize it with the company Claude account since it is approved",
|
| 201 |
+
"Remove the header and then summarize it",
|
| 202 |
+
"Stop and route it through the approved handling process",
|
| 203 |
+
"Summarize only the first half",
|
| 204 |
+
],
|
| 205 |
+
"answer": 2,
|
| 206 |
+
"why": "CUI, Federal Contract Information, and export controlled material never go into any AI tool, including the company one. Treat it as controlled until IT or leadership says otherwise.",
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
"q": "You are drafting a case study that will go to a prospect. How do you refer to the client?",
|
| 210 |
+
"options": [
|
| 211 |
+
"By name, since it is good marketing",
|
| 212 |
+
"Generically by vertical and size, unless the client approved attribution in writing",
|
| 213 |
+
"By first name only",
|
| 214 |
+
"By site address so it is credible",
|
| 215 |
+
],
|
| 216 |
+
"answer": 1,
|
| 217 |
+
"why": "External material never carries client names, site addresses, camera locations, account numbers, or contract values without written approval. Use the vertical and size instead.",
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"q": "Which of these can go into the company Claude account?",
|
| 221 |
+
"options": [
|
| 222 |
+
"A VMS login so the tool can write setup instructions",
|
| 223 |
+
"An incident narrative tied to a specific site for a client report",
|
| 224 |
+
"A compensation spreadsheet to reformat",
|
| 225 |
+
"Your own rough meeting notes with the client referred to as Site A",
|
| 226 |
+
],
|
| 227 |
+
"answer": 3,
|
| 228 |
+
"why": "Credentials, raw incident narratives, and personnel or payroll data never go in. Your own notes with placeholders for client specifics are exactly the right habit.",
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"q": "A subpoena arrives and you want help drafting a response. You should:",
|
| 232 |
+
"options": [
|
| 233 |
+
"Draft it with Claude and send it to legal for review",
|
| 234 |
+
"Route it to leadership without drafting anything",
|
| 235 |
+
"Draft it yourself without AI",
|
| 236 |
+
"Ask the AI what the law requires first",
|
| 237 |
+
],
|
| 238 |
+
"answer": 1,
|
| 239 |
+
"why": "Responses to legal notices, subpoenas, insurance claims, regulatory inquiries, and client incident disputes go to leadership. They are not drafted by staff, with or without the tool.",
|
| 240 |
+
},
|
| 241 |
+
{
|
| 242 |
+
"q": "The tool does something strange after you paste an email from an unknown sender. What should you do?",
|
| 243 |
+
"options": [
|
| 244 |
+
"Ignore it and keep working",
|
| 245 |
+
"Paste the email again to see if it repeats",
|
| 246 |
+
"Stop and report it to BOS IT",
|
| 247 |
+
"Delete the chat and start over",
|
| 248 |
+
],
|
| 249 |
+
"answer": 2,
|
| 250 |
+
"why": "Material you give the model can contain hidden instructions that redirect it. Unexpected behavior after pasting outside content is a report, not a retry.",
|
| 251 |
+
},
|
| 252 |
+
],
|
| 253 |
+
# Module 6: Everyday Workflows
|
| 254 |
+
[
|
| 255 |
+
{
|
| 256 |
+
"q": "Which is an appropriate Command Center use of the tool?",
|
| 257 |
+
"options": [
|
| 258 |
+
"Describing the video from last night's incident to draft the client report",
|
| 259 |
+
"Turning rough shift notes into a consistent report format with no incident specifics tied to sites",
|
| 260 |
+
"Pasting dispatch records to find patterns",
|
| 261 |
+
"Uploading the law enforcement interaction log",
|
| 262 |
+
],
|
| 263 |
+
"answer": 1,
|
| 264 |
+
"why": "Cleaning up shift notes and rewriting procedures are good uses. Video descriptions, dispatch records, and law enforcement details stay inside operations systems.",
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"q": "Which is written correctly per the BOS standard?",
|
| 268 |
+
"options": [
|
| 269 |
+
"Virtu Guard, a division of BOS",
|
| 270 |
+
"VirtuGuard, a division of BOS Security",
|
| 271 |
+
"VG (VirtuGuard) by B.O.S.",
|
| 272 |
+
"Virtuguard from Bos Security",
|
| 273 |
+
],
|
| 274 |
+
"answer": 1,
|
| 275 |
+
"why": "VirtuGuard is one word with a capital V and capital G. Spell out BOS Security on first reference.",
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"q": "Which formatting rule applies to client communications at BOS?",
|
| 279 |
+
"options": [
|
| 280 |
+
"Bullet lists for everything",
|
| 281 |
+
"Emojis to keep it friendly",
|
| 282 |
+
"Prose with bold inline labels, no em dashes, no emojis",
|
| 283 |
+
"All caps headings",
|
| 284 |
+
],
|
| 285 |
+
"answer": 2,
|
| 286 |
+
"why": "Client communications use prose with bold inline section labels where structure is needed. No em dashes and no emojis, and plain language for non technical readers.",
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"q": "When should you contact BOS IT?",
|
| 290 |
+
"options": [
|
| 291 |
+
"Only when the tool is completely down",
|
| 292 |
+
"Before pasting something you are unsure about, and any time the tool behaves unexpectedly",
|
| 293 |
+
"After you have already tried a few other tools",
|
| 294 |
+
"Only through your manager",
|
| 295 |
+
],
|
| 296 |
+
"answer": 1,
|
| 297 |
+
"why": "IT would much rather hear from you before you paste something than after. A two minute question on Teams is the cheapest step in the process.",
|
| 298 |
+
},
|
| 299 |
+
],
|
| 300 |
+
]
|
teams.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tools by team reference. Internal audience only. Each team lists the systems it
|
| 3 |
+
already uses and where Claude Enterprise fits next to them.
|
| 4 |
+
|
| 5 |
+
Note on connectors: unless BOS IT has set up a connector for your team, assume
|
| 6 |
+
Claude does not reach into Zoho, Apollo, or Microsoft 365 on its own. You copy
|
| 7 |
+
material in, work on it, and paste results back out. Ask IT what is connected.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
TEAMS_INTRO = """
|
| 11 |
+
<p>Every team at BOS works out of the same three platforms: Zoho for CRM, helpdesk,
|
| 12 |
+
and workflow, Apollo for prospecting data and outreach, and Microsoft 365 for
|
| 13 |
+
email, Teams, and files. Claude Enterprise sits alongside them as the drafting and
|
| 14 |
+
thinking layer. This page shows, team by team, where it fits and what stays out.</p>
|
| 15 |
+
<p><b>A note on connections.</b> Unless BOS IT has set up a connector for your team,
|
| 16 |
+
assume Claude does not reach into Zoho, Apollo, or Microsoft 365 on its own. You
|
| 17 |
+
copy material in, work on it, and paste the result back where it belongs. If a
|
| 18 |
+
connector is available to you, IT will have told you what it can see and what
|
| 19 |
+
rules apply. When in doubt, ask.</p>
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
TEAMS = [
|
| 23 |
+
{
|
| 24 |
+
"name": "BOS Staffing",
|
| 25 |
+
"html": """
|
| 26 |
+
<h3>What you already use</h3>
|
| 27 |
+
<p><b>Zoho.</b> Candidate and client records, tasks, and workflow.
|
| 28 |
+
<b>Microsoft 365.</b> Outlook for client and candidate email, Teams for the internal
|
| 29 |
+
back and forth, SharePoint and OneDrive for documents and templates.
|
| 30 |
+
<b>Internal job posting tool.</b> The BOS generator for formatted job postings, built
|
| 31 |
+
and maintained by IT.</p>
|
| 32 |
+
|
| 33 |
+
<h3>Where Claude fits</h3>
|
| 34 |
+
<p><b>Job postings.</b> Start from the internal posting tool, then use Claude to
|
| 35 |
+
adjust tone for a specific market or to write three subject line options for the
|
| 36 |
+
distribution email. Give it the role, the shift pattern, and the location. Do not
|
| 37 |
+
give it a candidate.</p>
|
| 38 |
+
<p><b>Client communication.</b> Paste your rough notes from a client call and ask for
|
| 39 |
+
a clean follow up in your voice. Refer to the client generically in the draft and
|
| 40 |
+
fill the name in afterward in Outlook.</p>
|
| 41 |
+
<p><b>Interview structure.</b> Ask for a competency based question set for a role
|
| 42 |
+
type, then adapt it. This produces the framework, not a judgment about any person.</p>
|
| 43 |
+
<p><b>Process writing.</b> Turning how you actually do a placement into a written
|
| 44 |
+
procedure a new coordinator can follow.</p>
|
| 45 |
+
|
| 46 |
+
<h3>What stays out</h3>
|
| 47 |
+
<p>Individual resumes, applications, background or drug screening results,
|
| 48 |
+
compensation, disciplinary notes, and anything from a personnel file. Any
|
| 49 |
+
AI assisted resume screening happens only through the workflow IT and HR have
|
| 50 |
+
approved, not through a chat window. If you are not sure whether that workflow
|
| 51 |
+
exists yet, ask BOS IT before you paste a single resume.</p>
|
| 52 |
+
""",
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "BOS Security",
|
| 56 |
+
"html": """
|
| 57 |
+
<h3>What you already use</h3>
|
| 58 |
+
<p><b>Zoho.</b> Client accounts and the helpdesk queue.
|
| 59 |
+
<b>Microsoft 365.</b> Outlook for client and officer communication, Teams for shift
|
| 60 |
+
coordination, SharePoint for post orders, schedules, and quality check records.</p>
|
| 61 |
+
|
| 62 |
+
<h3>Where Claude fits</h3>
|
| 63 |
+
<p><b>Post orders and procedures.</b> Paste an existing post order and ask for a
|
| 64 |
+
version a brand new officer can follow on night one. Keep the site referred to as
|
| 65 |
+
the site, and keep gate codes, alarm codes, and contact numbers out of the chat.</p>
|
| 66 |
+
<p><b>Client updates.</b> Turn a week of scheduling notes into a plain language
|
| 67 |
+
summary at the metric level: fills, coverage percentage, open shifts. Not which
|
| 68 |
+
guard was late at which site.</p>
|
| 69 |
+
<p><b>Training material.</b> Explaining a policy, a uniform standard, or a de-escalation
|
| 70 |
+
principle in language that lands with the officers who read it.</p>
|
| 71 |
+
<p><b>Trend analysis.</b> Describe the pattern you are seeing in incident counts by
|
| 72 |
+
type and month, without the narratives, and ask what questions a client would ask
|
| 73 |
+
about it.</p>
|
| 74 |
+
|
| 75 |
+
<h3>What stays out</h3>
|
| 76 |
+
<p>Incident narratives, dispatch records, law enforcement interaction details,
|
| 77 |
+
officer personnel and pay information, client site access details, and anything
|
| 78 |
+
tying a specific incident to a specific site in material that could leave the
|
| 79 |
+
company.</p>
|
| 80 |
+
""",
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"name": "Sales",
|
| 84 |
+
"html": """
|
| 85 |
+
<h3>What you already use</h3>
|
| 86 |
+
<p><b>Zoho CRM.</b> Pipeline, accounts, contacts, the integrator and channel partner
|
| 87 |
+
database, and the new site approval workflow.
|
| 88 |
+
<b>Apollo.</b> Prospect data, contact lists, and outreach sequences. Lead exports from
|
| 89 |
+
internal tools land here in the VirtuGuard Apollo template.
|
| 90 |
+
<b>Microsoft 365.</b> Outlook, Teams, and shared proposal files.
|
| 91 |
+
<b>Internal sales apps.</b> The VirtuGuard pricing calculator and the camera and VMS
|
| 92 |
+
compatibility lookup, both maintained by IT.</p>
|
| 93 |
+
|
| 94 |
+
<h3>Where Claude fits</h3>
|
| 95 |
+
<p><b>Outreach drafts.</b> Pull the prospect's company name and industry from Apollo,
|
| 96 |
+
give Claude the trigger event and the two differentiators that matter for that
|
| 97 |
+
vertical, and ask for a short first touch in your voice. Then edit. Sequences
|
| 98 |
+
still get built and sent from Apollo.</p>
|
| 99 |
+
<p><b>Call prep and recap.</b> Paste your notes after a discovery call and ask for a
|
| 100 |
+
follow up email plus a list of open questions. Log the recap in Zoho yourself.</p>
|
| 101 |
+
<p><b>Explaining the service.</b> Ask for a plain language explanation of remote video
|
| 102 |
+
monitoring for a property manager or general contractor who has never used it.
|
| 103 |
+
Strip the jargon, keep it honest about what a camera can and cannot see.</p>
|
| 104 |
+
<p><b>Proposal sections.</b> Draft the narrative sections. Pricing comes from the
|
| 105 |
+
pricing calculator, never from the AI. Compatibility comes from the lookup tool
|
| 106 |
+
and the Immix integration matrix, never from the AI.</p>
|
| 107 |
+
|
| 108 |
+
<h3>What stays out</h3>
|
| 109 |
+
<p>Contract values and client names in anything that could be forwarded, camera
|
| 110 |
+
locations and site addresses in external material, and any claim about
|
| 111 |
+
certifications, insurance, licensing, guaranteed response times, or regulatory
|
| 112 |
+
compliance that you do not have a source document for. Never let Claude fill in
|
| 113 |
+
a price or a compatibility answer.</p>
|
| 114 |
+
""",
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"name": "VirtuGuard",
|
| 118 |
+
"html": """
|
| 119 |
+
<h3>What you already use</h3>
|
| 120 |
+
<p><b>Immix.</b> The monitoring platform where alarms, patrols, and site details live.
|
| 121 |
+
<b>Camect and camera analytics.</b> Edge detection and filtering at the site.
|
| 122 |
+
<b>Zoho Desk.</b> Knowledge base for VMS sheets and SOPs, plus the helpdesk queue.
|
| 123 |
+
<b>Microsoft 365.</b> Outlook for end of shift reports and outage notices, Teams for
|
| 124 |
+
real time escalation, SharePoint for KPI workbooks and incident documents.
|
| 125 |
+
<b>TalentLMS.</b> Monthly assigned agent training.</p>
|
| 126 |
+
|
| 127 |
+
<h3>Where Claude fits</h3>
|
| 128 |
+
<p><b>Shift reports and notices.</b> Standardize the format of an end of shift report or
|
| 129 |
+
an outage notification without changing the facts. Refer to the site generically in
|
| 130 |
+
the chat, then fill it in when you paste back to Outlook.</p>
|
| 131 |
+
<p><b>SOPs and VMS sheets.</b> Paste a rough procedure and ask for a step by step version
|
| 132 |
+
a new agent can follow. Publish it in Zoho Desk. Credentials, IPs, and port details
|
| 133 |
+
never go into the chat.</p>
|
| 134 |
+
<p><b>Understanding the technology.</b> Explaining what a detection module does, why a
|
| 135 |
+
camera over alarms in wind or rain, what a voice down is meant to accomplish, or how
|
| 136 |
+
edge analytics reduce noise. Good for onboarding and for the weekly tech briefing.</p>
|
| 137 |
+
<p><b>KPI narrative.</b> Give it the numbers from the KPI workbook, response times,
|
| 138 |
+
alarm counts, patrol compliance, and ask for the two paragraph narrative that goes
|
| 139 |
+
with the charts.</p>
|
| 140 |
+
|
| 141 |
+
<h3>What stays out</h3>
|
| 142 |
+
<p>Video descriptions, incident narratives, dispatch and law enforcement details,
|
| 143 |
+
site network configurations, NVR and VMS logins, camera locations, and the
|
| 144 |
+
staffing ratio figures leadership has asked to keep internal.</p>
|
| 145 |
+
""",
|
| 146 |
+
},
|
| 147 |
+
]
|