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.
- wxcli/__init__.py +4 -0
- wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
- wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
- wxcli/_playbook/.claude/rules/cleanup.md +29 -0
- wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
- wxcli/_playbook/.claude/rules/org-health.md +14 -0
- wxcli/_playbook/.claude/settings.json +8 -0
- wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
- wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
- wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
- wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
- wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
- wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
- wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
- wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
- wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
- wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
- wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
- wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
- wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
- wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
- wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
- wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
- wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
- wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
- wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
- wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
- wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
- wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
- wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
- wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
- wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
- wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
- wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
- wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
- wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
- wxcli/_playbook/CLAUDE.md +373 -0
- wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
- wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
- wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
- wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
- wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
- wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
- wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
- wxcli/_playbook/docs/reference/admin-partner.md +357 -0
- wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
- wxcli/_playbook/docs/reference/authentication.md +965 -0
- wxcli/_playbook/docs/reference/call-control.md +1205 -0
- wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
- wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
- wxcli/_playbook/docs/reference/call-routing.md +2304 -0
- wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
- wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
- wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
- wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
- wxcli/_playbook/docs/reference/devices-core.md +1869 -0
- wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
- wxcli/_playbook/docs/reference/devices-platform.md +467 -0
- wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
- wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
- wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
- wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
- wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
- wxcli/_playbook/docs/reference/meetings-content.md +473 -0
- wxcli/_playbook/docs/reference/meetings-core.md +903 -0
- wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
- wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
- wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
- wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
- wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
- wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
- wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
- wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
- wxcli/_playbook/docs/reference/provisioning.md +1366 -0
- wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
- wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
- wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
- wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
- wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
- wxcli/_version.py +24 -0
- wxcli/auth.py +137 -0
- wxcli/commands/__init__.py +0 -0
- wxcli/commands/_registry.py +181 -0
- wxcli/commands/activation_email.py +103 -0
- wxcli/commands/admin_recordings.py +585 -0
- wxcli/commands/analytics.py +105 -0
- wxcli/commands/announcement_playlists.py +219 -0
- wxcli/commands/announcements.py +519 -0
- wxcli/commands/archive_users.py +36 -0
- wxcli/commands/attachment_actions.py +60 -0
- wxcli/commands/audit_events.py +83 -0
- wxcli/commands/authorizations.py +113 -0
- wxcli/commands/auto_attendant.py +642 -0
- wxcli/commands/broadworks_billing_reports.py +126 -0
- wxcli/commands/broadworks_enterprises.py +160 -0
- wxcli/commands/broadworks_subscribers.py +319 -0
- wxcli/commands/broadworks_workspaces.py +108 -0
- wxcli/commands/call_controls.py +1304 -0
- wxcli/commands/call_park.py +494 -0
- wxcli/commands/call_pickup.py +229 -0
- wxcli/commands/call_queue.py +1878 -0
- wxcli/commands/call_recording.py +707 -0
- wxcli/commands/call_routing.py +1610 -0
- wxcli/commands/call_settings_for_me_phase_5.py +212 -0
- wxcli/commands/caller_reputation.py +169 -0
- wxcli/commands/calling_service.py +305 -0
- wxcli/commands/cc_address_book.py +770 -0
- wxcli/commands/cc_agent_greetings.py +242 -0
- wxcli/commands/cc_agent_summaries.py +39 -0
- wxcli/commands/cc_agent_wellbeing.py +216 -0
- wxcli/commands/cc_agents.py +425 -0
- wxcli/commands/cc_ai_assistant.py +55 -0
- wxcli/commands/cc_ai_feature.py +294 -0
- wxcli/commands/cc_audio_files.py +149 -0
- wxcli/commands/cc_auto_csat.py +311 -0
- wxcli/commands/cc_aux_code.py +458 -0
- wxcli/commands/cc_business_hour.py +353 -0
- wxcli/commands/cc_call_monitoring.py +228 -0
- wxcli/commands/cc_callbacks.py +194 -0
- wxcli/commands/cc_campaign.py +212 -0
- wxcli/commands/cc_captures.py +39 -0
- wxcli/commands/cc_contact_list.py +169 -0
- wxcli/commands/cc_contact_number.py +295 -0
- wxcli/commands/cc_data_sources.py +235 -0
- wxcli/commands/cc_desktop_layout.py +380 -0
- wxcli/commands/cc_desktop_profile.py +322 -0
- wxcli/commands/cc_dial_number.py +511 -0
- wxcli/commands/cc_dial_plan.py +347 -0
- wxcli/commands/cc_dnc.py +101 -0
- wxcli/commands/cc_entry_point.py +545 -0
- wxcli/commands/cc_ewt.py +51 -0
- wxcli/commands/cc_flow.py +477 -0
- wxcli/commands/cc_global_vars.py +430 -0
- wxcli/commands/cc_holiday_list.py +335 -0
- wxcli/commands/cc_journey.py +1261 -0
- wxcli/commands/cc_legacy_flows.py +49 -0
- wxcli/commands/cc_multimedia_profile.py +436 -0
- wxcli/commands/cc_notification.py +51 -0
- wxcli/commands/cc_outdial_ani.py +604 -0
- wxcli/commands/cc_overrides.py +341 -0
- wxcli/commands/cc_queue.py +1083 -0
- wxcli/commands/cc_queue_stats.py +55 -0
- wxcli/commands/cc_realtime.py +51 -0
- wxcli/commands/cc_resource_collection.py +312 -0
- wxcli/commands/cc_search.py +46 -0
- wxcli/commands/cc_site.py +400 -0
- wxcli/commands/cc_skill.py +350 -0
- wxcli/commands/cc_skill_profile.py +289 -0
- wxcli/commands/cc_subscriptions.py +426 -0
- wxcli/commands/cc_summaries.py +130 -0
- wxcli/commands/cc_tasks.py +844 -0
- wxcli/commands/cc_team.py +325 -0
- wxcli/commands/cc_user_profiles.py +349 -0
- wxcli/commands/cc_users.py +701 -0
- wxcli/commands/cc_work_types.py +363 -0
- wxcli/commands/cdr.py +89 -0
- wxcli/commands/classifications.py +38 -0
- wxcli/commands/cleanup.py +1427 -0
- wxcli/commands/client_settings.py +72 -0
- wxcli/commands/conference.py +328 -0
- wxcli/commands/configure.py +72 -0
- wxcli/commands/converged_recordings.py +347 -0
- wxcli/commands/converged_recordings_export.py +310 -0
- wxcli/commands/cq_playlists.py +42 -0
- wxcli/commands/cucm.py +2794 -0
- wxcli/commands/cucm_config.py +129 -0
- wxcli/commands/customer_assist.py +373 -0
- wxcli/commands/data_sources.py +227 -0
- wxcli/commands/dect_devices.py +787 -0
- wxcli/commands/device_configurations.py +77 -0
- wxcli/commands/device_dynamic_settings.py +320 -0
- wxcli/commands/device_settings.py +1489 -0
- wxcli/commands/devices.py +280 -0
- wxcli/commands/domains.py +142 -0
- wxcli/commands/ecm.py +178 -0
- wxcli/commands/emergency_services.py +827 -0
- wxcli/commands/events.py +83 -0
- wxcli/commands/external_voicemail.py +52 -0
- wxcli/commands/groups.py +206 -0
- wxcli/commands/guest_management.py +76 -0
- wxcli/commands/hds.py +340 -0
- wxcli/commands/hot_desk.py +67 -0
- wxcli/commands/hot_desking_members.py +118 -0
- wxcli/commands/hot_desking_portal.py +124 -0
- wxcli/commands/hunt_group.py +577 -0
- wxcli/commands/hybrid_clusters.py +71 -0
- wxcli/commands/hybrid_connectors.py +67 -0
- wxcli/commands/identity_org.py +159 -0
- wxcli/commands/init_playbook.py +155 -0
- wxcli/commands/licenses.py +106 -0
- wxcli/commands/live_monitoring.py +40 -0
- wxcli/commands/location_call_handling.py +577 -0
- wxcli/commands/location_schedules.py +341 -0
- wxcli/commands/location_settings.py +1297 -0
- wxcli/commands/location_voicemail.py +494 -0
- wxcli/commands/locations.py +336 -0
- wxcli/commands/meeting_captions.py +110 -0
- wxcli/commands/meeting_chats.py +63 -0
- wxcli/commands/meeting_invitees.py +234 -0
- wxcli/commands/meeting_messages.py +28 -0
- wxcli/commands/meeting_participants.py +278 -0
- wxcli/commands/meeting_polls.py +111 -0
- wxcli/commands/meeting_preferences.py +515 -0
- wxcli/commands/meeting_qa.py +78 -0
- wxcli/commands/meeting_qualities.py +43 -0
- wxcli/commands/meeting_reports.py +101 -0
- wxcli/commands/meeting_session_types.py +105 -0
- wxcli/commands/meeting_site.py +57 -0
- wxcli/commands/meeting_slido.py +44 -0
- wxcli/commands/meeting_summaries.py +92 -0
- wxcli/commands/meeting_tracking_codes.py +233 -0
- wxcli/commands/meeting_transcripts.py +230 -0
- wxcli/commands/meetings.py +1997 -0
- wxcli/commands/memberships.py +164 -0
- wxcli/commands/messages.py +213 -0
- wxcli/commands/mode_management.py +244 -0
- wxcli/commands/my_call_settings.py +3577 -0
- wxcli/commands/numbers.py +405 -0
- wxcli/commands/operating_modes.py +410 -0
- wxcli/commands/org_contacts.py +284 -0
- wxcli/commands/org_health_cli.py +55 -0
- wxcli/commands/org_settings.py +71 -0
- wxcli/commands/organizations.py +83 -0
- wxcli/commands/paging_group.py +257 -0
- wxcli/commands/partner_admins.py +141 -0
- wxcli/commands/partner_reports.py +188 -0
- wxcli/commands/partner_tags.py +216 -0
- wxcli/commands/people.py +279 -0
- wxcli/commands/person_call_settings.py +71 -0
- wxcli/commands/pstn.py +280 -0
- wxcli/commands/recording_report.py +154 -0
- wxcli/commands/report_templates.py +38 -0
- wxcli/commands/reports.py +144 -0
- wxcli/commands/resource_group_memberships.py +160 -0
- wxcli/commands/resource_groups.py +67 -0
- wxcli/commands/roles.py +63 -0
- wxcli/commands/room_tabs.py +160 -0
- wxcli/commands/rooms.py +225 -0
- wxcli/commands/scim_bulk.py +46 -0
- wxcli/commands/scim_groups.py +246 -0
- wxcli/commands/scim_schemas.py +82 -0
- wxcli/commands/scim_users.py +283 -0
- wxcli/commands/security_audit.py +54 -0
- wxcli/commands/service_apps.py +51 -0
- wxcli/commands/single_number_reach.py +209 -0
- wxcli/commands/team_memberships.py +155 -0
- wxcli/commands/teams.py +153 -0
- wxcli/commands/ucm_profile.py +42 -0
- wxcli/commands/update.py +219 -0
- wxcli/commands/user_settings.py +5116 -0
- wxcli/commands/video_mesh.py +1090 -0
- wxcli/commands/virtual_extensions.py +502 -0
- wxcli/commands/virtual_line_settings.py +2072 -0
- wxcli/commands/webhooks.py +176 -0
- wxcli/commands/wholesale_billing_reports.py +147 -0
- wxcli/commands/wholesale_provisioning.py +523 -0
- wxcli/commands/workspace_locations.py +339 -0
- wxcli/commands/workspace_metrics.py +103 -0
- wxcli/commands/workspace_personalization.py +70 -0
- wxcli/commands/workspace_settings.py +3347 -0
- wxcli/commands/workspaces.py +279 -0
- wxcli/commands/xapi.py +114 -0
- wxcli/config.py +139 -0
- wxcli/errors.py +77 -0
- wxcli/main.py +210 -0
- wxcli/migration/CLAUDE.md +106 -0
- wxcli/migration/__init__.py +1 -0
- wxcli/migration/advisory/CLAUDE.md +196 -0
- wxcli/migration/advisory/__init__.py +46 -0
- wxcli/migration/advisory/advisor.py +107 -0
- wxcli/migration/advisory/advisory_patterns.py +2552 -0
- wxcli/migration/advisory/recommendation_rules.py +835 -0
- wxcli/migration/cucm/CLAUDE.md +74 -0
- wxcli/migration/cucm/__init__.py +5 -0
- wxcli/migration/cucm/connection.py +265 -0
- wxcli/migration/cucm/discovery.py +225 -0
- wxcli/migration/cucm/extractors/__init__.py +1 -0
- wxcli/migration/cucm/extractors/announcements.py +92 -0
- wxcli/migration/cucm/extractors/base.py +90 -0
- wxcli/migration/cucm/extractors/device_profiles.py +98 -0
- wxcli/migration/cucm/extractors/devices.py +193 -0
- wxcli/migration/cucm/extractors/e911.py +103 -0
- wxcli/migration/cucm/extractors/features.py +309 -0
- wxcli/migration/cucm/extractors/helpers.py +73 -0
- wxcli/migration/cucm/extractors/informational.py +295 -0
- wxcli/migration/cucm/extractors/locations.py +221 -0
- wxcli/migration/cucm/extractors/moh.py +100 -0
- wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
- wxcli/migration/cucm/extractors/routing.py +493 -0
- wxcli/migration/cucm/extractors/shared_lines.py +146 -0
- wxcli/migration/cucm/extractors/templates.py +229 -0
- wxcli/migration/cucm/extractors/tier4.py +173 -0
- wxcli/migration/cucm/extractors/users.py +240 -0
- wxcli/migration/cucm/extractors/voicemail.py +243 -0
- wxcli/migration/cucm/extractors/workspaces.py +88 -0
- wxcli/migration/cucm/unity_connection.py +304 -0
- wxcli/migration/execute/CLAUDE.md +372 -0
- wxcli/migration/execute/__init__.py +303 -0
- wxcli/migration/execute/batch.py +277 -0
- wxcli/migration/execute/dependency.py +483 -0
- wxcli/migration/execute/engine.py +964 -0
- wxcli/migration/execute/handlers.py +2232 -0
- wxcli/migration/execute/planner.py +1939 -0
- wxcli/migration/execute/runtime.py +571 -0
- wxcli/migration/export/CLAUDE.md +49 -0
- wxcli/migration/export/__init__.py +8 -0
- wxcli/migration/export/csv_export.py +126 -0
- wxcli/migration/export/deployment_plan.py +479 -0
- wxcli/migration/export/json_export.py +60 -0
- wxcli/migration/models.py +941 -0
- wxcli/migration/phone_models.py +50 -0
- wxcli/migration/preflight/CLAUDE.md +52 -0
- wxcli/migration/preflight/__init__.py +89 -0
- wxcli/migration/preflight/checks.py +911 -0
- wxcli/migration/preflight/runner.py +293 -0
- wxcli/migration/rate_limiter.py +144 -0
- wxcli/migration/report/CLAUDE.md +245 -0
- wxcli/migration/report/__init__.py +7 -0
- wxcli/migration/report/appendix.py +2437 -0
- wxcli/migration/report/assembler.py +183 -0
- wxcli/migration/report/charts.py +263 -0
- wxcli/migration/report/executive.py +776 -0
- wxcli/migration/report/explainer.py +704 -0
- wxcli/migration/report/helpers.py +74 -0
- wxcli/migration/report/ingest.py +152 -0
- wxcli/migration/report/notice_templates.py +192 -0
- wxcli/migration/report/score.py +375 -0
- wxcli/migration/report/styles.py +1052 -0
- wxcli/migration/report/user_diff.py +1052 -0
- wxcli/migration/report/user_notice.py +425 -0
- wxcli/migration/state.py +139 -0
- wxcli/migration/store.py +860 -0
- wxcli/migration/transform/CLAUDE.md +107 -0
- wxcli/migration/transform/__init__.py +12 -0
- wxcli/migration/transform/analysis_pipeline.py +453 -0
- wxcli/migration/transform/analyzers/__init__.py +142 -0
- wxcli/migration/transform/analyzers/css_permission.py +189 -0
- wxcli/migration/transform/analyzers/css_routing.py +335 -0
- wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
- wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
- wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
- wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
- wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
- wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
- wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
- wxcli/migration/transform/analyzers/missing_data.py +300 -0
- wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
- wxcli/migration/transform/analyzers/shared_line.py +170 -0
- wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
- wxcli/migration/transform/analyzers/workspace_license.py +212 -0
- wxcli/migration/transform/cross_reference.py +1136 -0
- wxcli/migration/transform/cucm_pattern.py +246 -0
- wxcli/migration/transform/decisions.py +301 -0
- wxcli/migration/transform/e164.py +145 -0
- wxcli/migration/transform/engine.py +256 -0
- wxcli/migration/transform/mappers/CLAUDE.md +123 -0
- wxcli/migration/transform/mappers/__init__.py +76 -0
- wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
- wxcli/migration/transform/mappers/base.py +190 -0
- wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
- wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
- wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
- wxcli/migration/transform/mappers/css_mapper.py +868 -0
- wxcli/migration/transform/mappers/dect_mapper.py +367 -0
- wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
- wxcli/migration/transform/mappers/device_mapper.py +227 -0
- wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
- wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
- wxcli/migration/transform/mappers/e911_mapper.py +129 -0
- wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
- wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
- wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
- wxcli/migration/transform/mappers/line_mapper.py +310 -0
- wxcli/migration/transform/mappers/location_mapper.py +235 -0
- wxcli/migration/transform/mappers/moh_mapper.py +114 -0
- wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
- wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
- wxcli/migration/transform/mappers/routing_mapper.py +821 -0
- wxcli/migration/transform/mappers/snr_mapper.py +201 -0
- wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
- wxcli/migration/transform/mappers/user_mapper.py +264 -0
- wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
- wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
- wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
- wxcli/migration/transform/normalizers.py +2008 -0
- wxcli/migration/transform/pattern_converter.py +106 -0
- wxcli/migration/transform/pipeline.py +271 -0
- wxcli/migration/transform/rules.py +255 -0
- wxcli/org_health/CLAUDE.md +63 -0
- wxcli/org_health/__init__.py +7 -0
- wxcli/org_health/analyze.py +93 -0
- wxcli/org_health/checks.py +458 -0
- wxcli/org_health/collector.py +62 -0
- wxcli/org_health/models.py +78 -0
- wxcli/org_health/report.py +300 -0
- wxcli/output.py +101 -0
- wxcli-1.2.0.dist-info/METADATA +505 -0
- wxcli-1.2.0.dist-info/RECORD +413 -0
- wxcli-1.2.0.dist-info/WHEEL +5 -0
- wxcli-1.2.0.dist-info/entry_points.txt +2 -0
- wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
- wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
- wxcli-1.2.0.dist-info/scm_version.json +8 -0
- 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
|