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,227 @@
|
|
|
1
|
+
"""Device mapper: CUCM Phones -> Webex Calling Devices.
|
|
2
|
+
|
|
3
|
+
Maps CUCM phone (SEP) devices to CanonicalDevice objects with four-tier
|
|
4
|
+
model compatibility classification. Skips common-area phones (those go to
|
|
5
|
+
workspace_mapper).
|
|
6
|
+
|
|
7
|
+
(from 03b-transform-mappers.md §4, device_mapper)
|
|
8
|
+
|
|
9
|
+
Cross-ref reads:
|
|
10
|
+
device_in_pool (Device -> DevicePool) — resolve pool
|
|
11
|
+
device_pool_to_location (DevicePool -> Location) — resolve location
|
|
12
|
+
device_has_dn (Device -> DN) — line appearances
|
|
13
|
+
device_owned_by_user (Device -> EndUser) — owner
|
|
14
|
+
|
|
15
|
+
Decisions generated:
|
|
16
|
+
DEVICE_INCOMPATIBLE — phone model in Incompatible tier
|
|
17
|
+
|
|
18
|
+
Convertible devices (``DeviceCompatibilityTier.CONVERTIBLE``) do NOT produce a
|
|
19
|
+
decision. Convertibility is a model classification; the planner unconditionally
|
|
20
|
+
emits a ``device:create_activation_code`` op for every convertible device.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from wxcli.migration.models import (
|
|
29
|
+
CanonicalDevice,
|
|
30
|
+
DecisionType,
|
|
31
|
+
DeviceCompatibilityTier,
|
|
32
|
+
MapperResult,
|
|
33
|
+
MigrationStatus,
|
|
34
|
+
)
|
|
35
|
+
from wxcli.migration.store import MigrationStore
|
|
36
|
+
from wxcli.migration.transform.cross_reference import classify_phone_model
|
|
37
|
+
from wxcli.migration.transform.mappers.base import (
|
|
38
|
+
Mapper,
|
|
39
|
+
accept_option,
|
|
40
|
+
decision_to_store_dict,
|
|
41
|
+
extract_provenance,
|
|
42
|
+
manual_option,
|
|
43
|
+
skip_option,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
# PhoneOS phones: 9800-series + 8875 use cloud deviceId ("cloud" surface).
|
|
49
|
+
# Classic MPP (6800-series, 7800-series, 8800-series) use callingDeviceId ("telephony").
|
|
50
|
+
from wxcli.migration.phone_models import PHONEOS_MODEL_NUMBERS as _CLOUD_SURFACE_SUBSTRINGS
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DeviceMapper(Mapper):
|
|
54
|
+
"""Maps CUCM Phones to Webex Calling Devices.
|
|
55
|
+
|
|
56
|
+
Skips common-area phones (is_common_area=True in pre_migration_state) —
|
|
57
|
+
those are handled by workspace_mapper.
|
|
58
|
+
|
|
59
|
+
(from 03b-transform-mappers.md §4)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
name = "device_mapper"
|
|
63
|
+
depends_on = ["location_mapper"]
|
|
64
|
+
|
|
65
|
+
def map(self, store: MigrationStore) -> MapperResult:
|
|
66
|
+
"""Read normalized phone objects and produce CanonicalDevice objects."""
|
|
67
|
+
result = MapperResult()
|
|
68
|
+
|
|
69
|
+
for phone_data in store.get_objects("phone"):
|
|
70
|
+
phone_id = phone_data["canonical_id"]
|
|
71
|
+
state = phone_data.get("pre_migration_state") or {}
|
|
72
|
+
|
|
73
|
+
# Skip common-area phones — workspace_mapper handles those
|
|
74
|
+
# (from 03b-transform-mappers.md §4: "does NOT process common-area phones")
|
|
75
|
+
if state.get("is_common_area", False):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
# --- Model classification ---
|
|
79
|
+
# Uses classify_phone_model() from cross_reference.py
|
|
80
|
+
# (from 03b-transform-mappers.md §4: phone model compatibility table)
|
|
81
|
+
model = state.get("model")
|
|
82
|
+
protocol = state.get("protocol", "SIP")
|
|
83
|
+
|
|
84
|
+
# SCCP-only phones are classified as Incompatible regardless of model
|
|
85
|
+
# (from 03b-transform-mappers.md §4: "SCCP-only phones classified as Incompatible")
|
|
86
|
+
if protocol and protocol.upper() == "SCCP":
|
|
87
|
+
compatibility_tier = DeviceCompatibilityTier.INCOMPATIBLE
|
|
88
|
+
else:
|
|
89
|
+
compatibility_tier = classify_phone_model(model)
|
|
90
|
+
|
|
91
|
+
# --- MAC extraction ---
|
|
92
|
+
# Strip "SEP" prefix from device name to get 12 hex digits
|
|
93
|
+
# (from 03b-transform-mappers.md §4: "Extract 12 hex digits after SEP prefix")
|
|
94
|
+
device_name = state.get("name", phone_id.replace("phone:", ""))
|
|
95
|
+
mac = None
|
|
96
|
+
if device_name.upper().startswith("SEP"):
|
|
97
|
+
mac = device_name[3:].upper()
|
|
98
|
+
elif len(device_name) == 12 and all(c in "0123456789ABCDEFabcdef" for c in device_name):
|
|
99
|
+
mac = device_name.upper()
|
|
100
|
+
|
|
101
|
+
# Cross-refs link device:{name}, not phone:{name}. Derive the device ID.
|
|
102
|
+
device_id = f"device:{device_name}"
|
|
103
|
+
|
|
104
|
+
# --- Location resolution ---
|
|
105
|
+
# Chain: device -> device_in_pool -> device_pool_to_location
|
|
106
|
+
# (from 03b-transform-mappers.md §4: cross-ref dependencies)
|
|
107
|
+
location_canonical_id = store.resolve_chain(
|
|
108
|
+
device_id, "device_in_pool", "device_pool_to_location"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# --- Owner resolution ---
|
|
112
|
+
# (from 03b-transform-mappers.md §4: "device_owned_by_user")
|
|
113
|
+
owner_refs = store.find_cross_refs(device_id, "device_owned_by_user")
|
|
114
|
+
owner_canonical_id = owner_refs[0] if owner_refs else None
|
|
115
|
+
|
|
116
|
+
# --- Line appearances ---
|
|
117
|
+
# (from 03b-transform-mappers.md §4: "line appearances stored for post-creation config")
|
|
118
|
+
dn_refs = store.find_cross_refs(device_id, "device_has_dn")
|
|
119
|
+
line_appearances = state.get("line_appearances", [])
|
|
120
|
+
|
|
121
|
+
# --- Display name ---
|
|
122
|
+
display_name = state.get("description") or device_name
|
|
123
|
+
|
|
124
|
+
# --- Device ID surface ---
|
|
125
|
+
# PhoneOS phones (9800-series, 8875) use cloud deviceId; all others use callingDeviceId.
|
|
126
|
+
device_id_surface = "cloud" if any(s in (model or "") for s in _CLOUD_SURFACE_SUBSTRINGS) else "telephony"
|
|
127
|
+
|
|
128
|
+
# --- Build CanonicalDevice ---
|
|
129
|
+
device = CanonicalDevice(
|
|
130
|
+
canonical_id=f"device:{device_name}",
|
|
131
|
+
provenance=extract_provenance(phone_data),
|
|
132
|
+
status=MigrationStatus.ANALYZED,
|
|
133
|
+
mac=mac,
|
|
134
|
+
model=model,
|
|
135
|
+
compatibility_tier=compatibility_tier,
|
|
136
|
+
display_name=display_name,
|
|
137
|
+
owner_canonical_id=owner_canonical_id,
|
|
138
|
+
location_canonical_id=location_canonical_id,
|
|
139
|
+
line_appearances=line_appearances,
|
|
140
|
+
cucm_protocol=protocol,
|
|
141
|
+
cucm_device_name=device_name,
|
|
142
|
+
device_id_surface=device_id_surface,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
store.upsert_object(device)
|
|
146
|
+
result.objects_created += 1
|
|
147
|
+
|
|
148
|
+
# --- Generate decisions for non-native devices ---
|
|
149
|
+
if compatibility_tier == DeviceCompatibilityTier.INFRASTRUCTURE:
|
|
150
|
+
# CTI Ports, CER, gateways, media resources — CUCM infrastructure,
|
|
151
|
+
# not user devices. Noted in inventory but no migration action needed.
|
|
152
|
+
# No decision generated — these are silently excluded from the plan.
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
elif compatibility_tier == DeviceCompatibilityTier.DECT:
|
|
156
|
+
# DECT handset — hardware is Webex-compatible but requires DECT
|
|
157
|
+
# network provisioning (Phase 2), not standard phone activation.
|
|
158
|
+
# No decision generated — the hardware itself is fine.
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
elif compatibility_tier == DeviceCompatibilityTier.WEBEX_APP:
|
|
162
|
+
# Software phones (Jabber, CSF, Dual Mode, IP Communicator) transition
|
|
163
|
+
# to Webex App. User keeps their number/extension, no device record needed
|
|
164
|
+
# in Webex — Webex App just works with a Calling license.
|
|
165
|
+
decision = self._create_decision(
|
|
166
|
+
store=store,
|
|
167
|
+
decision_type=DecisionType.DEVICE_INCOMPATIBLE,
|
|
168
|
+
severity="INFO",
|
|
169
|
+
summary=(
|
|
170
|
+
f"Soft client '{device_name}' ({model}) transitions to Webex App — "
|
|
171
|
+
f"no device migration needed"
|
|
172
|
+
),
|
|
173
|
+
context={
|
|
174
|
+
"device_id": device.canonical_id,
|
|
175
|
+
"device_name": device_name,
|
|
176
|
+
"model": model,
|
|
177
|
+
"protocol": protocol,
|
|
178
|
+
"compatibility_tier": compatibility_tier.value,
|
|
179
|
+
"webex_app_transition": True,
|
|
180
|
+
},
|
|
181
|
+
options=[
|
|
182
|
+
accept_option(
|
|
183
|
+
"User gets Webex Calling license — Webex App replaces "
|
|
184
|
+
f"{model}. Number/extension preserved for PSTN dialing."
|
|
185
|
+
),
|
|
186
|
+
manual_option("User also needs a physical phone (provision separately)"),
|
|
187
|
+
],
|
|
188
|
+
affected_objects=[device.canonical_id],
|
|
189
|
+
)
|
|
190
|
+
store.save_decision(decision_to_store_dict(decision))
|
|
191
|
+
result.decisions.append(decision)
|
|
192
|
+
|
|
193
|
+
elif compatibility_tier == DeviceCompatibilityTier.INCOMPATIBLE:
|
|
194
|
+
decision = self._create_decision(
|
|
195
|
+
store=store,
|
|
196
|
+
decision_type=DecisionType.DEVICE_INCOMPATIBLE,
|
|
197
|
+
severity="HIGH",
|
|
198
|
+
summary=(
|
|
199
|
+
f"Device '{device_name}' (model: {model or 'unknown'}, "
|
|
200
|
+
f"protocol: {protocol}) is incompatible with Webex Calling"
|
|
201
|
+
),
|
|
202
|
+
context={
|
|
203
|
+
"device_id": device.canonical_id,
|
|
204
|
+
"device_name": device_name,
|
|
205
|
+
"model": model,
|
|
206
|
+
"protocol": protocol,
|
|
207
|
+
"compatibility_tier": compatibility_tier.value,
|
|
208
|
+
},
|
|
209
|
+
options=[
|
|
210
|
+
skip_option("Migrate user without device — user must use Webex app or get new phone"),
|
|
211
|
+
manual_option("Replace with compatible MPP phone (68xx series recommended)"),
|
|
212
|
+
],
|
|
213
|
+
affected_objects=[device.canonical_id],
|
|
214
|
+
)
|
|
215
|
+
store.save_decision(decision_to_store_dict(decision))
|
|
216
|
+
result.decisions.append(decision)
|
|
217
|
+
|
|
218
|
+
elif compatibility_tier == DeviceCompatibilityTier.CONVERTIBLE:
|
|
219
|
+
# No decision produced. Convertibility is a phone model
|
|
220
|
+
# classification, not an operator choice. The planner
|
|
221
|
+
# unconditionally emits a device:create_activation_code op
|
|
222
|
+
# for convertible devices.
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""DeviceProfileMapper — CUCM Extension Mobility profiles → Webex hot desking advisory.
|
|
2
|
+
|
|
3
|
+
Reads device_profile objects from the store, resolves user ownership
|
|
4
|
+
(owner is the user whose default device profile this is), and produces
|
|
5
|
+
CanonicalDeviceProfile objects. If the user has a workspace at the same location,
|
|
6
|
+
the mapper flags it for hot desking enablement. If not, it produces a
|
|
7
|
+
FEATURE_APPROXIMATION decision.
|
|
8
|
+
|
|
9
|
+
CUCM EM is architecturally richer — profiles carry lines, speed dials, and services.
|
|
10
|
+
Webex hot desking is login/logout with the user's primary line only.
|
|
11
|
+
|
|
12
|
+
(from tier2-enterprise-expansion.md §5)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from wxcli.migration.models import (
|
|
22
|
+
CanonicalDeviceProfile,
|
|
23
|
+
DecisionType,
|
|
24
|
+
MigrationObject,
|
|
25
|
+
MigrationStatus,
|
|
26
|
+
MapperResult,
|
|
27
|
+
Provenance,
|
|
28
|
+
)
|
|
29
|
+
from wxcli.migration.store import MigrationStore
|
|
30
|
+
from wxcli.migration.transform.mappers.base import (
|
|
31
|
+
Mapper,
|
|
32
|
+
accept_option,
|
|
33
|
+
extract_provenance,
|
|
34
|
+
manual_option,
|
|
35
|
+
skip_option,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DeviceProfileMapper(Mapper):
|
|
42
|
+
"""Map CUCM Device Profiles to hot desking advisory objects.
|
|
43
|
+
|
|
44
|
+
Tier 2 expansion: produces CanonicalDeviceProfile objects and
|
|
45
|
+
FEATURE_APPROXIMATION decisions for EM → hot desking mapping.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
name = "device_profile_mapper"
|
|
49
|
+
depends_on = ["user_mapper", "workspace_mapper", "location_mapper"]
|
|
50
|
+
|
|
51
|
+
def map(self, store: MigrationStore) -> MapperResult:
|
|
52
|
+
result = MapperResult()
|
|
53
|
+
hoteling_locations: dict[str, int] = {} # loc_cid -> profile count
|
|
54
|
+
|
|
55
|
+
for dp_data in store.get_objects("device_profile"):
|
|
56
|
+
state = dp_data.get("pre_migration_state") or {}
|
|
57
|
+
profile_name = state.get("profile_name") or ""
|
|
58
|
+
if not profile_name:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
lines = state.get("lines") or []
|
|
62
|
+
model = state.get("model") or ""
|
|
63
|
+
protocol = state.get("protocol") or ""
|
|
64
|
+
device_pool = state.get("device_pool_name") or ""
|
|
65
|
+
sd_count = state.get("speed_dial_count", 0)
|
|
66
|
+
blf_count = state.get("blf_count", 0)
|
|
67
|
+
|
|
68
|
+
user_cid = self._find_profile_owner(store, profile_name)
|
|
69
|
+
|
|
70
|
+
# Resolve location from device pool cross-ref
|
|
71
|
+
location_cid: str | None = None
|
|
72
|
+
if device_pool:
|
|
73
|
+
dp_cid = f"device_pool:{device_pool}"
|
|
74
|
+
loc_refs = store.find_cross_refs(dp_cid, "device_pool_to_location")
|
|
75
|
+
if loc_refs:
|
|
76
|
+
location_cid = loc_refs[0] if isinstance(loc_refs[0], str) else loc_refs[0].get("target_id")
|
|
77
|
+
|
|
78
|
+
prov = extract_provenance(dp_data)
|
|
79
|
+
dp = CanonicalDeviceProfile(
|
|
80
|
+
canonical_id=f"device_profile:{profile_name}",
|
|
81
|
+
provenance=prov,
|
|
82
|
+
status=MigrationStatus.ANALYZED,
|
|
83
|
+
profile_name=profile_name,
|
|
84
|
+
user_canonical_id=user_cid,
|
|
85
|
+
model=model,
|
|
86
|
+
protocol=protocol,
|
|
87
|
+
lines=lines,
|
|
88
|
+
device_pool_name=device_pool,
|
|
89
|
+
speed_dial_count=sd_count,
|
|
90
|
+
blf_count=blf_count,
|
|
91
|
+
hoteling_guest_enabled=user_cid is not None,
|
|
92
|
+
auto_logout_minutes=0,
|
|
93
|
+
location_canonical_id=location_cid,
|
|
94
|
+
)
|
|
95
|
+
store.upsert_object(dp)
|
|
96
|
+
result.objects_created += 1
|
|
97
|
+
|
|
98
|
+
# Track locations for hoteling_location object creation
|
|
99
|
+
if location_cid:
|
|
100
|
+
hoteling_locations[location_cid] = hoteling_locations.get(location_cid, 0) + 1
|
|
101
|
+
|
|
102
|
+
if user_cid:
|
|
103
|
+
store.add_cross_ref(user_cid, dp.canonical_id, "user_has_device_profile")
|
|
104
|
+
|
|
105
|
+
# Decision: EM has features Webex hot desking doesn't support
|
|
106
|
+
has_multi_line = len(lines) > 1
|
|
107
|
+
has_extra_features = sd_count > 0 or blf_count > 0
|
|
108
|
+
|
|
109
|
+
if has_multi_line or has_extra_features:
|
|
110
|
+
features_lost = []
|
|
111
|
+
if has_multi_line:
|
|
112
|
+
features_lost.append(f"{len(lines)} line(s) — Webex hot desking uses primary line only")
|
|
113
|
+
if sd_count > 0:
|
|
114
|
+
features_lost.append(f"{sd_count} speed dial(s)")
|
|
115
|
+
if blf_count > 0:
|
|
116
|
+
features_lost.append(f"{blf_count} BLF entr(ies)")
|
|
117
|
+
|
|
118
|
+
decision = self._create_decision(
|
|
119
|
+
store=store,
|
|
120
|
+
decision_type=DecisionType.FEATURE_APPROXIMATION,
|
|
121
|
+
severity="LOW",
|
|
122
|
+
summary=(
|
|
123
|
+
f"Device profile '{profile_name}' has features that "
|
|
124
|
+
f"Webex hot desking cannot replicate"
|
|
125
|
+
),
|
|
126
|
+
context={
|
|
127
|
+
"profile_name": profile_name,
|
|
128
|
+
"user_id": user_cid,
|
|
129
|
+
"features_lost": features_lost,
|
|
130
|
+
"line_count": len(lines),
|
|
131
|
+
"speed_dial_count": sd_count,
|
|
132
|
+
"blf_count": blf_count,
|
|
133
|
+
"classification": "EXTENSION_MOBILITY",
|
|
134
|
+
},
|
|
135
|
+
options=[
|
|
136
|
+
accept_option("Enable hot desking — user gets primary line only, loses profile features"),
|
|
137
|
+
skip_option("Don't enable hot desking"),
|
|
138
|
+
manual_option("Admin creates workspace and configures manually"),
|
|
139
|
+
],
|
|
140
|
+
affected_objects=[dp.canonical_id] + ([user_cid] if user_cid else []),
|
|
141
|
+
)
|
|
142
|
+
result.decisions.append(decision)
|
|
143
|
+
|
|
144
|
+
# Create hoteling_location objects for location-level voice portal config
|
|
145
|
+
for loc_cid, profile_count in hoteling_locations.items():
|
|
146
|
+
loc_obj = MigrationObject(
|
|
147
|
+
canonical_id=f"hoteling_location:{loc_cid}",
|
|
148
|
+
provenance=Provenance(
|
|
149
|
+
source_system="cucm",
|
|
150
|
+
source_id=loc_cid,
|
|
151
|
+
source_name="hoteling_location",
|
|
152
|
+
extracted_at=datetime.now(timezone.utc),
|
|
153
|
+
),
|
|
154
|
+
status=MigrationStatus.ANALYZED,
|
|
155
|
+
pre_migration_state={
|
|
156
|
+
"location_canonical_id": loc_cid,
|
|
157
|
+
"em_profile_count": profile_count,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
store.upsert_object(loc_obj)
|
|
161
|
+
result.objects_created += 1
|
|
162
|
+
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _find_profile_owner(store: MigrationStore, profile_name: str) -> str | None:
|
|
167
|
+
"""Try to find the user who owns a device profile.
|
|
168
|
+
|
|
169
|
+
CUCM device profiles are typically named 'UDP-{userid}' or
|
|
170
|
+
'{model}-{userid}'. Also check cross-refs for user_has_device_profile
|
|
171
|
+
if already built.
|
|
172
|
+
"""
|
|
173
|
+
# Check common naming patterns
|
|
174
|
+
lower_name = profile_name.lower()
|
|
175
|
+
for prefix in ("udp-", "dp-", "em-"):
|
|
176
|
+
if lower_name.startswith(prefix):
|
|
177
|
+
userid = profile_name[len(prefix):]
|
|
178
|
+
user_cid = f"user:{userid}"
|
|
179
|
+
if store.get_object(user_cid):
|
|
180
|
+
return user_cid
|
|
181
|
+
|
|
182
|
+
# Brute force: check all users for matching profile reference
|
|
183
|
+
# (expensive but covers non-standard naming)
|
|
184
|
+
for user_data in store.get_objects("user"):
|
|
185
|
+
state = user_data.get("pre_migration_state") or {}
|
|
186
|
+
default_profile = state.get("defaultProfile") or state.get("deviceProfile") or ""
|
|
187
|
+
if default_profile == profile_name:
|
|
188
|
+
return user_data["canonical_id"]
|
|
189
|
+
|
|
190
|
+
return None
|