wxcli 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. wxcli/__init__.py +4 -0
  2. wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
  3. wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
  4. wxcli/_playbook/.claude/rules/cleanup.md +29 -0
  5. wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
  6. wxcli/_playbook/.claude/rules/org-health.md +14 -0
  7. wxcli/_playbook/.claude/settings.json +8 -0
  8. wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
  9. wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
  10. wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
  11. wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
  12. wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
  13. wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
  14. wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
  15. wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
  16. wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
  17. wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
  18. wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
  19. wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
  20. wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
  21. wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
  22. wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
  23. wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
  24. wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
  25. wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
  26. wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
  27. wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
  28. wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
  29. wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
  30. wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
  31. wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
  32. wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
  33. wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
  34. wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
  35. wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
  36. wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
  37. wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
  38. wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
  39. wxcli/_playbook/CLAUDE.md +373 -0
  40. wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
  41. wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
  42. wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
  43. wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
  44. wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
  45. wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
  46. wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
  47. wxcli/_playbook/docs/reference/admin-partner.md +357 -0
  48. wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
  49. wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
  50. wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
  51. wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
  52. wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
  53. wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
  54. wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
  55. wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
  56. wxcli/_playbook/docs/reference/authentication.md +965 -0
  57. wxcli/_playbook/docs/reference/call-control.md +1205 -0
  58. wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
  59. wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
  60. wxcli/_playbook/docs/reference/call-routing.md +2304 -0
  61. wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
  62. wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
  63. wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
  64. wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
  65. wxcli/_playbook/docs/reference/devices-core.md +1869 -0
  66. wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
  67. wxcli/_playbook/docs/reference/devices-platform.md +467 -0
  68. wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
  69. wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
  70. wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
  71. wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
  72. wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
  73. wxcli/_playbook/docs/reference/meetings-content.md +473 -0
  74. wxcli/_playbook/docs/reference/meetings-core.md +903 -0
  75. wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
  76. wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
  77. wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
  78. wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
  79. wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
  80. wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
  81. wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
  82. wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
  83. wxcli/_playbook/docs/reference/provisioning.md +1366 -0
  84. wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
  85. wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
  86. wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
  87. wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
  88. wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
  89. wxcli/_version.py +24 -0
  90. wxcli/auth.py +137 -0
  91. wxcli/commands/__init__.py +0 -0
  92. wxcli/commands/_registry.py +181 -0
  93. wxcli/commands/activation_email.py +103 -0
  94. wxcli/commands/admin_recordings.py +585 -0
  95. wxcli/commands/analytics.py +105 -0
  96. wxcli/commands/announcement_playlists.py +219 -0
  97. wxcli/commands/announcements.py +519 -0
  98. wxcli/commands/archive_users.py +36 -0
  99. wxcli/commands/attachment_actions.py +60 -0
  100. wxcli/commands/audit_events.py +83 -0
  101. wxcli/commands/authorizations.py +113 -0
  102. wxcli/commands/auto_attendant.py +642 -0
  103. wxcli/commands/broadworks_billing_reports.py +126 -0
  104. wxcli/commands/broadworks_enterprises.py +160 -0
  105. wxcli/commands/broadworks_subscribers.py +319 -0
  106. wxcli/commands/broadworks_workspaces.py +108 -0
  107. wxcli/commands/call_controls.py +1304 -0
  108. wxcli/commands/call_park.py +494 -0
  109. wxcli/commands/call_pickup.py +229 -0
  110. wxcli/commands/call_queue.py +1878 -0
  111. wxcli/commands/call_recording.py +707 -0
  112. wxcli/commands/call_routing.py +1610 -0
  113. wxcli/commands/call_settings_for_me_phase_5.py +212 -0
  114. wxcli/commands/caller_reputation.py +169 -0
  115. wxcli/commands/calling_service.py +305 -0
  116. wxcli/commands/cc_address_book.py +770 -0
  117. wxcli/commands/cc_agent_greetings.py +242 -0
  118. wxcli/commands/cc_agent_summaries.py +39 -0
  119. wxcli/commands/cc_agent_wellbeing.py +216 -0
  120. wxcli/commands/cc_agents.py +425 -0
  121. wxcli/commands/cc_ai_assistant.py +55 -0
  122. wxcli/commands/cc_ai_feature.py +294 -0
  123. wxcli/commands/cc_audio_files.py +149 -0
  124. wxcli/commands/cc_auto_csat.py +311 -0
  125. wxcli/commands/cc_aux_code.py +458 -0
  126. wxcli/commands/cc_business_hour.py +353 -0
  127. wxcli/commands/cc_call_monitoring.py +228 -0
  128. wxcli/commands/cc_callbacks.py +194 -0
  129. wxcli/commands/cc_campaign.py +212 -0
  130. wxcli/commands/cc_captures.py +39 -0
  131. wxcli/commands/cc_contact_list.py +169 -0
  132. wxcli/commands/cc_contact_number.py +295 -0
  133. wxcli/commands/cc_data_sources.py +235 -0
  134. wxcli/commands/cc_desktop_layout.py +380 -0
  135. wxcli/commands/cc_desktop_profile.py +322 -0
  136. wxcli/commands/cc_dial_number.py +511 -0
  137. wxcli/commands/cc_dial_plan.py +347 -0
  138. wxcli/commands/cc_dnc.py +101 -0
  139. wxcli/commands/cc_entry_point.py +545 -0
  140. wxcli/commands/cc_ewt.py +51 -0
  141. wxcli/commands/cc_flow.py +477 -0
  142. wxcli/commands/cc_global_vars.py +430 -0
  143. wxcli/commands/cc_holiday_list.py +335 -0
  144. wxcli/commands/cc_journey.py +1261 -0
  145. wxcli/commands/cc_legacy_flows.py +49 -0
  146. wxcli/commands/cc_multimedia_profile.py +436 -0
  147. wxcli/commands/cc_notification.py +51 -0
  148. wxcli/commands/cc_outdial_ani.py +604 -0
  149. wxcli/commands/cc_overrides.py +341 -0
  150. wxcli/commands/cc_queue.py +1083 -0
  151. wxcli/commands/cc_queue_stats.py +55 -0
  152. wxcli/commands/cc_realtime.py +51 -0
  153. wxcli/commands/cc_resource_collection.py +312 -0
  154. wxcli/commands/cc_search.py +46 -0
  155. wxcli/commands/cc_site.py +400 -0
  156. wxcli/commands/cc_skill.py +350 -0
  157. wxcli/commands/cc_skill_profile.py +289 -0
  158. wxcli/commands/cc_subscriptions.py +426 -0
  159. wxcli/commands/cc_summaries.py +130 -0
  160. wxcli/commands/cc_tasks.py +844 -0
  161. wxcli/commands/cc_team.py +325 -0
  162. wxcli/commands/cc_user_profiles.py +349 -0
  163. wxcli/commands/cc_users.py +701 -0
  164. wxcli/commands/cc_work_types.py +363 -0
  165. wxcli/commands/cdr.py +89 -0
  166. wxcli/commands/classifications.py +38 -0
  167. wxcli/commands/cleanup.py +1427 -0
  168. wxcli/commands/client_settings.py +72 -0
  169. wxcli/commands/conference.py +328 -0
  170. wxcli/commands/configure.py +72 -0
  171. wxcli/commands/converged_recordings.py +347 -0
  172. wxcli/commands/converged_recordings_export.py +310 -0
  173. wxcli/commands/cq_playlists.py +42 -0
  174. wxcli/commands/cucm.py +2794 -0
  175. wxcli/commands/cucm_config.py +129 -0
  176. wxcli/commands/customer_assist.py +373 -0
  177. wxcli/commands/data_sources.py +227 -0
  178. wxcli/commands/dect_devices.py +787 -0
  179. wxcli/commands/device_configurations.py +77 -0
  180. wxcli/commands/device_dynamic_settings.py +320 -0
  181. wxcli/commands/device_settings.py +1489 -0
  182. wxcli/commands/devices.py +280 -0
  183. wxcli/commands/domains.py +142 -0
  184. wxcli/commands/ecm.py +178 -0
  185. wxcli/commands/emergency_services.py +827 -0
  186. wxcli/commands/events.py +83 -0
  187. wxcli/commands/external_voicemail.py +52 -0
  188. wxcli/commands/groups.py +206 -0
  189. wxcli/commands/guest_management.py +76 -0
  190. wxcli/commands/hds.py +340 -0
  191. wxcli/commands/hot_desk.py +67 -0
  192. wxcli/commands/hot_desking_members.py +118 -0
  193. wxcli/commands/hot_desking_portal.py +124 -0
  194. wxcli/commands/hunt_group.py +577 -0
  195. wxcli/commands/hybrid_clusters.py +71 -0
  196. wxcli/commands/hybrid_connectors.py +67 -0
  197. wxcli/commands/identity_org.py +159 -0
  198. wxcli/commands/init_playbook.py +155 -0
  199. wxcli/commands/licenses.py +106 -0
  200. wxcli/commands/live_monitoring.py +40 -0
  201. wxcli/commands/location_call_handling.py +577 -0
  202. wxcli/commands/location_schedules.py +341 -0
  203. wxcli/commands/location_settings.py +1297 -0
  204. wxcli/commands/location_voicemail.py +494 -0
  205. wxcli/commands/locations.py +336 -0
  206. wxcli/commands/meeting_captions.py +110 -0
  207. wxcli/commands/meeting_chats.py +63 -0
  208. wxcli/commands/meeting_invitees.py +234 -0
  209. wxcli/commands/meeting_messages.py +28 -0
  210. wxcli/commands/meeting_participants.py +278 -0
  211. wxcli/commands/meeting_polls.py +111 -0
  212. wxcli/commands/meeting_preferences.py +515 -0
  213. wxcli/commands/meeting_qa.py +78 -0
  214. wxcli/commands/meeting_qualities.py +43 -0
  215. wxcli/commands/meeting_reports.py +101 -0
  216. wxcli/commands/meeting_session_types.py +105 -0
  217. wxcli/commands/meeting_site.py +57 -0
  218. wxcli/commands/meeting_slido.py +44 -0
  219. wxcli/commands/meeting_summaries.py +92 -0
  220. wxcli/commands/meeting_tracking_codes.py +233 -0
  221. wxcli/commands/meeting_transcripts.py +230 -0
  222. wxcli/commands/meetings.py +1997 -0
  223. wxcli/commands/memberships.py +164 -0
  224. wxcli/commands/messages.py +213 -0
  225. wxcli/commands/mode_management.py +244 -0
  226. wxcli/commands/my_call_settings.py +3577 -0
  227. wxcli/commands/numbers.py +405 -0
  228. wxcli/commands/operating_modes.py +410 -0
  229. wxcli/commands/org_contacts.py +284 -0
  230. wxcli/commands/org_health_cli.py +55 -0
  231. wxcli/commands/org_settings.py +71 -0
  232. wxcli/commands/organizations.py +83 -0
  233. wxcli/commands/paging_group.py +257 -0
  234. wxcli/commands/partner_admins.py +141 -0
  235. wxcli/commands/partner_reports.py +188 -0
  236. wxcli/commands/partner_tags.py +216 -0
  237. wxcli/commands/people.py +279 -0
  238. wxcli/commands/person_call_settings.py +71 -0
  239. wxcli/commands/pstn.py +280 -0
  240. wxcli/commands/recording_report.py +154 -0
  241. wxcli/commands/report_templates.py +38 -0
  242. wxcli/commands/reports.py +144 -0
  243. wxcli/commands/resource_group_memberships.py +160 -0
  244. wxcli/commands/resource_groups.py +67 -0
  245. wxcli/commands/roles.py +63 -0
  246. wxcli/commands/room_tabs.py +160 -0
  247. wxcli/commands/rooms.py +225 -0
  248. wxcli/commands/scim_bulk.py +46 -0
  249. wxcli/commands/scim_groups.py +246 -0
  250. wxcli/commands/scim_schemas.py +82 -0
  251. wxcli/commands/scim_users.py +283 -0
  252. wxcli/commands/security_audit.py +54 -0
  253. wxcli/commands/service_apps.py +51 -0
  254. wxcli/commands/single_number_reach.py +209 -0
  255. wxcli/commands/team_memberships.py +155 -0
  256. wxcli/commands/teams.py +153 -0
  257. wxcli/commands/ucm_profile.py +42 -0
  258. wxcli/commands/update.py +219 -0
  259. wxcli/commands/user_settings.py +5116 -0
  260. wxcli/commands/video_mesh.py +1090 -0
  261. wxcli/commands/virtual_extensions.py +502 -0
  262. wxcli/commands/virtual_line_settings.py +2072 -0
  263. wxcli/commands/webhooks.py +176 -0
  264. wxcli/commands/wholesale_billing_reports.py +147 -0
  265. wxcli/commands/wholesale_provisioning.py +523 -0
  266. wxcli/commands/workspace_locations.py +339 -0
  267. wxcli/commands/workspace_metrics.py +103 -0
  268. wxcli/commands/workspace_personalization.py +70 -0
  269. wxcli/commands/workspace_settings.py +3347 -0
  270. wxcli/commands/workspaces.py +279 -0
  271. wxcli/commands/xapi.py +114 -0
  272. wxcli/config.py +139 -0
  273. wxcli/errors.py +77 -0
  274. wxcli/main.py +210 -0
  275. wxcli/migration/CLAUDE.md +106 -0
  276. wxcli/migration/__init__.py +1 -0
  277. wxcli/migration/advisory/CLAUDE.md +196 -0
  278. wxcli/migration/advisory/__init__.py +46 -0
  279. wxcli/migration/advisory/advisor.py +107 -0
  280. wxcli/migration/advisory/advisory_patterns.py +2552 -0
  281. wxcli/migration/advisory/recommendation_rules.py +835 -0
  282. wxcli/migration/cucm/CLAUDE.md +74 -0
  283. wxcli/migration/cucm/__init__.py +5 -0
  284. wxcli/migration/cucm/connection.py +265 -0
  285. wxcli/migration/cucm/discovery.py +225 -0
  286. wxcli/migration/cucm/extractors/__init__.py +1 -0
  287. wxcli/migration/cucm/extractors/announcements.py +92 -0
  288. wxcli/migration/cucm/extractors/base.py +90 -0
  289. wxcli/migration/cucm/extractors/device_profiles.py +98 -0
  290. wxcli/migration/cucm/extractors/devices.py +193 -0
  291. wxcli/migration/cucm/extractors/e911.py +103 -0
  292. wxcli/migration/cucm/extractors/features.py +309 -0
  293. wxcli/migration/cucm/extractors/helpers.py +73 -0
  294. wxcli/migration/cucm/extractors/informational.py +295 -0
  295. wxcli/migration/cucm/extractors/locations.py +221 -0
  296. wxcli/migration/cucm/extractors/moh.py +100 -0
  297. wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
  298. wxcli/migration/cucm/extractors/routing.py +493 -0
  299. wxcli/migration/cucm/extractors/shared_lines.py +146 -0
  300. wxcli/migration/cucm/extractors/templates.py +229 -0
  301. wxcli/migration/cucm/extractors/tier4.py +173 -0
  302. wxcli/migration/cucm/extractors/users.py +240 -0
  303. wxcli/migration/cucm/extractors/voicemail.py +243 -0
  304. wxcli/migration/cucm/extractors/workspaces.py +88 -0
  305. wxcli/migration/cucm/unity_connection.py +304 -0
  306. wxcli/migration/execute/CLAUDE.md +372 -0
  307. wxcli/migration/execute/__init__.py +303 -0
  308. wxcli/migration/execute/batch.py +277 -0
  309. wxcli/migration/execute/dependency.py +483 -0
  310. wxcli/migration/execute/engine.py +964 -0
  311. wxcli/migration/execute/handlers.py +2232 -0
  312. wxcli/migration/execute/planner.py +1939 -0
  313. wxcli/migration/execute/runtime.py +571 -0
  314. wxcli/migration/export/CLAUDE.md +49 -0
  315. wxcli/migration/export/__init__.py +8 -0
  316. wxcli/migration/export/csv_export.py +126 -0
  317. wxcli/migration/export/deployment_plan.py +479 -0
  318. wxcli/migration/export/json_export.py +60 -0
  319. wxcli/migration/models.py +941 -0
  320. wxcli/migration/phone_models.py +50 -0
  321. wxcli/migration/preflight/CLAUDE.md +52 -0
  322. wxcli/migration/preflight/__init__.py +89 -0
  323. wxcli/migration/preflight/checks.py +911 -0
  324. wxcli/migration/preflight/runner.py +293 -0
  325. wxcli/migration/rate_limiter.py +144 -0
  326. wxcli/migration/report/CLAUDE.md +245 -0
  327. wxcli/migration/report/__init__.py +7 -0
  328. wxcli/migration/report/appendix.py +2437 -0
  329. wxcli/migration/report/assembler.py +183 -0
  330. wxcli/migration/report/charts.py +263 -0
  331. wxcli/migration/report/executive.py +776 -0
  332. wxcli/migration/report/explainer.py +704 -0
  333. wxcli/migration/report/helpers.py +74 -0
  334. wxcli/migration/report/ingest.py +152 -0
  335. wxcli/migration/report/notice_templates.py +192 -0
  336. wxcli/migration/report/score.py +375 -0
  337. wxcli/migration/report/styles.py +1052 -0
  338. wxcli/migration/report/user_diff.py +1052 -0
  339. wxcli/migration/report/user_notice.py +425 -0
  340. wxcli/migration/state.py +139 -0
  341. wxcli/migration/store.py +860 -0
  342. wxcli/migration/transform/CLAUDE.md +107 -0
  343. wxcli/migration/transform/__init__.py +12 -0
  344. wxcli/migration/transform/analysis_pipeline.py +453 -0
  345. wxcli/migration/transform/analyzers/__init__.py +142 -0
  346. wxcli/migration/transform/analyzers/css_permission.py +189 -0
  347. wxcli/migration/transform/analyzers/css_routing.py +335 -0
  348. wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
  349. wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
  350. wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
  351. wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
  352. wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
  353. wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
  354. wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
  355. wxcli/migration/transform/analyzers/missing_data.py +300 -0
  356. wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
  357. wxcli/migration/transform/analyzers/shared_line.py +170 -0
  358. wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
  359. wxcli/migration/transform/analyzers/workspace_license.py +212 -0
  360. wxcli/migration/transform/cross_reference.py +1136 -0
  361. wxcli/migration/transform/cucm_pattern.py +246 -0
  362. wxcli/migration/transform/decisions.py +301 -0
  363. wxcli/migration/transform/e164.py +145 -0
  364. wxcli/migration/transform/engine.py +256 -0
  365. wxcli/migration/transform/mappers/CLAUDE.md +123 -0
  366. wxcli/migration/transform/mappers/__init__.py +76 -0
  367. wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
  368. wxcli/migration/transform/mappers/base.py +190 -0
  369. wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
  370. wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
  371. wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
  372. wxcli/migration/transform/mappers/css_mapper.py +868 -0
  373. wxcli/migration/transform/mappers/dect_mapper.py +367 -0
  374. wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
  375. wxcli/migration/transform/mappers/device_mapper.py +227 -0
  376. wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
  377. wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
  378. wxcli/migration/transform/mappers/e911_mapper.py +129 -0
  379. wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
  380. wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
  381. wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
  382. wxcli/migration/transform/mappers/line_mapper.py +310 -0
  383. wxcli/migration/transform/mappers/location_mapper.py +235 -0
  384. wxcli/migration/transform/mappers/moh_mapper.py +114 -0
  385. wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
  386. wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
  387. wxcli/migration/transform/mappers/routing_mapper.py +821 -0
  388. wxcli/migration/transform/mappers/snr_mapper.py +201 -0
  389. wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
  390. wxcli/migration/transform/mappers/user_mapper.py +264 -0
  391. wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
  392. wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
  393. wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
  394. wxcli/migration/transform/normalizers.py +2008 -0
  395. wxcli/migration/transform/pattern_converter.py +106 -0
  396. wxcli/migration/transform/pipeline.py +271 -0
  397. wxcli/migration/transform/rules.py +255 -0
  398. wxcli/org_health/CLAUDE.md +63 -0
  399. wxcli/org_health/__init__.py +7 -0
  400. wxcli/org_health/analyze.py +93 -0
  401. wxcli/org_health/checks.py +458 -0
  402. wxcli/org_health/collector.py +62 -0
  403. wxcli/org_health/models.py +78 -0
  404. wxcli/org_health/report.py +300 -0
  405. wxcli/output.py +101 -0
  406. wxcli-1.2.0.dist-info/METADATA +505 -0
  407. wxcli-1.2.0.dist-info/RECORD +413 -0
  408. wxcli-1.2.0.dist-info/WHEEL +5 -0
  409. wxcli-1.2.0.dist-info/entry_points.txt +2 -0
  410. wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
  411. wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
  412. wxcli-1.2.0.dist-info/scm_version.json +8 -0
  413. wxcli-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,256 @@
