11"""
2- Teams presence and telephony status monitor — practical real-world example .
2+ Teams presence and telephony status monitor.
33
4- A call center / helpdesk scenario: monitor agent availability across a team ,
5- detect state changes, bulk-query presence, and respond programmatically .
4+ Covers: single/bulk presence queries, preferred presence, status messages ,
5+ polling for state changes, and business-rules routing logic .
66
7- Covers:
8- - Getting a single user's presence
9- - Getting presence for multiple users at once (bulk)
10- - Setting and clearing preferred presence
11- - Setting a status message with expiry
12- - Polling for state changes
13- - Mapping presence states to business rules
14-
15- Required permissions:
16- - Presence.Read (delegated) — read own presence
17- - Presence.Read.All (application) — read any user's presence
18- - Presence.ReadWrite (delegated) — set own presence + status message
19-
20- See https://learn.microsoft.com/en-us/graph/api/resources/presence
7+ Required permissions: Presence.Read, Presence.Read.All, Presence.ReadWrite.
218"""
229
23- from __future__ import annotations
24-
2510from datetime import datetime , timedelta , timezone
2611from time import sleep
27- from typing import Dict , List , Optional
12+ from typing import Optional
2813
2914from office365 .graph_client import GraphClient
3015
31- # ---------------------------------------------------------------------------
32- # Configuration
33- # ---------------------------------------------------------------------------
34-
35- SUPPORTED_AGENTS = [
36- "alice@company.com" ,
37- "bob@company.com" ,
38- "carol@company.com" ,
39- ]
40-
41- POLL_INTERVAL_SECONDS = 30
16+ POLL_INTERVAL = 30
4217BUSY_ACTIVITIES = {"InACall" , "InAConferenceCall" , "InAMeeting" , "Presenting" }
4318
4419
45- # ---------------------------------------------------------------------------
46- # Business logic helpers
47- # ---------------------------------------------------------------------------
48-
49-
5020def is_available (presence ) -> bool :
51- """Determine if a user is considered 'available' for routing."""
5221 availability = presence .get_property ("availability" )
5322 activity = presence .get_property ("activity" )
5423 if not availability or not activity :
@@ -61,175 +30,73 @@ def is_available(presence) -> bool:
6130
6231
6332def agent_status_summary (presence ) -> str :
64- """Human-readable summary of an agent's current state."""
65- availability = presence .get_property ("availability" ) or "Unknown"
66- activity = presence .get_property ("activity" ) or "Unknown"
67- return f"availability={ availability } , activity={ activity } "
68-
69-
70- # ---------------------------------------------------------------------------
71- # Core operations
72- # ---------------------------------------------------------------------------
73-
74-
75- def get_presence_for_user (client : GraphClient , user_id : str ):
76- """Get presence for a single user by ID or UPN."""
77- return client .communications .presences [user_id ].get ().execute_query ()
78-
79-
80- def get_presences_for_team (client : GraphClient , user_ids : List [str ]):
81- """Get presence for multiple users in a single API call (bulk)."""
82- return client .communications .get_presences_by_user_id (user_ids ).execute_query ()
33+ a = presence .get_property ("availability" ) or "Unknown"
34+ b = presence .get_property ("activity" ) or "Unknown"
35+ return f"availability={ a } , activity={ b } "
8336
8437
8538def take_agent_offline (client : GraphClient , user_id : str ):
86- """Mark an agent as DoNotDisturb + set an end-of-day status message."""
8739 expires = datetime .now (timezone .utc ) + timedelta (hours = 9 )
88-
8940 client .communications .presences [user_id ].set_user_preferred_presence (
90- availability = "DoNotDisturb" ,
91- activity = "OffWork" ,
92- expiration_duration = "PT9H" ,
41+ availability = "DoNotDisturb" , activity = "OffWork" , expiration_duration = "PT9H" ,
9342 ).execute_query ()
94-
9543 client .communications .presences [user_id ].set_status_message (
96- message = "Shift ended — back tomorrow" ,
97- expiry = expires ,
44+ message = "Shift ended" , expiry = expires ,
9845 ).execute_query ()
9946
10047
101- def set_agent_available (client : GraphClient , user_id : str ):
102- """Return an agent to available state after break."""
48+ def start_shift (client : GraphClient , user_id : str ):
49+ expires = datetime . now ( timezone . utc ) + timedelta ( hours = 9 )
10350 client .communications .presences [user_id ].set_user_preferred_presence (
104- availability = "Available" ,
105- activity = "Available" ,
106- expiration_duration = "PT8H" ,
51+ availability = "Available" , activity = "Available" , expiration_duration = "PT9H" ,
52+ ).execute_query ()
53+ client .communications .presences [user_id ].set_status_message (
54+ message = "Online and ready" , expiry = expires ,
10755 ).execute_query ()
10856
10957
110- # ---------------------------------------------------------------------------
111- # Scenario 1: snapshot — check current team status
112- # ---------------------------------------------------------------------------
113-
114-
115- def snapshot_team_status (client : GraphClient , agents : List [str ]):
116- """One-shot display of every agent's current presence."""
117- print ("=== Team presence snapshot ===" )
118- presences = get_presences_for_team (client , agents )
119- for entry in presences :
120- print (f" { entry .id :40s} { agent_status_summary (entry )} " )
121- print ()
122-
123-
124- # ---------------------------------------------------------------------------
125- # Scenario 2: monitor — poll for state changes
126- # ---------------------------------------------------------------------------
127-
128-
129- def monitor_team_status (client : GraphClient , agents : List [str ], cycles : int = 5 ):
130- """Poll presence for a team and report state changes."""
131- print (f"=== Monitoring { len (agents )} agents (poll every { POLL_INTERVAL_SECONDS } s) ===" )
58+ def find_available_agent (client : GraphClient , agents : list [str ]) -> Optional [str ]:
59+ presences = client .communications .get_presences_by_user_id (agents ).execute_query ()
60+ for e in presences :
61+ if is_available (e ):
62+ return e .id
63+ return None
13264
133- previous : Dict [str , str ] = {}
13465
66+ def monitor_presence (client : GraphClient , agents : list [str ], cycles : int = 3 ):
67+ print (f"Monitoring { len (agents )} agents (poll every { POLL_INTERVAL } s)" )
68+ previous = {}
13569 for cycle in range (1 , cycles + 1 ):
136- print (f"\n --- Cycle { cycle } /{ cycles } [{ datetime .now (timezone .utc ).isoformat ()} ] ---" )
137- presences = get_presences_for_team (client , agents )
138-
139- for entry in presences :
140- user_id = entry .id
141- state = agent_status_summary (entry )
142- old_state = previous .get (user_id )
143-
144- if old_state is None :
145- print (f" INIT { user_id :35s} { state } " )
146- elif state != old_state :
147- print (f" >>> { user_id :35s} { old_state } -> { state } " )
148- if is_available (entry ) and not is_available .__doc__ :
149- print (f" Agent { user_id } is now available for routing" )
150- else :
151- print (f" . { user_id :35s} { state } " )
152-
153- previous [user_id ] = state
154-
70+ presences = client .communications .get_presences_by_user_id (agents ).execute_query ()
71+ for e in presences :
72+ state = agent_status_summary (e )
73+ old = previous .get (e .id )
74+ if old is None :
75+ print (f" INIT { e .id } : { state } " )
76+ elif state != old :
77+ print (f" CHG { e .id } : { old } -> { state } " )
78+ previous [e .id ] = state
15579 if cycle < cycles :
156- sleep (POLL_INTERVAL_SECONDS )
157-
158- print ()
159-
160-
161- # ---------------------------------------------------------------------------
162- # Scenario 3: round-robin availability check for routing
163- # ---------------------------------------------------------------------------
80+ sleep (POLL_INTERVAL )
16481
16582
166- def find_available_agent (client : GraphClient , agents : List [str ]) -> Optional [str ]:
167- """Return the first available agent (useful for automatic call routing)."""
168- presences = get_presences_for_team (client , agents )
169- for entry in presences :
170- if is_available (entry ):
171- return entry .id
172- return None
173-
174-
175- # ---------------------------------------------------------------------------
176- # Scenario 4: set status message on shift start
177- # ---------------------------------------------------------------------------
83+ def main ():
84+ client = GraphClient ().with_device_flow (client_id = "your_client_id" )
85+ agents = ["alice@company.com" , "bob@company.com" , "carol@company.com" ]
17886
87+ presences = client .communications .get_presences_by_user_id (agents ).execute_query ()
88+ print ("Team presence:" )
89+ for e in presences :
90+ print (f" { e .id } : { agent_status_summary (e )} " )
17991
180- def start_shift (client : GraphClient , user_id : str , agent_name : str ):
181- """Begin shift: set available presence with a status message."""
182- expires = datetime .now (timezone .utc ) + timedelta (hours = 9 )
183- client .communications .presences [user_id ].set_user_preferred_presence (
184- availability = "Available" ,
185- activity = "Available" ,
186- expiration_duration = "PT9H" ,
187- ).execute_query ()
92+ available = find_available_agent (client , agents )
93+ print (f"\n Routing: { 'call to ' + available if available else 'no agents — queue' } " )
18894
189- client .communications .presences [user_id ].set_status_message (
190- message = f"{ agent_name } is online — ready to take calls" ,
191- expiry = expires ,
192- ).execute_query ()
95+ monitor_presence (client , agents , cycles = 3 )
19396
97+ take_agent_offline (client , agents [0 ])
98+ print (f"\n { agents [0 ]} marked offline" )
19499
195- # ---------------------------------------------------------------------------
196- # Main — run all scenarios
197- # ---------------------------------------------------------------------------
198100
199101if __name__ == "__main__" :
200- # Connect (choose one auth method)
201- #
202- # Option A: certificate
203- # client = GraphClient(tenant).with_client_certificate(client_id, thumbprint, key)
204- #
205- # Option B: client secret
206- # client = GraphClient(tenant).with_client_secret(client_id, client_secret)
207- #
208- # Option C: device code (interactive, works with MFA)
209- client = GraphClient ().with_device_flow (client_id = "your_client_id" )
210- # (For brevity replace with real credentials above)
211-
212- # ------------------------------------------------------------------
213- # Scenario 1 — read-only snapshot
214- # ------------------------------------------------------------------
215- snapshot_team_status (client , SUPPORTED_AGENTS )
216-
217- # ------------------------------------------------------------------
218- # Scenario 2 — find someone to route a call to
219- # ------------------------------------------------------------------
220- available = find_available_agent (client , SUPPORTED_AGENTS )
221- if available :
222- print (f"Routing call to: { available } " )
223- else :
224- print ("No agents available — queuing call" )
225-
226- # ------------------------------------------------------------------
227- # Scenario 3 — monitor for state changes (run 3 cycles)
228- # ------------------------------------------------------------------
229- monitor_team_status (client , SUPPORTED_AGENTS , cycles = 3 )
230-
231- # ------------------------------------------------------------------
232- # Scenario 4 — mark agent as off (end of shift)
233- # ------------------------------------------------------------------
234- take_agent_offline (client , SUPPORTED_AGENTS [0 ])
235- print (f"Agent { SUPPORTED_AGENTS [0 ]} marked as offline" )
102+ main ()
0 commit comments