trueppm-api 0.4.0b2__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.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"""Export TruePPM project data to MS Project XML format."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
from datetime import date
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from trueppm_api.apps.msproject.extended_attributes import (
|
|
10
|
+
DURATION1_FIELD_ID,
|
|
11
|
+
DURATION2_FIELD_ID,
|
|
12
|
+
DURATION3_FIELD_ID,
|
|
13
|
+
DURATION4_FIELD_ID,
|
|
14
|
+
PERT_ALIAS_LABELS,
|
|
15
|
+
PERT_EXPECTED_FORMULA,
|
|
16
|
+
PERT_FIELD_NAMES,
|
|
17
|
+
)
|
|
18
|
+
from trueppm_api.apps.scheduling.units import (
|
|
19
|
+
HOURS_PER_WORKING_DAY,
|
|
20
|
+
MSPDI_LAG_TENTHS_PER_WORKING_DAY,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# MS Project XML namespace.
|
|
24
|
+
_NS = "http://schemas.microsoft.com/project"
|
|
25
|
+
|
|
26
|
+
# MSPDI <ConstraintType> codes this exporter emits (#2891). Named rather than
|
|
27
|
+
# inlined because the importer's mapping table (`CONSTRAINT_TYPES_APPLIED_AS_SNET`
|
|
28
|
+
# in `dataclasses`) has to stay in agreement with them for the round trip to close.
|
|
29
|
+
MSPDI_CONSTRAINT_AS_SOON_AS_POSSIBLE = 0
|
|
30
|
+
MSPDI_CONSTRAINT_START_NO_EARLIER_THAN = 4
|
|
31
|
+
|
|
32
|
+
# TruePPM dep_type -> MS Project PredecessorLink/Type.
|
|
33
|
+
_DEP_TYPE_TO_LINK_TYPE = {
|
|
34
|
+
"FF": "0",
|
|
35
|
+
"FS": "1",
|
|
36
|
+
"SF": "2",
|
|
37
|
+
"SS": "3",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# MSPDI WeekDay/DayType (1=Sunday … 7=Saturday) paired with the TruePPM
|
|
41
|
+
# Calendar.working_days bit (Mon=1 … Sun=64), in MSPDI emission order.
|
|
42
|
+
# Keep in lockstep with parser._DAY_TYPE_TO_BIT.
|
|
43
|
+
_DAY_TYPES_AND_BITS = (
|
|
44
|
+
("1", 64), # Sunday
|
|
45
|
+
("2", 1), # Monday
|
|
46
|
+
("3", 2), # Tuesday
|
|
47
|
+
("4", 4), # Wednesday
|
|
48
|
+
("5", 8), # Thursday
|
|
49
|
+
("6", 16), # Friday
|
|
50
|
+
("7", 32), # Saturday
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def export_project_xml(project_id: str) -> bytes:
|
|
55
|
+
"""Export a TruePPM project to MS Project XML format.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
UTF-8 encoded XML bytes.
|
|
59
|
+
"""
|
|
60
|
+
from trueppm_api.apps.projects.models import Dependency, Project, Task
|
|
61
|
+
from trueppm_api.apps.resources.models import Resource, TaskResource
|
|
62
|
+
from trueppm_api.apps.scheduling.calendars import resolve_applied_calendars
|
|
63
|
+
|
|
64
|
+
# calendar__exceptions + calendar_layers__calendar__exceptions: the calendar
|
|
65
|
+
# emission below walks every applied calendar's exceptions (#1769).
|
|
66
|
+
project = Project.objects.prefetch_related(
|
|
67
|
+
"calendar__exceptions", "calendar_layers__calendar__exceptions"
|
|
68
|
+
).get(pk=project_id)
|
|
69
|
+
applied_calendars = resolve_applied_calendars(project)
|
|
70
|
+
tasks = list(
|
|
71
|
+
Task.objects.filter(project_id=project_id, is_deleted=False).order_by("wbs_path", "name")
|
|
72
|
+
)
|
|
73
|
+
dependencies = list(
|
|
74
|
+
Dependency.objects.filter(
|
|
75
|
+
predecessor__project_id=project_id, is_deleted=False
|
|
76
|
+
).select_related("predecessor", "successor")
|
|
77
|
+
)
|
|
78
|
+
task_ids = [str(t.pk) for t in tasks]
|
|
79
|
+
assignments = list(TaskResource.objects.filter(task_id__in=task_ids).select_related("resource"))
|
|
80
|
+
resource_ids = {a.resource_id for a in assignments}
|
|
81
|
+
resources = list(Resource.objects.filter(pk__in=resource_ids))
|
|
82
|
+
|
|
83
|
+
# UID maps - MS Project expects sequential integer UIDs.
|
|
84
|
+
task_pk_to_uid = {str(t.pk): i for i, t in enumerate(tasks, start=1)}
|
|
85
|
+
resource_pk_to_uid = {str(r.pk): i for i, r in enumerate(resources, start=1)}
|
|
86
|
+
dep_by_successor = _build_dep_lookup(dependencies)
|
|
87
|
+
|
|
88
|
+
# --- Build XML ---
|
|
89
|
+
ET.register_namespace("", _NS)
|
|
90
|
+
root = ET.Element(f"{{{_NS}}}Project")
|
|
91
|
+
|
|
92
|
+
_sub_text(root, "Name", project.name)
|
|
93
|
+
_sub_text(root, "StartDate", _format_date(project.start_date))
|
|
94
|
+
if applied_calendars:
|
|
95
|
+
# The merged working calendar is always emitted as UID 1 (see
|
|
96
|
+
# _add_calendars_block). Header element order follows the MSPDI schema:
|
|
97
|
+
# CalendarUID sits among the project properties, before
|
|
98
|
+
# ExtendedAttributes and Calendars.
|
|
99
|
+
_sub_text(root, "CalendarUID", "1")
|
|
100
|
+
|
|
101
|
+
# --- Project-level ExtendedAttribute definitions (#798, ADR-0093) ---
|
|
102
|
+
# Emit the four PERT slots only when at least one non-summary, non-milestone
|
|
103
|
+
# task carries all three values. MS Project tolerates the block being
|
|
104
|
+
# absent; emitting it unconditionally would clutter files that don't use
|
|
105
|
+
# three-point estimates.
|
|
106
|
+
pert_tasks = _select_pert_tasks(tasks)
|
|
107
|
+
if pert_tasks:
|
|
108
|
+
_add_pert_extended_attribute_defs(root)
|
|
109
|
+
|
|
110
|
+
# --- Calendars (#1769) ---
|
|
111
|
+
# Emitted between ExtendedAttributes and Tasks, matching the MSPDI element
|
|
112
|
+
# order. Omitted entirely when the project has no applied calendar (the
|
|
113
|
+
# engine's implicit Mon-Fri/8h default), which MS Project also defaults to.
|
|
114
|
+
if applied_calendars:
|
|
115
|
+
_add_calendars_block(root, applied_calendars)
|
|
116
|
+
|
|
117
|
+
# --- Tasks ---
|
|
118
|
+
tasks_el = ET.SubElement(root, f"{{{_NS}}}Tasks")
|
|
119
|
+
|
|
120
|
+
# Task UID 0: project summary task
|
|
121
|
+
_add_summary_task(tasks_el, project)
|
|
122
|
+
|
|
123
|
+
pert_task_pks = {str(t.pk) for t in pert_tasks}
|
|
124
|
+
for task in tasks:
|
|
125
|
+
_add_task_element(
|
|
126
|
+
tasks_el,
|
|
127
|
+
task,
|
|
128
|
+
task_pk_to_uid[str(task.pk)],
|
|
129
|
+
is_pert=str(task.pk) in pert_task_pks,
|
|
130
|
+
preds=dep_by_successor.get(str(task.pk), []),
|
|
131
|
+
task_pk_to_uid=task_pk_to_uid,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
_add_resources_block(root, resources, resource_pk_to_uid)
|
|
135
|
+
_add_assignments_block(root, assignments, task_pk_to_uid, resource_pk_to_uid)
|
|
136
|
+
|
|
137
|
+
# Serialize
|
|
138
|
+
tree = ET.ElementTree(root)
|
|
139
|
+
ET.indent(tree, space=" ")
|
|
140
|
+
|
|
141
|
+
xml_decl = b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
142
|
+
return xml_decl + ET.tostring(root, encoding="unicode").encode("utf-8")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _build_dep_lookup(dependencies: list[Any]) -> dict[str, list[tuple[str, str, int]]]:
|
|
146
|
+
"""Index dependencies by successor: ``successor_pk -> [(pred_pk, dep_type, lag)]``."""
|
|
147
|
+
dep_by_successor: dict[str, list[tuple[str, str, int]]] = {}
|
|
148
|
+
for dep in dependencies:
|
|
149
|
+
dep_by_successor.setdefault(str(dep.successor_id), []).append(
|
|
150
|
+
(str(dep.predecessor_id), dep.dep_type, dep.lag)
|
|
151
|
+
)
|
|
152
|
+
return dep_by_successor
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _select_pert_tasks(tasks: list[Any]) -> list[Any]:
|
|
156
|
+
"""Return the leaf, non-milestone tasks carrying all three PERT durations.
|
|
157
|
+
|
|
158
|
+
The summary set is precomputed once for O(N) filtering — a summary or
|
|
159
|
+
milestone task never emits three-point values (ADR-0093 Q5).
|
|
160
|
+
"""
|
|
161
|
+
summary_pks = _summary_pks_from_tasks(tasks)
|
|
162
|
+
return [
|
|
163
|
+
t
|
|
164
|
+
for t in tasks
|
|
165
|
+
if (
|
|
166
|
+
str(t.pk) not in summary_pks
|
|
167
|
+
and not t.is_milestone
|
|
168
|
+
and t.optimistic_duration is not None
|
|
169
|
+
and t.most_likely_duration is not None
|
|
170
|
+
and t.pessimistic_duration is not None
|
|
171
|
+
)
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _add_task_dates(task_el: ET.Element, task: Any) -> None:
|
|
176
|
+
"""Emit the contiguous Start/Finish/Constraint/Actual date run for one ``<Task>``.
|
|
177
|
+
|
|
178
|
+
MSPDI is order-sensitive, and ``ET.SubElement`` appends at call time, so the
|
|
179
|
+
caller's invocation position *is* the positional guarantee for every element
|
|
180
|
+
below — it must stay exactly where it sits in ``_add_task_element``'s append
|
|
181
|
+
sequence (after ``Notes``, before the PERT ExtendedAttribute values).
|
|
182
|
+
|
|
183
|
+
Constraint + actuals round-trip (#2891). ``planned_start`` IS a
|
|
184
|
+
start-no-earlier-than floor, so emitting it as MSPDI ConstraintType 4 with a
|
|
185
|
+
ConstraintDate is a faithful translation rather than an approximation — and
|
|
186
|
+
it is what makes the round trip closed: the importer reads the constraint
|
|
187
|
+
date back as ``planned_start``. Emitting only <Start> (which is CPM output, and
|
|
188
|
+
per ADR-0132 a remaining-work date on a partially-complete task) meant a
|
|
189
|
+
re-import silently promoted computed dates to committed ones.
|
|
190
|
+
|
|
191
|
+
Tasks with no floor get ConstraintType 0 (As Soon As Possible), which is
|
|
192
|
+
TruePPM's own default: an omitted element would leave MS Project to infer a
|
|
193
|
+
constraint from the <Start> we emit, re-creating the same promotion.
|
|
194
|
+
"""
|
|
195
|
+
if task.early_start:
|
|
196
|
+
_sub_text(task_el, "Start", _format_date(task.early_start))
|
|
197
|
+
elif task.planned_start:
|
|
198
|
+
_sub_text(task_el, "Start", _format_date(task.planned_start))
|
|
199
|
+
if task.early_finish:
|
|
200
|
+
_sub_text(task_el, "Finish", _format_date(task.early_finish))
|
|
201
|
+
|
|
202
|
+
if task.planned_start:
|
|
203
|
+
_sub_text(task_el, "ConstraintType", str(MSPDI_CONSTRAINT_START_NO_EARLIER_THAN))
|
|
204
|
+
_sub_text(task_el, "ConstraintDate", _format_date(task.planned_start))
|
|
205
|
+
else:
|
|
206
|
+
_sub_text(task_el, "ConstraintType", str(MSPDI_CONSTRAINT_AS_SOON_AS_POSSIBLE))
|
|
207
|
+
if task.actual_start:
|
|
208
|
+
_sub_text(task_el, "ActualStart", _format_date(task.actual_start))
|
|
209
|
+
if task.actual_finish:
|
|
210
|
+
_sub_text(task_el, "ActualFinish", _format_date(task.actual_finish))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _add_task_element(
|
|
214
|
+
tasks_el: ET.Element,
|
|
215
|
+
task: Any,
|
|
216
|
+
uid: int,
|
|
217
|
+
*,
|
|
218
|
+
is_pert: bool,
|
|
219
|
+
preds: list[tuple[str, str, int]],
|
|
220
|
+
task_pk_to_uid: dict[str, int],
|
|
221
|
+
) -> None:
|
|
222
|
+
"""Emit one ``<Task>`` element, including PERT values and predecessor links."""
|
|
223
|
+
task_el = ET.SubElement(tasks_el, f"{{{_NS}}}Task")
|
|
224
|
+
_sub_text(task_el, "UID", str(uid))
|
|
225
|
+
_sub_text(task_el, "Name", task.name)
|
|
226
|
+
_sub_text(task_el, "Duration", _days_to_duration(task.duration))
|
|
227
|
+
_sub_text(task_el, "DurationFormat", "7")
|
|
228
|
+
if task.wbs_path:
|
|
229
|
+
_sub_text(task_el, "OutlineNumber", task.wbs_path)
|
|
230
|
+
_sub_text(task_el, "OutlineLevel", str(task.wbs_path.count(".") + 1))
|
|
231
|
+
else:
|
|
232
|
+
_sub_text(task_el, "OutlineLevel", "1")
|
|
233
|
+
_sub_text(task_el, "Milestone", "1" if task.is_milestone else "0")
|
|
234
|
+
# Task.percent_complete is already a 0-100 percent (#1759) and MSPDI
|
|
235
|
+
# PercentComplete is the same 0-100 scale, so emit it as-is. The earlier
|
|
236
|
+
# `* 100` turned a native 50% task into an invalid PercentComplete=5000.
|
|
237
|
+
_sub_text(task_el, "PercentComplete", str(round(task.percent_complete or 0)))
|
|
238
|
+
if task.notes:
|
|
239
|
+
_sub_text(task_el, "Notes", task.notes)
|
|
240
|
+
_add_task_dates(task_el, task)
|
|
241
|
+
|
|
242
|
+
# PERT three-point per-task values. Only emitted for the leaf, non-milestone
|
|
243
|
+
# tasks selected by _select_pert_tasks. Duration4 is the PERT-Expected
|
|
244
|
+
# formula slot; MS Project derives it on read from the project-level Formula
|
|
245
|
+
# definition, so we never emit a per-task Duration4 value.
|
|
246
|
+
if is_pert:
|
|
247
|
+
# All three are non-None by the _select_pert_tasks filter, but mypy
|
|
248
|
+
# can't carry that proof across the call boundary.
|
|
249
|
+
assert task.optimistic_duration is not None
|
|
250
|
+
assert task.most_likely_duration is not None
|
|
251
|
+
assert task.pessimistic_duration is not None
|
|
252
|
+
_add_pert_task_values(
|
|
253
|
+
task_el,
|
|
254
|
+
task.optimistic_duration,
|
|
255
|
+
task.most_likely_duration,
|
|
256
|
+
task.pessimistic_duration,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
for pred_pk, dep_type, lag in preds:
|
|
260
|
+
pred_uid = task_pk_to_uid.get(pred_pk)
|
|
261
|
+
if pred_uid is None:
|
|
262
|
+
continue
|
|
263
|
+
pred_el = ET.SubElement(task_el, f"{{{_NS}}}PredecessorLink")
|
|
264
|
+
_sub_text(pred_el, "PredecessorUID", str(pred_uid))
|
|
265
|
+
_sub_text(pred_el, "Type", _DEP_TYPE_TO_LINK_TYPE.get(dep_type, "1"))
|
|
266
|
+
# LinkLag is tenths of a minute; one working day of lag is
|
|
267
|
+
# MSPDI_LAG_TENTHS_PER_WORKING_DAY (#2290 unit seam).
|
|
268
|
+
_sub_text(pred_el, "LinkLag", str(lag * MSPDI_LAG_TENTHS_PER_WORKING_DAY))
|
|
269
|
+
_sub_text(pred_el, "LagFormat", "7")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _add_resources_block(
|
|
273
|
+
root: ET.Element, resources: list[Any], resource_pk_to_uid: dict[str, int]
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Emit the ``<Resources>`` block."""
|
|
276
|
+
resources_el = ET.SubElement(root, f"{{{_NS}}}Resources")
|
|
277
|
+
for resource in resources:
|
|
278
|
+
uid = resource_pk_to_uid[str(resource.pk)]
|
|
279
|
+
res_el = ET.SubElement(resources_el, f"{{{_NS}}}Resource")
|
|
280
|
+
_sub_text(res_el, "UID", str(uid))
|
|
281
|
+
_sub_text(res_el, "Name", resource.name)
|
|
282
|
+
# The resource's own catalog capacity, NOT its project-effective figure
|
|
283
|
+
# (#3574). MSPDI has no per-project override concept, so writing the
|
|
284
|
+
# effective value here would bake one project's slice into the resource
|
|
285
|
+
# default — and the importer reads MaxUnits straight back into
|
|
286
|
+
# ``Resource.max_units``, so a round trip would make the override permanent
|
|
287
|
+
# and global. The override is a TruePPM-side fact with no MSPDI counterpart.
|
|
288
|
+
_sub_text(res_el, "MaxUnits", f"{float(resource.max_units):.2f}")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _add_assignments_block(
|
|
292
|
+
root: ET.Element,
|
|
293
|
+
assignments: list[Any],
|
|
294
|
+
task_pk_to_uid: dict[str, int],
|
|
295
|
+
resource_pk_to_uid: dict[str, int],
|
|
296
|
+
) -> None:
|
|
297
|
+
"""Emit the ``<Assignments>`` block, skipping any dangling task/resource ref."""
|
|
298
|
+
assignments_el = ET.SubElement(root, f"{{{_NS}}}Assignments")
|
|
299
|
+
asgn_uid = 1
|
|
300
|
+
for asgn in assignments:
|
|
301
|
+
task_uid = task_pk_to_uid.get(str(asgn.task_id))
|
|
302
|
+
res_uid = resource_pk_to_uid.get(str(asgn.resource_id))
|
|
303
|
+
if task_uid is None or res_uid is None:
|
|
304
|
+
continue
|
|
305
|
+
asgn_el = ET.SubElement(assignments_el, f"{{{_NS}}}Assignment")
|
|
306
|
+
_sub_text(asgn_el, "UID", str(asgn_uid))
|
|
307
|
+
_sub_text(asgn_el, "TaskUID", str(task_uid))
|
|
308
|
+
_sub_text(asgn_el, "ResourceUID", str(res_uid))
|
|
309
|
+
_sub_text(asgn_el, "Units", f"{float(asgn.units):.2f}")
|
|
310
|
+
asgn_uid += 1
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _sub_text(parent: ET.Element, tag: str, text: str) -> ET.Element:
|
|
314
|
+
"""Add a child element with text content."""
|
|
315
|
+
el = ET.SubElement(parent, f"{{{_NS}}}{tag}")
|
|
316
|
+
el.text = text
|
|
317
|
+
return el
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _format_date(d: date) -> str:
|
|
321
|
+
"""Format a date as MS Project expects: YYYY-MM-DDT00:00:00."""
|
|
322
|
+
return f"{d.isoformat()}T00:00:00"
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _days_to_duration(days: int) -> str:
|
|
326
|
+
"""Convert working days to MS Project ISO 8601 duration.
|
|
327
|
+
|
|
328
|
+
Uses the nominal working-day length from the unit seam (#2290) rather than a
|
|
329
|
+
bare ``* 8`` so a 0.5 canonical-unit change re-homes in one place.
|
|
330
|
+
"""
|
|
331
|
+
hours = days * HOURS_PER_WORKING_DAY
|
|
332
|
+
return f"PT{hours}H0M0S"
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _minutes_to_time(minutes: int) -> str:
|
|
336
|
+
"""Format minutes-since-midnight as an MSPDI time-of-day (``HH:MM:00``)."""
|
|
337
|
+
return f"{minutes // 60:02d}:{minutes % 60:02d}:00"
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _add_calendars_block(root: ET.Element, applied_calendars: list[Any]) -> None:
|
|
341
|
+
"""Emit ``<Calendars>`` with one base calendar folding every applied layer (#1769).
|
|
342
|
+
|
|
343
|
+
MSPDI has no equivalent of TruePPM's composable calendar layers (#906,
|
|
344
|
+
ADR-0251), so the export flattens them exactly the way the CPM pass composes
|
|
345
|
+
them: weekday masks AND-ed, exception ranges unioned, ``hours_per_day`` and
|
|
346
|
+
name from the base (first) calendar. The exported file then schedules on the
|
|
347
|
+
same non-working mask TruePPM computed, at the cost of the layer structure.
|
|
348
|
+
|
|
349
|
+
TruePPM stores only a daily hour total, not shift times, so each working day
|
|
350
|
+
is synthesized as a single continuous shift starting at 08:00 (falling back
|
|
351
|
+
to midnight when the total would run past midnight). The caller must invoke
|
|
352
|
+
this before the ``<Tasks>`` block is appended — ``ET.SubElement`` appends at
|
|
353
|
+
call time, so call ordering is the positional guarantee (same contract as
|
|
354
|
+
``_add_pert_extended_attribute_defs``).
|
|
355
|
+
"""
|
|
356
|
+
base = applied_calendars[0]
|
|
357
|
+
mask, merged_exceptions = _merge_applied_calendars(applied_calendars)
|
|
358
|
+
|
|
359
|
+
cals_el = ET.SubElement(root, f"{{{_NS}}}Calendars")
|
|
360
|
+
cal_el = ET.SubElement(cals_el, f"{{{_NS}}}Calendar")
|
|
361
|
+
_sub_text(cal_el, "UID", "1")
|
|
362
|
+
_sub_text(cal_el, "Name", base.name)
|
|
363
|
+
_sub_text(cal_el, "IsBaseCalendar", "1")
|
|
364
|
+
_sub_text(cal_el, "BaseCalendarUID", "-1")
|
|
365
|
+
|
|
366
|
+
start_minute = 8 * 60
|
|
367
|
+
shift_minutes = max(0, min(round(base.hours_per_day * 60), 24 * 60))
|
|
368
|
+
if start_minute + shift_minutes > 24 * 60:
|
|
369
|
+
start_minute = 0
|
|
370
|
+
|
|
371
|
+
_add_weekday_elements(cal_el, mask, start_minute, shift_minutes)
|
|
372
|
+
if merged_exceptions:
|
|
373
|
+
_add_calendar_exception_elements(cal_el, merged_exceptions)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _merge_applied_calendars(
|
|
377
|
+
applied_calendars: list[Any],
|
|
378
|
+
) -> tuple[int, dict[tuple[Any, Any], str]]:
|
|
379
|
+
"""Fold the applied calendar layers into one working mask and exception set.
|
|
380
|
+
|
|
381
|
+
Weekday masks are AND-ed and exception ranges unioned, matching how the CPM
|
|
382
|
+
pass composes layers. First writer wins on a duplicate range so the base
|
|
383
|
+
calendar's description (not an overlay's) labels the exception.
|
|
384
|
+
"""
|
|
385
|
+
mask = applied_calendars[0].working_days
|
|
386
|
+
merged_exceptions: dict[tuple[Any, Any], str] = {}
|
|
387
|
+
for cal in applied_calendars:
|
|
388
|
+
mask &= cal.working_days
|
|
389
|
+
for exc in cal.exceptions.all():
|
|
390
|
+
merged_exceptions.setdefault((exc.exc_start, exc.exc_end), exc.description)
|
|
391
|
+
return mask, merged_exceptions
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _add_weekday_elements(
|
|
395
|
+
cal_el: ET.Element, mask: int, start_minute: int, shift_minutes: int
|
|
396
|
+
) -> None:
|
|
397
|
+
"""Emit the seven ``<WeekDay>`` entries, each with a single synthesized shift."""
|
|
398
|
+
weekdays_el = ET.SubElement(cal_el, f"{{{_NS}}}WeekDays")
|
|
399
|
+
for day_type, bit in _DAY_TYPES_AND_BITS:
|
|
400
|
+
day_el = ET.SubElement(weekdays_el, f"{{{_NS}}}WeekDay")
|
|
401
|
+
_sub_text(day_el, "DayType", day_type)
|
|
402
|
+
working = bool(mask & bit)
|
|
403
|
+
_sub_text(day_el, "DayWorking", "1" if working else "0")
|
|
404
|
+
if working and shift_minutes > 0:
|
|
405
|
+
times_el = ET.SubElement(day_el, f"{{{_NS}}}WorkingTimes")
|
|
406
|
+
wt_el = ET.SubElement(times_el, f"{{{_NS}}}WorkingTime")
|
|
407
|
+
_sub_text(wt_el, "FromTime", _minutes_to_time(start_minute))
|
|
408
|
+
_sub_text(wt_el, "ToTime", _minutes_to_time(start_minute + shift_minutes))
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _add_calendar_exception_elements(
|
|
412
|
+
cal_el: ET.Element, merged_exceptions: dict[tuple[Any, Any], str]
|
|
413
|
+
) -> None:
|
|
414
|
+
"""Emit the ``<Exceptions>`` block for the merged non-working date ranges."""
|
|
415
|
+
excs_el = ET.SubElement(cal_el, f"{{{_NS}}}Exceptions")
|
|
416
|
+
for (exc_start, exc_end), description in sorted(merged_exceptions.items()):
|
|
417
|
+
exc_el = ET.SubElement(excs_el, f"{{{_NS}}}Exception")
|
|
418
|
+
period_el = ET.SubElement(exc_el, f"{{{_NS}}}TimePeriod")
|
|
419
|
+
_sub_text(period_el, "FromDate", f"{exc_start.isoformat()}T00:00:00")
|
|
420
|
+
_sub_text(period_el, "ToDate", f"{exc_end.isoformat()}T23:59:00")
|
|
421
|
+
if description:
|
|
422
|
+
_sub_text(exc_el, "Name", description)
|
|
423
|
+
_sub_text(exc_el, "DayWorking", "0")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _add_summary_task(tasks_el: ET.Element, project: object) -> None:
|
|
427
|
+
"""Add the UID 0 project summary task."""
|
|
428
|
+
task_el = ET.SubElement(tasks_el, f"{{{_NS}}}Task")
|
|
429
|
+
_sub_text(task_el, "UID", "0")
|
|
430
|
+
_sub_text(task_el, "Name", project.name) # type: ignore[attr-defined]
|
|
431
|
+
_sub_text(task_el, "OutlineLevel", "0")
|
|
432
|
+
_sub_text(task_el, "OutlineNumber", "0")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _summary_pks_from_tasks(tasks: list[Any]) -> set[str]:
|
|
436
|
+
"""Return the set of task primary keys that are summary tasks.
|
|
437
|
+
|
|
438
|
+
A task is a summary when another task's ``wbs_path`` is strictly descended
|
|
439
|
+
from this one's (e.g. ``"1"`` is a summary if any task has path ``"1.1"``).
|
|
440
|
+
O(N) over the task list: build the set of strict ancestor paths from every
|
|
441
|
+
leaf path in one pass, then mark every task whose own path is in that set.
|
|
442
|
+
"""
|
|
443
|
+
ancestor_paths: set[str] = set()
|
|
444
|
+
for t in tasks:
|
|
445
|
+
path = getattr(t, "wbs_path", None)
|
|
446
|
+
if not path:
|
|
447
|
+
continue
|
|
448
|
+
path = str(path)
|
|
449
|
+
parts = path.split(".")
|
|
450
|
+
for end in range(1, len(parts)):
|
|
451
|
+
ancestor_paths.add(".".join(parts[:end]))
|
|
452
|
+
return {
|
|
453
|
+
str(t.pk)
|
|
454
|
+
for t in tasks
|
|
455
|
+
if getattr(t, "wbs_path", None) and str(t.wbs_path) in ancestor_paths
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _add_pert_extended_attribute_defs(root: ET.Element) -> None:
|
|
460
|
+
"""Emit the four PERT ExtendedAttribute definitions at project level.
|
|
461
|
+
|
|
462
|
+
Duration1–3 carry the aliases Optimistic / Most Likely / Pessimistic.
|
|
463
|
+
Duration4 carries the formula MS Project uses to derive PERT Expected
|
|
464
|
+
on file open. The caller is responsible for invoking this **before** the
|
|
465
|
+
``<Tasks>`` block is appended, so the resulting element order
|
|
466
|
+
(``<ExtendedAttributes>`` then ``<Tasks>``) matches MS Project's own
|
|
467
|
+
element order. ``ET.SubElement`` appends at call time, so call ordering
|
|
468
|
+
is the positional guarantee — don't move this call after ``<Tasks>``.
|
|
469
|
+
"""
|
|
470
|
+
ea_block = ET.SubElement(root, f"{{{_NS}}}ExtendedAttributes")
|
|
471
|
+
for fid in (DURATION1_FIELD_ID, DURATION2_FIELD_ID, DURATION3_FIELD_ID, DURATION4_FIELD_ID):
|
|
472
|
+
ea = ET.SubElement(ea_block, f"{{{_NS}}}ExtendedAttribute")
|
|
473
|
+
_sub_text(ea, "FieldID", fid)
|
|
474
|
+
_sub_text(ea, "FieldName", PERT_FIELD_NAMES[fid])
|
|
475
|
+
_sub_text(ea, "Alias", PERT_ALIAS_LABELS[fid])
|
|
476
|
+
if fid == DURATION4_FIELD_ID:
|
|
477
|
+
_sub_text(ea, "Formula", PERT_EXPECTED_FORMULA)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _add_pert_task_values(
|
|
481
|
+
task_el: ET.Element,
|
|
482
|
+
optimistic_days: int,
|
|
483
|
+
most_likely_days: int,
|
|
484
|
+
pessimistic_days: int,
|
|
485
|
+
) -> None:
|
|
486
|
+
"""Emit per-task <ExtendedAttribute> values for Duration1/2/3.
|
|
487
|
+
|
|
488
|
+
Values are encoded as ISO-8601 ``PT{hours}H0M0S`` (8h working day) using
|
|
489
|
+
the same convention as the primary Duration field. ``DurationFormat=7``
|
|
490
|
+
marks the value as a duration in days for MS Project's UI.
|
|
491
|
+
"""
|
|
492
|
+
for fid, days in (
|
|
493
|
+
(DURATION1_FIELD_ID, optimistic_days),
|
|
494
|
+
(DURATION2_FIELD_ID, most_likely_days),
|
|
495
|
+
(DURATION3_FIELD_ID, pessimistic_days),
|
|
496
|
+
):
|
|
497
|
+
ea = ET.SubElement(task_el, f"{{{_NS}}}ExtendedAttribute")
|
|
498
|
+
_sub_text(ea, "FieldID", fid)
|
|
499
|
+
_sub_text(ea, "Value", _days_to_duration(days))
|
|
500
|
+
_sub_text(ea, "DurationFormat", "7")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""MS Project ExtendedAttribute FieldID constants and PERT mapping.
|
|
2
|
+
|
|
3
|
+
These FieldIDs are the canonical MS Project Custom Field identifiers for the
|
|
4
|
+
idiomatic three-point / PERT estimate convention. They are sourced from MPXJ's
|
|
5
|
+
``TaskField`` enum (via the mpp-sample-generator's ``field_id_reference.md``)
|
|
6
|
+
and cross-checked against Microsoft's one documented anchor:
|
|
7
|
+
``pjCustomTaskText1 = 188743731``
|
|
8
|
+
(https://learn.microsoft.com/en-us/previous-versions/office/developer/office-2007/bb968474(v=office.12)).
|
|
9
|
+
|
|
10
|
+
The Duration family is **non-contiguous** within the Custom Field enumeration:
|
|
11
|
+
``Duration1``–``Duration3`` sit at 188743783–785 (contiguous), but ``Duration4``
|
|
12
|
+
(the PERT-Expected formula slot) lives 170 IDs higher at 188743955, in a
|
|
13
|
+
separate sub-range. Inferring one Duration FieldID by offset arithmetic from
|
|
14
|
+
another is wrong. Use these named constants — never a range.
|
|
15
|
+
|
|
16
|
+
ADR-0093 covers the import/export mapping decisions (alias-vs-FieldID
|
|
17
|
+
precedence, all-or-none rule, summary/milestone skipping, round-trip
|
|
18
|
+
tolerance).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
# Locked FieldIDs (sourced, not guessed). Strings because MSPDI carries them
|
|
24
|
+
# as text and we compare against text from the parsed XML.
|
|
25
|
+
DURATION1_FIELD_ID = "188743783" # Optimistic
|
|
26
|
+
DURATION2_FIELD_ID = "188743784" # Most Likely
|
|
27
|
+
DURATION3_FIELD_ID = "188743785" # Pessimistic
|
|
28
|
+
DURATION4_FIELD_ID = "188743955" # PERT Expected (formula slot, not imported)
|
|
29
|
+
|
|
30
|
+
# Semantic role -> FieldID. Duration4 is informational only: the formula slot
|
|
31
|
+
# is derived from the other three at file-read time by MS Project, so the
|
|
32
|
+
# importer never reads its value. The exporter still emits the definition so
|
|
33
|
+
# Project knows the alias and formula.
|
|
34
|
+
PERT_FIELD_IDS: dict[str, str] = {
|
|
35
|
+
"optimistic": DURATION1_FIELD_ID,
|
|
36
|
+
"most_likely": DURATION2_FIELD_ID,
|
|
37
|
+
"pessimistic": DURATION3_FIELD_ID,
|
|
38
|
+
"expected": DURATION4_FIELD_ID,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# FieldID -> the role it should play (used during import to recognize standard
|
|
42
|
+
# definitions).
|
|
43
|
+
PERT_ROLE_BY_FIELD_ID: dict[str, str] = {v: k for k, v in PERT_FIELD_IDS.items()}
|
|
44
|
+
|
|
45
|
+
# Canonical alias labels emitted on export.
|
|
46
|
+
PERT_ALIAS_LABELS: dict[str, str] = {
|
|
47
|
+
DURATION1_FIELD_ID: "Optimistic",
|
|
48
|
+
DURATION2_FIELD_ID: "Most Likely",
|
|
49
|
+
DURATION3_FIELD_ID: "Pessimistic",
|
|
50
|
+
DURATION4_FIELD_ID: "PERT Expected",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Canonical FieldName labels (Duration1..4). MS Project uses these as the
|
|
54
|
+
# native field identifiers; the Alias is the user-visible label.
|
|
55
|
+
PERT_FIELD_NAMES: dict[str, str] = {
|
|
56
|
+
DURATION1_FIELD_ID: "Duration1",
|
|
57
|
+
DURATION2_FIELD_ID: "Duration2",
|
|
58
|
+
DURATION3_FIELD_ID: "Duration3",
|
|
59
|
+
DURATION4_FIELD_ID: "Duration4",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Duration4 carries the PERT expected formula so MS Project re-derives the
|
|
63
|
+
# value on file open. Not a TruePPM field.
|
|
64
|
+
PERT_EXPECTED_FORMULA = "([Duration1] + 4*[Duration2] + [Duration3]) / 6"
|
|
65
|
+
|
|
66
|
+
# Substring tokens for alias confirmation (case-insensitive, first-match).
|
|
67
|
+
# These let us spot when a file has repurposed Duration1/2/3 with an alias
|
|
68
|
+
# that explicitly contradicts the PERT role (e.g. Duration1 aliased
|
|
69
|
+
# "Risk Score") and refuse the import for that field rather than silently
|
|
70
|
+
# treat unrelated values as estimates. Empty / missing aliases are treated
|
|
71
|
+
# as "no contradiction" — we trust the FieldID.
|
|
72
|
+
PERT_ALIAS_TOKENS: dict[str, tuple[str, ...]] = {
|
|
73
|
+
DURATION1_FIELD_ID: ("optimistic",),
|
|
74
|
+
DURATION2_FIELD_ID: ("most likely", "most-likely"),
|
|
75
|
+
DURATION3_FIELD_ID: ("pessimistic",),
|
|
76
|
+
DURATION4_FIELD_ID: ("pert", "expected"),
|
|
77
|
+
}
|