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,516 @@
|
|
|
1
|
+
"""Models for the scheduling app — Celery task infrastructure."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.db import models
|
|
9
|
+
|
|
10
|
+
# Lazy FK target — Django serializes the FK target to this resolved string, so
|
|
11
|
+
# using the constant produces no migration change.
|
|
12
|
+
_PROJECT_MODEL = "projects.Project"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ScheduleRequestStatus(models.TextChoices):
|
|
16
|
+
"""Lifecycle of a transactional outbox row for CPM recalculation."""
|
|
17
|
+
|
|
18
|
+
PENDING = "pending", "Pending"
|
|
19
|
+
DISPATCHED = "dispatched", "Dispatched"
|
|
20
|
+
DONE = "done", "Done"
|
|
21
|
+
DEAD = "dead", "Dead"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ScheduleRequestReason(models.TextChoices):
|
|
25
|
+
"""Why a CPM recalculation was requested — used for audit trail and drain dedup."""
|
|
26
|
+
|
|
27
|
+
TASK_CHANGE = "task_change", "Task Change"
|
|
28
|
+
DEPENDENCY_CHANGE = "dependency_change", "Dependency Change"
|
|
29
|
+
# A project's working calendar is a CPM input — lag is calendar-aware — so a
|
|
30
|
+
# calendar swap shifts every task's working-day math and forces a full recompute.
|
|
31
|
+
CALENDAR_CHANGE = "calendar_change", "Calendar Change"
|
|
32
|
+
# An accepted cross-project edge mutated (created/accepted/deleted): the
|
|
33
|
+
# project's program now needs a merged, program-scoped recompute so the
|
|
34
|
+
# cross-project critical path stays program-true (ADR-0120 D3). Recorded on
|
|
35
|
+
# the outbox rows of *both* endpoint projects; the drain/dispatch coalesces
|
|
36
|
+
# them into one program-scoped pass.
|
|
37
|
+
CROSS_PROJECT_DEPENDENCY = "cross_project_dependency", "Cross-Project Dependency"
|
|
38
|
+
SPRINT_CLOSED = "sprint_closed", "Sprint Closed"
|
|
39
|
+
MANUAL = "manual", "Manual"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ScheduleRequest(models.Model):
|
|
43
|
+
"""Transactional outbox record for CPM recalculation requests.
|
|
44
|
+
|
|
45
|
+
Each write operation on a project's task graph inserts one row here
|
|
46
|
+
(or silently ignores a duplicate via the partial unique constraint) in
|
|
47
|
+
the same DB transaction. A Celery Beat drain task dispatches pending rows
|
|
48
|
+
every 30 seconds, and recalculate_schedule marks its own row done on
|
|
49
|
+
completion.
|
|
50
|
+
|
|
51
|
+
Two partial unique constraints enforce at-most-one pending and at-most-one
|
|
52
|
+
dispatched row per project at any time, so duplicate suppression is cheap
|
|
53
|
+
and correct under concurrent writes.
|
|
54
|
+
|
|
55
|
+
Does NOT inherit VersionedModel — not synced to mobile clients.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
59
|
+
project = models.ForeignKey(
|
|
60
|
+
_PROJECT_MODEL,
|
|
61
|
+
on_delete=models.CASCADE,
|
|
62
|
+
related_name="schedule_requests",
|
|
63
|
+
)
|
|
64
|
+
status = models.CharField(
|
|
65
|
+
max_length=16,
|
|
66
|
+
choices=ScheduleRequestStatus.choices,
|
|
67
|
+
default=ScheduleRequestStatus.PENDING,
|
|
68
|
+
)
|
|
69
|
+
reason = models.CharField(
|
|
70
|
+
max_length=24,
|
|
71
|
+
choices=ScheduleRequestReason.choices,
|
|
72
|
+
default=ScheduleRequestReason.TASK_CHANGE,
|
|
73
|
+
db_index=True,
|
|
74
|
+
)
|
|
75
|
+
requested_at = models.DateTimeField(auto_now_add=True)
|
|
76
|
+
dispatched_at = models.DateTimeField(null=True, blank=True)
|
|
77
|
+
celery_task_id = models.CharField(max_length=255, blank=True, default="")
|
|
78
|
+
|
|
79
|
+
class Meta:
|
|
80
|
+
ordering = ["requested_at"]
|
|
81
|
+
constraints = [
|
|
82
|
+
models.UniqueConstraint(
|
|
83
|
+
fields=["project"],
|
|
84
|
+
condition=models.Q(status="pending"),
|
|
85
|
+
name="schedule_request_one_pending_per_project",
|
|
86
|
+
),
|
|
87
|
+
models.UniqueConstraint(
|
|
88
|
+
fields=["project"],
|
|
89
|
+
condition=models.Q(status="dispatched"),
|
|
90
|
+
name="schedule_request_one_dispatched_per_project",
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
indexes = [
|
|
94
|
+
models.Index(
|
|
95
|
+
fields=["status", "requested_at"],
|
|
96
|
+
name="schedule_request_status_idx",
|
|
97
|
+
),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
def __str__(self) -> str:
|
|
101
|
+
return f"ScheduleRequest({self.project_id}, {self.status})"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MCAttributionAudience(models.TextChoices):
|
|
105
|
+
"""Who may see the run-author name on Monte Carlo forecast history (ADR-0144).
|
|
106
|
+
|
|
107
|
+
Defined here (not on Workspace) because both the per-workspace config columns
|
|
108
|
+
and the ``MonteCarloHistoryView`` attribution gate reference it; placing it on
|
|
109
|
+
the scheduling model keeps a single source and avoids a workspace ``→`` scheduling
|
|
110
|
+
import cycle (workspace/projects import this enum, never the reverse).
|
|
111
|
+
|
|
112
|
+
The default ``ADMIN_OWNER`` preserves the pre-0143 hardcoded Admin/Owner gate
|
|
113
|
+
exactly, so the three-valued enum is backward-compatible and reversible.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
ADMIN_OWNER = "admin_owner", "Admins and Owners"
|
|
117
|
+
SCHEDULER_PLUS = "scheduler_plus", "Schedulers and above"
|
|
118
|
+
NONE = "none", "No one"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class FailedTaskStatus(models.TextChoices):
|
|
122
|
+
"""Lifecycle of a dead-lettered Celery task."""
|
|
123
|
+
|
|
124
|
+
PENDING_RETRY = "pending_retry", "Pending Retry"
|
|
125
|
+
DEAD = "dead", "Dead"
|
|
126
|
+
DISMISSED = "dismissed", "Dismissed"
|
|
127
|
+
RETRIED = "retried", "Retried"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FailedTask(models.Model):
|
|
131
|
+
"""Persistent record of a Celery task that exhausted all retries.
|
|
132
|
+
|
|
133
|
+
Does NOT inherit VersionedModel — not synced to mobile, not part of the
|
|
134
|
+
project data graph. Exists for admin visibility and manual retry/dismiss.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
138
|
+
task_name = models.CharField(max_length=255, db_index=True)
|
|
139
|
+
task_id = models.CharField(max_length=255, unique=True)
|
|
140
|
+
# Attribution for the dead-letter queue (#1917). Nullable: not every
|
|
141
|
+
# dead-lettered task is project-scoped (e.g. the nightly cross-project purge
|
|
142
|
+
# tasks), and existing rows predate this column. SET_NULL rather than CASCADE
|
|
143
|
+
# — deleting a project must never delete the failure audit trail that explains
|
|
144
|
+
# what it was doing when it broke; the row just loses its project reference.
|
|
145
|
+
# Populated by ``deadletter.record_failed_task`` from whatever ``project_id``
|
|
146
|
+
# the failing task already knew (most scheduling tasks take one as an arg).
|
|
147
|
+
project = models.ForeignKey(
|
|
148
|
+
_PROJECT_MODEL,
|
|
149
|
+
on_delete=models.SET_NULL,
|
|
150
|
+
null=True,
|
|
151
|
+
blank=True,
|
|
152
|
+
related_name="failed_tasks",
|
|
153
|
+
help_text="Project the failing task acted on, when known.",
|
|
154
|
+
)
|
|
155
|
+
args = models.JSONField(default=list)
|
|
156
|
+
kwargs = models.JSONField(default=dict)
|
|
157
|
+
exception_type = models.CharField(max_length=255)
|
|
158
|
+
exception_message = models.TextField()
|
|
159
|
+
traceback = models.TextField()
|
|
160
|
+
failure_count = models.PositiveIntegerField(default=1)
|
|
161
|
+
first_failed_at = models.DateTimeField(auto_now_add=True)
|
|
162
|
+
last_failed_at = models.DateTimeField(auto_now=True)
|
|
163
|
+
status = models.CharField(
|
|
164
|
+
max_length=16,
|
|
165
|
+
choices=FailedTaskStatus.choices,
|
|
166
|
+
default=FailedTaskStatus.DEAD,
|
|
167
|
+
db_index=True,
|
|
168
|
+
)
|
|
169
|
+
# Operator-action audit (ADR-0210). Stamped on both requeue and drop so every
|
|
170
|
+
# write action against a parked task is attributable (who/when), and drop can
|
|
171
|
+
# carry a free-text reason. Nullable/defaulted because the vast majority of
|
|
172
|
+
# rows are written by the automatic dead-letter path, which never resolves them
|
|
173
|
+
# — only an explicit operator action sets these. Retained (never hard-deleted)
|
|
174
|
+
# so the audit survives a drop; ADR-0084 "no silent discards".
|
|
175
|
+
resolution_note = models.TextField(
|
|
176
|
+
blank=True,
|
|
177
|
+
default="",
|
|
178
|
+
help_text="Operator's free-text note recorded when the task was dropped.",
|
|
179
|
+
)
|
|
180
|
+
resolved_by = models.ForeignKey(
|
|
181
|
+
settings.AUTH_USER_MODEL,
|
|
182
|
+
on_delete=models.SET_NULL,
|
|
183
|
+
null=True,
|
|
184
|
+
blank=True,
|
|
185
|
+
related_name="+",
|
|
186
|
+
help_text="Workspace admin who requeued or dropped this task.",
|
|
187
|
+
)
|
|
188
|
+
resolved_at = models.DateTimeField(
|
|
189
|
+
null=True,
|
|
190
|
+
blank=True,
|
|
191
|
+
help_text="When an operator requeued or dropped this task.",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
class Meta:
|
|
195
|
+
ordering = ["-last_failed_at"]
|
|
196
|
+
indexes = [
|
|
197
|
+
models.Index(fields=["status", "last_failed_at"], name="failed_task_status_idx"),
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
def __str__(self) -> str:
|
|
201
|
+
return f"{self.task_name} ({self.task_id}) — {self.status}"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class VelocitySuggestion(models.Model):
|
|
205
|
+
"""Non-destructive duration recommendation generated on sprint close (ADR-0065).
|
|
206
|
+
|
|
207
|
+
When a sprint closes, the drain computes a rolling 6-sprint team velocity
|
|
208
|
+
(``completed_points / sprint_working_days``) and, for tasks in the closing
|
|
209
|
+
sprint with ``story_points`` set, records a suggested
|
|
210
|
+
``most_likely_duration = story_points / team_velocity_per_day`` here. The
|
|
211
|
+
PM accepts or dismisses each suggestion from the Task Detail Drawer; the
|
|
212
|
+
underlying ``Task.most_likely_duration`` is never overwritten without
|
|
213
|
+
explicit consent so PM-committed baselines stay intact and the
|
|
214
|
+
accept/dismiss history is auditable per (task, sprint).
|
|
215
|
+
|
|
216
|
+
A unique constraint on (task, sprint) makes the sprint-close drain
|
|
217
|
+
idempotent: a duplicate run upserts the row rather than producing
|
|
218
|
+
duplicate prompts.
|
|
219
|
+
|
|
220
|
+
Not synced to mobile clients — surfaces only in the PM-facing Task Detail
|
|
221
|
+
Drawer.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
225
|
+
task = models.ForeignKey(
|
|
226
|
+
"projects.Task",
|
|
227
|
+
on_delete=models.CASCADE,
|
|
228
|
+
related_name="velocity_suggestions",
|
|
229
|
+
)
|
|
230
|
+
sprint = models.ForeignKey(
|
|
231
|
+
"projects.Sprint",
|
|
232
|
+
on_delete=models.CASCADE,
|
|
233
|
+
related_name="velocity_suggestions",
|
|
234
|
+
)
|
|
235
|
+
# Suggested most_likely_duration in working days. Stored as integer to match
|
|
236
|
+
# Task.most_likely_duration; rounding happens in the service layer.
|
|
237
|
+
suggested_duration = models.PositiveIntegerField()
|
|
238
|
+
team_velocity_per_day = models.DecimalField(
|
|
239
|
+
max_digits=6,
|
|
240
|
+
decimal_places=3,
|
|
241
|
+
help_text="Rolling 6-sprint average of completed_points / sprint_working_days.",
|
|
242
|
+
)
|
|
243
|
+
# When estimation_mode=SUGGEST_APPROVE the suggestion is flagged so the
|
|
244
|
+
# governance review surface can route the accept decision through a
|
|
245
|
+
# Scheduler-role user; in OPEN and PM_ONLY modes any PM may accept directly.
|
|
246
|
+
flag_for_review = models.BooleanField(default=False)
|
|
247
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
248
|
+
# Exactly one of accepted_at / dismissed_at is non-null at a time; the
|
|
249
|
+
# other stays null. The pair encodes the PM decision lifecycle without an
|
|
250
|
+
# extra status enum (similar to the actual_start / actual_finish pattern).
|
|
251
|
+
accepted_at = models.DateTimeField(null=True, blank=True)
|
|
252
|
+
accepted_by = models.ForeignKey(
|
|
253
|
+
settings.AUTH_USER_MODEL,
|
|
254
|
+
on_delete=models.SET_NULL,
|
|
255
|
+
null=True,
|
|
256
|
+
blank=True,
|
|
257
|
+
related_name="accepted_velocity_suggestions",
|
|
258
|
+
)
|
|
259
|
+
dismissed_at = models.DateTimeField(null=True, blank=True)
|
|
260
|
+
dismissed_by = models.ForeignKey(
|
|
261
|
+
settings.AUTH_USER_MODEL,
|
|
262
|
+
on_delete=models.SET_NULL,
|
|
263
|
+
null=True,
|
|
264
|
+
blank=True,
|
|
265
|
+
related_name="dismissed_velocity_suggestions",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
class Meta:
|
|
269
|
+
ordering = ["-created_at"]
|
|
270
|
+
constraints = [
|
|
271
|
+
models.UniqueConstraint(
|
|
272
|
+
fields=["task", "sprint"],
|
|
273
|
+
name="unique_velocity_suggestion_per_task_sprint",
|
|
274
|
+
),
|
|
275
|
+
]
|
|
276
|
+
indexes = [
|
|
277
|
+
# Pending suggestions for a task — the Task Detail Drawer query.
|
|
278
|
+
models.Index(
|
|
279
|
+
fields=["task", "accepted_at", "dismissed_at"],
|
|
280
|
+
name="velocity_sugg_task_pending_idx",
|
|
281
|
+
),
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
def __str__(self) -> str:
|
|
285
|
+
return f"VelocitySuggestion(task={self.task_id}, sprint={self.sprint_id})"
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def project_id(self) -> object:
|
|
289
|
+
"""Expose the task's project_id so _get_project_id_from_obj can find it.
|
|
290
|
+
|
|
291
|
+
Required for IsProjectAdmin.has_object_permission to resolve the project
|
|
292
|
+
context when DRF's get_object() runs check_object_permissions on a
|
|
293
|
+
VelocitySuggestion (no direct FK to Project). Mirrors the same pattern
|
|
294
|
+
used by resources.TaskResource.
|
|
295
|
+
"""
|
|
296
|
+
return self.task.project_id
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def is_pending(self) -> bool:
|
|
300
|
+
"""True when neither accepted nor dismissed — pending PM decision."""
|
|
301
|
+
return self.accepted_at is None and self.dismissed_at is None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class MonteCarloRun(models.Model):
|
|
305
|
+
"""One persisted project-level Monte Carlo simulation run (ADR-0175, #961).
|
|
306
|
+
|
|
307
|
+
Written synchronously per ``POST /projects/<pk>/monte-carlo/`` run so the PM
|
|
308
|
+
can read finish-date forecast *drift* over time ("my P80 was Aug 14 two weeks
|
|
309
|
+
ago, now Aug 28"). Distinct from ``projects.ForecastSnapshot`` (ADR-0106 §5),
|
|
310
|
+
which is milestone-scoped, latest-per-milestone, and carries the velocity
|
|
311
|
+
-privacy band — this model is the explicit project-level CPM Monte Carlo run
|
|
312
|
+
history and stores P95 (which ForecastSnapshot does not).
|
|
313
|
+
|
|
314
|
+
A plain ``models.Model`` (not a ``VersionedModel``) — display/forecast
|
|
315
|
+
metadata, consistent with ``ForecastSnapshot`` / ``VelocitySuggestion``; not
|
|
316
|
+
on the mobile sync surface (Monte Carlo requires server compute, so history
|
|
317
|
+
is an online read).
|
|
318
|
+
|
|
319
|
+
Retention is the OSS cap applied to run *count*: a nightly purge keeps the
|
|
320
|
+
newest ``settings.MC_HISTORY_CAP`` rows per project (Enterprise overrides to
|
|
321
|
+
``None`` = unlimited). Bounded history, never unlimited — the portfolio /
|
|
322
|
+
cross-program rollup is the Enterprise upsell (ADR-0175).
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
326
|
+
project = models.ForeignKey(
|
|
327
|
+
_PROJECT_MODEL,
|
|
328
|
+
on_delete=models.CASCADE,
|
|
329
|
+
related_name="monte_carlo_runs",
|
|
330
|
+
)
|
|
331
|
+
taken_at = models.DateTimeField(auto_now_add=True)
|
|
332
|
+
# SET_NULL + null: account deletion never cascades away forecast history, and
|
|
333
|
+
# the attribution is optional metadata. Serialized ONLY to Admin/Owner so
|
|
334
|
+
# forecast drift cannot become a named-individual performance signal at the
|
|
335
|
+
# team level (ADR-0175 / VoC Morgan). related_name="+": no reverse accessor
|
|
336
|
+
# needed (we never list a user's runs).
|
|
337
|
+
triggered_by = models.ForeignKey(
|
|
338
|
+
settings.AUTH_USER_MODEL,
|
|
339
|
+
on_delete=models.SET_NULL,
|
|
340
|
+
null=True,
|
|
341
|
+
blank=True,
|
|
342
|
+
related_name="+",
|
|
343
|
+
)
|
|
344
|
+
# The probabilistic finish-date percentiles as of this run. Nullable: a
|
|
345
|
+
# project with no committed tasks yields no distribution to anchor on.
|
|
346
|
+
p50 = models.DateField(null=True, blank=True)
|
|
347
|
+
p80 = models.DateField(null=True, blank=True)
|
|
348
|
+
p95 = models.DateField(null=True, blank=True)
|
|
349
|
+
# The deterministic CPM spine at run time (max early_finish of committed
|
|
350
|
+
# tasks), kept for context alongside the probabilistic band.
|
|
351
|
+
cpm_finish = models.DateField(null=True, blank=True)
|
|
352
|
+
# Inputs needed to interpret the run.
|
|
353
|
+
n_simulations = models.PositiveIntegerField()
|
|
354
|
+
task_count = models.PositiveIntegerField(null=True, blank=True)
|
|
355
|
+
# The full per-run distribution payload — the same
|
|
356
|
+
# ``{histogram_buckets, confidence_curve, sensitivity}`` shape written to the
|
|
357
|
+
# ``mc_latest`` cache (#1231, ADR-0144; key built by
|
|
358
|
+
# ``views.mc_latest_cache_key``). Persisted so the histogram +
|
|
359
|
+
# tornado survive cache expiry and a past run stays re-viewable.
|
|
360
|
+
#
|
|
361
|
+
# Nullable with NO backfill: pre-0143 runs have no stored distribution and the
|
|
362
|
+
# frontend renders the empty-state prose for them (legacy runs are a tail that
|
|
363
|
+
# ages out under the retention cap). Capped at MC_DISTRIBUTION_MAX_BYTES with
|
|
364
|
+
# bucket down-sampling at persist time (the cache copy stays full) so a
|
|
365
|
+
# pathological high-bucket run cannot bloat the row.
|
|
366
|
+
distribution = models.JSONField(null=True, blank=True)
|
|
367
|
+
# Why this run has — or lacks — an uncertainty band: the
|
|
368
|
+
# ``{deterministic, reason, tasks_total, tasks_with_variance, ...}`` payload
|
|
369
|
+
# from :func:`forecast_diagnostic` (#1340), persisted with the run (#2483).
|
|
370
|
+
#
|
|
371
|
+
# Load-bearing for the "added time" metric, not merely informational. A flat
|
|
372
|
+
# run yields a premium of exactly 0 days, and a project with no three-point
|
|
373
|
+
# estimates is always flat — so without the reason code a structural zero is
|
|
374
|
+
# indistinguishable from a measured zero, and Overview would tell an
|
|
375
|
+
# unestimated project it carries no schedule risk. Re-deriving the reason at
|
|
376
|
+
# read time would mean loading every committed task on a hot endpoint, so the
|
|
377
|
+
# run carries its own.
|
|
378
|
+
#
|
|
379
|
+
# Nullable with NO backfill: runs recorded before #2483 have no diagnostic and
|
|
380
|
+
# the frontend falls back to the measured presentation with the band collapsed
|
|
381
|
+
# (it never guesses "unmeasurable" from a missing reason).
|
|
382
|
+
diagnostic = models.JSONField(null=True, blank=True)
|
|
383
|
+
# The data date this run was actually computed against — ADR-0132 floors a null
|
|
384
|
+
# ``Project.status_date`` at "today" for Monte Carlo (never forecast remaining
|
|
385
|
+
# work in the past), but that substitution used to happen only in memory and was
|
|
386
|
+
# never recorded, so a past run could not be re-derived and two runs of an
|
|
387
|
+
# unchanged project on different days returned different percentiles with nothing
|
|
388
|
+
# in the row explaining why (#2638). ADR-0132's Implementation Notes already
|
|
389
|
+
# called this out as "additive on ADR-0175" when this model was designed; it
|
|
390
|
+
# simply never landed until now.
|
|
391
|
+
#
|
|
392
|
+
# Nullable with NO backfill, same pattern as ``distribution``/``diagnostic``:
|
|
393
|
+
# runs recorded before #2638 have no stored data date and read back as
|
|
394
|
+
# ``status_date: null`` rather than a guessed value.
|
|
395
|
+
status_date = models.DateField(null=True, blank=True)
|
|
396
|
+
# The plan version this run was computed against — the project's
|
|
397
|
+
# ``last_sync_version`` (ADR-0686) read *before* the task set was loaded (#3140).
|
|
398
|
+
#
|
|
399
|
+
# This is what makes "is the forecast still about the current plan?" a server fact
|
|
400
|
+
# instead of a client guess. Compared against the project's live value at response
|
|
401
|
+
# time by :mod:`.forecast_staleness`; a difference means the plan has been written to
|
|
402
|
+
# since the run. The web forecast bar previously answered the same question with a
|
|
403
|
+
# session-local mutation counter, which missed every write path but one and reset on
|
|
404
|
+
# reload — and once #3132 gated the Rerun *action* on it, a stale forecast could
|
|
405
|
+
# offer no recompute at all.
|
|
406
|
+
#
|
|
407
|
+
# Read before the tasks, not after, and the ordering is the correctness property: a
|
|
408
|
+
# write landing mid-simulation then leaves ``plan_version`` behind the project's
|
|
409
|
+
# value, so the run reads stale. Capturing it afterwards would claim the run covered
|
|
410
|
+
# a write it never saw — false-fresh, which is the direction that loses the action.
|
|
411
|
+
#
|
|
412
|
+
# Nullable with NO backfill, the same pattern as ``distribution``/``diagnostic``/
|
|
413
|
+
# ``status_date`` above: runs recorded before #3140 have no plan version and classify
|
|
414
|
+
# as ``unknown`` rather than a guessed ``current``. No index — this model is only ever
|
|
415
|
+
# read as "newest row for this project", which ``mcrun_project_recent_idx`` covers.
|
|
416
|
+
plan_version = models.BigIntegerField(null=True, blank=True)
|
|
417
|
+
|
|
418
|
+
class Meta:
|
|
419
|
+
db_table = "scheduling_montecarlorun"
|
|
420
|
+
ordering = ["-taken_at"]
|
|
421
|
+
indexes = [
|
|
422
|
+
# Newest-first history read + the nightly rank-based purge.
|
|
423
|
+
models.Index(fields=["project", "-taken_at"], name="mcrun_project_recent_idx"),
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
def __str__(self) -> str:
|
|
427
|
+
return f"MonteCarloRun(project={self.project_id} @ {self.taken_at:%Y-%m-%d})"
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class ForecastSnapshotTrigger(models.TextChoices):
|
|
431
|
+
"""What caused a ``ProjectForecastSnapshot`` to be captured."""
|
|
432
|
+
|
|
433
|
+
RECOMPUTE = "recompute", "Recompute"
|
|
434
|
+
SCHEDULED = "scheduled", "Scheduled"
|
|
435
|
+
MANUAL = "manual", "Manual"
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class ProjectForecastSnapshot(models.Model):
|
|
439
|
+
"""One project-grain forecast point-in-time, captured on each recompute (ADR-0154, #388).
|
|
440
|
+
|
|
441
|
+
The continuous record of how a project's *whole-project* forecast drifts over
|
|
442
|
+
time — "we were saying end of June a month ago, now we're saying end of August".
|
|
443
|
+
Written best-effort after every CPM recompute (plus a daily floor) so ~30 days of
|
|
444
|
+
real history accrue before the forecast-trend chart (#368) and history-aware
|
|
445
|
+
sample loader (#376) consume it.
|
|
446
|
+
|
|
447
|
+
Distinct from the two adjacent forecast-history models, and deliberately not a
|
|
448
|
+
merge of either:
|
|
449
|
+
|
|
450
|
+
* ``projects.ForecastSnapshot`` (ADR-0106 §5) is *milestone*-grain,
|
|
451
|
+
latest-per-milestone, and carries a velocity-privacy band — a different read
|
|
452
|
+
contract. (Its FK already owns ``related_name="forecast_snapshots"`` on
|
|
453
|
+
Project, which is why this model uses ``project_forecast_snapshots``.)
|
|
454
|
+
* ``MonteCarloRun`` (ADR-0175) is written only when a user *explicitly* runs
|
|
455
|
+
Monte Carlo and is capped to the newest N runs — it never captures CPM-finish
|
|
456
|
+
drift on a project that has never been simulated, and its flat cap drops the
|
|
457
|
+
long tail the trend chart needs.
|
|
458
|
+
|
|
459
|
+
This model is CPM-primary: ``cpm_finish``/``total_float_days`` come from the
|
|
460
|
+
just-recomputed schedule, while the Monte Carlo percentiles are copied
|
|
461
|
+
best-effort from the project's most-recent ``MonteCarloRun`` (so the MC line
|
|
462
|
+
stays flat — truthfully — until someone reruns MC).
|
|
463
|
+
|
|
464
|
+
A plain ``models.Model`` (not a ``VersionedModel``) with no ``server_version``
|
|
465
|
+
and not on the mobile sync surface — consistent with ``MonteCarloRun`` /
|
|
466
|
+
``ForecastSnapshot``; this is a high-churn, server-generated, online-read-only
|
|
467
|
+
history table with no mobile consumer (ADR-0154 §2).
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
471
|
+
project = models.ForeignKey(
|
|
472
|
+
_PROJECT_MODEL,
|
|
473
|
+
on_delete=models.CASCADE,
|
|
474
|
+
related_name="project_forecast_snapshots",
|
|
475
|
+
)
|
|
476
|
+
captured_at = models.DateTimeField(auto_now_add=True)
|
|
477
|
+
triggered_by = models.CharField(
|
|
478
|
+
max_length=16,
|
|
479
|
+
choices=ForecastSnapshotTrigger.choices,
|
|
480
|
+
default=ForecastSnapshotTrigger.RECOMPUTE,
|
|
481
|
+
)
|
|
482
|
+
# Every aggregate below is over ``Task.committed`` — the population CPM is
|
|
483
|
+
# actually run on — never over all non-deleted tasks (#3539). A BACKLOG/EPIC/
|
|
484
|
+
# recurring row is never scheduled, so any early_finish/total_float it carries
|
|
485
|
+
# is scheduling output left over from before it was groomed out, and admitting
|
|
486
|
+
# it here reports a forecast for a schedule that was never computed.
|
|
487
|
+
#
|
|
488
|
+
# Deterministic CPM spine at capture time: the project's latest committed task
|
|
489
|
+
# finish. Nullable — a project with no scheduled tasks has no finish to anchor on.
|
|
490
|
+
cpm_finish = models.DateField(null=True, blank=True)
|
|
491
|
+
# The tightest total float (minimum across the committed set): 0 on an
|
|
492
|
+
# unconstrained critical path, negative when a deadline/constraint is breached —
|
|
493
|
+
# so drift in schedule *pressure* is visible, not just the finish date.
|
|
494
|
+
total_float_days = models.IntegerField(null=True, blank=True)
|
|
495
|
+
# Probabilistic finish-date percentiles, copied best-effort from the project's
|
|
496
|
+
# most-recent MonteCarloRun at capture time. Null when MC has never run; may
|
|
497
|
+
# predate this capture (no newer probabilistic data exists to report).
|
|
498
|
+
mc_p50_finish = models.DateField(null=True, blank=True)
|
|
499
|
+
mc_p80_finish = models.DateField(null=True, blank=True)
|
|
500
|
+
mc_p95_finish = models.DateField(null=True, blank=True)
|
|
501
|
+
mc_iterations = models.PositiveIntegerField(null=True, blank=True)
|
|
502
|
+
# Schedule shape at capture time — context for interpreting the forecast, and
|
|
503
|
+
# therefore counted over the same committed population as cpm_finish above.
|
|
504
|
+
task_count = models.PositiveIntegerField(default=0)
|
|
505
|
+
completed_task_count = models.PositiveIntegerField(default=0)
|
|
506
|
+
|
|
507
|
+
class Meta:
|
|
508
|
+
db_table = "scheduling_projectforecastsnapshot"
|
|
509
|
+
ordering = ["-captured_at"]
|
|
510
|
+
indexes = [
|
|
511
|
+
# Newest-first history read + the per-project tiered prune scan.
|
|
512
|
+
models.Index(fields=["project", "-captured_at"], name="projfcast_proj_recent_idx"),
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
def __str__(self) -> str:
|
|
516
|
+
return f"ProjectForecastSnapshot(project={self.project_id} @ {self.captured_at:%Y-%m-%d})"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""OSS signal receivers for the scheduling app.
|
|
2
|
+
|
|
3
|
+
The default dead-letter alerting receiver (ADR-0084). When a Celery task is
|
|
4
|
+
permanently dead-lettered, OSS emits a single structured WARNING line that an
|
|
5
|
+
operator (or a log-based metrics aggregator) can alert on. Enterprise registers
|
|
6
|
+
*additional* receivers on the same signal for PagerDuty/Slack in its own
|
|
7
|
+
AppConfig.ready() — this module is never imported by enterprise and imports
|
|
8
|
+
nothing from it.
|
|
9
|
+
|
|
10
|
+
Also holds the start/success/retry structured-log receivers (#1917) that
|
|
11
|
+
complement the dead-letter alert above — each Celery task's full lifecycle
|
|
12
|
+
(started → succeeded, or started → retried → ... → dead-lettered) is now a
|
|
13
|
+
structured, greppable/aggregatable log line rather than the ad-hoc
|
|
14
|
+
``logger.info`` calls scattered across individual task bodies.
|
|
15
|
+
|
|
16
|
+
Wired by SchedulingConfig.ready() via ``from . import receivers``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from django.dispatch import receiver
|
|
25
|
+
|
|
26
|
+
from trueppm_api.apps.scheduling.signals import (
|
|
27
|
+
celery_task_permanently_failed,
|
|
28
|
+
celery_task_retried,
|
|
29
|
+
celery_task_started,
|
|
30
|
+
celery_task_succeeded,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@receiver(celery_task_permanently_failed)
|
|
37
|
+
def log_dead_letter_alert(
|
|
38
|
+
sender: Any,
|
|
39
|
+
task_id: str = "",
|
|
40
|
+
task_name: str = "",
|
|
41
|
+
exception: BaseException | None = None,
|
|
42
|
+
project_id: str | None = None,
|
|
43
|
+
**_kwargs: Any,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Emit a WARNING alert line for a newly dead-lettered task.
|
|
46
|
+
|
|
47
|
+
Distinct from the ERROR line in ``record_failed_task`` (which records the
|
|
48
|
+
raw failure): this is the *alert*, carrying structured ``extra`` fields so
|
|
49
|
+
log pipelines can build the ``trueppm_task_dead_letter_count`` series by
|
|
50
|
+
``task_name`` without parsing the message. Broad ``except`` because an
|
|
51
|
+
alerting failure must never propagate back into the dead-letter path — the
|
|
52
|
+
signal is dispatched with ``send_robust`` for the same reason, so this is
|
|
53
|
+
belt-and-braces against a logging-config error masking a task failure.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
logger.warning(
|
|
57
|
+
"dead-letter alert: task %s (%s) permanently failed: %s",
|
|
58
|
+
task_name,
|
|
59
|
+
task_id,
|
|
60
|
+
type(exception).__qualname__ if exception is not None else "unknown",
|
|
61
|
+
extra={
|
|
62
|
+
"task_name": task_name,
|
|
63
|
+
"task_id": task_id,
|
|
64
|
+
"exception_type": (
|
|
65
|
+
type(exception).__qualname__ if exception is not None else "unknown"
|
|
66
|
+
),
|
|
67
|
+
"project_id": project_id,
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
except Exception:
|
|
71
|
+
logger.exception("dead-letter alert receiver failed")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@receiver(celery_task_started)
|
|
75
|
+
def log_task_started(
|
|
76
|
+
sender: Any,
|
|
77
|
+
task_id: str = "",
|
|
78
|
+
task_name: str = "",
|
|
79
|
+
**_kwargs: Any,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Emit a structured INFO line when a Celery task begins execution (#1917).
|
|
82
|
+
|
|
83
|
+
Bridged from the ``task_prerun`` framework signal in
|
|
84
|
+
``SchedulingConfig.ready()`` for every task in the process. Broad ``except``
|
|
85
|
+
(matching ``log_dead_letter_alert``): this is bridged via a plain (not
|
|
86
|
+
``send_robust``) Django signal fired synchronously inside the worker's task
|
|
87
|
+
execution path, so a logging-config error here must never surface as (or
|
|
88
|
+
interrupt) a task failure.
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
logger.info(
|
|
92
|
+
"task started: %s (%s)",
|
|
93
|
+
task_name,
|
|
94
|
+
task_id,
|
|
95
|
+
extra={"task_name": task_name, "task_id": task_id, "outcome": "started"},
|
|
96
|
+
)
|
|
97
|
+
except Exception:
|
|
98
|
+
logger.exception("task-started log receiver failed")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@receiver(celery_task_succeeded)
|
|
102
|
+
def log_task_succeeded(
|
|
103
|
+
sender: Any,
|
|
104
|
+
task_id: str = "",
|
|
105
|
+
task_name: str = "",
|
|
106
|
+
runtime_seconds: float = 0.0,
|
|
107
|
+
**_kwargs: Any,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Emit a structured INFO line when a Celery task completes successfully (#1917)."""
|
|
110
|
+
try:
|
|
111
|
+
logger.info(
|
|
112
|
+
"task succeeded: %s (%s) in %.3fs",
|
|
113
|
+
task_name,
|
|
114
|
+
task_id,
|
|
115
|
+
runtime_seconds,
|
|
116
|
+
extra={
|
|
117
|
+
"task_name": task_name,
|
|
118
|
+
"task_id": task_id,
|
|
119
|
+
"outcome": "succeeded",
|
|
120
|
+
"runtime_seconds": runtime_seconds,
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
except Exception:
|
|
124
|
+
logger.exception("task-succeeded log receiver failed")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@receiver(celery_task_retried)
|
|
128
|
+
def log_task_retried(
|
|
129
|
+
sender: Any,
|
|
130
|
+
task_id: str = "",
|
|
131
|
+
task_name: str = "",
|
|
132
|
+
attempt: int = 0,
|
|
133
|
+
exception: BaseException | None = None,
|
|
134
|
+
**_kwargs: Any,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Emit a structured WARNING line when a Celery task is about to be retried (#1917).
|
|
137
|
+
|
|
138
|
+
Distinct from ``log_dead_letter_alert``: this fires on *every* retry attempt
|
|
139
|
+
(the task will run again), the dead-letter alert fires once, only on the
|
|
140
|
+
terminal transition after retries are exhausted.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
logger.warning(
|
|
144
|
+
"task retry: %s (%s) attempt %s: %s",
|
|
145
|
+
task_name,
|
|
146
|
+
task_id,
|
|
147
|
+
attempt,
|
|
148
|
+
exception,
|
|
149
|
+
extra={
|
|
150
|
+
"task_name": task_name,
|
|
151
|
+
"task_id": task_id,
|
|
152
|
+
"outcome": "retried",
|
|
153
|
+
"attempt": attempt,
|
|
154
|
+
"exception_type": (
|
|
155
|
+
type(exception).__qualname__ if exception is not None else "unknown"
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
except Exception:
|
|
160
|
+
logger.exception("task-retried log receiver failed")
|