1
+ """TransformEngine — orchestrates all 21 mappers in dependency order.
2
+
3
+ Runs each mapper sequentially in MAPPER_ORDER (tier-sorted), aggregates
4
+ decisions and errors into a single TransformResult. If a mapper raises
5
+ an exception, the engine logs the error, records a MapperError, and
6
+ continues to the next mapper. Downstream mappers that depend on the
7
+ failed mapper's output will find no objects and produce MISSING_DATA
8
+ decisions naturally.
9
+
10
+ (from 03b-transform-mappers.md section 13, TransformEngine)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import traceback
17
+ from typing import Any
18
+
19
+ from wxcli.migration.models import (
20
+ MapperError,
21
+ TransformResult,
22
+ )
23
+ from wxcli.migration.store import MigrationStore
24
+ from wxcli.migration.transform.mappers.base import (
25
+ Mapper,
26
+ decision_to_store_dict,
27
+ )
28
+ from wxcli.migration.transform.mappers.css_mapper import CSSMapper
29
+ from wxcli.migration.transform.mappers.device_mapper import DeviceMapper
30
+ from wxcli.migration.transform.mappers.feature_mapper import FeatureMapper
31
+ from wxcli.migration.transform.mappers.line_mapper import LineMapper
32
+ from wxcli.migration.transform.mappers.location_mapper import LocationMapper
33
+ from wxcli.migration.transform.mappers.routing_mapper import RoutingMapper
34
+ from wxcli.migration.transform.mappers.user_mapper import UserMapper
35
+ from wxcli.migration.transform.mappers.call_forwarding_mapper import CallForwardingMapper
36
+ from wxcli.migration.transform.mappers.monitoring_mapper import MonitoringMapper
37
+ from wxcli.migration.transform.mappers.voicemail_mapper import VoicemailMapper
38
+ from wxcli.migration.transform.mappers.button_template_mapper import ButtonTemplateMapper
39
+ from wxcli.migration.transform.mappers.device_layout_mapper import DeviceLayoutMapper
40
+ from wxcli.migration.transform.mappers.device_profile_mapper import DeviceProfileMapper
41
+ from wxcli.migration.transform.mappers.e911_mapper import E911Mapper
42
+ from wxcli.migration.transform.mappers.ecbn_mapper import EcbnMapper
43
+ from wxcli.migration.transform.mappers.announcement_mapper import AnnouncementMapper
44
+ from wxcli.migration.transform.mappers.moh_mapper import MOHMapper
45
+ from wxcli.migration.transform.mappers.snr_mapper import SNRMapper
46
+ from wxcli.migration.transform.mappers.softkey_mapper import SoftkeyMapper
47
+ from wxcli.migration.transform.mappers.workspace_mapper import WorkspaceMapper
48
+ from wxcli.migration.transform.mappers.call_settings_mapper import CallSettingsMapper
49
+ from wxcli.migration.transform.mappers.executive_assistant_mapper import ExecutiveAssistantMapper
50
+ from wxcli.migration.transform.mappers.device_settings_mapper import DeviceSettingsMapper
51
+ from wxcli.migration.transform.mappers.receptionist_mapper import ReceptionistMapper
52
+ from wxcli.migration.transform.mappers.voicemail_group_mapper import VoicemailGroupMapper
53
+ from wxcli.migration.transform.mappers.dect_mapper import DECTMapper
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ # Dependency-sorted execution order.
58
+ # (from 03b-transform-mappers.md section 13: MAPPER_ORDER)
59
+ #
60
+ # Tier 0: LocationMapper, RoutingMapper (no upstream dependencies)
61
+ # Tier 2: UserMapper, LineMapper, WorkspaceMapper (depend on locations)
62
+ # Tier 3: DeviceMapper (depends on users, lines)
63
+ # Tier 4: FeatureMapper (depends on users, lines, locations)
64
+ # Tier 5: CSSMapper (depends on routing_mapper output), VoicemailMapper (depends on users)
65
+ MAPPER_ORDER: list[type[Mapper]] = [
66
+ LocationMapper, # Tier 0 — produces locations
67
+ RoutingMapper, # Tier 0-2 (trunks, route groups, dial plans)
68
+ UserMapper, # Tier 2 (depends on locations)
69
+ LineMapper, # Tier 2 (depends on locations for country code)
70
+ WorkspaceMapper, # Tier 2 (depends on locations)
71
+ DeviceMapper, # Tier 3 (depends on users, lines)
72
+ DeviceSettingsMapper, # Tier 3: after DeviceMapper, before other dependent mappers
73
+ DECTMapper, # Tier 3: after device/location/user mappers — enriches DECT networks
74
+ FeatureMapper, # Tier 4 (depends on users, lines, locations)
75
+ VoicemailGroupMapper, # Tier 4 (depends on locations + features — runs after FeatureMapper)
76
+ CSSMapper, # Tier 5 (depends on routing_mapper output)
77
+ VoicemailMapper, # Tier 5 (depends on users)
78
+ CallForwardingMapper, # Tier 6 (depends on users, phones/device cross-refs)
79
+ MonitoringMapper, # Tier 6 (depends on users, phones, lines)
80
+ SNRMapper, # Tier 6 (depends on users — remote destinations)
81
+ DeviceProfileMapper, # Tier 6 (depends on users, workspaces — EM/hot desking)
82
+ E911Mapper, # Tier 6 (depends on locations — E911 advisory)
83
+ EcbnMapper, # Tier 6 (depends on users, workspaces, locations, lines, devices)
84
+ MOHMapper, # Tier 6 (depends on locations — MOH audio sources)
85
+ AnnouncementMapper, # Tier 6 (depends on locations, features — announcements)
86
+ SoftkeyMapper, # Tier 6 (depends on devices for phone model info)
87
+ ButtonTemplateMapper, # Tier 6 (depends on devices for phone→template cross-refs)
88
+ CallSettingsMapper, # Tier 6 (depends on users — call settings enrichment)
89
+ ExecutiveAssistantMapper, # Tier 6 (depends on users — executive/assistant pairings)
90
+ DeviceLayoutMapper, # Tier 7 (depends on button_template, monitoring, line, device mappers)
91
+ ReceptionistMapper, # Tier 8 (depends on monitoring, device_layout, location, line, user)
92
+ ]
93
+
94
+
95
+ class TransformEngine:
96
+ """Orchestrates all 20 transform mappers in dependency order.
97
+
98
+ (from 03b-transform-mappers.md section 13)
99
+ """
100
+
101
+ def __init__(self, config: dict[str, Any] | None = None) -> None:
102
+ """
103
+ Args:
104
+ config: Optional configuration dict passed to mappers that need it.
105
+ Keys used:
106
+ default_language -> LocationMapper
107
+ default_country -> LocationMapper, LineMapper
108
+ outside_dial_digit -> LocationMapper, RoutingMapper, CSSMapper
109
+ site_prefix_rules -> LocationMapper, LineMapper
110
+ create_method -> UserMapper
111
+ include_phoneless_users -> UserMapper
112
+ country_code -> RoutingMapper, CSSMapper
113
+ category_rules -> CSSMapper
114
+ """
115
+ self.config = config or {}
116
+
117
+ def run(self, store: MigrationStore) -> TransformResult:
118
+ """Run all 21 mappers in dependency order.
119
+
120
+ Failure handling: if a mapper raises an exception, log the error,
121
+ record a MapperError, and continue to the next mapper. Downstream
122
+ mappers that depend on the failed mapper's output will find no objects
123
+ and produce MISSING_DATA decisions naturally.
124
+
125
+ Returns TransformResult with aggregated decisions and any mapper errors.
126
+ """
127
+ result = TransformResult()
128
+
129
+ for mapper_cls in MAPPER_ORDER:
130
+ mapper = self._instantiate_mapper(mapper_cls)
131
+ mapper_name = mapper.name or mapper_cls.__name__
132
+
133
+ logger.info("Running mapper: %s", mapper_name)
134
+ try:
135
+ mapper_result = mapper.map(store)
136
+
137
+ # Save decisions to store and aggregate into result
138
+ for decision in mapper_result.decisions:
139
+ store.save_decision(decision_to_store_dict(decision))
140
+ result.decisions.append(decision)
141
+
142
+ logger.info(
143
+ "Mapper %s complete: %d created, %d updated, %d decisions",
144
+ mapper_name,
145
+ mapper_result.objects_created,
146
+ mapper_result.objects_updated,
147
+ len(mapper_result.decisions),
148
+ )
149
+
150
+ except Exception as exc:
151
+ tb = traceback.format_exc()
152
+ logger.error(
153
+ "Mapper %s failed with %s: %s",
154
+ mapper_name,
155
+ type(exc).__name__,
156
+ exc,
157
+ )
158
+ result.errors.append(
159
+ MapperError(
160
+ mapper_name=mapper_name,
161
+ error_message=str(exc),
162
+ traceback=tb,
163
+ )
164
+ )
165
+ # Continue to next mapper — do NOT abort
166
+
167
+ return result
168
+
169
+ def _instantiate_mapper(self, mapper_cls: type[Mapper]) -> Mapper:
170
+ """Create a mapper instance with the appropriate config subset.
171
+
172
+ Each mapper's __init__ takes different keyword arguments. We inspect
173
+ the mapper class and pass the relevant keys from self.config.
174
+ """
175
+ cfg = self.config
176
+
177
+ if mapper_cls is LocationMapper:
178
+ return LocationMapper(
179
+ default_language=cfg.get("default_language", "en_us"),
180
+ default_country=cfg.get("default_country", "US"),
181
+ outside_dial_digit=str(cfg.get("outside_dial_digit", "9")),
182
+ site_prefix_rules=cfg.get("site_prefix_rules"),
183
+ )
184
+
185
+ if mapper_cls is RoutingMapper:
186
+ return RoutingMapper(
187
+ country_code=cfg.get("country_code", "+1"),
188
+ outside_dial_digit=str(cfg.get("outside_dial_digit", "9")),
189
+ )
190
+
191
+ if mapper_cls is UserMapper:
192
+ return UserMapper(
193
+ create_method=cfg.get("create_method", "people_api"),
194
+ include_phoneless_users=cfg.get("include_phoneless_users", False),
195
+ )
196
+
197
+ if mapper_cls is LineMapper:
198
+ return LineMapper(
199
+ default_country_code=cfg.get("default_country", "US"),
200
+ site_prefix_rules=cfg.get("site_prefix_rules"),
201
+ )
202
+
203
+ if mapper_cls is WorkspaceMapper:
204
+ return WorkspaceMapper()
205
+
206
+ if mapper_cls is DeviceMapper:
207
+ return DeviceMapper()
208
+
209
+ if mapper_cls is DeviceSettingsMapper:
210
+ return DeviceSettingsMapper()
211
+
212
+ if mapper_cls is FeatureMapper:
213
+ return FeatureMapper()
214
+
215
+ if mapper_cls is CSSMapper:
216
+ return CSSMapper(
217
+ category_rules=cfg.get("category_rules"),
218
+ country_code=cfg.get("country_code", "+1"),
219
+ outside_dial_digit=str(cfg.get("outside_dial_digit", "9")),
220
+ )
221
+
222
+ if mapper_cls is VoicemailMapper:
223
+ return VoicemailMapper()
224
+
225
+ if mapper_cls is CallForwardingMapper:
226
+ return CallForwardingMapper()
227
+
228
+ if mapper_cls is MonitoringMapper:
229
+ return MonitoringMapper()
230
+
231
+ if mapper_cls is SoftkeyMapper:
232
+ return SoftkeyMapper()
233
+
234
+ if mapper_cls is ButtonTemplateMapper:
235
+ return ButtonTemplateMapper()
236
+
237
+ if mapper_cls is MOHMapper:
238
+ return MOHMapper()
239
+
240
+ if mapper_cls is AnnouncementMapper:
241
+ return AnnouncementMapper()
242
+
243
+ if mapper_cls is DeviceLayoutMapper:
244
+ return DeviceLayoutMapper()
245
+
246
+ if mapper_cls is CallSettingsMapper:
247
+ return CallSettingsMapper()
248
+
249
+ if mapper_cls is EcbnMapper:
250
+ return EcbnMapper()
251
+
252
+ if mapper_cls is DECTMapper:
253
+ return DECTMapper()
254
+
255
+ # Fallback: attempt no-arg construction
256
+ return mapper_cls() # type: ignore[call-arg]
@@ -0,0 +1,123 @@
1
+ # mappers/ — CUCM-to-Webex Transform Mappers (Phase 05)
2
+
3
+ 26 mapper classes that read normalized CUCM objects from the store, resolve cross-references, and produce Webex-ready canonical objects. Each mapper extends `Mapper` (base.py) and implements `map(store) -> MapperResult`.
4
+
5
+ ## Mapper Contract
6
+
7
+ ```python
8
+ class Mapper(ABC):
9
+ name: str = "" # unique name used in depends_on lists
10
+ depends_on: list[str] = [] # other mapper names that must run first
11
+
12
+ def map(self, store: MigrationStore) -> MapperResult:
13
+ """Read CUCM objects, produce Webex canonical objects + decisions."""
14
+ ```
15
+
16
+ Mappers are run by `transform/engine.py` in topological order. Each mapper:
17
+ 1. Reads objects from the store (`store.get_objects(object_type)`)
18
+ 2. Resolves cross-refs (`store.find_cross_refs(canonical_id, relationship)`)
19
+ 3. Calls `store.upsert_object(canonical_obj)` for each output object
20
+ 4. Calls `store.save_decision(decision_to_store_dict(d))` for unresolvable cases
21
+ 5. Returns `MapperResult(objects_created=N, decisions_created=M, ...)`
22
+
23
+ **base.py utilities:**
24
+ - `extract_provenance(data)` — reconstructs `Provenance` from a stored object dict
25
+ - `hash_id(value)` — deterministic 12-char hex ID from SHA256
26
+ - `decision_to_store_dict(decision)` — converts `Decision` model to store-ready dict (embeds `affected_objects` into `context._affected_objects`)
27
+ - `skip_option()`, `manual_option()`, `accept_option()` — standard decision option builders
28
+
29
+ ## Mapper Inventory
30
+
31
+ Execution order determined by `depends_on` (topological sort):
32
+
33
+ ### Tier 1 — No dependencies
34
+
35
+ | Mapper | `name` | Produces | Source objects |
36
+ |--------|--------|---------|----------------|
37
+ | `LocationMapper` | `location_mapper` | `CanonicalLocation`, `CanonicalSchedule` (operating hours) | `device_pool`, `datetime_group`, `cucm_location` |
38
+ | `RoutingMapper` | `routing_mapper` | `CanonicalTrunk`, `CanonicalRouteGroup`, `CanonicalRouteList`, `CanonicalTranslationPattern`, `CanonicalOperatingMode` | `gateway`, `sip_trunk`, `route_group`, `route_list`, `route_pattern`, `time_schedule`, `time_period` |
39
+
40
+ ### Tier 2 — Depends on location/routing
41
+
42
+ | Mapper | `name` | Depends on | Produces | Source objects |
43
+ |--------|--------|-----------|---------|----------------|
44
+ | `LineMapper` | `line_mapper` | `location_mapper` | `CanonicalLine` (data-only, consumed by user:create / workspace:assign_number) | `dn` (directory number) |
45
+ | `UserMapper` | `user_mapper` | `location_mapper` | `CanonicalUser` | `user` |
46
+ | `DeviceMapper` | `device_mapper` | `location_mapper` | `CanonicalDevice` (5-tier: NATIVE_MPP / CONVERTIBLE / WEBEX_APP / INCOMPATIBLE / DECT) | `phone` (raw) |
47
+ | `DECTMapper` | `dect_mapper` | `location_mapper`, `device_mapper`, `user_mapper` | `CanonicalDECTNetwork` (enriched with resolved location, model, handset assignments, base stations) | `dect_network` (normalized by `normalize_dect_group`) |
48
+
49
+ ### Tier 3 — Depends on users/lines/devices
50
+
51
+ | Mapper | `name` | Depends on | Produces | Source objects |
52
+ |--------|--------|-----------|---------|----------------|
53
+ | `DeviceSettingsMapper` | `device_settings_mapper` | `device_mapper`, `location_mapper` | `device_settings_template` | `phone`, `device` | Groups phones by (model_family, location), maps CUCM device settings to Webex, generates templates with per-device overrides |
54
+ | `FeatureMapper` | `feature_mapper` | `location_mapper`, `line_mapper`, `user_mapper` | `CanonicalHuntGroup`, `CanonicalCallQueue`, `CanonicalAutoAttendant`, `CanonicalCallPark`, `CanonicalPickupGroup`, `CanonicalPagingGroup`, `CanonicalLocationSchedule`, `CanonicalOperatingMode` | `hunt_pilot`, `hunt_list`, `line_group`, `call_park`, `pickup_group`, `time_schedule`, `time_period` |
55
+ | `WorkspaceMapper` | `workspace_mapper` | `location_mapper` | `CanonicalWorkspace` (common-area phones) with `call_settings` populated from raw phone state | `phone` (raw, `ownerUserName=None`) |
56
+ | `MonitoringMapper` | `monitoring_mapper` | `user_mapper`, `line_mapper` | `CanonicalMonitoringList` | `phone` (raw, `busyLampFields`) |
57
+ | `CallForwardingMapper` | `call_forwarding_mapper` | `user_mapper`, `line_mapper` | `CanonicalCallForwarding` | `phone` (raw, per-line forwarding) |
58
+ | `CallSettingsMapper` | `call_settings_mapper` | `user_mapper` | (call settings enrichment) | `user` | Also detects intercept candidates via `user_has_intercept_signal` cross-ref (Pass 2 — independent of phone iteration) |
59
+ | `CSSMapper` | `css_mapper` | `routing_mapper`, `user_mapper`, `line_mapper`, `device_mapper` | `CanonicalCallingPermission`, `CanonicalDialPlan` | `css`, `partition`, `route_pattern` |
60
+ | `ButtonTemplateMapper` | `button_template_mapper` | `device_mapper` | `CanonicalLineKeyTemplate` | `button_template` (raw) |
61
+ | `ExecutiveAssistantMapper` | `executive_assistant_mapper` | `user_mapper` | `CanonicalExecutiveAssistant` | `exec_asst_pair`, `exec_setting` |
62
+ | `EcbnMapper` | `ecbn_mapper` | `user_mapper`, `workspace_mapper`, `location_mapper`, `line_mapper`, `device_mapper` | `CanonicalEcbnConfig` | `user`, `workspace`, `line`, `device` (cross-refs) |
63
+
64
+ ### Tier 4 — Phase 3 mappers (depend on button templates + device layout inputs)
65
+
66
+ | Mapper | `name` | Depends on | Produces | Source objects |
67
+ |--------|--------|-----------|---------|----------------|
68
+ | `DeviceLayoutMapper` | `device_layout_mapper` | `button_template_mapper`, `monitoring_mapper`, `line_mapper`, `device_mapper` | `CanonicalDeviceLayout` | `phone` (raw) + `CanonicalLineKeyTemplate` |
69
+ | `SoftkeyMapper` | `softkey_mapper` | `device_mapper` | `CanonicalSoftkeyConfig` (2 per template: 1 template-level + N per-device) | `button_template` (raw softkey data), `phone` (raw) |
70
+ | `VoicemailMapper` | `voicemail_mapper` | `user_mapper` | (voicemail config enrichment) | `voicemail_profile`, `user` |
71
+ | `VoicemailGroupMapper` | `voicemail_group_mapper` | `location_mapper`, `feature_mapper` | `CanonicalVoicemailGroup` (Unity Connection shared mailboxes) | `voicemail_group` (raw Unity Connection CallHandlers) |
72
+
73
+ ---
74
+
75
+ ## Phase 3 Mappers in Detail
76
+
77
+ ### ButtonTemplateMapper
78
+
79
+ Reads `button_template` raw objects, resolves model-specific line key counts, produces `CanonicalLineKeyTemplate`. Filters UNMAPPED keys before writing. Only produces templates where `phones_using > 0` (dead templates are skipped by the planner).
80
+
81
+ Key fields on output: `name`, `device_model`, `line_keys` (list of `{index, key_type, label?, value?}`), `kem_keys`, `kem_module_type`, `phones_using`.
82
+
83
+ ### DeviceLayoutMapper
84
+
85
+ Merges `CanonicalLineKeyTemplate` key structure with per-phone line appearances from raw phone objects. Detects shared lines via `CanonicalSharedLine` objects.
86
+
87
+ Reads raw phone via `store.get_objects("phone")`, looks up the phone's button template via cross-ref, resolves line members from `busyLampFields` and `lines`.
88
+
89
+ Key fields on `CanonicalDeviceLayout`: `device_canonical_id`, `device_id_surface` (`"cloud"` or `"telephony"`), `template_canonical_id`, `owner_canonical_id`, `line_members` (list of `{port, member_canonical_id, line_type}`), `resolved_line_keys`, `resolved_kem_keys`.
90
+
91
+ **`member_canonical_id`** on `line_members` is what `handle_device_layout_configure` uses to resolve Webex IDs from `deps`. It's the canonical_id of the user or workspace assigned to that port.
92
+
93
+ **Convertible-device skip.** Devices classified as `DeviceCompatibilityTier.CONVERTIBLE` do not get a `CanonicalDeviceLayout` emitted. The mapper checks the device's `compatibility_tier` and continues past the phone silently. Rationale: these phones use activation-code registration (`POST /devices/activationCode`) and auto-configure post-registration, so applying line key templates during the main execution pipeline would race registration. Post-registration bulk layout application is tracked as future work.
94
+
95
+ ### SoftkeyMapper
96
+
97
+ Reads softkey template data (via SQL — not AXL) and PSK-capable phone references. Produces **two kinds** of `CanonicalSoftkeyConfig`:
98
+
99
+ 1. **Template-level** (`is_psk_target=False`) — `canonical_id=f"softkey_config:{template_name}"`. Report-only. The planner's `_expand_softkey_config` returns `[]` for these — no execution ops produced.
100
+ 2. **Per-device** (`is_psk_target=True`) — `canonical_id=f"softkey_config:device:{device_name}"`. One per PSK-capable phone referencing the template. These drive `handle_softkey_config_configure`.
101
+
102
+ Key fields: `is_psk_target`, `device_canonical_id` (set only for per-device objects), `psk_mappings` (list of `{psk_slot, keyword}`), `state_key_lists` (`{webex_state: [keywords]}`), `cucm_template_name`, `phones_using`.
103
+
104
+ **PSK-capable models:** 9861 (with 120 KEM buttons), 8875. The mapper checks `_is_psk_capable_model(model)` before creating per-device objects.
105
+
106
+ ---
107
+
108
+ ## Key Gotchas
109
+
110
+ - **Raw phones vs CanonicalDevice.** Mappers that need `speeddials`, `busyLampFields`, or per-line forwarding call `store.get_objects("phone")` to get raw phone dicts (object_type="phone", canonical_id="phone:{name}"). Do NOT call `store.get_objects("device")` for this — that returns `CanonicalDevice` objects which have already lost the raw AXL fields.
111
+ - **DeviceSettingsMapper requires raw phone data.** Reads `product_specific_config`, `cucm_common_phone_config`, `cucm_user_locale`, etc. from `pre_migration_state` — these fields come from the normalized phone objects, not CanonicalDevice.
112
+ - **`device_id_surface` field.** Added to `CanonicalDevice` and `CanonicalDeviceLayout` in Phase 3. Values: `"cloud"` (9800-series + 8875 PhoneOS phones using cloud `deviceId`) or `"telephony"` (classic MPP phones using `callingDeviceId`). The execute handler uses this to decide which API surface to call.
113
+ - **`device_canonical_id` field on `CanonicalSoftkeyConfig`.** Only set on per-device objects (`is_psk_target=True`). The planner checks `device_canonical_id` presence before adding dependency edges.
114
+ - **PSK slot lowercasing.** The mapper stores `psk_slot` as uppercase (e.g., `"PSK1"`). The execute handler lowercases it when building `softKeyLayout.psk.psk1` keys. The state key list state names in `state_key_lists` are already in Webex format (e.g., `"idle"`, `"progressing"` — NOT CUCM names).
115
+ - **`ringOut` → `progressing`.** `CUCM_STATE_TO_PSK_STATE` maps CUCM's `ringOut` to Webex's `progressing`, producing key `softKeyLayout.softKeyMenu.progressingKeyList`. The incorrect value `"processing"` was fixed — see comment in `softkey_mapper.py`.
116
+ - **DECT handsets (6823/6825/6825ip) are classified as `DECT` tier** — compatible hardware that needs DECT network provisioning, not standard phone activation. No decision is generated by the mapper or analyzer.
117
+ - **DECTMapper auto-selects network model.** DBS-110 is chosen when handset_count ≤ 30; DBS-210 for >30. This selection is overridable by the operator via `DECT_NETWORK_DESIGN` decision. Without a `--dect-inventory` CSV (columns: `coverage_zone`, `base_station_mac`, `base_station_model`), base_stations stays empty and the mapper raises `DECT_NETWORK_DESIGN` at MEDIUM severity.
118
+ - **DECT networks group by CUCM device pool.** `normalize_dect_group()` groups all DECT-tier devices that share a device pool into one `dect_network:` object. DECTMapper then resolves that pool to a Webex location via `device_pool_to_location` cross-refs. Multi-zone ambiguity (same location, multiple pools) produces a `DECT_NETWORK_DESIGN` HIGH decision.
119
+ - **CSSMapper produces both permissions AND dial plans.** It reads the full CSS→partition→route-pattern graph to classify calling permissions (international/national/local) and generate `CanonicalDialPlan` objects from blocking/non-blocking route patterns.
120
+ - **FeatureMapper converts CUCM hunt pilots → hunt groups.** CUCM's hunt pilot → hunt list → line group chain collapses into a single `CanonicalHuntGroup`. Agent resolution goes via DN → user cross-refs.
121
+ - **LocationMapper writes `device_pool_to_location` cross-refs.** This is the only cross-ref NOT written by `CrossReferenceBuilder` — it requires a decision when device pool → location mapping is ambiguous.
122
+ - **FeatureMapper extracts forwarding/overflow.** `_map_hunt_pilots()` reads `forwardHuntNoAnswer.destination`, `forwardHuntBusy.destination`, and `queueCalls.{queueFullDestination,maxWaitTimeDestination,maxWaitTime,noAgentDestination}` from the hunt pilot AXL state and writes them onto `CanonicalHuntGroup` / `CanonicalCallQueue`. `_map_cti_route_points()` reads `callForwardAll.destination` from the CTI RP and writes it onto `CanonicalAutoAttendant`. The planner emits tier-5 `configure_*` ops only when these fields are non-empty.
123
+ - **FeatureMapper writes `feature_forwards_to_voicemail_group` cross-refs.** When a hunt group/call queue forwarding destination matches a voicemail group extension, the mapper writes a cross-ref from the feature's canonical_id to the voicemail group's canonical_id. This drives a dependency edge in the execution graph: `voicemail_group:create` must complete before `hunt_group:configure_forwarding` / `call_queue:configure_forwarding` / `call_queue:configure_stranded_calls` can run (Phase C overflow linkage).
@@ -0,0 +1,76 @@
1
+ """Transform mappers — CUCM canonical objects to Webex canonical objects.
2
+
3
+ Each mapper reads resolved canonical objects (post-normalization, post-cross-reference)
4
+ from the MigrationStore and produces Webex-ready canonical objects plus decisions for
5
+ anything that can't be cleanly mapped.
6
+
7
+ (from 03b-transform-mappers.md, mapper contract and execution order)
8
+ """
9
+
10
+ from wxcli.migration.transform.mappers.base import (
11
+ Mapper,
12
+ accept_option,
13
+ decision_to_store_dict,
14
+ manual_option,
15
+ skip_option,
16
+ )
17
+ from wxcli.migration.transform.mappers.button_template_mapper import ButtonTemplateMapper
18
+ from wxcli.migration.transform.mappers.call_forwarding_mapper import CallForwardingMapper
19
+ from wxcli.migration.transform.mappers.css_mapper import CSSMapper
20
+ from wxcli.migration.transform.mappers.monitoring_mapper import MonitoringMapper
21
+ from wxcli.migration.transform.mappers.device_mapper import DeviceMapper
22
+ from wxcli.migration.transform.mappers.feature_mapper import FeatureMapper
23
+ from wxcli.migration.transform.mappers.line_mapper import LineMapper
24
+ from wxcli.migration.transform.mappers.location_mapper import LocationMapper
25
+ from wxcli.migration.transform.mappers.routing_mapper import RoutingMapper
26
+ from wxcli.migration.transform.mappers.user_mapper import UserMapper
27
+ from wxcli.migration.transform.mappers.voicemail_mapper import VoicemailMapper
28
+ from wxcli.migration.transform.mappers.voicemail_group_mapper import VoicemailGroupMapper
29
+ from wxcli.migration.transform.mappers.device_layout_mapper import DeviceLayoutMapper
30
+ from wxcli.migration.transform.mappers.softkey_mapper import SoftkeyMapper
31
+ from wxcli.migration.transform.mappers.device_profile_mapper import DeviceProfileMapper
32
+ from wxcli.migration.transform.mappers.e911_mapper import E911Mapper
33
+ from wxcli.migration.transform.mappers.moh_mapper import MOHMapper
34
+ from wxcli.migration.transform.mappers.snr_mapper import SNRMapper
35
+ from wxcli.migration.transform.mappers.announcement_mapper import AnnouncementMapper
36
+ from wxcli.migration.transform.mappers.workspace_mapper import WorkspaceMapper
37
+ from wxcli.migration.transform.mappers.executive_assistant_mapper import ExecutiveAssistantMapper
38
+ from wxcli.migration.transform.mappers.receptionist_mapper import ReceptionistMapper
39
+ from wxcli.migration.transform.mappers.device_settings_mapper import DeviceSettingsMapper
40
+ from wxcli.migration.transform.mappers.call_settings_mapper import CallSettingsMapper
41
+ from wxcli.migration.transform.mappers.ecbn_mapper import EcbnMapper
42
+ from wxcli.migration.transform.mappers.dect_mapper import DECTMapper
43
+
44
+ __all__ = [
45
+ "Mapper",
46
+ "LocationMapper",
47
+ "UserMapper",
48
+ "LineMapper",
49
+ "DeviceMapper",
50
+ "WorkspaceMapper",
51
+ "RoutingMapper",
52
+ "FeatureMapper",
53
+ "CSSMapper",
54
+ "VoicemailMapper",
55
+ "VoicemailGroupMapper",
56
+ "CallForwardingMapper",
57
+ "MonitoringMapper",
58
+ "ButtonTemplateMapper",
59
+ "DeviceLayoutMapper",
60
+ "SoftkeyMapper",
61
+ "SNRMapper",
62
+ "E911Mapper",
63
+ "MOHMapper",
64
+ "AnnouncementMapper",
65
+ "DeviceProfileMapper",
66
+ "ExecutiveAssistantMapper",
67
+ "ReceptionistMapper",
68
+ "DeviceSettingsMapper",
69
+ "CallSettingsMapper",
70
+ "EcbnMapper",
71
+ "DECTMapper",
72
+ "skip_option",
73
+ "manual_option",
74
+ "accept_option",
75
+ "decision_to_store_dict",
76
+ ]
@@ -0,0 +1,122 @@
1
+ """AnnouncementMapper — CUCM Announcements → audio asset advisory.
2
+
3
+ This mapper is advisory-only — there is no automated audio file transfer
4
+ from CUCM to Webex. Every announcement produces an AUDIO_ASSET_MANUAL
5
+ decision requiring the admin to manually download and re-upload audio.
6
+
7
+ (from tier2-enterprise-expansion.md §2.4)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+
14
+ from wxcli.migration.models import (
15
+ CanonicalAnnouncement,
16
+ DecisionOption,
17
+ DecisionType,
18
+ MigrationStatus,
19
+ MapperResult,
20
+ )
21
+ from wxcli.migration.store import MigrationStore
22
+ from wxcli.migration.transform.mappers.base import (
23
+ Mapper,
24
+ accept_option,
25
+ extract_provenance,
26
+ skip_option,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # File extension → media type mapping
32
+ _EXT_TO_MEDIA_TYPE: dict[str, str] = {
33
+ ".wav": "WAV",
34
+ ".wma": "WMA",
35
+ ".mp3": "MP3",
36
+ ".au": "AU",
37
+ }
38
+
39
+
40
+ def _detect_media_type(file_name: str | None) -> str:
41
+ """Detect media type from file extension."""
42
+ if not file_name:
43
+ return "UNKNOWN"
44
+ lower = file_name.lower()
45
+ for ext, media_type in _EXT_TO_MEDIA_TYPE.items():
46
+ if lower.endswith(ext):
47
+ return media_type
48
+ return "UNKNOWN"
49
+
50
+
51
+ class AnnouncementMapper(Mapper):
52
+ """Map CUCM Announcements to CanonicalAnnouncement objects with decisions.
53
+
54
+ Every announcement produces an AUDIO_ASSET_MANUAL decision because
55
+ there is no automated audio transfer path from CUCM to Webex.
56
+
57
+ Tier 2 expansion: depends on location_mapper and feature_mapper
58
+ for cross-reference context.
59
+ """
60
+
61
+ name = "announcement_mapper"
62
+ depends_on = ["location_mapper", "feature_mapper"]
63
+
64
+ def map(self, store: MigrationStore) -> MapperResult:
65
+ result = MapperResult()
66
+
67
+ announcements = store.get_objects("announcement")
68
+ if not announcements:
69
+ return result
70
+
71
+ for ann_data in announcements:
72
+ state = ann_data.get("pre_migration_state") or {}
73
+ name = state.get("name") or ""
74
+ description = state.get("description") or ""
75
+ file_name = state.get("file_name") or ""
76
+ media_type = _detect_media_type(file_name)
77
+
78
+ prov = extract_provenance(ann_data)
79
+ canonical = CanonicalAnnouncement(
80
+ canonical_id=f"announcement:{name}",
81
+ provenance=prov,
82
+ status=MigrationStatus.ANALYZED,
83
+ name=name,
84
+ file_name=file_name,
85
+ media_type=media_type,
86
+ source_system="cucm",
87
+ )
88
+ store.upsert_object(canonical)
89
+ result.objects_created += 1
90
+
91
+ # Every announcement needs manual audio migration
92
+ decision = self._create_decision(
93
+ store=store,
94
+ decision_type=DecisionType.AUDIO_ASSET_MANUAL,
95
+ severity="MEDIUM",
96
+ summary=(
97
+ f"Announcement '{name}' requires manual audio migration"
98
+ f"{f' ({media_type} file: {file_name})' if file_name else ''}"
99
+ ),
100
+ context={
101
+ "announcement_name": name,
102
+ "file_name": file_name,
103
+ "media_type": media_type,
104
+ "description": description,
105
+ "advisory_type": "audio_asset_manual",
106
+ },
107
+ options=[
108
+ accept_option(
109
+ "Admin downloads audio from CUCM and uploads to Webex announcement repository"
110
+ ),
111
+ DecisionOption(
112
+ id="use_default",
113
+ label="Use Webex default",
114
+ impact="Feature uses Webex default greeting instead of custom audio",
115
+ ),
116
+ skip_option("Feature configured without custom audio"),
117
+ ],
118
+ affected_objects=[f"announcement:{name}"],
119
+ )
120
+ result.decisions.append(decision)
121
+
122
+ return result