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,221 @@
|
|
|
1
|
+
"""MonitoringMapper — CUCM BLF → Webex per-person monitoring list.
|
|
2
|
+
|
|
3
|
+
Reads phone objects from the store (which contain busyLampFields from getPhone),
|
|
4
|
+
resolves user ownership via device_owned_by_user cross-ref, resolves BLF
|
|
5
|
+
targets to user canonical IDs via line cross-refs, and produces
|
|
6
|
+
CanonicalMonitoringList objects.
|
|
7
|
+
|
|
8
|
+
Speed dials are NOT mapped — they stay in phone metadata for reporting.
|
|
9
|
+
Webex monitoring list max is 50 entries; overflow produces FEATURE_APPROXIMATION.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from wxcli.migration.models import (
|
|
18
|
+
CanonicalMonitoringList,
|
|
19
|
+
DecisionType,
|
|
20
|
+
MigrationStatus,
|
|
21
|
+
)
|
|
22
|
+
from wxcli.migration.store import MigrationStore
|
|
23
|
+
from wxcli.migration.models import MapperResult
|
|
24
|
+
from wxcli.migration.transform.mappers.base import (
|
|
25
|
+
Mapper,
|
|
26
|
+
accept_option,
|
|
27
|
+
extract_provenance,
|
|
28
|
+
skip_option,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
WEBEX_MONITORING_MAX = 50
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _extract_blf_entries(state: dict[str, Any]) -> list[dict[str, Any]]:
|
|
37
|
+
"""Extract BLF entries from phone pre_migration_state.
|
|
38
|
+
|
|
39
|
+
Handles both raw AXL format (nested dict) and pre-normalized list.
|
|
40
|
+
"""
|
|
41
|
+
blf = state.get("busyLampFields")
|
|
42
|
+
if blf is None:
|
|
43
|
+
return []
|
|
44
|
+
if isinstance(blf, list):
|
|
45
|
+
return blf
|
|
46
|
+
if isinstance(blf, dict):
|
|
47
|
+
entries = blf.get("busyLampField", [])
|
|
48
|
+
if isinstance(entries, list):
|
|
49
|
+
return entries
|
|
50
|
+
if isinstance(entries, dict):
|
|
51
|
+
return [entries]
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _extract_speed_dials(state: dict[str, Any]) -> list[dict[str, Any]]:
|
|
56
|
+
"""Extract speed dial entries from phone pre_migration_state."""
|
|
57
|
+
sd = state.get("speeddials")
|
|
58
|
+
if sd is None:
|
|
59
|
+
return []
|
|
60
|
+
if isinstance(sd, list):
|
|
61
|
+
return sd
|
|
62
|
+
if isinstance(sd, dict):
|
|
63
|
+
entries = sd.get("speeddial", [])
|
|
64
|
+
if isinstance(entries, list):
|
|
65
|
+
return entries
|
|
66
|
+
if isinstance(entries, dict):
|
|
67
|
+
return [entries]
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MonitoringMapper(Mapper):
|
|
72
|
+
"""Map CUCM BLF entries to Webex per-person monitoring lists.
|
|
73
|
+
|
|
74
|
+
Tier 2 expansion: produces CanonicalMonitoringList objects and
|
|
75
|
+
FEATURE_APPROXIMATION decisions for overflow (>50 entries).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
name = "monitoring_mapper"
|
|
79
|
+
depends_on = ["user_mapper", "line_mapper"]
|
|
80
|
+
|
|
81
|
+
def map(self, store: MigrationStore) -> MapperResult:
|
|
82
|
+
result = MapperResult()
|
|
83
|
+
|
|
84
|
+
for phone_data in store.get_objects("phone"):
|
|
85
|
+
phone_id = phone_data["canonical_id"]
|
|
86
|
+
state = phone_data.get("pre_migration_state") or {}
|
|
87
|
+
|
|
88
|
+
blf_entries = _extract_blf_entries(state)
|
|
89
|
+
if not blf_entries:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Resolve owner user
|
|
93
|
+
user_refs = store.find_cross_refs(phone_id, "device_owned_by_user")
|
|
94
|
+
if not user_refs:
|
|
95
|
+
continue
|
|
96
|
+
user_id = user_refs[0]
|
|
97
|
+
user_name = user_id.split(":", 1)[-1]
|
|
98
|
+
|
|
99
|
+
# Skip if already created for this user (multi-device)
|
|
100
|
+
existing = store.get_object(f"monitoring_list:{user_name}")
|
|
101
|
+
if existing:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Resolve BLF targets
|
|
105
|
+
monitored_members = []
|
|
106
|
+
for entry in blf_entries:
|
|
107
|
+
if not isinstance(entry, dict):
|
|
108
|
+
continue
|
|
109
|
+
blf_dest = entry.get("blfDest") or entry.get("blfDirn") or ""
|
|
110
|
+
label = entry.get("label") or blf_dest
|
|
111
|
+
|
|
112
|
+
# Try to resolve BLF destination to a user
|
|
113
|
+
target_id = self._resolve_blf_target(store, blf_dest)
|
|
114
|
+
if target_id:
|
|
115
|
+
monitored_members.append({
|
|
116
|
+
"target_canonical_id": target_id,
|
|
117
|
+
"display_label": label,
|
|
118
|
+
})
|
|
119
|
+
else:
|
|
120
|
+
# Still add with unresolved marker
|
|
121
|
+
monitored_members.append({
|
|
122
|
+
"target_canonical_id": None,
|
|
123
|
+
"display_label": label,
|
|
124
|
+
"unresolved_dn": blf_dest,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
# Check overflow
|
|
128
|
+
truncated = False
|
|
129
|
+
total_count = len(monitored_members)
|
|
130
|
+
if total_count > WEBEX_MONITORING_MAX:
|
|
131
|
+
truncated = True
|
|
132
|
+
monitored_members = monitored_members[:WEBEX_MONITORING_MAX]
|
|
133
|
+
|
|
134
|
+
# Build canonical monitoring list
|
|
135
|
+
prov = extract_provenance(phone_data)
|
|
136
|
+
ml = CanonicalMonitoringList(
|
|
137
|
+
canonical_id=f"monitoring_list:{user_name}",
|
|
138
|
+
provenance=prov,
|
|
139
|
+
status=MigrationStatus.ANALYZED,
|
|
140
|
+
user_canonical_id=user_id,
|
|
141
|
+
monitored_members=monitored_members,
|
|
142
|
+
)
|
|
143
|
+
store.upsert_object(ml)
|
|
144
|
+
result.objects_created += 1
|
|
145
|
+
|
|
146
|
+
# Write cross-refs
|
|
147
|
+
store.add_cross_ref(user_id, ml.canonical_id, "user_has_monitoring_list")
|
|
148
|
+
for member in monitored_members:
|
|
149
|
+
target = member.get("target_canonical_id")
|
|
150
|
+
if target:
|
|
151
|
+
store.add_cross_ref(ml.canonical_id, target, "monitoring_watches")
|
|
152
|
+
|
|
153
|
+
# Decision: unresolved BLF targets
|
|
154
|
+
unresolved = [m for m in monitored_members if m.get("target_canonical_id") is None]
|
|
155
|
+
if unresolved:
|
|
156
|
+
decision = self._create_decision(
|
|
157
|
+
store=store,
|
|
158
|
+
decision_type=DecisionType.MISSING_DATA,
|
|
159
|
+
severity="LOW",
|
|
160
|
+
summary=(
|
|
161
|
+
f"User '{user_name}' has {len(unresolved)} BLF "
|
|
162
|
+
f"target(s) that cannot be resolved to a Webex user"
|
|
163
|
+
),
|
|
164
|
+
context={
|
|
165
|
+
"user_id": user_id,
|
|
166
|
+
"unresolved_dns": [m.get("unresolved_dn", "") for m in unresolved],
|
|
167
|
+
"total_blf_entries": total_count,
|
|
168
|
+
},
|
|
169
|
+
options=[
|
|
170
|
+
accept_option("Skip unresolved entries — exclude from monitoring list"),
|
|
171
|
+
skip_option("Skip entire monitoring list migration for this user"),
|
|
172
|
+
],
|
|
173
|
+
affected_objects=[user_id, ml.canonical_id],
|
|
174
|
+
)
|
|
175
|
+
result.decisions.append(decision)
|
|
176
|
+
|
|
177
|
+
# Decision: overflow
|
|
178
|
+
if truncated:
|
|
179
|
+
decision = self._create_decision(
|
|
180
|
+
store=store,
|
|
181
|
+
decision_type=DecisionType.FEATURE_APPROXIMATION,
|
|
182
|
+
severity="MEDIUM",
|
|
183
|
+
summary=(
|
|
184
|
+
f"User '{user_name}' has {total_count} BLF entries, "
|
|
185
|
+
f"exceeding Webex monitoring list max of {WEBEX_MONITORING_MAX}"
|
|
186
|
+
),
|
|
187
|
+
context={
|
|
188
|
+
"user_id": user_id,
|
|
189
|
+
"total_blf_entries": total_count,
|
|
190
|
+
"max_allowed": WEBEX_MONITORING_MAX,
|
|
191
|
+
"truncated_count": total_count - WEBEX_MONITORING_MAX,
|
|
192
|
+
},
|
|
193
|
+
options=[
|
|
194
|
+
accept_option(f"Truncate to first {WEBEX_MONITORING_MAX} entries"),
|
|
195
|
+
skip_option("Skip monitoring list migration for this user"),
|
|
196
|
+
],
|
|
197
|
+
affected_objects=[user_id, ml.canonical_id],
|
|
198
|
+
)
|
|
199
|
+
result.decisions.append(decision)
|
|
200
|
+
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
def _resolve_blf_target(self, store: MigrationStore, dn: str) -> str | None:
|
|
204
|
+
"""Try to resolve a BLF DN to a user canonical_id via line cross-refs."""
|
|
205
|
+
if not dn:
|
|
206
|
+
return None
|
|
207
|
+
# Look for a line object matching this DN
|
|
208
|
+
for line_data in store.get_objects("line"):
|
|
209
|
+
line_state = line_data.get("pre_migration_state") or {}
|
|
210
|
+
line_ext = (
|
|
211
|
+
line_data.get("extension")
|
|
212
|
+
or line_state.get("extension")
|
|
213
|
+
or line_state.get("pattern")
|
|
214
|
+
)
|
|
215
|
+
if line_ext == dn:
|
|
216
|
+
# Found the line — now find its owner via cross-ref
|
|
217
|
+
line_id = line_data["canonical_id"]
|
|
218
|
+
user_refs = store.find_cross_refs(line_id, "line_assigned_to_user")
|
|
219
|
+
if user_refs:
|
|
220
|
+
return user_refs[0]
|
|
221
|
+
return None
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""ReceptionistMapper — detect CUCM receptionist users from phone layout signals.
|
|
2
|
+
|
|
3
|
+
Scores each phone owner for receptionist likelihood using 4 signals:
|
|
4
|
+
1. BLF count (10+ = 2pts, 20+ = 3pts)
|
|
5
|
+
2. KEM presence (2pts)
|
|
6
|
+
3. Template name keywords (2pts)
|
|
7
|
+
4. Main number assignment (1pt)
|
|
8
|
+
|
|
9
|
+
Users scoring >= 3 produce a CanonicalReceptionistConfig object. The mapper
|
|
10
|
+
reads the existing MonitoringMapper output to populate monitored_members and
|
|
11
|
+
DeviceLayoutMapper output to detect KEM presence.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from wxcli.migration.models import (
|
|
20
|
+
CanonicalReceptionistConfig,
|
|
21
|
+
MapperResult,
|
|
22
|
+
MigrationStatus,
|
|
23
|
+
)
|
|
24
|
+
from wxcli.migration.store import MigrationStore
|
|
25
|
+
from wxcli.migration.transform.mappers.base import (
|
|
26
|
+
Mapper,
|
|
27
|
+
extract_provenance,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
RECEPTIONIST_THRESHOLD = 3
|
|
33
|
+
|
|
34
|
+
_TEMPLATE_KEYWORDS = ("attendant", "reception", "lobby", "operator", "front desk")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _extract_blf_entries(state: dict[str, Any]) -> list[dict[str, Any]]:
|
|
38
|
+
"""Extract BLF entries from phone pre_migration_state."""
|
|
39
|
+
blf = state.get("busyLampFields")
|
|
40
|
+
if blf is None:
|
|
41
|
+
return []
|
|
42
|
+
if isinstance(blf, list):
|
|
43
|
+
return blf
|
|
44
|
+
if isinstance(blf, dict):
|
|
45
|
+
entries = blf.get("busyLampField", [])
|
|
46
|
+
if isinstance(entries, list):
|
|
47
|
+
return entries
|
|
48
|
+
if isinstance(entries, dict):
|
|
49
|
+
return [entries]
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ReceptionistMapper(Mapper):
|
|
54
|
+
"""Detect CUCM receptionist users and produce CanonicalReceptionistConfig."""
|
|
55
|
+
|
|
56
|
+
name = "receptionist_mapper"
|
|
57
|
+
depends_on = [
|
|
58
|
+
"monitoring_mapper",
|
|
59
|
+
"device_layout_mapper",
|
|
60
|
+
"location_mapper",
|
|
61
|
+
"line_mapper",
|
|
62
|
+
"user_mapper",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def map(self, store: MigrationStore) -> MapperResult:
|
|
66
|
+
result = MapperResult()
|
|
67
|
+
seen_users: set[str] = set()
|
|
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
|
+
# Resolve owner
|
|
74
|
+
user_refs = store.find_cross_refs(phone_id, "device_owned_by_user")
|
|
75
|
+
if not user_refs:
|
|
76
|
+
continue
|
|
77
|
+
user_id = user_refs[0]
|
|
78
|
+
user_name = user_id.split(":", 1)[-1]
|
|
79
|
+
|
|
80
|
+
# Skip duplicate (multi-device)
|
|
81
|
+
if user_name in seen_users:
|
|
82
|
+
continue
|
|
83
|
+
seen_users.add(user_name)
|
|
84
|
+
|
|
85
|
+
# Score receptionist likelihood
|
|
86
|
+
score, reasons = self._score(store, user_id, phone_id, state)
|
|
87
|
+
if score < RECEPTIONIST_THRESHOLD:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
blf_entries = _extract_blf_entries(state)
|
|
91
|
+
blf_count = len(blf_entries)
|
|
92
|
+
|
|
93
|
+
# Get KEM info from device layout
|
|
94
|
+
phone_name = phone_id.split(":", 1)[-1]
|
|
95
|
+
layout = store.get_object(f"device_layout:{phone_name}")
|
|
96
|
+
layout_data = layout or {}
|
|
97
|
+
kem_keys = (
|
|
98
|
+
layout_data.get("resolved_kem_keys")
|
|
99
|
+
or (layout_data.get("pre_migration_state") or {}).get("resolved_kem_keys")
|
|
100
|
+
or []
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Get monitored members from existing monitoring list
|
|
104
|
+
monitored_members = self._get_monitored_members(store, user_name)
|
|
105
|
+
|
|
106
|
+
# Check main number
|
|
107
|
+
is_main = self._is_main_number_holder(store, user_id)
|
|
108
|
+
|
|
109
|
+
# Get location
|
|
110
|
+
user_obj = store.get_object(user_id)
|
|
111
|
+
user_data = user_obj or {}
|
|
112
|
+
location_cid = (
|
|
113
|
+
user_data.get("location_id")
|
|
114
|
+
or (user_data.get("pre_migration_state") or {}).get("location_id")
|
|
115
|
+
or ""
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
template_name = state.get("phoneTemplateName") or ""
|
|
119
|
+
|
|
120
|
+
prov = extract_provenance(phone_data)
|
|
121
|
+
config = CanonicalReceptionistConfig(
|
|
122
|
+
canonical_id=f"receptionist_config:{user_name}",
|
|
123
|
+
provenance=prov,
|
|
124
|
+
status=MigrationStatus.ANALYZED,
|
|
125
|
+
user_canonical_id=user_id,
|
|
126
|
+
location_canonical_id=location_cid,
|
|
127
|
+
blf_count=blf_count,
|
|
128
|
+
has_kem=bool(kem_keys),
|
|
129
|
+
kem_key_count=len(kem_keys),
|
|
130
|
+
template_name=template_name,
|
|
131
|
+
detection_score=score,
|
|
132
|
+
detection_reasons=reasons,
|
|
133
|
+
monitored_members=monitored_members,
|
|
134
|
+
is_main_number_holder=is_main,
|
|
135
|
+
)
|
|
136
|
+
store.upsert_object(config)
|
|
137
|
+
result.objects_created += 1
|
|
138
|
+
|
|
139
|
+
# Cross-refs
|
|
140
|
+
store.add_cross_ref(user_id, config.canonical_id, "user_has_receptionist_config")
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
def _score(
|
|
145
|
+
self,
|
|
146
|
+
store: MigrationStore,
|
|
147
|
+
user_id: str,
|
|
148
|
+
phone_id: str,
|
|
149
|
+
state: dict[str, Any],
|
|
150
|
+
) -> tuple[int, list[str]]:
|
|
151
|
+
"""Score a phone owner for receptionist likelihood."""
|
|
152
|
+
score = 0
|
|
153
|
+
reasons: list[str] = []
|
|
154
|
+
|
|
155
|
+
# Signal 1: BLF count
|
|
156
|
+
blf_entries = _extract_blf_entries(state)
|
|
157
|
+
blf_count = len(blf_entries)
|
|
158
|
+
if blf_count >= 20:
|
|
159
|
+
score += 3
|
|
160
|
+
reasons.append(f"{blf_count} BLF entries (20+)")
|
|
161
|
+
elif blf_count >= 10:
|
|
162
|
+
score += 2
|
|
163
|
+
reasons.append(f"{blf_count} BLF entries (10+)")
|
|
164
|
+
|
|
165
|
+
# Signal 2: KEM presence (via device layout)
|
|
166
|
+
phone_name = phone_id.split(":", 1)[-1]
|
|
167
|
+
layout = store.get_object(f"device_layout:{phone_name}")
|
|
168
|
+
if layout:
|
|
169
|
+
kem_keys = (
|
|
170
|
+
layout.get("resolved_kem_keys")
|
|
171
|
+
or (layout.get("pre_migration_state") or {}).get("resolved_kem_keys")
|
|
172
|
+
or []
|
|
173
|
+
)
|
|
174
|
+
if kem_keys:
|
|
175
|
+
score += 2
|
|
176
|
+
reasons.append(f"KEM with {len(kem_keys)} keys")
|
|
177
|
+
|
|
178
|
+
# Signal 3: Template name keywords
|
|
179
|
+
# AXL returns reference fields as dicts with _value_1 + uuid keys
|
|
180
|
+
raw_template = state.get("phoneTemplateName")
|
|
181
|
+
if isinstance(raw_template, dict):
|
|
182
|
+
raw_template = raw_template.get("_value_1", "")
|
|
183
|
+
template_name = (raw_template or "").lower()
|
|
184
|
+
for keyword in _TEMPLATE_KEYWORDS:
|
|
185
|
+
if keyword in template_name:
|
|
186
|
+
score += 2
|
|
187
|
+
reasons.append(f"Template name contains '{keyword}'")
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
# Signal 4: Main number assignment
|
|
191
|
+
if self._is_main_number_holder(store, user_id):
|
|
192
|
+
score += 1
|
|
193
|
+
reasons.append("Holds location main number")
|
|
194
|
+
|
|
195
|
+
return score, reasons
|
|
196
|
+
|
|
197
|
+
def _is_main_number_holder(self, store: MigrationStore, user_id: str) -> bool:
|
|
198
|
+
"""Check if the user holds a line matching their location's main number."""
|
|
199
|
+
loc_refs = store.find_cross_refs(user_id, "user_in_location")
|
|
200
|
+
if not loc_refs:
|
|
201
|
+
return False
|
|
202
|
+
loc_id = loc_refs[0]
|
|
203
|
+
loc_obj = store.get_object(loc_id)
|
|
204
|
+
if not loc_obj:
|
|
205
|
+
return False
|
|
206
|
+
main_number = (
|
|
207
|
+
loc_obj.get("main_number")
|
|
208
|
+
or (loc_obj.get("pre_migration_state") or {}).get("main_number")
|
|
209
|
+
or ""
|
|
210
|
+
)
|
|
211
|
+
if not main_number:
|
|
212
|
+
return False
|
|
213
|
+
# Check via user's own extension first
|
|
214
|
+
user_obj = store.get_object(user_id)
|
|
215
|
+
if user_obj:
|
|
216
|
+
user_state = user_obj.get("pre_migration_state") or {}
|
|
217
|
+
user_ext = user_state.get("extension") or ""
|
|
218
|
+
if user_ext == main_number:
|
|
219
|
+
return True
|
|
220
|
+
# Fallback: check via line cross-refs
|
|
221
|
+
for line_data in store.get_objects("line"):
|
|
222
|
+
line_state = line_data.get("pre_migration_state") or {}
|
|
223
|
+
line_ext = line_state.get("extension") or line_state.get("pattern") or ""
|
|
224
|
+
if line_ext == main_number:
|
|
225
|
+
line_id = line_data["canonical_id"]
|
|
226
|
+
user_refs = store.find_cross_refs(line_id, "line_assigned_to_user")
|
|
227
|
+
if user_id in user_refs:
|
|
228
|
+
return True
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
def _get_monitored_members(self, store: MigrationStore, user_name: str) -> list[str]:
|
|
232
|
+
"""Get resolved monitored member canonical IDs from the monitoring list."""
|
|
233
|
+
ml = store.get_object(f"monitoring_list:{user_name}")
|
|
234
|
+
if not ml:
|
|
235
|
+
return []
|
|
236
|
+
members = (
|
|
237
|
+
ml.get("monitored_members")
|
|
238
|
+
or (ml.get("pre_migration_state") or {}).get("monitored_members")
|
|
239
|
+
or []
|
|
240
|
+
)
|
|
241
|
+
return [
|
|
242
|
+
m["target_canonical_id"]
|
|
243
|
+
for m in members
|
|
244
|
+
if isinstance(m, dict) and m.get("target_canonical_id")
|
|
245
|
+
]
|