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.
Files changed (799) hide show
  1. trueppm_api/__init__.py +27 -0
  2. trueppm_api/apps/access/__init__.py +0 -0
  3. trueppm_api/apps/access/apps.py +19 -0
  4. trueppm_api/apps/access/groups.py +518 -0
  5. trueppm_api/apps/access/management/__init__.py +0 -0
  6. trueppm_api/apps/access/management/commands/__init__.py +0 -0
  7. trueppm_api/apps/access/management/commands/create_admin.py +346 -0
  8. trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
  9. trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -0
  10. trueppm_api/apps/access/migrations/0001_initial.py +61 -0
  11. trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +184 -0
  12. trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
  13. trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
  14. trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
  15. trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
  16. trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
  17. trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
  18. trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
  19. trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
  20. trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
  21. trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
  22. trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
  23. trueppm_api/apps/access/migrations/0013_squashed_0019_release_0_4.py +302 -0
  24. trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
  25. trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
  26. trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
  27. trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
  28. trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +143 -0
  29. trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
  30. trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
  31. trueppm_api/apps/access/migrations/__init__.py +0 -0
  32. trueppm_api/apps/access/minter_role.py +38 -0
  33. trueppm_api/apps/access/models.py +588 -0
  34. trueppm_api/apps/access/permissions.py +3263 -0
  35. trueppm_api/apps/access/serializers.py +1007 -0
  36. trueppm_api/apps/access/services.py +878 -0
  37. trueppm_api/apps/access/signals.py +171 -0
  38. trueppm_api/apps/access/tasks.py +50 -0
  39. trueppm_api/apps/access/throttles.py +96 -0
  40. trueppm_api/apps/access/urls.py +191 -0
  41. trueppm_api/apps/access/views.py +1965 -0
  42. trueppm_api/apps/agents/__init__.py +6 -0
  43. trueppm_api/apps/agents/apps.py +11 -0
  44. trueppm_api/apps/agents/canonical.py +73 -0
  45. trueppm_api/apps/agents/deferred.py +108 -0
  46. trueppm_api/apps/agents/management/__init__.py +0 -0
  47. trueppm_api/apps/agents/management/commands/__init__.py +0 -0
  48. trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
  49. trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
  50. trueppm_api/apps/agents/middleware.py +44 -0
  51. trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
  52. trueppm_api/apps/agents/migrations/0001_squashed_0003_release_0_4.py +344 -0
  53. trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
  54. trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
  55. trueppm_api/apps/agents/migrations/__init__.py +0 -0
  56. trueppm_api/apps/agents/models.py +371 -0
  57. trueppm_api/apps/agents/refusal.py +103 -0
  58. trueppm_api/apps/agents/serializers.py +85 -0
  59. trueppm_api/apps/agents/services.py +397 -0
  60. trueppm_api/apps/agents/signals.py +30 -0
  61. trueppm_api/apps/agents/urls.py +15 -0
  62. trueppm_api/apps/agents/views.py +109 -0
  63. trueppm_api/apps/csvimport/__init__.py +12 -0
  64. trueppm_api/apps/csvimport/apps.py +11 -0
  65. trueppm_api/apps/csvimport/mapping.py +363 -0
  66. trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
  67. trueppm_api/apps/csvimport/migrations/0001_squashed_0003_release_0_4.py +90 -0
  68. trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
  69. trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
  70. trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
  71. trueppm_api/apps/csvimport/models.py +111 -0
  72. trueppm_api/apps/csvimport/parser.py +1825 -0
  73. trueppm_api/apps/csvimport/services.py +48 -0
  74. trueppm_api/apps/csvimport/tasks.py +459 -0
  75. trueppm_api/apps/csvimport/template.py +37 -0
  76. trueppm_api/apps/csvimport/urls.py +50 -0
  77. trueppm_api/apps/csvimport/views.py +793 -0
  78. trueppm_api/apps/history/__init__.py +0 -0
  79. trueppm_api/apps/history/apps.py +12 -0
  80. trueppm_api/apps/history/changelog.py +359 -0
  81. trueppm_api/apps/history/diff_policy.py +178 -0
  82. trueppm_api/apps/history/serializers.py +104 -0
  83. trueppm_api/apps/history/signals.py +82 -0
  84. trueppm_api/apps/history/tasks.py +105 -0
  85. trueppm_api/apps/history/urls.py +38 -0
  86. trueppm_api/apps/history/views.py +443 -0
  87. trueppm_api/apps/idempotency/__init__.py +0 -0
  88. trueppm_api/apps/idempotency/apps.py +11 -0
  89. trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
  90. trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
  91. trueppm_api/apps/idempotency/mixins.py +226 -0
  92. trueppm_api/apps/idempotency/models.py +75 -0
  93. trueppm_api/apps/idempotency/tasks.py +61 -0
  94. trueppm_api/apps/integrations/__init__.py +13 -0
  95. trueppm_api/apps/integrations/apps.py +59 -0
  96. trueppm_api/apps/integrations/connections.py +806 -0
  97. trueppm_api/apps/integrations/encryption.py +92 -0
  98. trueppm_api/apps/integrations/external_sources.py +863 -0
  99. trueppm_api/apps/integrations/git_automation_services.py +231 -0
  100. trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
  101. trueppm_api/apps/integrations/http.py +597 -0
  102. trueppm_api/apps/integrations/me_work.py +218 -0
  103. trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
  104. trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
  105. trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
  106. trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
  107. trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
  108. trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
  109. trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
  110. trueppm_api/apps/integrations/migrations/0007_squashed_0014_release_0_4.py +225 -0
  111. trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
  112. trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
  113. trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
  114. trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
  115. trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
  116. trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
  117. trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
  118. trueppm_api/apps/integrations/migrations/0014_tasklink_created_by.py +27 -0
  119. trueppm_api/apps/integrations/migrations/__init__.py +0 -0
  120. trueppm_api/apps/integrations/models.py +635 -0
  121. trueppm_api/apps/integrations/notification_channels.py +57 -0
  122. trueppm_api/apps/integrations/opengraph.py +229 -0
  123. trueppm_api/apps/integrations/outgoing.py +201 -0
  124. trueppm_api/apps/integrations/providers.py +528 -0
  125. trueppm_api/apps/integrations/registry.py +345 -0
  126. trueppm_api/apps/integrations/serializers.py +453 -0
  127. trueppm_api/apps/integrations/services.py +171 -0
  128. trueppm_api/apps/integrations/tasks.py +734 -0
  129. trueppm_api/apps/integrations/throttles.py +165 -0
  130. trueppm_api/apps/integrations/urls.py +77 -0
  131. trueppm_api/apps/integrations/views.py +1158 -0
  132. trueppm_api/apps/jiraimport/__init__.py +8 -0
  133. trueppm_api/apps/jiraimport/apps.py +11 -0
  134. trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
  135. trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
  136. trueppm_api/apps/jiraimport/models.py +70 -0
  137. trueppm_api/apps/jiraimport/parser.py +350 -0
  138. trueppm_api/apps/jiraimport/services.py +46 -0
  139. trueppm_api/apps/jiraimport/tasks.py +280 -0
  140. trueppm_api/apps/jiraimport/urls.py +15 -0
  141. trueppm_api/apps/jiraimport/views.py +153 -0
  142. trueppm_api/apps/msproject/__init__.py +0 -0
  143. trueppm_api/apps/msproject/apps.py +11 -0
  144. trueppm_api/apps/msproject/dataclasses.py +153 -0
  145. trueppm_api/apps/msproject/exporter.py +551 -0
  146. trueppm_api/apps/msproject/extended_attributes.py +77 -0
  147. trueppm_api/apps/msproject/importer.py +1095 -0
  148. trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
  149. trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
  150. trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
  151. trueppm_api/apps/msproject/migrations/__init__.py +0 -0
  152. trueppm_api/apps/msproject/models.py +81 -0
  153. trueppm_api/apps/msproject/parser.py +1276 -0
  154. trueppm_api/apps/msproject/serializers.py +136 -0
  155. trueppm_api/apps/msproject/services.py +58 -0
  156. trueppm_api/apps/msproject/tasks.py +439 -0
  157. trueppm_api/apps/msproject/urls.py +39 -0
  158. trueppm_api/apps/msproject/views.py +531 -0
  159. trueppm_api/apps/notifications/__init__.py +7 -0
  160. trueppm_api/apps/notifications/apps.py +15 -0
  161. trueppm_api/apps/notifications/backfill.py +79 -0
  162. trueppm_api/apps/notifications/categories.py +112 -0
  163. trueppm_api/apps/notifications/delivery_limits.py +258 -0
  164. trueppm_api/apps/notifications/digests.py +462 -0
  165. trueppm_api/apps/notifications/email_backend.py +350 -0
  166. trueppm_api/apps/notifications/email_health.py +128 -0
  167. trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
  168. trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +299 -0
  169. trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
  170. trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
  171. trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
  172. trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
  173. trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
  174. trueppm_api/apps/notifications/migrations/0007_squashed_0011_release_0_4.py +204 -0
  175. trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
  176. trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
  177. trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
  178. trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
  179. trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
  180. trueppm_api/apps/notifications/migrations/__init__.py +0 -0
  181. trueppm_api/apps/notifications/models.py +1148 -0
  182. trueppm_api/apps/notifications/receivers.py +155 -0
  183. trueppm_api/apps/notifications/schema_migrations.py +56 -0
  184. trueppm_api/apps/notifications/serializers.py +1094 -0
  185. trueppm_api/apps/notifications/services.py +1489 -0
  186. trueppm_api/apps/notifications/tasks.py +762 -0
  187. trueppm_api/apps/notifications/throttles.py +98 -0
  188. trueppm_api/apps/notifications/urls.py +97 -0
  189. trueppm_api/apps/notifications/views.py +919 -0
  190. trueppm_api/apps/observability/__init__.py +0 -0
  191. trueppm_api/apps/observability/apps.py +31 -0
  192. trueppm_api/apps/observability/logging.py +213 -0
  193. trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
  194. trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
  195. trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
  196. trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
  197. trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
  198. trueppm_api/apps/observability/migrations/__init__.py +0 -0
  199. trueppm_api/apps/observability/models.py +141 -0
  200. trueppm_api/apps/observability/otel/__init__.py +45 -0
  201. trueppm_api/apps/observability/otel/attributes.py +247 -0
  202. trueppm_api/apps/observability/otel/export_health.py +533 -0
  203. trueppm_api/apps/observability/otel/instrumentation.py +351 -0
  204. trueppm_api/apps/observability/otel/metrics.py +566 -0
  205. trueppm_api/apps/observability/otel/provider.py +511 -0
  206. trueppm_api/apps/observability/otel/request_attributes.py +204 -0
  207. trueppm_api/apps/observability/purge_registry.py +124 -0
  208. trueppm_api/apps/observability/retention.py +139 -0
  209. trueppm_api/apps/observability/selectors.py +993 -0
  210. trueppm_api/apps/observability/serializers.py +142 -0
  211. trueppm_api/apps/observability/services.py +407 -0
  212. trueppm_api/apps/observability/tasks.py +266 -0
  213. trueppm_api/apps/observability/urls.py +27 -0
  214. trueppm_api/apps/observability/views.py +537 -0
  215. trueppm_api/apps/profiles/__init__.py +0 -0
  216. trueppm_api/apps/profiles/apps.py +15 -0
  217. trueppm_api/apps/profiles/constants.py +54 -0
  218. trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
  219. trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
  220. trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
  221. trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
  222. trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
  223. trueppm_api/apps/profiles/migrations/0005_squashed_0009_release_0_4.py +128 -0
  224. trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
  225. trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
  226. trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
  227. trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
  228. trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
  229. trueppm_api/apps/profiles/migrations/__init__.py +0 -0
  230. trueppm_api/apps/profiles/models.py +276 -0
  231. trueppm_api/apps/profiles/serializers.py +199 -0
  232. trueppm_api/apps/profiles/services.py +541 -0
  233. trueppm_api/apps/profiles/views.py +124 -0
  234. trueppm_api/apps/projects/__init__.py +0 -0
  235. trueppm_api/apps/projects/actual_date_rules.py +163 -0
  236. trueppm_api/apps/projects/amend.py +133 -0
  237. trueppm_api/apps/projects/apps.py +38 -0
  238. trueppm_api/apps/projects/asset_feed.py +459 -0
  239. trueppm_api/apps/projects/asset_views.py +317 -0
  240. trueppm_api/apps/projects/attachment_policy.py +278 -0
  241. trueppm_api/apps/projects/authentication.py +458 -0
  242. trueppm_api/apps/projects/backfill.py +459 -0
  243. trueppm_api/apps/projects/backlog_services.py +324 -0
  244. trueppm_api/apps/projects/backlog_views.py +302 -0
  245. trueppm_api/apps/projects/batch_operation_views.py +264 -0
  246. trueppm_api/apps/projects/blocker_services.py +510 -0
  247. trueppm_api/apps/projects/board_activity.py +462 -0
  248. trueppm_api/apps/projects/board_activity_views.py +186 -0
  249. trueppm_api/apps/projects/board_lanes.py +134 -0
  250. trueppm_api/apps/projects/bulk_settings.py +194 -0
  251. trueppm_api/apps/projects/bundled_templates.py +215 -0
  252. trueppm_api/apps/projects/calendar_settings.py +180 -0
  253. trueppm_api/apps/projects/ceremony_views.py +244 -0
  254. trueppm_api/apps/projects/commit_moment.py +212 -0
  255. trueppm_api/apps/projects/config_notice.py +1330 -0
  256. trueppm_api/apps/projects/custom_field_values.py +266 -0
  257. trueppm_api/apps/projects/decisions_services.py +132 -0
  258. trueppm_api/apps/projects/decisions_views.py +97 -0
  259. trueppm_api/apps/projects/estimation_scale.py +126 -0
  260. trueppm_api/apps/projects/export_bundle.py +423 -0
  261. trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
  262. trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
  263. trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
  264. trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
  265. trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -0
  266. trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
  267. trueppm_api/apps/projects/inbound_sync.py +555 -0
  268. trueppm_api/apps/projects/iteration_label.py +85 -0
  269. trueppm_api/apps/projects/key_views.py +468 -0
  270. trueppm_api/apps/projects/keys.py +205 -0
  271. trueppm_api/apps/projects/lifecycle.py +191 -0
  272. trueppm_api/apps/projects/management/__init__.py +0 -0
  273. trueppm_api/apps/projects/management/commands/__init__.py +0 -0
  274. trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
  275. trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
  276. trueppm_api/apps/projects/management/commands/export_program.py +50 -0
  277. trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
  278. trueppm_api/apps/projects/management/commands/load_sample_project.py +514 -0
  279. trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
  280. trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -0
  281. trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
  282. trueppm_api/apps/projects/mcp_settings.py +305 -0
  283. trueppm_api/apps/projects/methodology.py +178 -0
  284. trueppm_api/apps/projects/migration_helpers.py +75 -0
  285. trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
  286. trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -0
  287. trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
  288. trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
  289. trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
  290. trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
  291. trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
  292. trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
  293. trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
  294. trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
  295. trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
  296. trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
  297. trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
  298. trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
  299. trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
  300. trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
  301. trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
  302. trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
  303. trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
  304. trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
  305. trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
  306. trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
  307. trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
  308. trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
  309. trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
  310. trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
  311. trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
  312. trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
  313. trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
  314. trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
  315. trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
  316. trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
  317. trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
  318. trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
  319. trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
  320. trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
  321. trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
  322. trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
  323. trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
  324. trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
  325. trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
  326. trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
  327. trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
  328. trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
  329. trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
  330. trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
  331. trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
  332. trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
  333. trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
  334. trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
  335. trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
  336. trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
  337. trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
  338. trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
  339. trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
  340. trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
  341. trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
  342. trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
  343. trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
  344. trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
  345. trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
  346. trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
  347. trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
  348. trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
  349. trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
  350. trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
  351. trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
  352. trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
  353. trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
  354. trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
  355. trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
  356. trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
  357. trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
  358. trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
  359. trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
  360. trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
  361. trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
  362. trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
  363. trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
  364. trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
  365. trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
  366. trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
  367. trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
  368. trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
  369. trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
  370. trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
  371. trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
  372. trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
  373. trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
  374. trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
  375. trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
  376. trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
  377. trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
  378. trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
  379. trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +59 -0
  380. trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
  381. trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
  382. trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
  383. trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
  384. trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
  385. trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
  386. trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
  387. trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
  388. trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
  389. trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
  390. trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +75 -0
  391. trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
  392. trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
  393. trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
  394. trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
  395. trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
  396. trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +76 -0
  397. trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
  398. trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -0
  399. trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
  400. trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
  401. trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
  402. trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
  403. trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
  404. trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
  405. trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
  406. trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
  407. trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
  408. trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
  409. trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
  410. trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
  411. trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
  412. trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
  413. trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
  414. trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
  415. trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
  416. trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
  417. trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
  418. trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
  419. trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
  420. trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
  421. trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
  422. trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
  423. trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
  424. trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
  425. trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
  426. trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
  427. trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
  428. trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
  429. trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
  430. trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
  431. trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
  432. trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
  433. trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
  434. trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
  435. trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
  436. trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
  437. trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
  438. trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
  439. trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
  440. trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
  441. trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
  442. trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
  443. trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
  444. trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
  445. trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
  446. trueppm_api/apps/projects/migrations/0154_object_keys.py +184 -0
  447. trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
  448. trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
  449. trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
  450. trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
  451. trueppm_api/apps/projects/migrations/__init__.py +0 -0
  452. trueppm_api/apps/projects/models.py +9209 -0
  453. trueppm_api/apps/projects/poker_services.py +154 -0
  454. trueppm_api/apps/projects/poker_views.py +398 -0
  455. trueppm_api/apps/projects/product_backlog_services.py +450 -0
  456. trueppm_api/apps/projects/program_label_services.py +94 -0
  457. trueppm_api/apps/projects/program_label_views.py +189 -0
  458. trueppm_api/apps/projects/program_rollup.py +903 -0
  459. trueppm_api/apps/projects/program_schedule.py +767 -0
  460. trueppm_api/apps/projects/program_views.py +3445 -0
  461. trueppm_api/apps/projects/project_templates.py +422 -0
  462. trueppm_api/apps/projects/receivers.py +234 -0
  463. trueppm_api/apps/projects/refusal_codes.py +140 -0
  464. trueppm_api/apps/projects/reorder_services.py +145 -0
  465. trueppm_api/apps/projects/restructure_hooks.py +54 -0
  466. trueppm_api/apps/projects/retro_board_services.py +368 -0
  467. trueppm_api/apps/projects/retro_services.py +285 -0
  468. trueppm_api/apps/projects/risk_import.py +396 -0
  469. trueppm_api/apps/projects/row_identity.py +113 -0
  470. trueppm_api/apps/projects/schema_migrations.py +228 -0
  471. trueppm_api/apps/projects/schemas/seed_v1.json +492 -0
  472. trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
  473. trueppm_api/apps/projects/seed/__init__.py +61 -0
  474. trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
  475. trueppm_api/apps/projects/seed/exporter.py +1717 -0
  476. trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
  477. trueppm_api/apps/projects/seed/importer.py +2764 -0
  478. trueppm_api/apps/projects/seed/reanchor.py +551 -0
  479. trueppm_api/apps/projects/seed/reldates.py +126 -0
  480. trueppm_api/apps/projects/seed/replace.py +128 -0
  481. trueppm_api/apps/projects/seed/replay.py +1695 -0
  482. trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
  483. trueppm_api/apps/projects/seed/samples.py +427 -0
  484. trueppm_api/apps/projects/seed/validation.py +1169 -0
  485. trueppm_api/apps/projects/serializers.py +12904 -0
  486. trueppm_api/apps/projects/services.py +7525 -0
  487. trueppm_api/apps/projects/share_serializers.py +142 -0
  488. trueppm_api/apps/projects/share_services.py +373 -0
  489. trueppm_api/apps/projects/share_views.py +395 -0
  490. trueppm_api/apps/projects/sharing_settings.py +187 -0
  491. trueppm_api/apps/projects/signal_privacy_services.py +910 -0
  492. trueppm_api/apps/projects/signal_privacy_views.py +485 -0
  493. trueppm_api/apps/projects/signals.py +132 -0
  494. trueppm_api/apps/projects/slip_conflict.py +288 -0
  495. trueppm_api/apps/projects/sprint_cadence.py +453 -0
  496. trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
  497. trueppm_api/apps/projects/standup.py +304 -0
  498. trueppm_api/apps/projects/standup_views.py +99 -0
  499. trueppm_api/apps/projects/structural_operation_services.py +727 -0
  500. trueppm_api/apps/projects/structural_operation_views.py +311 -0
  501. trueppm_api/apps/projects/surface_visibility.py +126 -0
  502. trueppm_api/apps/projects/task_batch_services.py +379 -0
  503. trueppm_api/apps/projects/task_bulk.py +1244 -0
  504. trueppm_api/apps/projects/task_classification.py +561 -0
  505. trueppm_api/apps/projects/task_classification_defaults.py +95 -0
  506. trueppm_api/apps/projects/task_duration_settings.py +142 -0
  507. trueppm_api/apps/projects/task_grouping.py +754 -0
  508. trueppm_api/apps/projects/tasks.py +1979 -0
  509. trueppm_api/apps/projects/template_divergence.py +168 -0
  510. trueppm_api/apps/projects/template_services.py +160 -0
  511. trueppm_api/apps/projects/template_tasks.py +261 -0
  512. trueppm_api/apps/projects/template_views.py +829 -0
  513. trueppm_api/apps/projects/throttles.py +311 -0
  514. trueppm_api/apps/projects/urls.py +1016 -0
  515. trueppm_api/apps/projects/utilization.py +909 -0
  516. trueppm_api/apps/projects/views.py +21225 -0
  517. trueppm_api/apps/projects/wbs_paths.py +119 -0
  518. trueppm_api/apps/resources/__init__.py +0 -0
  519. trueppm_api/apps/resources/apps.py +8 -0
  520. trueppm_api/apps/resources/capacity.py +121 -0
  521. trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
  522. trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
  523. trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
  524. trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
  525. trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
  526. trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
  527. trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
  528. trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
  529. trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
  530. trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
  531. trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
  532. trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
  533. trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
  534. trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
  535. trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
  536. trueppm_api/apps/resources/migrations/0014_squashed_0015_release_0_4.py +47 -0
  537. trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
  538. trueppm_api/apps/resources/migrations/__init__.py +0 -0
  539. trueppm_api/apps/resources/models.py +298 -0
  540. trueppm_api/apps/resources/serializers.py +426 -0
  541. trueppm_api/apps/resources/services.py +465 -0
  542. trueppm_api/apps/resources/urls.py +28 -0
  543. trueppm_api/apps/resources/views.py +1752 -0
  544. trueppm_api/apps/scheduling/__init__.py +0 -0
  545. trueppm_api/apps/scheduling/apps.py +153 -0
  546. trueppm_api/apps/scheduling/calendars.py +144 -0
  547. trueppm_api/apps/scheduling/deadletter.py +91 -0
  548. trueppm_api/apps/scheduling/finish_reading.py +198 -0
  549. trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
  550. trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
  551. trueppm_api/apps/scheduling/graph_guard.py +263 -0
  552. trueppm_api/apps/scheduling/management/__init__.py +0 -0
  553. trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
  554. trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
  555. trueppm_api/apps/scheduling/mc_admission.py +181 -0
  556. trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
  557. trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
  558. trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
  559. trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
  560. trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
  561. trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
  562. trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
  563. trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
  564. trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
  565. trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
  566. trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
  567. trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
  568. trueppm_api/apps/scheduling/migrations/0010_squashed_0016_release_0_4.py +106 -0
  569. trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
  570. trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
  571. trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
  572. trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
  573. trueppm_api/apps/scheduling/migrations/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
  574. trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
  575. trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
  576. trueppm_api/apps/scheduling/models.py +542 -0
  577. trueppm_api/apps/scheduling/receivers.py +160 -0
  578. trueppm_api/apps/scheduling/risk_premium.py +334 -0
  579. trueppm_api/apps/scheduling/serializers.py +723 -0
  580. trueppm_api/apps/scheduling/services.py +1251 -0
  581. trueppm_api/apps/scheduling/signals.py +56 -0
  582. trueppm_api/apps/scheduling/tasks.py +2248 -0
  583. trueppm_api/apps/scheduling/telemetry.py +80 -0
  584. trueppm_api/apps/scheduling/units.py +35 -0
  585. trueppm_api/apps/scheduling/urls.py +57 -0
  586. trueppm_api/apps/scheduling/views.py +2908 -0
  587. trueppm_api/apps/sso/__init__.py +0 -0
  588. trueppm_api/apps/sso/apps.py +19 -0
  589. trueppm_api/apps/sso/extensions.py +130 -0
  590. trueppm_api/apps/sso/management/__init__.py +0 -0
  591. trueppm_api/apps/sso/management/commands/__init__.py +0 -0
  592. trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
  593. trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
  594. trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
  595. trueppm_api/apps/sso/migrations/__init__.py +0 -0
  596. trueppm_api/apps/sso/models.py +148 -0
  597. trueppm_api/apps/sso/serializers.py +446 -0
  598. trueppm_api/apps/sso/services.py +1473 -0
  599. trueppm_api/apps/sso/urls.py +47 -0
  600. trueppm_api/apps/sso/views.py +935 -0
  601. trueppm_api/apps/sync/__init__.py +0 -0
  602. trueppm_api/apps/sync/apps.py +15 -0
  603. trueppm_api/apps/sync/backfill.py +208 -0
  604. trueppm_api/apps/sync/broadcast.py +369 -0
  605. trueppm_api/apps/sync/conflict.py +262 -0
  606. trueppm_api/apps/sync/consumers.py +548 -0
  607. trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
  608. trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
  609. trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
  610. trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
  611. trueppm_api/apps/sync/migrations/0003_squashed_0005_release_0_4.py +77 -0
  612. trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
  613. trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
  614. trueppm_api/apps/sync/migrations/0006_boardevent_per_project_seq.py +77 -0
  615. trueppm_api/apps/sync/migrations/__init__.py +0 -0
  616. trueppm_api/apps/sync/models.py +272 -0
  617. trueppm_api/apps/sync/pagination.py +231 -0
  618. trueppm_api/apps/sync/sequence.py +488 -0
  619. trueppm_api/apps/sync/serializers.py +688 -0
  620. trueppm_api/apps/sync/tasks.py +260 -0
  621. trueppm_api/apps/sync/throttles.py +175 -0
  622. trueppm_api/apps/sync/upload.py +556 -0
  623. trueppm_api/apps/sync/urls.py +17 -0
  624. trueppm_api/apps/sync/views.py +957 -0
  625. trueppm_api/apps/sync/ws_auth.py +193 -0
  626. trueppm_api/apps/taskruns/__init__.py +0 -0
  627. trueppm_api/apps/taskruns/apps.py +11 -0
  628. trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
  629. trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
  630. trueppm_api/apps/taskruns/models.py +68 -0
  631. trueppm_api/apps/taskruns/serializers.py +92 -0
  632. trueppm_api/apps/taskruns/tasks.py +60 -0
  633. trueppm_api/apps/taskruns/tracker.py +286 -0
  634. trueppm_api/apps/taskruns/urls.py +46 -0
  635. trueppm_api/apps/taskruns/views.py +195 -0
  636. trueppm_api/apps/teams/__init__.py +8 -0
  637. trueppm_api/apps/teams/apps.py +16 -0
  638. trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
  639. trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +159 -0
  640. trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
  641. trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
  642. trueppm_api/apps/teams/migrations/0004_backfill_default_teams_on_upgrade.py +49 -0
  643. trueppm_api/apps/teams/migrations/__init__.py +0 -0
  644. trueppm_api/apps/teams/models.py +145 -0
  645. trueppm_api/apps/teams/permissions.py +149 -0
  646. trueppm_api/apps/teams/serializers.py +95 -0
  647. trueppm_api/apps/teams/services.py +440 -0
  648. trueppm_api/apps/teams/signals.py +61 -0
  649. trueppm_api/apps/teams/urls.py +34 -0
  650. trueppm_api/apps/teams/views.py +201 -0
  651. trueppm_api/apps/timetracking/__init__.py +0 -0
  652. trueppm_api/apps/timetracking/apps.py +9 -0
  653. trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
  654. trueppm_api/apps/timetracking/migrations/0001_squashed_0004_release_0_4.py +167 -0
  655. trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
  656. trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
  657. trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
  658. trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
  659. trueppm_api/apps/timetracking/models.py +178 -0
  660. trueppm_api/apps/timetracking/serializers.py +175 -0
  661. trueppm_api/apps/timetracking/services.py +211 -0
  662. trueppm_api/apps/timetracking/urls.py +47 -0
  663. trueppm_api/apps/timetracking/views.py +597 -0
  664. trueppm_api/apps/webhooks/__init__.py +0 -0
  665. trueppm_api/apps/webhooks/apps.py +8 -0
  666. trueppm_api/apps/webhooks/backfill.py +90 -0
  667. trueppm_api/apps/webhooks/dispatch.py +279 -0
  668. trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
  669. trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -0
  670. trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
  671. trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
  672. trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
  673. trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
  674. trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
  675. trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
  676. trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
  677. trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +100 -0
  678. trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
  679. trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
  680. trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
  681. trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
  682. trueppm_api/apps/webhooks/models.py +437 -0
  683. trueppm_api/apps/webhooks/serializers.py +326 -0
  684. trueppm_api/apps/webhooks/tasks.py +680 -0
  685. trueppm_api/apps/webhooks/urls.py +22 -0
  686. trueppm_api/apps/webhooks/views.py +533 -0
  687. trueppm_api/apps/workflow_engine/__init__.py +0 -0
  688. trueppm_api/apps/workflow_engine/apps.py +23 -0
  689. trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
  690. trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
  691. trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
  692. trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
  693. trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
  694. trueppm_api/apps/workflow_engine/models.py +265 -0
  695. trueppm_api/apps/workflow_engine/tasks.py +371 -0
  696. trueppm_api/apps/workspace/__init__.py +0 -0
  697. trueppm_api/apps/workspace/apps.py +10 -0
  698. trueppm_api/apps/workspace/backfill.py +111 -0
  699. trueppm_api/apps/workspace/export.py +254 -0
  700. trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
  701. trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
  702. trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
  703. trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
  704. trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
  705. trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
  706. trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
  707. trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
  708. trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
  709. trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
  710. trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
  711. trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
  712. trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
  713. trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
  714. trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
  715. trueppm_api/apps/workspace/migrations/0015_squashed_0027_release_0_4.py +199 -0
  716. trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
  717. trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
  718. trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
  719. trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
  720. trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
  721. trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
  722. trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
  723. trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
  724. trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
  725. trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
  726. trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
  727. trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
  728. trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
  729. trueppm_api/apps/workspace/migrations/__init__.py +0 -0
  730. trueppm_api/apps/workspace/models.py +857 -0
  731. trueppm_api/apps/workspace/permissions.py +187 -0
  732. trueppm_api/apps/workspace/serializers.py +590 -0
  733. trueppm_api/apps/workspace/services.py +967 -0
  734. trueppm_api/apps/workspace/signals.py +37 -0
  735. trueppm_api/apps/workspace/tasks.py +616 -0
  736. trueppm_api/apps/workspace/urls.py +113 -0
  737. trueppm_api/apps/workspace/views.py +1581 -0
  738. trueppm_api/asgi.py +49 -0
  739. trueppm_api/celery.py +40 -0
  740. trueppm_api/core/__init__.py +0 -0
  741. trueppm_api/core/admin_site.py +206 -0
  742. trueppm_api/core/auth_views.py +934 -0
  743. trueppm_api/core/constant_time.py +51 -0
  744. trueppm_api/core/csp.py +54 -0
  745. trueppm_api/core/db.py +87 -0
  746. trueppm_api/core/db_session.py +37 -0
  747. trueppm_api/core/demo_read_only.py +314 -0
  748. trueppm_api/core/exception_handlers.py +157 -0
  749. trueppm_api/core/export_downloads.py +56 -0
  750. trueppm_api/core/extension_providers.py +56 -0
  751. trueppm_api/core/extension_signals.py +147 -0
  752. trueppm_api/core/idempotent.py +260 -0
  753. trueppm_api/core/metadata.py +42 -0
  754. trueppm_api/core/middleware.py +51 -0
  755. trueppm_api/core/openapi.py +1221 -0
  756. trueppm_api/core/password_reset.py +513 -0
  757. trueppm_api/core/protect_conflict.py +100 -0
  758. trueppm_api/core/ratelimit.py +148 -0
  759. trueppm_api/core/redis_throttle.py +82 -0
  760. trueppm_api/core/refresh_cookie_policy.py +156 -0
  761. trueppm_api/core/request_body.py +86 -0
  762. trueppm_api/core/security_checks.py +1034 -0
  763. trueppm_api/core/storage_config.py +105 -0
  764. trueppm_api/core/text_decode.py +168 -0
  765. trueppm_api/core/throttling.py +514 -0
  766. trueppm_api/core/valkey.py +214 -0
  767. trueppm_api/core/valkey_cache.py +66 -0
  768. trueppm_api/core/valkey_checks.py +104 -0
  769. trueppm_api/core/valkey_config.py +39 -0
  770. trueppm_api/core/worker_broker_wait.py +149 -0
  771. trueppm_api/core/worker_heartbeat.py +134 -0
  772. trueppm_api/core/worker_selfheal.py +205 -0
  773. trueppm_api/fields.py +36 -0
  774. trueppm_api/permissions.py +30 -0
  775. trueppm_api/py.typed +0 -0
  776. trueppm_api/routing.py +11 -0
  777. trueppm_api/settings/__init__.py +0 -0
  778. trueppm_api/settings/base.py +2605 -0
  779. trueppm_api/settings/dev.py +296 -0
  780. trueppm_api/settings/prod.py +265 -0
  781. trueppm_api/urls.py +217 -0
  782. trueppm_api/workflows/__init__.py +38 -0
  783. trueppm_api/workflows/backends/__init__.py +6 -0
  784. trueppm_api/workflows/backends/default.py +297 -0
  785. trueppm_api/workflows/consumers/__init__.py +11 -0
  786. trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
  787. trueppm_api/workflows/interface.py +148 -0
  788. trueppm_api/workflows/registry.py +106 -0
  789. trueppm_api/workflows/services.py +90 -0
  790. trueppm_api/wsgi.py +11 -0
  791. trueppm_api-0.4.0.dist-info/METADATA +273 -0
  792. trueppm_api-0.4.0.dist-info/RECORD +799 -0
  793. trueppm_api-0.4.0.dist-info/WHEEL +4 -0
  794. trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
  795. trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
  796. trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
  797. trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
  798. trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
  799. trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
