IT-BOS commited on
Commit
8cea6d4
·
verified ·
1 Parent(s): 75ab8df

Upload 7 files

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. README.md +2 -1
  3. app.py +254 -72
  4. logo.png +3 -0
  5. quizzes.py +300 -0
  6. 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. Content is in `modules.py`; UI and styling are 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.
 
 
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. Content lives in modules.py.
 
 
 
5
  """
6
 
 
7
  import html
 
 
8
  import gradio as gr
9
  from modules import MODULES, IT_HELP
 
 
10
 
11
- NAVY = "#0B2545"
 
 
12
  TEAL = "#13A89E"
13
- TEAL_DARK = "#0E7F78"
14
- INK = "#1F2933"
15
- MUTED = "#5B6B7A"
16
  PAPER = "#FFFFFF"
17
- WASH = "#F3F6F8"
18
- RULE = "#D9E1E7"
 
 
 
 
 
 
 
 
 
 
19
 
20
  CSS = f"""
21
- :root {{ --navy: {NAVY}; --teal: {TEAL}; --teal-dark: {TEAL_DARK}; --ink: {INK};
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: 1240px !important; margin: 0 auto !important; padding: 0 20px 48px !important; }}
26
  footer {{ display: none !important; }}
27
 
28
  /* Masthead */
29
- .masthead {{ background: var(--navy); color: #fff; padding: 26px 32px 24px; border-radius: 6px 6px 0 0;
30
- display: flex; align-items: baseline; justify-content: space-between; gap: 24px; flex-wrap: wrap; }}
31
- .masthead h1 {{ margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -0.01em; }}
32
- .masthead .tag {{ margin: 6px 0 0; font-size: 14px; color: #C7D4E0; }}
33
- .masthead .org {{ font-size: 14px; color: #C7D4E0; }}
34
- .masthead .org b {{ color: #fff; }}
 
 
 
 
 
 
 
 
35
 
36
  /* Rail */
37
- .rail {{ background: var(--paper); border: 1px solid var(--rule); border-top: 4px solid var(--teal);
38
- padding: 8px 0 12px; border-radius: 0 0 6px 6px; }}
39
- .rail .rail-head {{ padding: 14px 20px 8px; font-size: 13px; color: var(--muted); border-bottom: 1px solid var(--rule); }}
40
- .rail .wrap {{ border: none !important; background: transparent !important; padding: 0 !important; }}
41
- .rail label.svelte-1gfkn6j, .rail .wrap > label {{ display: none !important; }}
42
- .rail fieldset {{ border: none !important; }}
43
- .rail .wrap label {{
44
- display: flex !important; align-items: flex-start; gap: 12px; padding: 12px 20px !important; margin: 0 !important;
45
- border: none !important; border-left: 4px solid transparent !important; border-radius: 0 !important;
46
- background: transparent !important; cursor: pointer; font-size: 15px; line-height: 1.35; color: var(--ink);
47
- box-shadow: none !important; }}
48
- .rail .wrap label:hover {{ background: var(--wash) !important; }}
49
- .rail .wrap label.selected {{ border-left-color: var(--teal) !important; background: #EAF7F6 !important; font-weight: 700; color: var(--navy); }}
50
- .rail .wrap label input {{ display: none !important; }}
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: 0 0 6px 6px; padding: 40px 56px 32px; }}
55
- .reader .depth {{ font-size: 13px; color: var(--teal-dark); font-weight: 700; margin: 0 0 6px; }}
56
- .reader h2 {{ font-size: 32px; line-height: 1.15; color: var(--navy); margin: 0 0 10px; letter-spacing: -0.015em; }}
57
- .reader .summary {{ font-size: 17px; color: var(--muted); margin: 0 0 30px; padding-bottom: 22px; border-bottom: 1px solid var(--rule); max-width: 66ch; }}
58
- .reader h3 {{ font-size: 19px; color: var(--navy); margin: 34px 0 10px; }}
 
 
 
 
 
 
 
59
  .reader h3:first-of-type {{ margin-top: 0; }}
60
- .reader p {{ font-size: 16px; line-height: 1.6; margin: 0 0 14px; max-width: 70ch; color: var(--ink); }}
61
- .reader p b {{ color: var(--navy); }}
62
- .reader .help {{ margin-top: 40px; padding: 18px 22px; background: #EAF7F6; border-left: 4px solid var(--teal); }}
63
- .reader .help p {{ margin: 0; max-width: none; }}
64
- .reader .help p b {{ color: var(--teal-dark); }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  /* Nav */
67
- .navrow {{ margin-top: 14px; }}
68
- .navrow button {{ border-radius: 4px !important; font-weight: 700 !important; font-size: 14px !important; }}
69
- .navrow .primary {{ background: var(--teal) !important; border: 1px solid var(--teal) !important; color: #fff !important; }}
70
- .navrow .primary:hover {{ background: var(--teal-dark) !important; }}
71
- .navrow .secondary {{ background: var(--paper) !important; border: 1px solid var(--rule) !important; color: var(--navy) !important; }}
72
- .progress {{ text-align: center; color: var(--muted); font-size: 14px; padding-top: 10px; }}
 
 
 
73
 
74
  @media (max-width: 900px) {{
 
75
  .reader {{ padding: 28px 22px; }}
76
- .reader h2 {{ font-size: 26px; }}
 
 
77
  }}
78
  """
79
 
80
- LABELS = [f"{i + 1}. {m['title']}" for i, m in enumerate(MODULES)]
81
 
 
82
 
83
- def render(idx: int) -> str:
84
  m = MODULES[idx]
 
85
  parts = [
86
- f'<div class="reader">',
87
- f'<p class="depth">Module {idx + 1} of {len(MODULES)}. {html.escape(m["depth"])}</p>',
 
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 f'<div class="progress">Module {idx + 1} of {len(MODULES)}</div>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
 
103
- def go_to(label: str):
104
- idx = LABELS.index(label)
105
- return idx, render(idx), progress_text(idx)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
 
108
- def step(idx: int, delta: int):
109
- idx = max(0, min(len(MODULES) - 1, idx + delta))
110
- return idx, LABELS[idx], render(idx), progress_text(idx)
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- <h1>AI Training Platform</h1>
119
- <p class="tag">Six modules, in order, from what AI is to how we use it safely at BOS.</p>
 
 
 
 
120
  </div>
121
- <div class="org"><b>BOS Security</b> including BOS Staffing, BOS Medical Staffing, and VirtuGuard</div>
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=260, elem_classes=["rail"]):
130
  gr.HTML('<div class="rail-head">Learning path</div>')
131
- picker = gr.Radio(choices=LABELS, value=LABELS[0], show_label=False, container=False)
 
 
 
 
 
 
132
  with gr.Column(scale=8):
133
- content = gr.HTML(render(0))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  with gr.Row(elem_classes=["navrow"]):
135
- prev_btn = gr.Button("Previous module", elem_classes=["secondary"])
136
  progress = gr.HTML(progress_text(0))
137
- next_btn = gr.Button("Next module", elem_classes=["primary"])
138
 
139
- picker.change(go_to, inputs=picker, outputs=[state, content, progress])
140
- prev_btn.click(lambda i: step(i, -1), inputs=state, outputs=[state, picker, content, progress])
141
- next_btn.click(lambda i: step(i, 1), inputs=state, outputs=[state, picker, content, progress])
 
 
 
 
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

  • SHA256: af354a63102090c5af39d0d25d532df6366481af8b2608180c1b1175f728d390
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB
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
+ ]