trueppm-api 0.4.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.
- trueppm_api/__init__.py +27 -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 +346 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +184 -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_squashed_0019_release_0_4.py +302 -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 +143 -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/minter_role.py +38 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +3263 -0
- trueppm_api/apps/access/serializers.py +1007 -0
- trueppm_api/apps/access/services.py +878 -0
- trueppm_api/apps/access/signals.py +171 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +96 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1965 -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/0001_squashed_0003_release_0_4.py +344 -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 +109 -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/0001_squashed_0003_release_0_4.py +90 -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 +459 -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 +178 -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 +863 -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 +597 -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_squashed_0014_release_0_4.py +225 -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/0014_tasklink_created_by.py +27 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +635 -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 +528 -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 +734 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1158 -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 +280 -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 +551 -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 +1276 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +439 -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 +462 -0
- trueppm_api/apps/notifications/email_backend.py +350 -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 +299 -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_squashed_0011_release_0_4.py +204 -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 +1148 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +1094 -0
- trueppm_api/apps/notifications/services.py +1489 -0
- trueppm_api/apps/notifications/tasks.py +762 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +919 -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 +566 -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 +993 -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 +537 -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_squashed_0009_release_0_4.py +128 -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 +541 -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 +458 -0
- trueppm_api/apps/projects/backfill.py +459 -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 +212 -0
- trueppm_api/apps/projects/config_notice.py +1330 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +132 -0
- trueppm_api/apps/projects/decisions_views.py +97 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +423 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -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/key_views.py +468 -0
- trueppm_api/apps/projects/keys.py +205 -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 +514 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -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/migration_helpers.py +75 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -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 +59 -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 +75 -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 +76 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -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/0154_object_keys.py +184 -0
- trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
- trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
- trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +9209 -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 +903 -0
- trueppm_api/apps/projects/program_schedule.py +767 -0
- trueppm_api/apps/projects/program_views.py +3445 -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 +492 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
- trueppm_api/apps/projects/seed/exporter.py +1717 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2764 -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 +128 -0
- trueppm_api/apps/projects/seed/replay.py +1695 -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 +1169 -0
- trueppm_api/apps/projects/serializers.py +12904 -0
- trueppm_api/apps/projects/services.py +7525 -0
- trueppm_api/apps/projects/share_serializers.py +142 -0
- trueppm_api/apps/projects/share_services.py +373 -0
- trueppm_api/apps/projects/share_views.py +395 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +910 -0
- trueppm_api/apps/projects/signal_privacy_views.py +485 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +288 -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 +1244 -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 +1979 -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 +261 -0
- trueppm_api/apps/projects/template_views.py +829 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1016 -0
- trueppm_api/apps/projects/utilization.py +909 -0
- trueppm_api/apps/projects/views.py +21225 -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/0014_squashed_0015_release_0_4.py +47 -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 +1752 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +144 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/finish_reading.py +198 -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/mc_admission.py +181 -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/0010_squashed_0016_release_0_4.py +106 -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/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +542 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +334 -0
- trueppm_api/apps/scheduling/serializers.py +723 -0
- trueppm_api/apps/scheduling/services.py +1251 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +2248 -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 +2908 -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 +446 -0
- trueppm_api/apps/sso/services.py +1473 -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 +369 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +548 -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/0003_squashed_0005_release_0_4.py +77 -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/0006_boardevent_per_project_seq.py +77 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +272 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +488 -0
- trueppm_api/apps/sync/serializers.py +688 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +556 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +957 -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 +159 -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/0004_backfill_default_teams_on_upgrade.py +49 -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 +440 -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/0001_squashed_0004_release_0_4.py +167 -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 +211 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +597 -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 +279 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -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 +100 -0
- trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
- trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +437 -0
- trueppm_api/apps/webhooks/serializers.py +326 -0
- trueppm_api/apps/webhooks/tasks.py +680 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +533 -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 +371 -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 +254 -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_squashed_0027_release_0_4.py +199 -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 +967 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +616 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1581 -0
- trueppm_api/asgi.py +49 -0
- trueppm_api/celery.py +40 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +934 -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/demo_read_only.py +314 -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 +1221 -0
- trueppm_api/core/password_reset.py +513 -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 +1034 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +514 -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/core/worker_selfheal.py +205 -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 +2605 -0
- trueppm_api/settings/dev.py +296 -0
- trueppm_api/settings/prod.py +265 -0
- trueppm_api/urls.py +217 -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.0.dist-info/METADATA +273 -0
- trueppm_api-0.4.0.dist-info/RECORD +799 -0
- trueppm_api-0.4.0.dist-info/WHEEL +4 -0
- trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
|
File without changes
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""App config for scheduling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from django.apps import AppConfig
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# Wall-clock start time (time.monotonic(), immune to system clock adjustments) for
|
|
14
|
+
# each in-flight Celery task execution, keyed by task_id. Populated in
|
|
15
|
+
# _on_task_prerun and consumed (popped) in _on_task_postrun — that pair always
|
|
16
|
+
# brackets one execution 1:1, including retried and failed ones, so duration is
|
|
17
|
+
# available for every outcome, not just success (#1917). Celery itself does not
|
|
18
|
+
# set a usable `.runtime` attribute on the request for anything but the success
|
|
19
|
+
# path, which is why this is timed here instead of read off `task.request`.
|
|
20
|
+
_task_start_times: dict[str, float] = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SchedulingConfig(AppConfig):
|
|
24
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
25
|
+
name = "trueppm_api.apps.scheduling"
|
|
26
|
+
|
|
27
|
+
def ready(self) -> None:
|
|
28
|
+
"""Bridge Celery framework signals to Django signals.
|
|
29
|
+
|
|
30
|
+
Enterprise packages connect receivers to the Django signals in
|
|
31
|
+
scheduling.signals — they never need to import from Celery directly.
|
|
32
|
+
|
|
33
|
+
Every ``.connect(...)`` below now passes ``weak=False`` (#1917 fix). Celery's
|
|
34
|
+
``Signal.connect`` defaults to a *weak* reference to the receiver, and the
|
|
35
|
+
four ``_on_task_*`` functions are closures local to this method call — with
|
|
36
|
+
no other strong reference anywhere, they were garbage-collected the moment
|
|
37
|
+
``ready()`` returned, silently turning the whole Celery→Django task-lifecycle
|
|
38
|
+
bridge into dead code (confirmed via ``task_prerun.receivers`` showing a
|
|
39
|
+
dead weakref immediately after startup). None of ``celery_task_started`` /
|
|
40
|
+
``_succeeded`` / ``_failed`` / ``_retried`` were ever actually being sent in
|
|
41
|
+
a real worker, which is why #1917 found no start/success/retry log lines to
|
|
42
|
+
begin with — this was the root cause, not just a missing receiver. A stable
|
|
43
|
+
``dispatch_uid`` per connect call is required alongside ``weak=False``: without
|
|
44
|
+
it, a second ``ready()`` call (the test runner / autoreloader can trigger one)
|
|
45
|
+
would register a *second*, now-permanent strong-ref closure rather than being
|
|
46
|
+
recognized as the same receiver, doubling every log line and metric sample
|
|
47
|
+
from then on.
|
|
48
|
+
"""
|
|
49
|
+
from celery.signals import task_failure, task_postrun, task_prerun, task_retry
|
|
50
|
+
|
|
51
|
+
# Register the OSS dead-letter alerting receiver (ADR-0084) and the
|
|
52
|
+
# start/success/retry structured-log receivers (#1917). Side-effect
|
|
53
|
+
# import: the @receiver decorators in receivers.py connect on load.
|
|
54
|
+
from trueppm_api.apps.observability.otel.metrics import record_task_duration
|
|
55
|
+
from trueppm_api.apps.scheduling import receivers # noqa: F401
|
|
56
|
+
from trueppm_api.apps.scheduling.signals import (
|
|
57
|
+
celery_task_failed,
|
|
58
|
+
celery_task_retried,
|
|
59
|
+
celery_task_started,
|
|
60
|
+
celery_task_succeeded,
|
|
61
|
+
)
|
|
62
|
+
from trueppm_api.core.extension_signals import dispatch_extension_signal
|
|
63
|
+
|
|
64
|
+
self._register_workflows()
|
|
65
|
+
|
|
66
|
+
@task_prerun.connect(weak=False, dispatch_uid="scheduling.on_task_prerun") # type: ignore[untyped-decorator]
|
|
67
|
+
def _on_task_prerun(sender: Any, task_id: str, task: Any, **kwargs: Any) -> None:
|
|
68
|
+
_task_start_times[task_id] = time.monotonic()
|
|
69
|
+
# sender=None: these signals are about a worker run, not a model row,
|
|
70
|
+
# so there is no class for the dispatcher to filter on. Receivers
|
|
71
|
+
# branch on the task_name kwarg (#3777).
|
|
72
|
+
dispatch_extension_signal(
|
|
73
|
+
celery_task_started,
|
|
74
|
+
sender=None,
|
|
75
|
+
task_id=task_id,
|
|
76
|
+
task_name=getattr(task, "name", ""),
|
|
77
|
+
args=kwargs.get("args", ()),
|
|
78
|
+
kwargs=kwargs.get("kwargs", {}),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@task_postrun.connect(weak=False, dispatch_uid="scheduling.on_task_postrun") # type: ignore[untyped-decorator]
|
|
82
|
+
def _on_task_postrun(
|
|
83
|
+
sender: Any, task_id: str, task: Any, retval: Any, **kwargs: Any
|
|
84
|
+
) -> None:
|
|
85
|
+
# task_postrun fires for every terminal state (success, failure, retry,
|
|
86
|
+
# rejected, ignored) — unlike celery_task_succeeded below, the duration
|
|
87
|
+
# metric is recorded for all of them so a stuck/slow retry loop is
|
|
88
|
+
# visible in the histogram too, not just clean runs.
|
|
89
|
+
state = kwargs.get("state", "") or ""
|
|
90
|
+
task_name = getattr(task, "name", "")
|
|
91
|
+
start = _task_start_times.pop(task_id, None)
|
|
92
|
+
duration = time.monotonic() - start if start is not None else 0.0
|
|
93
|
+
record_task_duration(
|
|
94
|
+
task_name=task_name,
|
|
95
|
+
duration_seconds=duration,
|
|
96
|
+
outcome=state.lower() or "unknown",
|
|
97
|
+
)
|
|
98
|
+
if state == "SUCCESS":
|
|
99
|
+
dispatch_extension_signal(
|
|
100
|
+
celery_task_succeeded,
|
|
101
|
+
sender=None,
|
|
102
|
+
task_id=task_id,
|
|
103
|
+
task_name=task_name,
|
|
104
|
+
runtime_seconds=duration,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@task_failure.connect(weak=False, dispatch_uid="scheduling.on_task_failure") # type: ignore[untyped-decorator]
|
|
108
|
+
def _on_task_failure(
|
|
109
|
+
sender: Any, task_id: str, exception: BaseException, **kwargs: Any
|
|
110
|
+
) -> None:
|
|
111
|
+
import traceback as tb_module
|
|
112
|
+
|
|
113
|
+
dispatch_extension_signal(
|
|
114
|
+
celery_task_failed,
|
|
115
|
+
sender=None,
|
|
116
|
+
task_id=task_id,
|
|
117
|
+
task_name=getattr(sender, "name", ""),
|
|
118
|
+
exception=exception,
|
|
119
|
+
traceback_str="".join(
|
|
120
|
+
tb_module.format_exception(type(exception), exception, exception.__traceback__)
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@task_retry.connect(weak=False, dispatch_uid="scheduling.on_task_retry") # type: ignore[untyped-decorator]
|
|
125
|
+
def _on_task_retry(sender: Any, request: Any, reason: Any, **kwargs: Any) -> None:
|
|
126
|
+
dispatch_extension_signal(
|
|
127
|
+
celery_task_retried,
|
|
128
|
+
sender=None,
|
|
129
|
+
task_id=getattr(request, "id", ""),
|
|
130
|
+
task_name=getattr(sender, "name", ""),
|
|
131
|
+
attempt=getattr(request, "retries", 0),
|
|
132
|
+
exception=reason,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _register_workflows() -> None:
|
|
137
|
+
"""Register the scheduling app's durable workflow definitions (ADR-0080 §A).
|
|
138
|
+
|
|
139
|
+
The requeue-failed-task workflow (ADR-0210) is the first consumer of the
|
|
140
|
+
durable backend. Registered here from the owning app's ``ready()`` — the
|
|
141
|
+
scheduling app owns ``FailedTask`` and already bridges Celery signals in
|
|
142
|
+
``ready()``, so the workflow lives with its domain rather than in the
|
|
143
|
+
backend-neutral engine app. Guarded against duplicate registration because
|
|
144
|
+
``ready()`` can run more than once under the test runner and the registry
|
|
145
|
+
raises on a duplicate name.
|
|
146
|
+
"""
|
|
147
|
+
from trueppm_api.workflows.consumers.requeue_failed_task import (
|
|
148
|
+
RequeueFailedTaskWorkflow,
|
|
149
|
+
)
|
|
150
|
+
from trueppm_api.workflows.registry import WORKFLOWS
|
|
151
|
+
|
|
152
|
+
if RequeueFailedTaskWorkflow.name not in WORKFLOWS.all():
|
|
153
|
+
WORKFLOWS.register(RequeueFailedTaskWorkflow())
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Composable working-calendar resolution (#906, ADR-0251).
|
|
2
|
+
|
|
3
|
+
A project's effective non-working mask for CPM is the overlay (union) of several
|
|
4
|
+
applied calendars: a base ``Project.calendar`` plus zero or more
|
|
5
|
+
``ProjectCalendarLayer`` overlays. A day is non-working if *any* applied calendar
|
|
6
|
+
marks it so — masks AND-composed, exception ranges unioned.
|
|
7
|
+
|
|
8
|
+
The fold itself lives in ``trueppm_scheduler.models.Calendar.compose`` (the Apache
|
|
9
|
+
package). This module is the Django-side seam: it resolves *which* calendars apply
|
|
10
|
+
to a project (through a small resolver registry) and converts them to scheduler
|
|
11
|
+
dataclasses via the single ``build_sched_calendar`` primitive.
|
|
12
|
+
|
|
13
|
+
The resolver registry is the one-way (enterprise → core) extension point
|
|
14
|
+
(ADR-0251 §6): OSS registers exactly one resolver (base + overlay layers);
|
|
15
|
+
enterprise appends more (per-resource, cross-program calendars) against the same
|
|
16
|
+
signature. Because composition is AND-of-masks + union-of-exceptions, any added
|
|
17
|
+
resolver is always additive and order-independent, so a new source can never be a
|
|
18
|
+
breaking change to an existing project's schedule.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Callable, Iterable
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
|
+
|
|
26
|
+
from trueppm_api.apps.scheduling.services import build_sched_calendar
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from trueppm_api.apps.projects.models import Calendar, Project
|
|
30
|
+
|
|
31
|
+
# A resolver maps a project to the ordered Django ``Calendar`` rows it applies.
|
|
32
|
+
CalendarLayerResolver = Callable[["Project"], "list[Calendar]"]
|
|
33
|
+
|
|
34
|
+
_LAYER_RESOLVERS: list[CalendarLayerResolver] = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def register_calendar_layer_resolver(resolver: CalendarLayerResolver) -> None:
|
|
38
|
+
"""Register a resolver that contributes calendars to a project's composed mask.
|
|
39
|
+
|
|
40
|
+
Idempotent: registering the same resolver twice is a no-op, so an
|
|
41
|
+
``AppConfig.ready()`` that runs more than once (or a re-imported test module)
|
|
42
|
+
cannot double-count a source.
|
|
43
|
+
"""
|
|
44
|
+
if resolver not in _LAYER_RESOLVERS:
|
|
45
|
+
_LAYER_RESOLVERS.append(resolver)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def oss_project_layers(project: Project) -> list[Calendar]:
|
|
49
|
+
"""OSS resolver: the effective base calendar plus its overlay layers, in order.
|
|
50
|
+
|
|
51
|
+
The base is resolved through the inheritance chain (ADR-0441): the project's own
|
|
52
|
+
``calendar`` if set, else its program's, else the workspace default — computed-on-read
|
|
53
|
+
in ``apps.projects.calendar_settings`` so CPM and the serializers can never disagree
|
|
54
|
+
on which calendar a project schedules against. ``None`` up the whole chain means the
|
|
55
|
+
system default, so ``compose`` falls back to Mon-Fri/8h/UTC exactly as before.
|
|
56
|
+
|
|
57
|
+
Overlays (``ProjectCalendarLayer``, ADR-0251) still stack on top of the inherited
|
|
58
|
+
base — inheritance only decides the base; it does not change overlay composition.
|
|
59
|
+
|
|
60
|
+
Callers must have prefetched ``calendar__exceptions``,
|
|
61
|
+
``calendar_layers__calendar__exceptions`` and — so the inherited tiers don't N+1 —
|
|
62
|
+
``program__calendar__exceptions`` when the project may inherit from its program.
|
|
63
|
+
"""
|
|
64
|
+
from trueppm_api.apps.projects.calendar_settings import resolve_effective_base_calendar
|
|
65
|
+
|
|
66
|
+
calendars: list[Calendar] = []
|
|
67
|
+
base = resolve_effective_base_calendar(project)
|
|
68
|
+
if base is not None:
|
|
69
|
+
calendars.append(base)
|
|
70
|
+
# Ordered by ProjectCalendarLayer.Meta.ordering = ["sort_order"].
|
|
71
|
+
for layer in project.calendar_layers.all():
|
|
72
|
+
calendars.append(layer.calendar)
|
|
73
|
+
return calendars
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Registered here (module import) rather than in AppConfig.ready() because the
|
|
77
|
+
# OSS resolver has no app-loading dependency — importing this module is what wires
|
|
78
|
+
# it up. Enterprise registers its extra resolvers from its own AppConfig.ready().
|
|
79
|
+
register_calendar_layer_resolver(oss_project_layers)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_applied_calendars(project: Project) -> list[Calendar]:
|
|
83
|
+
"""The de-duplicated, ordered list of every ``Calendar`` applied to a project.
|
|
84
|
+
|
|
85
|
+
Folds the output of every registered resolver into one ordered list, dropping
|
|
86
|
+
a calendar already contributed by an earlier resolver (the base calendar, for
|
|
87
|
+
instance, must not be counted twice if a later resolver also returns it). The
|
|
88
|
+
first occurrence — the base calendar under the OSS resolver — leads, so its
|
|
89
|
+
scalar ``hours_per_day``/``timezone`` win in :func:`compose_project_calendar`.
|
|
90
|
+
"""
|
|
91
|
+
seen: set[Any] = set()
|
|
92
|
+
result: list[Calendar] = []
|
|
93
|
+
for resolver in _LAYER_RESOLVERS:
|
|
94
|
+
for cal in resolver(project):
|
|
95
|
+
if cal.id not in seen:
|
|
96
|
+
seen.add(cal.id)
|
|
97
|
+
result.append(cal)
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def compose_project_calendar(project: Project) -> Any:
|
|
102
|
+
"""Fold a project's applied calendars into one scheduler ``Calendar`` (#906).
|
|
103
|
+
|
|
104
|
+
``working_days`` = bitwise-AND of every applied mask; ``exceptions`` = union of
|
|
105
|
+
every applied calendar's ``CalendarException`` ranges; ``hours_per_day`` /
|
|
106
|
+
``timezone`` from the base (first) calendar. Compute-on-read (ADR-0251 §3): the
|
|
107
|
+
fold is O(layers) and the expensive per-day lookup is already indexed inside
|
|
108
|
+
the scheduler ``Calendar``. A project with no applied calendars falls back to
|
|
109
|
+
the Mon-Fri/8h/UTC default, identical to ``build_sched_calendar(None)``.
|
|
110
|
+
|
|
111
|
+
Every scheduling call site (CPM pass, Monte Carlo, program schedule) routes
|
|
112
|
+
through this one helper so composition can never drift between engines. Callers
|
|
113
|
+
must prefetch ``calendar__exceptions`` and
|
|
114
|
+
``calendar_layers__calendar__exceptions``.
|
|
115
|
+
"""
|
|
116
|
+
from trueppm_scheduler.models import Calendar as SchedCalendar
|
|
117
|
+
|
|
118
|
+
applied = resolve_applied_calendars(project)
|
|
119
|
+
return SchedCalendar.compose([build_sched_calendar(c) for c in applied])
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def project_sched_calendars(project_ids: Iterable[Any]) -> dict[str, Any]:
|
|
123
|
+
"""Composed scheduler ``Calendar`` per project, keyed by ``str(project_id)`` (#4197).
|
|
124
|
+
|
|
125
|
+
One project query plus a fixed set of prefetches for any number of projects,
|
|
126
|
+
so a read that compares finishes across a page of projects or milestones in
|
|
127
|
+
working time (:mod:`.finish_reading`) composes each calendar once instead of
|
|
128
|
+
once per row. A project that no longer exists is absent from the map.
|
|
129
|
+
"""
|
|
130
|
+
from trueppm_api.apps.projects.models import Project
|
|
131
|
+
|
|
132
|
+
ids = {pid for pid in project_ids if pid is not None}
|
|
133
|
+
if not ids:
|
|
134
|
+
return {}
|
|
135
|
+
projects = (
|
|
136
|
+
Project.objects.filter(pk__in=ids)
|
|
137
|
+
.select_related("calendar", "program__calendar")
|
|
138
|
+
.prefetch_related(
|
|
139
|
+
"calendar__exceptions",
|
|
140
|
+
"calendar_layers__calendar__exceptions",
|
|
141
|
+
"program__calendar__exceptions",
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
return {str(p.pk): compose_project_calendar(p) for p in projects}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Dead-letter helper — records permanently failed Celery tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import traceback as tb_module
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def record_failed_task(
|
|
12
|
+
task_name: str,
|
|
13
|
+
task_id: str,
|
|
14
|
+
args: list[object] | tuple[object, ...],
|
|
15
|
+
kwargs: dict[str, object],
|
|
16
|
+
exception: BaseException,
|
|
17
|
+
project_id: str | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Write a FailedTask row for a permanently failed Celery task.
|
|
20
|
+
|
|
21
|
+
Called from the on_failure handler when max_retries is exhausted.
|
|
22
|
+
Safe to call from within a Celery worker — uses its own DB connection.
|
|
23
|
+
|
|
24
|
+
On the *terminal* transition (a FailedTask row is newly created) this fires
|
|
25
|
+
``celery_task_permanently_failed`` so OSS logs an alert and Enterprise can
|
|
26
|
+
register PagerDuty/Slack receivers (ADR-0084). A repeat dead-letter of the
|
|
27
|
+
same ``task_id`` only bumps ``failure_count`` and does **not** re-alert, so
|
|
28
|
+
the signal means "newly dead-lettered", not "failed again". ``project_id``
|
|
29
|
+
is threaded through from the caller when known (it is not derivable here).
|
|
30
|
+
"""
|
|
31
|
+
from trueppm_api.apps.scheduling.models import FailedTask, FailedTaskStatus
|
|
32
|
+
from trueppm_api.apps.scheduling.signals import celery_task_permanently_failed
|
|
33
|
+
from trueppm_api.core.extension_signals import dispatch_extension_signal
|
|
34
|
+
|
|
35
|
+
exc_type = type(exception).__qualname__
|
|
36
|
+
exc_msg = str(exception)
|
|
37
|
+
exc_tb = "".join(
|
|
38
|
+
tb_module.format_exception(type(exception), exception, exception.__traceback__)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
defaults: dict[str, object] = {
|
|
42
|
+
"task_name": task_name,
|
|
43
|
+
"args": list(args) if args else [],
|
|
44
|
+
"kwargs": dict(kwargs) if kwargs else {},
|
|
45
|
+
"exception_type": exc_type,
|
|
46
|
+
"exception_message": exc_msg,
|
|
47
|
+
"traceback": exc_tb,
|
|
48
|
+
"status": FailedTaskStatus.DEAD,
|
|
49
|
+
}
|
|
50
|
+
# Only set project_id when this call actually knows it (#1917).
|
|
51
|
+
# update_or_create's defaults overwrite unconditionally, so unconditionally
|
|
52
|
+
# including a bare `None` here would erase a project attribution recorded by
|
|
53
|
+
# an earlier failure of the same task_id the moment a later re-failure's
|
|
54
|
+
# call site didn't have project_id in scope.
|
|
55
|
+
if project_id is not None:
|
|
56
|
+
defaults["project_id"] = project_id
|
|
57
|
+
|
|
58
|
+
obj, created = FailedTask.objects.update_or_create(
|
|
59
|
+
task_id=task_id,
|
|
60
|
+
defaults=defaults,
|
|
61
|
+
)
|
|
62
|
+
if not created:
|
|
63
|
+
FailedTask.objects.filter(pk=obj.pk).update(
|
|
64
|
+
failure_count=obj.failure_count + 1,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
logger.error(
|
|
68
|
+
"Dead-lettered task %s (%s): %s: %s",
|
|
69
|
+
task_name,
|
|
70
|
+
task_id,
|
|
71
|
+
exc_type,
|
|
72
|
+
exc_msg,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if created:
|
|
76
|
+
# A misbehaving receiver (incl. enterprise PagerDuty/Slack) must never
|
|
77
|
+
# break the dead-letter recording path.
|
|
78
|
+
#
|
|
79
|
+
# sender=None: this signal is about a worker run, not a model row. This
|
|
80
|
+
# site is also why the Celery family could not use a class even if one
|
|
81
|
+
# were wanted — the dead-letter recorder has the task's name and no task
|
|
82
|
+
# object at all. Receivers branch on the task_name kwarg (#3777).
|
|
83
|
+
dispatch_extension_signal(
|
|
84
|
+
celery_task_permanently_failed,
|
|
85
|
+
sender=None,
|
|
86
|
+
task_id=task_id,
|
|
87
|
+
task_name=task_name,
|
|
88
|
+
exception=exception,
|
|
89
|
+
traceback_str=exc_tb,
|
|
90
|
+
project_id=project_id,
|
|
91
|
+
)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Compare two project finishes in working time, not by their shown day (#4178).
|
|
2
|
+
|
|
3
|
+
``ScheduleResult.project_finish`` and a milestone's ``early_finish`` are *days*,
|
|
4
|
+
and the engine chooses which edge of that day a zero-duration milestone sits on
|
|
5
|
+
(``Task.milestone_at_day_end``, #4079). After #4173 a milestone that lands just
|
|
6
|
+
after non-working time is shown at the **start of the next working day**, while
|
|
7
|
+
one that follows work is shown at the **end of its day**. The end of Friday and
|
|
8
|
+
the start of the following Monday are the same position in working time, so the
|
|
9
|
+
shown finish can jump Friday -> Monday while the working-time finish does not move
|
|
10
|
+
at all. Diffing the two shown days as calendar days reports that as a three-day
|
|
11
|
+
slip — a phantom.
|
|
12
|
+
|
|
13
|
+
This module turns a shown finish ``(day, at_day_start)`` into the day whose *end*
|
|
14
|
+
is the same position in working time — a start-of-day finish becomes the end of
|
|
15
|
+
the last working day before it — and diffs those. A finish whose shown day hops
|
|
16
|
+
non-working time without moving in working time therefore gets a delta of zero.
|
|
17
|
+
Every delta is still reported in calendar days, so the unit of
|
|
18
|
+
``end_date_shift_threshold_days`` and of the activity feed's ``finish +Nd`` does
|
|
19
|
+
not change, and a finish that is the end of its day diffs exactly as before.
|
|
20
|
+
|
|
21
|
+
Why the reading is reconstructed here rather than read from the engine: the engine
|
|
22
|
+
computes the project's finish *instant* internally but exposes only the day and the
|
|
23
|
+
per-task ``milestone_at_day_end`` flag, and #4178 is scoped to consumers, not to the
|
|
24
|
+
engine's public surface.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
30
|
+
from datetime import date, timedelta
|
|
31
|
+
from types import SimpleNamespace
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
#: A shown finish: ``(day, at_day_start)``. ``at_day_start`` True means the finish
|
|
35
|
+
#: is the *start* of ``day`` (a start-of-day milestone), False the end of it, and
|
|
36
|
+
#: ``None`` that the reading is unknown — a snapshot row written before the reading
|
|
37
|
+
#: was recorded (see :func:`finish_shift_days`).
|
|
38
|
+
Finish = tuple[date, bool | None]
|
|
39
|
+
|
|
40
|
+
#: The ``Task`` columns :func:`task_finish_at_day_start` reads. Name them in any
|
|
41
|
+
#: ``.only()`` / ``.values()`` that feeds it, or each deferred read is a query.
|
|
42
|
+
FINISH_READING_FIELDS: tuple[str, ...] = (
|
|
43
|
+
"is_milestone",
|
|
44
|
+
"milestone_at_day_end",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def task_finish_at_day_start(task: Any, *, milestone_at_day_end: bool | None = None) -> bool:
|
|
49
|
+
"""Whether a task's ``early_finish`` is the *start* of that day.
|
|
50
|
+
|
|
51
|
+
Mirrors the engine's own rule (``trueppm_scheduler.engine._finish_instant``):
|
|
52
|
+
a milestone the engine did not put at the end of its day
|
|
53
|
+
(``milestone_at_day_end`` False) ends at the start of its day, and work always
|
|
54
|
+
ends at the end of its finish day. That holds for a completed milestone too,
|
|
55
|
+
and the flag alone decides it: the engine pins it (``_pinned_placement``) but
|
|
56
|
+
keeps it an instant — with no actuals at the start of its pinned day (#4343);
|
|
57
|
+
with actuals on its recorded day, at the edge of that day it had live (#4433),
|
|
58
|
+
so the end of the day when completed work finishing that day placed it or the
|
|
59
|
+
last live run showed it at the end of that day (#4525), the start otherwise.
|
|
60
|
+
(Before #4433 the engine never flagged a milestone pinned by actuals, and this
|
|
61
|
+
function derived an end-of-day reading from its actuals instead.)
|
|
62
|
+
|
|
63
|
+
This is the engine's *forecast* reading. The schedule-variance metrics read a
|
|
64
|
+
recorded ``actual_finish`` as the end of its day on purpose — a variance
|
|
65
|
+
convention shared by ``ProjectTaskSerializer.get_schedule_variance_days``,
|
|
66
|
+
``program_rollup._schedule_variance_by_project`` and the sprint-close
|
|
67
|
+
milestone slip — and do not call this function for it.
|
|
68
|
+
|
|
69
|
+
``milestone_at_day_end`` overrides the task's own flag, for reading a finish
|
|
70
|
+
the task held before a recalculation overwrote it.
|
|
71
|
+
|
|
72
|
+
``task`` is a Django ``Task`` (milestone = ``is_milestone``) or an engine
|
|
73
|
+
``trueppm_scheduler.Task`` from a ``ScheduleResult``, which has no
|
|
74
|
+
``is_milestone`` and is a milestone when its ``duration`` is zero.
|
|
75
|
+
"""
|
|
76
|
+
if milestone_at_day_end is None:
|
|
77
|
+
milestone_at_day_end = bool(getattr(task, "milestone_at_day_end", False))
|
|
78
|
+
is_milestone = getattr(task, "is_milestone", None)
|
|
79
|
+
if is_milestone is None:
|
|
80
|
+
is_milestone = getattr(task, "duration", None) == timedelta(0)
|
|
81
|
+
return bool(is_milestone) and not milestone_at_day_end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def values_finish_at_day_start(row: Mapping[str, Any]) -> bool | None:
|
|
85
|
+
""":func:`task_finish_at_day_start` for a ``Task`` ``.values()`` row (#4197).
|
|
86
|
+
|
|
87
|
+
``None`` when the row has no ``early_finish`` — there is no finish to read an
|
|
88
|
+
edge of. The row must carry :data:`FINISH_READING_FIELDS`. Used at baseline
|
|
89
|
+
capture, whose task read is a ``.values()`` query.
|
|
90
|
+
"""
|
|
91
|
+
if row.get("early_finish") is None:
|
|
92
|
+
return None
|
|
93
|
+
return task_finish_at_day_start(SimpleNamespace(**{f: row[f] for f in FINISH_READING_FIELDS}))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def latest_finish(rows: Iterable[tuple[date | None, bool, Any]]) -> Finish | None:
|
|
97
|
+
"""The project finish, with its reading, from ``(early_finish, at_day_start, wbs_path)`` rows.
|
|
98
|
+
|
|
99
|
+
The day is the latest ``early_finish``, exactly as ``project_finish`` is. It is
|
|
100
|
+
the start of that day only when every task finishing that day does so at its
|
|
101
|
+
start. A summary row carries its children's rolled-up finish but not their
|
|
102
|
+
reading, so it is dropped when another row on the same day sits inside its WBS
|
|
103
|
+
subtree — a summary's finish is always one of its leaves', so this loses
|
|
104
|
+
nothing. Returns ``None`` when no row has a finish.
|
|
105
|
+
"""
|
|
106
|
+
on_day: list[tuple[bool, str | None]] = []
|
|
107
|
+
day: date | None = None
|
|
108
|
+
for finish, at_start, wbs_path in rows:
|
|
109
|
+
if finish is None:
|
|
110
|
+
continue
|
|
111
|
+
path = str(wbs_path) if wbs_path else None
|
|
112
|
+
if day is None or finish > day:
|
|
113
|
+
day, on_day = finish, [(at_start, path)]
|
|
114
|
+
elif finish == day:
|
|
115
|
+
on_day.append((at_start, path))
|
|
116
|
+
if day is None:
|
|
117
|
+
return None
|
|
118
|
+
# Every proper ancestor of a path on the day: O(rows x depth), so a project
|
|
119
|
+
# whose whole plan finishes on one day does not go quadratic.
|
|
120
|
+
ancestors: set[str] = set()
|
|
121
|
+
for _, p in on_day:
|
|
122
|
+
if p:
|
|
123
|
+
parts = p.split(".")
|
|
124
|
+
ancestors.update(".".join(parts[:i]) for i in range(1, len(parts)))
|
|
125
|
+
leaves = [at_start for at_start, p in on_day if not (p and p in ancestors)]
|
|
126
|
+
return day, bool(leaves) and all(leaves)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def schedule_result_finish(result: Any) -> Finish | None:
|
|
130
|
+
"""``ScheduleResult.project_finish`` with its reading.
|
|
131
|
+
|
|
132
|
+
A ``ScheduleResult`` holds leaf tasks only (summaries are stripped before the
|
|
133
|
+
pass), so no WBS pruning is needed.
|
|
134
|
+
"""
|
|
135
|
+
return latest_finish((t.early_finish, task_finish_at_day_start(t), None) for t in result.tasks)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def working_time_end_day(finish: Finish, calendar: Any) -> date:
|
|
139
|
+
"""The day whose end is the same working-time position as ``finish``.
|
|
140
|
+
|
|
141
|
+
The end of a day is that day. The start of a day is the end of the last
|
|
142
|
+
working day before it — the start of a Monday is the end of the Friday before
|
|
143
|
+
it. A finish recorded on a non-working day (a task pinned to a Saturday actual
|
|
144
|
+
finish) is kept as recorded: it is a fact the user entered, not a reading the
|
|
145
|
+
engine chose, and it cannot hop a weekend between two recalculations.
|
|
146
|
+
"""
|
|
147
|
+
from trueppm_scheduler.engine import _prev_working_day
|
|
148
|
+
|
|
149
|
+
day, at_day_start = finish
|
|
150
|
+
if not at_day_start:
|
|
151
|
+
return day
|
|
152
|
+
return _prev_working_day(day - timedelta(days=1), calendar)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def finish_shift_days(prior: Finish, new: Finish, calendar: Any | None) -> int:
|
|
156
|
+
"""Signed calendar-day move of the project finish in working time.
|
|
157
|
+
|
|
158
|
+
Positive means the finish moved later. Zero for the Friday-end ->
|
|
159
|
+
Monday-start jump across a weekend that the shown day makes with no move in
|
|
160
|
+
working time (#4178). ``calendar`` is the project's
|
|
161
|
+
composed scheduler ``Calendar``; with ``None`` it falls back to the plain
|
|
162
|
+
shown-day difference.
|
|
163
|
+
|
|
164
|
+
An unknown reading (``None``, a row written before the reading was recorded)
|
|
165
|
+
cannot be told apart from either edge of its day. When the two shown days are
|
|
166
|
+
equal the finish is taken as unmoved — otherwise the first comparison after an
|
|
167
|
+
upgrade would read a start-of-day milestone against an assumed end of the same
|
|
168
|
+
day and report a phantom three-day pull-in. When the days differ, the unknown
|
|
169
|
+
side is read as the end of its day, which is how every finish was shown before
|
|
170
|
+
#4079. An install that ran an untagged main image from after #4079 (which could
|
|
171
|
+
already show a start-of-day milestone) but before this field may therefore
|
|
172
|
+
misread that one post-upgrade comparison by up to the weekend or holiday gap.
|
|
173
|
+
"""
|
|
174
|
+
unknown = prior[1] is None or new[1] is None
|
|
175
|
+
if calendar is None or (unknown and prior[0] == new[0]):
|
|
176
|
+
return (new[0] - prior[0]).days
|
|
177
|
+
prior_known: Finish = (prior[0], bool(prior[1]))
|
|
178
|
+
new_known: Finish = (new[0], bool(new[1]))
|
|
179
|
+
return (
|
|
180
|
+
working_time_end_day(new_known, calendar) - working_time_end_day(prior_known, calendar)
|
|
181
|
+
).days
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def lazy_finish_shift_days(
|
|
185
|
+
prior: Finish, new: Finish, calendar_for: Callable[[], Any | None] | None
|
|
186
|
+
) -> int:
|
|
187
|
+
""":func:`finish_shift_days`, composing the calendar only when a reading needs it.
|
|
188
|
+
|
|
189
|
+
The calendar only changes the answer when one side is a known start-of-day
|
|
190
|
+
reading: two end-of-day finishes (or an unknown one, which reads as the end of
|
|
191
|
+
its day or as unmoved) diff as their shown days. So a read path that has no
|
|
192
|
+
calendar in hand — the Monte Carlo history, the ``latest`` fallback, the project
|
|
193
|
+
overview's risk premium (#4204) — pays for composing one only on a project
|
|
194
|
+
whose finish actually is a start-of-day milestone.
|
|
195
|
+
"""
|
|
196
|
+
if not prior[1] and not new[1]:
|
|
197
|
+
return (new[0] - prior[0]).days
|
|
198
|
+
return finish_shift_days(prior, new, calendar_for() if calendar_for is not None else None)
|