@@ -0,0 +1,723 @@
1
+ """Serializers for the scheduling app admin API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from drf_spectacular.utils import extend_schema_field
8
+ from rest_framework import serializers
9
+ from trueppm_scheduler.derive import Quantity as CpmDerivationQuantity
10
+
11
+ from trueppm_api.apps.scheduling.models import (
12
+ FailedTask,
13
+ MonteCarloRun,
14
+ ProjectForecastSnapshot,
15
+ VelocitySuggestion,
16
+ )
17
+ from trueppm_api.apps.scheduling.services import (
18
+ FORECAST_ALL_COMPLETE,
19
+ FORECAST_ESTIMATES_BELOW_PLAN_DURATION,
20
+ FORECAST_ESTIMATES_OFF_CRITICAL_PATH,
21
+ FORECAST_ESTIMATES_PENDING_APPROVAL,
22
+ FORECAST_NO_COMMITTED_TASKS,
23
+ FORECAST_NO_ESTIMATES,
24
+ FORECAST_NO_VELOCITY_HISTORY,
25
+ )
26
+
27
+
28
+ class MonteCarloWhatIfRequestSerializer(serializers.Serializer[dict[str, Any]]):
29
+ """Validate the non-mutating Monte Carlo what-if query (#993).
30
+
31
+ The what-if endpoint perturbs exactly one task's duration and recomputes the
32
+ forecast in memory without persisting anything, so this is a *request* (query
33
+ param) validator only — there is no model behind it. The caller must supply a
34
+ ``task_id`` and exactly one of ``duration_delta`` (signed day offset applied to
35
+ the task's current duration) or ``new_duration`` (an absolute day count the
36
+ task's duration is set to). Requiring exactly one keeps the perturbation
37
+ unambiguous — a request carrying both, or neither, cannot express a single
38
+ well-defined schedule and is rejected up front rather than resolved by a silent
39
+ precedence rule.
40
+ """
41
+
42
+ task_id = serializers.UUIDField()
43
+ duration_delta = serializers.IntegerField(required=False)
44
+ new_duration = serializers.IntegerField(required=False, min_value=0)
45
+ n_simulations = serializers.IntegerField(required=False, min_value=1)
46
+
47
+ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
48
+ has_delta = "duration_delta" in attrs
49
+ has_absolute = "new_duration" in attrs
50
+ if has_delta == has_absolute:
51
+ raise serializers.ValidationError(
52
+ "Supply exactly one of 'duration_delta' or 'new_duration'."
53
+ )
54
+ return attrs
55
+
56
+
57
+ _WORKING_TIME_DELTA_HELP = (
58
+ "Signed calendar days, measured in working time (#4204): each finish is read "
59
+ "with its edge of the day (the `*_at_day_start` readings), so a start-of-day "
60
+ "milestone finish on a Monday equals the end of the Friday before it and diffs "
61
+ "as 0 rather than 3. A reading persisted before #4204 is unknown: two equal days "
62
+ "diff as 0, otherwise it is read as the end of its day."
63
+ )
64
+
65
+ _AT_DAY_START_HELP = (
66
+ "Which edge of its shown day this finish is (#4204): true = the start of the "
67
+ "day (a start-of-day milestone finish), false = the end. Null on a run "
68
+ "persisted before #4204."
69
+ )
70
+
71
+
72
+ class MonteCarloDeltaSerializer(serializers.Serializer[dict[str, Any]]):
73
+ """Signed delta of a percentile finish, per field, in working time (#987/#993/#2483/#4204).
74
+
75
+ Positive means the field slipped later (worse). Shared shape for
76
+ ``run_monte_carlo``/``latest``'s ``delta_vs_cpm`` (each percentile vs the
77
+ deterministic CPM finish), the what-if endpoint's ``delta_vs_current``, and
78
+ the history endpoint's per-run ``delta`` (ADR-0108) — one derivation, one
79
+ schema, three call sites. ``None`` whenever either date it is measured
80
+ between is missing. Measured in working time from each date's edge-of-day
81
+ reading, still reported in calendar days.
82
+ """
83
+
84
+ p50 = serializers.IntegerField(
85
+ allow_null=True, help_text=f"Signed P50 delta; positive = later. {_WORKING_TIME_DELTA_HELP}"
86
+ )
87
+ p80 = serializers.IntegerField(
88
+ allow_null=True, help_text=f"Signed P80 delta; positive = later. {_WORKING_TIME_DELTA_HELP}"
89
+ )
90
+ p95 = serializers.IntegerField(
91
+ allow_null=True, help_text=f"Signed P95 delta; positive = later. {_WORKING_TIME_DELTA_HELP}"
92
+ )
93
+
94
+
95
+ class MonteCarloHistogramBucketSerializer(serializers.Serializer[dict[str, Any]]):
96
+ """One ascending finish-date bucket of a Monte Carlo distribution."""
97
+
98
+ date = serializers.DateField()
99
+ count = serializers.IntegerField()
100
+
101
+
102
+ class MonteCarloConfidencePointSerializer(serializers.Serializer[dict[str, Any]]):
103
+ """One point of the cumulative P(finish <= date) S-curve derived from the histogram."""
104
+
105
+ date = serializers.DateField()
106
+ pct = serializers.FloatField()
107
+
108
+
109
+ class MonteCarloSensitivitySerializer(serializers.Serializer[dict[str, Any]]):
110
+ """One task's duration-sensitivity tornado entry (ADR-0140).
111
+
112
+ ``index`` is the absolute Spearman rank correlation between the task's
113
+ sampled duration and the project's finish, in [0, 1]. Tasks whose duration
114
+ cannot vary the finish are omitted from the list entirely rather than
115
+ reported as 0.
116
+ """
117
+
118
+ task_id = serializers.CharField()
119
+ index = serializers.FloatField()
120
+
121
+
122
+ class MonteCarloDistributionFieldsSerializer(serializers.Serializer[dict[str, Any]]):
123
+ """The ``{histogram_buckets, confidence_curve, sensitivity}`` shape (#1231).
124
+
125
+ Mixed into :class:`MonteCarloForecastSerializer` as flat top-level fields
126
+ (the live-run and ``latest`` payloads), and used standalone — nested under
127
+ a ``distribution`` key — by :meth:`MonteCarloRunSerializer.get_distribution`
128
+ for the history endpoint's opt-in ``?expand=distribution``. One field
129
+ definition, two positions on the wire.
130
+ """
131
+
132
+ histogram_buckets = MonteCarloHistogramBucketSerializer(many=True)
133
+ confidence_curve = MonteCarloConfidencePointSerializer(many=True)
134
+ sensitivity = MonteCarloSensitivitySerializer(many=True)
135
+
136
+
137
+ class MonteCarloForecastDiagnosticSerializer(serializers.Serializer[dict[str, Any]]):
138
+ """Why a Monte Carlo forecast carries -- or lacks -- an uncertainty band (#1340).
139
+
140
+ ``reason`` is populated only when ``deterministic`` is true (the forecast
141
+ collapsed to a single date); it is ``None`` whenever a real band exists.
142
+ """
143
+
144
+ deterministic = serializers.BooleanField()
145
+ reason = serializers.ChoiceField(
146
+ choices=[
147
+ FORECAST_NO_COMMITTED_TASKS,
148
+ FORECAST_ALL_COMPLETE,
149
+ FORECAST_ESTIMATES_BELOW_PLAN_DURATION,
150
+ FORECAST_ESTIMATES_OFF_CRITICAL_PATH,
151
+ FORECAST_ESTIMATES_PENDING_APPROVAL,
152
+ FORECAST_NO_VELOCITY_HISTORY,
153
+ FORECAST_NO_ESTIMATES,
154
+ ],
155
+ allow_null=True,
156
+ )
157
+ tasks_total = serializers.IntegerField()
158
+ tasks_with_variance = serializers.IntegerField()
159
+ tasks_pending_approval = serializers.IntegerField()
160
+ agile_tasks_without_velocity = serializers.IntegerField()
161
+ tasks_estimates_below_plan = serializers.IntegerField(
162
+ required=False,
163
+ help_text=(
164
+ "Tasks whose whole three-point range sits at or below their planned "
165
+ "duration, which the engine floors every sample at (#3765) — so the "
166
+ "range samples to a constant. Absent on a run persisted before #3765 "
167
+ "and replayed from history; clients read an absent value as 0, the same "
168
+ "way they already treat an absent `diagnostic` as 'no reason known'."
169
+ ),
170
+ )
171
+
172
+
173
+ class RiskPremiumFieldsSerializer(serializers.Serializer[dict[str, Any]]):
174
+ """The flat ``risk_premium_*`` family shared by every forecast payload (ADR-0698).
175
+
176
+ Mixed into :class:`MonteCarloForecastSerializer` rather than nested — the
177
+ view spreads these keys directly onto the response dict (``**risk_premium_from_values(...)``),
178
+ and the schema mirrors that shape.
179
+ """
180
+
181
+ risk_premium_days = serializers.IntegerField(
182
+ allow_null=True,
183
+ help_text=f"P80 finish minus the deterministic CPM finish. {_WORKING_TIME_DELTA_HELP}",
184
+ )
185
+ risk_premium_ratio = serializers.FloatField(allow_null=True)
186
+ # Always null until #2299 (the calibration flywheel); declared nullable now so
187
+ # the schema does not have to change shape when a band is first populated.
188
+ risk_premium_band = serializers.CharField(allow_null=True)
189
+ risk_premium_as_of = serializers.DateTimeField(allow_null=True)
190
+ risk_premium_reason = serializers.CharField(allow_null=True)
191
+ risk_premium_state = serializers.ChoiceField(
192
+ choices=["not_run", "unmeasurable", "stale", "zero", "premium", "negative"]
193
+ )
194
+ risk_premium_cpm_finish = serializers.DateField(allow_null=True)
195
+ risk_premium_p80 = serializers.DateField(allow_null=True)
196
+
197
+
198
+ class ForecastStalenessFieldsSerializer(serializers.Serializer[dict[str, Any]]):
199
+ """The flat forecast-staleness family shared by every forecast payload (#3140).
200
+
201
+ Mixed into :class:`MonteCarloForecastSerializer` for the same reason as
202
+ :class:`RiskPremiumFieldsSerializer` — the view spreads
203
+ ``**forecast_staleness_facts(...)`` directly onto the response dict.
204
+ """
205
+
206
+ forecast_staleness = serializers.ChoiceField(
207
+ choices=["current", "project_changed", "aged", "unknown"]
208
+ )
209
+ plan_version = serializers.IntegerField(allow_null=True)
210
+ plan_version_current = serializers.IntegerField(allow_null=True)
211
+
212
+
213
+ class ScheduleDerivationContributionSerializer(serializers.Serializer[dict[str, Any]]):
214
+ """One candidate constraint the CPM engine weighed for a derived value (ADR-0218).
215
+
216
+ Mirrors :meth:`trueppm_scheduler.derive.DerivationContribution.to_dict`. Exactly
217
+ one contribution in a given derivation's ``contributions`` list carries
218
+ ``is_binding=True`` — the term the engine's ``max``/``min`` actually selected.
219
+ """
220
+
221
+ kind = serializers.CharField()
222
+ source_task_id = serializers.CharField(allow_null=True)
223
+ source_task_name = serializers.CharField(allow_null=True)
224
+ dep_type = serializers.CharField(allow_null=True)
225
+ lag_days = serializers.IntegerField(allow_null=True)
226
+ imposed_date = serializers.DateField(allow_null=True)
227
+ calendar_days_added = serializers.IntegerField(allow_null=True)
228
+ slack_days = serializers.IntegerField(allow_null=True)
229
+ is_binding = serializers.BooleanField()
230
+
231
+
232
+ @extend_schema_field({"oneOf": [{"type": "string"}, {"type": "integer"}], "nullable": True})
233
+ class ScheduleDerivationValueField(serializers.CharField):
234
+ """A CPM quantity's derived value (ADR-0218).
235
+
236
+ An ISO-8601 date string for a date quantity, or a plain integer working-day
237
+ count for ``total_float``/``free_float`` — never both on one response, since the
238
+ shape is fixed by the ``quantity`` query param. Declaration-only: this field is
239
+ never bound to real data (:class:`ScheduleDerivationView` returns a plain dict),
240
+ so the inherited ``CharField`` behavior is never exercised — only the
241
+ ``@extend_schema_field`` override, which states the true wire type.
242
+ """
243
+
244
+
245
+ class ScheduleCpmDerivationSerializer(serializers.Serializer[dict[str, Any]]):
246
+ """CPM-quantity derivation payload — the *why* behind one computed value (ADR-0218).
247
+
248
+ Mirrors :meth:`trueppm_scheduler.derive.Derivation.to_dict`. Returned for a CPM
249
+ ``quantity`` (``early_start``/``early_finish``/``late_start``/``late_finish``/
250
+ ``total_float``/``free_float``/``scheduled_start``); see
251
+ :class:`ScheduleMonteCarloDerivationSerializer` for the percentile shape returned
252
+ for ``p50``/``p80``/``p95``.
253
+ """
254
+
255
+ task_id = serializers.CharField()
256
+ task_name = serializers.CharField()
257
+ quantity = serializers.ChoiceField(choices=[q.value for q in CpmDerivationQuantity])
258
+ value = ScheduleDerivationValueField(allow_null=True)
259
+ is_critical = serializers.BooleanField()
260
+ binding = ScheduleDerivationContributionSerializer(allow_null=True)
261
+ contributions = ScheduleDerivationContributionSerializer(many=True)
262
+
263
+
264
+ # "pass" is a Python keyword and cannot be a class-body attribute name, but the wire
265
+ # key is unsuffixed — Derivation.to_dict() emits it as "pass", not "pass_" — so the
266
+ # field is added to the declared-fields map directly instead of following the
267
+ # trailing-underscore convention into the schema, which would misdeclare the key.
268
+ ScheduleCpmDerivationSerializer._declared_fields["pass"] = serializers.ChoiceField(
269
+ choices=["forward", "backward", "float"]
270
+ )
271
+
272
+
273
+ class ScheduleMonteCarloDerivationSerializer(ForecastStalenessFieldsSerializer):
274
+ """Monte Carlo percentile derivation payload (ADR-0218, #987, #3140).
275
+
276
+ Mirrors the dict built by ``ScheduleDerivationView._monte_carlo_derivation`` —
277
+ shared verbatim by its cache-hit and persisted-run-fallback branches. Returned for
278
+ a percentile ``quantity`` (``p50``/``p80``/``p95``); see
279
+ :class:`ScheduleCpmDerivationSerializer` for the CPM shape.
280
+ """
281
+
282
+ quantity = serializers.ChoiceField(choices=["p50", "p80", "p95"])
283
+ value = serializers.DateField(allow_null=True)
284
+ value_at_day_start = serializers.BooleanField(allow_null=True, help_text=_AT_DAY_START_HELP)
285
+ cpm_finish = serializers.DateField(allow_null=True)
286
+ cpm_finish_at_day_start = serializers.BooleanField(
287
+ allow_null=True, help_text=_AT_DAY_START_HELP
288
+ )
289
+ delta_vs_cpm_days = serializers.IntegerField(
290
+ allow_null=True,
291
+ help_text=f"The percentile minus the deterministic CPM finish. {_WORKING_TIME_DELTA_HELP}",
292
+ )
293
+ drivers = MonteCarloSensitivitySerializer(many=True)
294
+ runs = serializers.IntegerField()
295
+ last_run_at = serializers.DateTimeField()
296
+
297
+
298
+ # Same reserved-keyword workaround as ScheduleCpmDerivationSerializer above — this
299
+ # payload's "pass" is always the literal "monte_carlo".
300
+ ScheduleMonteCarloDerivationSerializer._declared_fields["pass"] = serializers.ChoiceField(
301
+ choices=["monte_carlo"]
302
+ )
303
+
304
+
305
+ class MonteCarloForecastSerializer(
306
+ RiskPremiumFieldsSerializer,
307
+ ForecastStalenessFieldsSerializer,
308
+ MonteCarloDistributionFieldsSerializer,
309
+ ):
310
+ """Monte Carlo forecast payload shared by the live run and the ``latest`` read.
311
+
312
+ Two response-producing paths share this exact shape and are not identical on
313
+ the wire:
314
+
315
+ - The **live run** (``POST .../monte-carlo/``) and a **cache hit**
316
+ (``GET .../monte-carlo/latest/`` inside the 24h TTL) always carry
317
+ ``distribution`` (the full sorted per-run finish-date sample — the large
318
+ field) and never carry ``from_history``.
319
+ - The **persisted-history fallback** (``GET .../latest/`` once the cache
320
+ entry has expired, ADR-0175) always carries ``from_history: true`` and
321
+ never carries ``distribution`` — only the derived
322
+ ``histogram_buckets``/``confidence_curve``/``sensitivity`` (and
323
+ ``forecast_diagnostic``) survive past the TTL, and only for runs
324
+ persisted after #1231/#2483.
325
+
326
+ Both fields are declared ``required=False`` rather than modeled as two
327
+ named variants (``PolymorphicProxySerializer``): nothing else about the
328
+ shape differs between the two paths, so a discriminated union would only
329
+ duplicate every other field for no added precision.
330
+ """
331
+
332
+ project_id = serializers.CharField()
333
+ runs = serializers.IntegerField()
334
+ # Nullable: a project with no committed tasks yields no distribution to
335
+ # anchor the percentiles on (mirrors MonteCarloRun.p50/p80/p95).
336
+ p50 = serializers.DateField(allow_null=True)
337
+ p80 = serializers.DateField(allow_null=True)
338
+ p95 = serializers.DateField(allow_null=True)
339
+ p50_at_day_start = serializers.BooleanField(allow_null=True, help_text=_AT_DAY_START_HELP)
340
+ p80_at_day_start = serializers.BooleanField(allow_null=True, help_text=_AT_DAY_START_HELP)
341
+ p95_at_day_start = serializers.BooleanField(allow_null=True, help_text=_AT_DAY_START_HELP)
342
+ distribution = serializers.ListField(
343
+ child=serializers.DateField(),
344
+ required=False,
345
+ help_text=(
346
+ "Full sorted per-run finish-date sample. Present on a live run or a "
347
+ "cache hit; absent on the persisted-history fallback."
348
+ ),
349
+ )
350
+ cpm_finish = serializers.DateField(allow_null=True)
351
+ cpm_finish_at_day_start = serializers.BooleanField(
352
+ allow_null=True, help_text=_AT_DAY_START_HELP
353
+ )
354
+ delta_vs_cpm = MonteCarloDeltaSerializer()
355
+ forecast_diagnostic = MonteCarloForecastDiagnosticSerializer(allow_null=True)
356
+ last_run_at = serializers.DateTimeField()
357
+ status_date = serializers.DateField(allow_null=True)
358
+ from_history = serializers.BooleanField(
359
+ required=False,
360
+ help_text=(
361
+ "True only on the persisted-history fallback past the 24h cache TTL; "
362
+ "absent (never false) on a live run or cache hit."
363
+ ),
364
+ )
365
+
366
+
367
+ class MonteCarloWhatIfAppliedSerializer(serializers.Serializer[dict[str, Any]]):
368
+ """The resolved perturbation the what-if endpoint applied to the target task (#993)."""
369
+
370
+ base_duration_days = serializers.IntegerField()
371
+ duration_delta_days = serializers.IntegerField()
372
+ new_duration_days = serializers.IntegerField()
373
+
374
+
375
+ class MonteCarloWhatIfLegSerializer(serializers.Serializer[dict[str, Any]]):
376
+ """One forecast leg (``current`` or ``whatif``) of the what-if response (#993)."""
377
+
378
+ p50 = serializers.DateField()
379
+ p80 = serializers.DateField()
380
+ p95 = serializers.DateField()
381
+ p50_at_day_start = serializers.BooleanField(help_text=_AT_DAY_START_HELP)
382
+ p80_at_day_start = serializers.BooleanField(help_text=_AT_DAY_START_HELP)
383
+ p95_at_day_start = serializers.BooleanField(help_text=_AT_DAY_START_HELP)
384
+ cpm_finish = serializers.DateField(allow_null=True)
385
+ cpm_finish_at_day_start = serializers.BooleanField(
386
+ allow_null=True, help_text=_AT_DAY_START_HELP
387
+ )
388
+ critical_path = serializers.ListField(child=serializers.CharField())
389
+
390
+
391
+ class MonteCarloWhatIfDeltaSerializer(serializers.Serializer[dict[str, Any]]):
392
+ """Signed shift of ``whatif`` vs ``current``, per field, in working time (#993/#4204).
393
+
394
+ Same ``p50``/``p80``/``p95`` shape as :class:`MonteCarloDeltaSerializer`,
395
+ plus ``cpm_finish`` — the what-if endpoint's own deterministic-pass delta,
396
+ distinct from ``delta_vs_cpm``'s MC-vs-CPM comparison elsewhere. All four are
397
+ measured in working time and reported in calendar days.
398
+ """
399
+
400
+ p50 = serializers.IntegerField(
401
+ allow_null=True, help_text=f"Signed P50 shift; positive = later. {_WORKING_TIME_DELTA_HELP}"
402
+ )
403
+ p80 = serializers.IntegerField(
404
+ allow_null=True, help_text=f"Signed P80 shift; positive = later. {_WORKING_TIME_DELTA_HELP}"
405
+ )
406
+ p95 = serializers.IntegerField(
407
+ allow_null=True, help_text=f"Signed P95 shift; positive = later. {_WORKING_TIME_DELTA_HELP}"
408
+ )
409
+ cpm_finish = serializers.IntegerField(allow_null=True, help_text=_WORKING_TIME_DELTA_HELP)
410
+
411
+
412
+ class MonteCarloWhatIfResponseSerializer(serializers.Serializer[dict[str, Any]]):
413
+ """Non-mutating Monte Carlo what-if result (#993).
414
+
415
+ Perturbs exactly one committed task's duration and recomputes both the
416
+ deterministic CPM pass and a seeded Monte Carlo simulation in memory,
417
+ without persisting anything — see the view docstring for the
418
+ non-persistence guarantee.
419
+ """
420
+
421
+ task_id = serializers.CharField()
422
+ applied = MonteCarloWhatIfAppliedSerializer()
423
+ current = MonteCarloWhatIfLegSerializer()
424
+ whatif = MonteCarloWhatIfLegSerializer()
425
+ critical_path_changed = serializers.BooleanField()
426
+ delta_vs_current = MonteCarloWhatIfDeltaSerializer()
427
+ runs = serializers.IntegerField()
428
+ seed = serializers.IntegerField(
429
+ help_text="Fixed RNG seed shared by both runs so the delta isolates the perturbation."
430
+ )
431
+ cpm_status_date = serializers.DateField()
432
+ mc_status_date = serializers.DateField()
433
+
434
+
435
+ class ProjectForecastSnapshotSerializer(serializers.ModelSerializer[ProjectForecastSnapshot]):
436
+ """Read-only serializer for a project-grain forecast snapshot (ADR-0154, #388).
437
+
438
+ Server-generated history; the endpoint is list-only, so every field is
439
+ read-only and there is no create/update path.
440
+
441
+ ``cpm_finish_at_day_start`` is published so a client diffing ``cpm_finish``
442
+ across snapshots can do it in working time (#4178): a start-of-day finish on
443
+ a Monday is the same finish as the end of the Friday before it.
444
+ """
445
+
446
+ cpm_finish_at_day_start = serializers.BooleanField(
447
+ read_only=True,
448
+ allow_null=True,
449
+ help_text=(
450
+ "True when cpm_finish is the START of that day: every task finishing on "
451
+ "it is a milestone shown at the start of its day. The start of a day is "
452
+ "the same point in working time as the end of the working day before "
453
+ "it, so compare finishes in working time, not as a date difference. "
454
+ "Null when unknown: no finish, or a snapshot captured before this field "
455
+ "existed."
456
+ ),
457
+ )
458
+
459
+ class Meta:
460
+ model = ProjectForecastSnapshot
461
+ fields = [
462
+ "id",
463
+ "captured_at",
464
+ "triggered_by",
465
+ "cpm_finish",
466
+ "cpm_finish_at_day_start",
467
+ "total_float_days",
468
+ "mc_p50_finish",
469
+ "mc_p80_finish",
470
+ "mc_p95_finish",
471
+ "mc_iterations",
472
+ "task_count",
473
+ "completed_task_count",
474
+ ]
475
+ read_only_fields = fields
476
+
477
+
478
+ class FailedTaskSerializer(serializers.ModelSerializer[FailedTask]):
479
+ """Read-only serializer for the failed-task dead-letter queue.
480
+
481
+ Exposed by the admin API so operators can inspect and requeue or discard
482
+ tasks that exceeded their retry budget. The ``resolution_*`` fields surface the
483
+ operator-action audit (ADR-0210): who requeued/dropped the task, when, and the
484
+ drop note.
485
+ """
486
+
487
+ resolved_by_display = serializers.SerializerMethodField()
488
+
489
+ class Meta:
490
+ model = FailedTask
491
+ fields = [
492
+ "id",
493
+ "task_name",
494
+ "task_id",
495
+ "project_id",
496
+ "args",
497
+ "kwargs",
498
+ "exception_type",
499
+ "exception_message",
500
+ "traceback",
501
+ "failure_count",
502
+ "first_failed_at",
503
+ "last_failed_at",
504
+ "status",
505
+ "resolution_note",
506
+ "resolved_by_display",
507
+ "resolved_at",
508
+ ]
509
+ read_only_fields = fields
510
+
511
+ def get_resolved_by_display(self, obj: FailedTask) -> str | None:
512
+ """Display name of the operator who last acted, or None if unresolved."""
513
+ user: Any = obj.resolved_by
514
+ if user is None:
515
+ return None
516
+ full_name = user.get_full_name() if hasattr(user, "get_full_name") else ""
517
+ return full_name or user.get_username()
518
+
519
+
520
+ class FailedTaskRequeueSerializer(serializers.Serializer[dict[str, Any]]):
521
+ """Validate the requeue-with-backoff request body (ADR-0210).
522
+
523
+ ``backoff_seconds`` is the operator-chosen delay applied as a Celery
524
+ ``countdown`` on the re-dispatched task. Bounded to a day so a fat-fingered
525
+ value cannot park a task for a year; ``0`` means dispatch immediately.
526
+ """
527
+
528
+ backoff_seconds = serializers.IntegerField(
529
+ required=False,
530
+ default=0,
531
+ min_value=0,
532
+ max_value=86_400,
533
+ help_text="Backoff before re-dispatch, in seconds (0-86400). 0 = immediate.",
534
+ )
535
+
536
+
537
+ class FailedTaskDropSerializer(serializers.Serializer[dict[str, Any]]):
538
+ """Validate the drop-with-note request body (ADR-0210).
539
+
540
+ ``note`` is optional free-text audit context. Bounded and trimmed; stored as
541
+ data and rendered as text (never interpolated into task args), so it carries no
542
+ injection surface.
543
+ """
544
+
545
+ note = serializers.CharField(
546
+ required=False,
547
+ allow_blank=True,
548
+ default="",
549
+ max_length=1000,
550
+ trim_whitespace=True,
551
+ help_text="Optional audit note recorded with the drop.",
552
+ )
553
+
554
+
555
+ class VelocitySuggestionSerializer(serializers.ModelSerializer[VelocitySuggestion]):
556
+ """Read serializer for a velocity-calibration suggestion (ADR-0065).
557
+
558
+ Surfaces the sprint that triggered the suggestion (name + id) so the Task
559
+ Detail Drawer can render "Suggested from Sprint 12 close" without a second
560
+ fetch. The accept/dismiss audit fields are exposed so the drawer can also
561
+ render a quiet history row once a decision is made.
562
+ """
563
+
564
+ sprint_name = serializers.CharField(source="sprint.name", read_only=True)
565
+ sprint_id = serializers.UUIDField(source="sprint.id", read_only=True)
566
+ is_pending = serializers.BooleanField(read_only=True)
567
+ # Both team_velocity_per_day and suggested_duration are declared explicitly with
568
+ # allow_null because to_representation nulls them for readers below the velocity
569
+ # audience (ADR-0104 gate, #949/#1099) — the schema must advertise the suppressed
570
+ # shape for schema-driven clients (#997 contract class).
571
+ team_velocity_per_day = serializers.DecimalField(
572
+ max_digits=6,
573
+ decimal_places=3,
574
+ read_only=True,
575
+ allow_null=True,
576
+ help_text="Rolling 6-sprint average of completed_points / sprint_working_days.",
577
+ )
578
+ # suggested_duration is computed *from* team_velocity_per_day
579
+ # (round(story_points / velocity)); leaving it ungated lets a below-audience
580
+ # reader back into the team's pace via the calibration value, so it is gated by
581
+ # the same velocity check (#1099 — a new instance of the #949 leak class).
582
+ suggested_duration = serializers.IntegerField(
583
+ read_only=True,
584
+ allow_null=True,
585
+ help_text=(
586
+ "Velocity-calibrated duration in working days; null when the reader is "
587
+ "below the velocity audience."
588
+ ),
589
+ )
590
+
591
+ class Meta:
592
+ model = VelocitySuggestion
593
+ fields = [
594
+ "id",
595
+ "task",
596
+ "sprint_id",
597
+ "sprint_name",
598
+ "suggested_duration",
599
+ "team_velocity_per_day",
600
+ "flag_for_review",
601
+ "is_pending",
602
+ "created_at",
603
+ "accepted_at",
604
+ "accepted_by",
605
+ "dismissed_at",
606
+ "dismissed_by",
607
+ ]
608
+ read_only_fields = fields
609
+
610
+ # Velocity-derived fields stripped for a reader below the velocity audience.
611
+ # team_velocity_per_day is the raw rate; suggested_duration is computed from it
612
+ # (#1099) — both must fall to the same gate or the rate leaks via the suggestion.
613
+ _VELOCITY_GATED_FIELDS = ("team_velocity_per_day", "suggested_duration")
614
+
615
+ def to_representation(self, instance: VelocitySuggestion) -> dict[str, Any]:
616
+ data = super().to_representation(instance)
617
+ # ADR-0104 velocity gate (#949/#1099): these are the same point-based velocity
618
+ # number — and a value derived from it — that suppress_velocity_summary strips
619
+ # from /velocity/. A reader below the velocity audience is suppressed there, so
620
+ # they must not recover it from this calibration-suggestion surface.
621
+ request = self.context.get("request")
622
+ # Fail closed: a render with no request context can't establish the
623
+ # reader's tier, so suppress rather than leak (the only callers are HTTP
624
+ # responses, which always carry a request).
625
+ if request is None:
626
+ for field in self._VELOCITY_GATED_FIELDS:
627
+ data[field] = None
628
+ return data
629
+ # The verdict is per-project; cache it on the (reused) child serializer so
630
+ # a list render is not N+1 on the gate query.
631
+ project_id = instance.task.project_id
632
+ cache: dict[Any, bool] | None = getattr(self, "_velocity_gate_cache", None)
633
+ if cache is None:
634
+ cache = {}
635
+ self._velocity_gate_cache = cache
636
+ if project_id not in cache:
637
+ from trueppm_api.apps.projects.signal_privacy_services import can_read_signal
638
+
639
+ cache[project_id] = can_read_signal(request, project_id, "velocity")
640
+ if not cache[project_id]:
641
+ for field in self._VELOCITY_GATED_FIELDS:
642
+ data[field] = None
643
+ return data
644
+
645
+
646
+ class MonteCarloRunSerializer(serializers.ModelSerializer[MonteCarloRun]):
647
+ """Read serializer for one project Monte Carlo run in the forecast history (ADR-0175).
648
+
649
+ Two pieces of context the view must supply:
650
+
651
+ - ``delta`` is computed-on-read (ADR-0108): the view attaches ``_delta`` to
652
+ each instance — a ``{"p50"|"p80"|"p95": signed-int-days|None}`` map of the
653
+ change versus the immediately-previous (older) run, or ``None`` on the
654
+ oldest/baseline row. Positive = the forecast slipped later (worse).
655
+ - ``triggered_by_name`` ("who ran it") is emitted **only** when the context
656
+ flag ``can_see_attribution`` is true (requester is Admin/Owner). For every
657
+ other member the field is ``None`` so forecast drift cannot be read as a
658
+ named-individual performance signal (VoC Morgan). The FK is never exposed
659
+ directly; only a display name, and only to admins.
660
+ """
661
+
662
+ delta = serializers.SerializerMethodField()
663
+ triggered_by_name = serializers.SerializerMethodField()
664
+ distribution = serializers.SerializerMethodField()
665
+
666
+ class Meta:
667
+ model = MonteCarloRun
668
+ fields = [
669
+ "id",
670
+ "taken_at",
671
+ "p50",
672
+ "p80",
673
+ "p95",
674
+ "cpm_finish",
675
+ "p50_at_day_start",
676
+ "p80_at_day_start",
677
+ "p95_at_day_start",
678
+ "cpm_finish_at_day_start",
679
+ "n_simulations",
680
+ "task_count",
681
+ "status_date",
682
+ "delta",
683
+ "triggered_by_name",
684
+ "distribution",
685
+ ]
686
+ read_only_fields = fields
687
+
688
+ @extend_schema_field(MonteCarloDeltaSerializer(allow_null=True))
689
+ def get_delta(self, obj: MonteCarloRun) -> dict[str, int | None] | None:
690
+ """Return the per-percentile day delta vs the previous run (view-attached)."""
691
+ return getattr(obj, "_delta", None)
692
+
693
+ @extend_schema_field(serializers.CharField(allow_null=True))
694
+ def get_triggered_by_name(self, obj: MonteCarloRun) -> str | None:
695
+ """Run-author display name — gated by the resolved attribution audience.
696
+
697
+ The view sets ``can_see_attribution`` per the workspace-effective
698
+ ``mc_history_attribution_audience`` (ADR-0144): ADMIN_OWNER → Admin/Owner,
699
+ SCHEDULER_PLUS → Scheduler+, NONE → nobody. None when the reader is below
700
+ the audience so forecast drift cannot become a named-individual signal.
701
+ """
702
+ if not self.context.get("can_see_attribution"):
703
+ return None
704
+ user: Any = obj.triggered_by
705
+ if user is None:
706
+ return None
707
+ full_name = user.get_full_name() if hasattr(user, "get_full_name") else ""
708
+ return full_name or user.get_username()
709
+
710
+ @extend_schema_field(MonteCarloDistributionFieldsSerializer(allow_null=True))
711
+ def get_distribution(self, obj: MonteCarloRun) -> dict[str, Any] | None:
712
+ """The persisted per-run distribution — only when explicitly expanded (#1231).
713
+
714
+ Returns the stored ``{histogram_buckets, confidence_curve, sensitivity}``
715
+ payload (snake_case, matching the ``/latest/`` response shape) ONLY when the
716
+ view sets ``context['expand_distribution']`` — the history *list* stays
717
+ lightweight by default and a single detail fetch opts in via
718
+ ``?expand=distribution``. ``None`` for legacy runs with no stored
719
+ distribution, or whenever the flag is unset.
720
+ """
721
+ if not self.context.get("expand_distribution"):
722
+ return None
723
+ return obj.distribution