pulse-engine 0.2.20__tar.gz → 0.2.22__tar.gz

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 (180) hide show
  1. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/PKG-INFO +1 -1
  2. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/pyproject.toml +2 -2
  3. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/__init__.py +5 -0
  4. pulse_engine-0.2.22/src/pulse_engine/adapters/document_event_campaign_mapper.py +489 -0
  5. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/models.py +14 -0
  6. pulse_engine-0.2.22/src/pulse_engine/adapters/prompts.py +32 -0
  7. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/config.py +1 -1
  8. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/README.md +0 -0
  9. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/__init__.py +0 -0
  10. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/audio_transcription.py +0 -0
  11. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/batcher.py +0 -0
  12. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/digital_news.py +0 -0
  13. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/digital_news_metadata.py +0 -0
  14. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/exceptions.py +0 -0
  15. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/mart_export.py +0 -0
  16. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/opensearch_sql.py +0 -0
  17. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/opensearch_storage.py +0 -0
  18. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/report_generator.py +0 -0
  19. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/speech_content.py +0 -0
  20. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/speech_metadata.py +0 -0
  21. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/__init__.py +0 -0
  22. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/base.py +0 -0
  23. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/config.py +0 -0
  24. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/factory.py +0 -0
  25. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/providers/__init__.py +0 -0
  26. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/providers/deepgram.py +0 -0
  27. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/providers/openai_whisper.py +0 -0
  28. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/providers/selfhosted_job.py +0 -0
  29. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/providers/youtube_captions.py +0 -0
  30. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/service.py +0 -0
  31. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/transcription/source.py +0 -0
  32. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/twitter.py +0 -0
  33. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/youtube_downloader.py +0 -0
  34. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/adapters/youtube_metadata.py +0 -0
  35. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/api/__init__.py +0 -0
  36. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/api/v1/__init__.py +0 -0
  37. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/api/v1/auth.py +0 -0
  38. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/api/v1/health.py +0 -0
  39. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/api/v1/router.py +0 -0
  40. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/__init__.py +0 -0
  41. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/main.py +0 -0
  42. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/cookiecutter.json +0 -0
  43. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/.gitignore +0 -0
  44. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/Dockerfile +0 -0
  45. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/pipeline.yaml +0 -0
  46. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/pyproject.toml +0 -0
  47. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/src/pulse_{{cookiecutter.product_slug}}/__init__.py +0 -0
  48. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/tests/__init__.py +0 -0
  49. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/tests/unit/__init__.py +0 -0
  50. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cli/templates/pulse-{{cookiecutter.product_name}}/tests/unit/test_manifest.py +0 -0
  51. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/client.py +0 -0
  52. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/__init__.py +0 -0
  53. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/error_handlers.py +0 -0
  54. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/exceptions.py +0 -0
  55. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/job_token.py +0 -0
  56. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/logging.py +0 -0
  57. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/scope.py +0 -0
  58. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/core/security.py +0 -0
  59. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/__init__.py +0 -0
  60. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/models.py +0 -0
  61. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/pipeline_sync.py +0 -0
  62. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/repository.py +0 -0
  63. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/router.py +0 -0
  64. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/runner.py +0 -0
  65. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/schemas.py +0 -0
  66. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/service.py +0 -0
  67. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/tasks.py +0 -0
  68. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/cron/translator.py +0 -0
  69. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/database.py +0 -0
  70. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/dependencies.py +0 -0
  71. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/deployment/__init__.py +0 -0
  72. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/deployment/infra_provisioner.py +0 -0
  73. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/evolution/__init__.py +0 -0
  74. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/evolution/adapter.py +0 -0
  75. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/evolution/client.py +0 -0
  76. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/evolution/models.py +0 -0
  77. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/evolution/router.py +0 -0
  78. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/evolution/service.py +0 -0
  79. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/extractor/__init__.py +0 -0
  80. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/extractor/adapters/__init__.py +0 -0
  81. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/extractor/base.py +0 -0
  82. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/main.py +0 -0
  83. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/__init__.py +0 -0
  84. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/__main__.py +0 -0
  85. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/server.py +0 -0
  86. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/tools_kb.py +0 -0
  87. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/tools_modules.py +0 -0
  88. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/tools_pipelines.py +0 -0
  89. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/mcp/tools_processor.py +0 -0
  90. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/middleware/__init__.py +0 -0
  91. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/middleware/rate_limit.py +0 -0
  92. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/middleware/request_id.py +0 -0
  93. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/middleware/security_headers.py +0 -0
  94. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/middleware/tenant.py +0 -0
  95. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/notifications/__init__.py +0 -0
  96. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/notifications/apprise_notifier.py +0 -0
  97. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/__init__.py +0 -0
  98. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/config_parser.py +0 -0
  99. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/expression.py +0 -0
  100. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/models.py +0 -0
  101. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/repositories.py +0 -0
  102. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/router_modules.py +0 -0
  103. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/router_pipelines.py +0 -0
  104. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/schemas.py +0 -0
  105. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/service.py +0 -0
  106. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/translators/__init__.py +0 -0
  107. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/translators/airflow_status.py +0 -0
  108. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/translators/airflow_translator.py +0 -0
  109. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/translators/base.py +0 -0
  110. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/translators/prefect_status.py +0 -0
  111. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/pipeline/translators/prefect_translator.py +0 -0
  112. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/__init__.py +0 -0
  113. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/base.py +0 -0
  114. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/core/__init__.py +0 -0
  115. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/core/analysis.py +0 -0
  116. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/core/chunking.py +0 -0
  117. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/core/prompts.py +0 -0
  118. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/core/topic_splitter.py +0 -0
  119. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/defaults/__init__.py +0 -0
  120. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/defaults/core_processor.py +0 -0
  121. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/defaults/postprocessor.py +0 -0
  122. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/defaults/preprocessor.py +0 -0
  123. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/llm/__init__.py +0 -0
  124. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/llm/provider.py +0 -0
  125. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/ocr/gemini.py +0 -0
  126. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/pipeline.py +0 -0
  127. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/postprocessor/__init__.py +0 -0
  128. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/postprocessor/embeddings.py +0 -0
  129. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/postprocessor/tasks.py +0 -0
  130. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/preprocessor/__init__.py +0 -0
  131. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/preprocessor/tasks.py +0 -0
  132. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/router.py +0 -0
  133. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/processor/schemas.py +0 -0
  134. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/registry.py +0 -0
  135. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/__init__.py +0 -0
  136. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/agent.py +0 -0
  137. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/fixtures.py +0 -0
  138. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/models.py +0 -0
  139. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/prompts.py +0 -0
  140. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/service.py +0 -0
  141. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/tools.py +0 -0
  142. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/reporting/wiring.py +0 -0
  143. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/runners/__init__.py +0 -0
  144. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/runners/lambda_runner.py +0 -0
  145. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/runners/pipeline_runner.py +0 -0
  146. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/runners/prefect_pipeline_flow.py +0 -0
  147. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/runners/prefect_runner.py +0 -0
  148. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/s3.py +0 -0
  149. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/__init__.py +0 -0
  150. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/backend.py +0 -0
  151. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/lambda_microvm.py +0 -0
  152. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/microvm_agent/Dockerfile +0 -0
  153. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/microvm_agent/__init__.py +0 -0
  154. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/microvm_agent/server.py +0 -0
  155. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/sandbox/session.py +0 -0
  156. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/secrets.py +0 -0
  157. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/services/__init__.py +0 -0
  158. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/services/bootstrap.py +0 -0
  159. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/services/opensearch.py +0 -0
  160. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/__init__.py +0 -0
  161. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/catalog.py +0 -0
  162. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/models.py +0 -0
  163. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/repo.py +0 -0
  164. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/seed.py +0 -0
  165. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/seed_data/skill_files.json +0 -0
  166. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/seed_data/skills.json +0 -0
  167. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/sync.py +0 -0
  168. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/skills/tools.py +0 -0
  169. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/__init__.py +0 -0
  170. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/connectors/__init__.py +0 -0
  171. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/connectors/athena.py +0 -0
  172. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/connectors/base.py +0 -0
  173. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/connectors/opensearch.py +0 -0
  174. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/knowledge_base.py +0 -0
  175. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/router.py +0 -0
  176. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/storage/schemas.py +0 -0
  177. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/testing/__init__.py +0 -0
  178. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/testing/fixtures.py +0 -0
  179. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/testing/mocks.py +0 -0
  180. {pulse_engine-0.2.20 → pulse_engine-0.2.22}/src/pulse_engine/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pulse-engine
3
- Version: 0.2.20
3
+ Version: 0.2.22
4
4
  Summary: Pulse Engine — Hybrid framework for building Pulse products
5
5
  Author: Pulse Team
6
6
  Requires-Python: >=3.11,<3.13
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pulse-engine"
3
- version = "0.2.20"
3
+ version = "0.2.22"
4
4
  description = "Pulse Engine — Hybrid framework for building Pulse products"
5
5
  authors = ["Pulse Team"]
6
6
  readme = "README.md"
@@ -122,7 +122,7 @@ module = ["alembic.*", "alembic.versions.*", "alembic.env"]
122
122
  ignore_errors = true
123
123
 
124
124
  [[tool.mypy.overrides]]
125
- module = ["pulse_engine.database"]
125
+ module = ["pulse_engine.database", "pulse_engine.sandbox.lambda_microvm"]
126
126
  disallow_subclassing_any = false
127
127
 
128
128
  [[tool.mypy.overrides]]
@@ -4,11 +4,13 @@ from .audio_transcription import AudioTranscriptionAdapter
4
4
  from .batcher import BatcherAdapter
5
5
  from .digital_news import DigitalNewsAdapter
6
6
  from .digital_news_metadata import DigitalNewsMetadataAdapter
7
+ from .document_event_campaign_mapper import DocumentEventCampaignMapper
7
8
  from .exceptions import SitemapDiscoveryError
8
9
  from .mart_export import MartExportAdapter
9
10
  from .models import (
10
11
  ArticleContent,
11
12
  ArticleResult,
13
+ DocumentMapping,
12
14
  NewsArticleMetadata,
13
15
  SpeechContent,
14
16
  SpeechMetadata,
@@ -37,6 +39,9 @@ __all__ = [
37
39
  "OpenSearchSqlAdapter",
38
40
  "MartExportAdapter",
39
41
  "ReportGeneratorAdapter",
42
+ # Document → event/campaign mapping
43
+ "DocumentEventCampaignMapper",
44
+ "DocumentMapping",
40
45
  # Digital news
41
46
  "DigitalNewsMetadataAdapter",
42
47
  "DigitalNewsAdapter",
@@ -0,0 +1,489 @@
1
+ """Maps documents to event code(s) + campaign id(s) via date-windowed LLM matching.
2
+
3
+ For each document the adapter pre-filters the events/campaigns catalog by date
4
+ window, batches date-adjacent documents together (one shared candidate list per
5
+ batch), and asks the LLM which candidates each document is actually about. Events
6
+ and campaigns are read from the media_coverage Postgres tables using a caller-
7
+ supplied ``database_url``; the LLM is resolved via the engine's ``get_llm``
8
+ provider (OpenAI by default, per ``Settings``).
9
+
10
+ Usage::
11
+
12
+ adapter = DocumentEventCampaignMapper(
13
+ database_url="postgresql+asyncpg://user:pass@host:5432/media_coverage",
14
+ )
15
+ mappings = await adapter.map(documents)
16
+ # [DocumentMapping(doc_id="123", event_codes=["SM_2026_131"],
17
+ # campaign_ids=["3"]), ...]
18
+
19
+ Provider/model/api_key/temperature override the engine defaults from ``Settings``::
20
+
21
+ adapter = DocumentEventCampaignMapper(
22
+ database_url=url, provider="openai", model="gpt-5-mini",
23
+ )
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import asyncio
29
+ import json
30
+ from datetime import date, datetime, timedelta
31
+ from typing import Any
32
+
33
+ import structlog
34
+ from langchain_core.messages import HumanMessage, SystemMessage
35
+ from pydantic import BaseModel, Field
36
+ from sqlalchemy import text
37
+
38
+ from pulse_engine.processor.llm.provider import get_llm
39
+
40
+ from .models import DocumentMapping
41
+ from .prompts import (
42
+ DOCUMENT_BLOCK,
43
+ MAPPING_PROMPT,
44
+ MAX_CONTENT_CHARS,
45
+ )
46
+
47
+ logger = structlog.get_logger(__name__)
48
+
49
+
50
+ class _DocPrediction(BaseModel):
51
+ """One document's predicted event/campaign matches."""
52
+
53
+ doc_id: str = Field(description="The document id, echoed back")
54
+ event_codes: list[str] = Field(
55
+ default_factory=list, description="Matched event codes, or empty list"
56
+ )
57
+ campaign_ids: list[str] = Field(
58
+ default_factory=list, description="Matched campaign ids, or empty list"
59
+ )
60
+
61
+
62
+ class _BatchPrediction(BaseModel):
63
+ """Structured LLM output: one prediction per document in the batch."""
64
+
65
+ predictions: list[_DocPrediction] = Field(default_factory=list)
66
+
67
+
68
+ class DocumentEventCampaignMapper:
69
+ """Map documents to event codes and campaign ids using date-windowed LLM matching.
70
+
71
+ Args:
72
+ database_url: Async Postgres URL for the media_coverage DB, e.g.
73
+ ``postgresql+asyncpg://user:pass@host:5432/media_coverage``. Events and
74
+ campaigns are read from its ``events`` / ``campaigns`` tables.
75
+ provider: LLM provider override (defaults to ``Settings.pulse_llm_provider``).
76
+ model: LLM model override (defaults to ``"gpt-5-mini"``).
77
+ api_key: LLM API key override (defaults to ``Settings.pulse_llm_api_key``).
78
+ temperature: LLM temperature override (defaults to ``Settings``).
79
+ seed: OpenAI request seed for reproducible output (default 42). The gpt-5
80
+ family only supports temperature 1, so a fixed seed is what makes
81
+ identical inputs return identical results. Pass ``None`` to disable.
82
+ event_window_days: Days before a document's date to include events
83
+ (default 3). An event is a candidate when its date falls in
84
+ ``[doc_date - window, doc_date]``.
85
+ campaign_window_days: Days after a campaign start to keep it eligible
86
+ (default 30).
87
+ batch_size: Max documents per LLM call (default 10).
88
+ batch_gap_days: Start a new batch when consecutive documents are more than this
89
+ many days apart, so a batch never mixes far-apart documents (default 3).
90
+ concurrency: Max batches processed concurrently (default 4).
91
+ """
92
+
93
+ def __init__(
94
+ self,
95
+ database_url: str,
96
+ *,
97
+ provider: str | None = None,
98
+ model: str | None = "gpt-5-mini",
99
+ api_key: str | None = None,
100
+ temperature: float | None = None,
101
+ seed: int | None = 42,
102
+ event_window_days: int = 3,
103
+ campaign_window_days: int = 30,
104
+ batch_size: int = 10,
105
+ batch_gap_days: int = 3,
106
+ concurrency: int = 4,
107
+ ) -> None:
108
+ if not database_url:
109
+ raise ValueError("database_url is required")
110
+ self._database_url = database_url
111
+ self._provider = provider
112
+ self._model = model
113
+ self._api_key = api_key
114
+ self._temperature = temperature
115
+ self._seed = seed
116
+ self._event_window_days = event_window_days
117
+ self._campaign_window_days = campaign_window_days
118
+ self._batch_size = batch_size
119
+ self._batch_gap_days = batch_gap_days
120
+ self._concurrency = concurrency
121
+
122
+ async def map(self, documents: list[dict[str, Any]]) -> list[DocumentMapping]:
123
+ """Map each document to its matching event codes and campaign ids.
124
+
125
+ ``documents`` are dicts, each either flat (id/content/title/timestamp) or an
126
+ OpenSearch ``_source``-style dict (content, metadata.title, timestamp,
127
+ identifier). Returns one :class:`DocumentMapping` per input document, in input
128
+ order (empty lists when nothing matched).
129
+ """
130
+ if not documents:
131
+ return []
132
+
133
+ docs = [_normalize_document(d, i) for i, d in enumerate(documents)]
134
+
135
+ events, campaigns = await self._load_catalog()
136
+ valid_event_codes = {e["event_code"] for e in events}
137
+ valid_campaign_ids = {c["campaign_id"] for c in campaigns}
138
+
139
+ batches = _build_batches(docs, self._batch_size, self._batch_gap_days)
140
+ logger.info(
141
+ "mapping_started",
142
+ documents=len(docs),
143
+ batches=len(batches),
144
+ event_window_days=self._event_window_days,
145
+ campaign_window_days=self._campaign_window_days,
146
+ batch_size=self._batch_size,
147
+ batch_gap_days=self._batch_gap_days,
148
+ concurrency=self._concurrency,
149
+ )
150
+
151
+ llm = get_llm(
152
+ provider=self._provider,
153
+ model=self._model,
154
+ api_key=self._api_key,
155
+ temperature=self._temperature,
156
+ )
157
+ # The gpt-5 family only supports temperature 1 (non-deterministic), so pin a
158
+ # seed to make identical inputs return identical output across runs. The seed
159
+ # is bound per-call (not on the shared get_llm singleton).
160
+ if self._seed is not None:
161
+ llm = llm.bind(seed=self._seed)
162
+ structured_llm = llm.with_structured_output(_BatchPrediction)
163
+
164
+ semaphore = asyncio.Semaphore(self._concurrency)
165
+
166
+ async def _guarded(batch: list[dict[str, Any]]) -> dict[str, DocumentMapping]:
167
+ async with semaphore:
168
+ return await self._process_batch(
169
+ batch,
170
+ structured_llm,
171
+ events,
172
+ campaigns,
173
+ valid_event_codes,
174
+ valid_campaign_ids,
175
+ )
176
+
177
+ per_batch = await asyncio.gather(*(_guarded(b) for b in batches))
178
+
179
+ merged: dict[str, DocumentMapping] = {}
180
+ for batch_result in per_batch:
181
+ merged.update(batch_result)
182
+
183
+ # One entry per input document, in input order.
184
+ return [
185
+ merged.get(d["doc_id"], DocumentMapping(doc_id=d["doc_id"])) for d in docs
186
+ ]
187
+
188
+ # -- catalog loading ----------------------------------------------------
189
+
190
+ async def _load_catalog(
191
+ self,
192
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
193
+ """Read events and campaigns from the media_coverage DB (read-only)."""
194
+ from pulse_engine.database import build_async_engine, build_session_factory
195
+
196
+ engine = build_async_engine(self._database_url)
197
+ factory = build_session_factory(engine)
198
+ try:
199
+ async with factory() as session:
200
+ event_rows = (
201
+ await session.execute(
202
+ text(
203
+ "SELECT event_id, event_type, description, key_issue, "
204
+ "date_of_event FROM events"
205
+ )
206
+ )
207
+ ).all()
208
+ campaign_rows = (
209
+ await session.execute(
210
+ text(
211
+ "SELECT id, campaign_name, description, "
212
+ "campaign_start_date FROM campaigns"
213
+ )
214
+ )
215
+ ).all()
216
+ except Exception:
217
+ logger.error("catalog_load_failed", exc_info=True)
218
+ raise
219
+ finally:
220
+ await engine.dispose()
221
+
222
+ events = [
223
+ {
224
+ "event_code": r.event_id,
225
+ "event_type": r.event_type or "",
226
+ "description": r.description or "",
227
+ "key_issue": r.key_issue or "",
228
+ "date": _coerce_date(r.date_of_event),
229
+ }
230
+ for r in event_rows
231
+ ]
232
+ campaigns = [
233
+ {
234
+ "campaign_id": str(r.id),
235
+ "campaign_name": r.campaign_name or "",
236
+ "description": r.description or "",
237
+ "start_date": _coerce_date(r.campaign_start_date),
238
+ }
239
+ for r in campaign_rows
240
+ ]
241
+ logger.info("catalog_loaded", events=len(events), campaigns=len(campaigns))
242
+ return events, campaigns
243
+
244
+ # -- per-batch mapping --------------------------------------------------
245
+
246
+ async def _process_batch(
247
+ self,
248
+ batch: list[dict[str, Any]],
249
+ structured_llm: Any,
250
+ events: list[dict[str, Any]],
251
+ campaigns: list[dict[str, Any]],
252
+ valid_event_codes: set[str],
253
+ valid_campaign_ids: set[str],
254
+ ) -> dict[str, DocumentMapping]:
255
+ """Filter candidates for a batch, call the LLM, return per-doc mappings."""
256
+ for doc in batch:
257
+ doc["cand_events"] = _candidate_events(
258
+ doc["timestamp"], events, self._event_window_days
259
+ )
260
+ doc["cand_campaigns"] = _candidate_campaigns(
261
+ doc["timestamp"], campaigns, self._campaign_window_days
262
+ )
263
+
264
+ # Documents with no date-eligible candidates skip the LLM entirely.
265
+ llm_docs = [d for d in batch if d["cand_events"] or d["cand_campaigns"]]
266
+
267
+ predictions: dict[str, _DocPrediction] = {}
268
+ if llm_docs:
269
+ shared_events = _dedup_by(
270
+ (e for d in llm_docs for e in d["cand_events"]),
271
+ "event_code",
272
+ "date",
273
+ )
274
+ shared_campaigns = _dedup_by(
275
+ (c for d in llm_docs for c in d["cand_campaigns"]),
276
+ "campaign_id",
277
+ "start_date",
278
+ )
279
+ try:
280
+ parsed = await self._call_llm(
281
+ structured_llm, llm_docs, shared_events, shared_campaigns
282
+ )
283
+ predictions = {p.doc_id.strip(): p for p in parsed.predictions}
284
+ except Exception as exc: # noqa: BLE001 — one batch failing is non-fatal
285
+ logger.warning(
286
+ "batch_failed",
287
+ size=len(llm_docs),
288
+ doc_ids=[d["doc_id"] for d in llm_docs],
289
+ error=str(exc),
290
+ exc_info=True,
291
+ )
292
+
293
+ result: dict[str, DocumentMapping] = {}
294
+ for doc in batch:
295
+ p = predictions.get(doc["doc_id"])
296
+ result[doc["doc_id"]] = DocumentMapping(
297
+ doc_id=doc["doc_id"],
298
+ event_codes=_valid_ids(p.event_codes if p else [], valid_event_codes),
299
+ campaign_ids=_valid_ids(
300
+ p.campaign_ids if p else [], valid_campaign_ids
301
+ ),
302
+ )
303
+ return result
304
+
305
+ async def _call_llm(
306
+ self,
307
+ structured_llm: Any,
308
+ batch: list[dict[str, Any]],
309
+ shared_events: list[dict[str, Any]],
310
+ shared_campaigns: list[dict[str, Any]],
311
+ ) -> _BatchPrediction:
312
+ """Send one batch against its candidate lists; return structured output."""
313
+ events_json = json.dumps(
314
+ [
315
+ {
316
+ "event_code": e["event_code"],
317
+ "event_type": e["event_type"],
318
+ "description": e["description"],
319
+ "key_issue": e["key_issue"],
320
+ }
321
+ for e in shared_events
322
+ ],
323
+ ensure_ascii=False,
324
+ )
325
+ campaigns_json = json.dumps(
326
+ [
327
+ {
328
+ "campaign_id": c["campaign_id"],
329
+ "campaign_name": c["campaign_name"],
330
+ "description": c["description"],
331
+ }
332
+ for c in shared_campaigns
333
+ ],
334
+ ensure_ascii=False,
335
+ )
336
+ documents_text = "\n".join(
337
+ f"--- Document {i + 1} ---\n{self._render_document(doc)}"
338
+ for i, doc in enumerate(batch)
339
+ )
340
+
341
+ system = MAPPING_PROMPT.format(
342
+ candidate_events=events_json,
343
+ candidate_campaigns=campaigns_json,
344
+ )
345
+ messages = [
346
+ SystemMessage(content=system),
347
+ HumanMessage(content=f"DOCUMENTS:\n{documents_text}"),
348
+ ]
349
+ result: _BatchPrediction = await structured_llm.ainvoke(messages)
350
+ return result
351
+
352
+ def _render_document(self, doc: dict[str, Any]) -> str:
353
+ title_line = f"title: {doc['title']}\n" if doc["title"] else ""
354
+ timestamp = (
355
+ doc["timestamp"].date().isoformat() if doc["timestamp"] else "unknown"
356
+ )
357
+ return DOCUMENT_BLOCK.format(
358
+ doc_id=doc["doc_id"],
359
+ title_line=title_line,
360
+ content=doc["content"][:MAX_CONTENT_CHARS],
361
+ timestamp=timestamp,
362
+ event_window_days=self._event_window_days,
363
+ campaign_window_days=self._campaign_window_days,
364
+ )
365
+
366
+
367
+ # ---------------------------------------------------------------------------
368
+ # Pure helpers
369
+ # ---------------------------------------------------------------------------
370
+
371
+
372
+ def _parse_timestamp(value: Any) -> datetime | None:
373
+ """Parse an ISO date/datetime string (or date/datetime) to a naive datetime."""
374
+ if value is None or value == "":
375
+ return None
376
+ if isinstance(value, datetime):
377
+ return value.replace(tzinfo=None)
378
+ if isinstance(value, date):
379
+ return datetime(value.year, value.month, value.day)
380
+ try:
381
+ return datetime.fromisoformat(str(value).replace("Z", "+00:00")).replace(
382
+ tzinfo=None
383
+ )
384
+ except (ValueError, TypeError):
385
+ return None
386
+
387
+
388
+ def _coerce_date(value: Any) -> datetime | None:
389
+ """Coerce a DB date/datetime column value to a naive datetime for comparison."""
390
+ return _parse_timestamp(value)
391
+
392
+
393
+ def _normalize_document(doc: dict[str, Any], index: int) -> dict[str, Any]:
394
+ """Coerce an input document into the internal shape the mapper works with."""
395
+ doc_id = (
396
+ doc.get("id")
397
+ or doc.get("doc_id")
398
+ or doc.get("identifier")
399
+ or doc.get("sn")
400
+ or doc.get("SN")
401
+ or index
402
+ )
403
+ metadata = doc.get("metadata") or {}
404
+ title = doc.get("title") or metadata.get("title") or ""
405
+ content = doc.get("content") or ""
406
+ return {
407
+ "doc_id": str(doc_id).strip(),
408
+ "content": str(content),
409
+ "title": str(title),
410
+ "timestamp": _parse_timestamp(doc.get("timestamp")),
411
+ }
412
+
413
+
414
+ def _candidate_events(
415
+ doc_ts: datetime | None, events: list[dict[str, Any]], window_days: int
416
+ ) -> list[dict[str, Any]]:
417
+ if doc_ts is None:
418
+ return []
419
+ # Events dated in [doc_ts - window_days, doc_ts]: the window_days before the
420
+ # document, up to and including the document's own date.
421
+ lo, hi = doc_ts - timedelta(days=window_days), doc_ts
422
+ return [e for e in events if e["date"] is not None and lo <= e["date"] <= hi]
423
+
424
+
425
+ def _candidate_campaigns(
426
+ doc_ts: datetime | None, campaigns: list[dict[str, Any]], window_days: int
427
+ ) -> list[dict[str, Any]]:
428
+ if doc_ts is None:
429
+ return []
430
+ return [
431
+ c
432
+ for c in campaigns
433
+ if c["start_date"] is not None
434
+ and c["start_date"] <= doc_ts <= c["start_date"] + timedelta(days=window_days)
435
+ ]
436
+
437
+
438
+ def _dedup_by(items: Any, key: str, key_date: str) -> list[dict[str, Any]]:
439
+ """Dedup dicts by ``key`` (keep first seen), sorted by ``key_date`` (None last)."""
440
+ seen: dict[Any, dict[str, Any]] = {}
441
+ for item in items:
442
+ seen.setdefault(item[key], item)
443
+ return sorted(
444
+ seen.values(),
445
+ key=lambda x: (x.get(key_date) is None, x.get(key_date) or datetime.min),
446
+ )
447
+
448
+
449
+ def _build_batches(
450
+ docs: list[dict[str, Any]], batch_size: int, gap_days: int
451
+ ) -> list[list[dict[str, Any]]]:
452
+ """Sort docs by timestamp, then split into batches of date-adjacent documents.
453
+
454
+ A new batch starts whenever the batch is full (``batch_size``) or the gap between
455
+ consecutive documents' timestamps exceeds ``gap_days`` — so a batch never mixes
456
+ documents far apart in time. Undated documents sort last and group by size only.
457
+ """
458
+ ordered = sorted(
459
+ docs, key=lambda d: (d["timestamp"] is None, d["timestamp"] or datetime.min)
460
+ )
461
+ batches: list[list[dict[str, Any]]] = []
462
+ current: list[dict[str, Any]] = []
463
+ for doc in ordered:
464
+ if current:
465
+ prev_ts, ts = current[-1]["timestamp"], doc["timestamp"]
466
+ gap_too_large = (
467
+ prev_ts is not None
468
+ and ts is not None
469
+ and (ts - prev_ts) > timedelta(days=gap_days)
470
+ ) or (prev_ts is None) != (ts is None)
471
+ if len(current) >= batch_size or gap_too_large:
472
+ batches.append(current)
473
+ current = []
474
+ current.append(doc)
475
+ if current:
476
+ batches.append(current)
477
+ return batches
478
+
479
+
480
+ def _valid_ids(values: list[str], valid: set[str]) -> list[str]:
481
+ """Keep only known, non-empty ids, deduped, preserving order."""
482
+ out: list[str] = []
483
+ for v in values:
484
+ if v in ("", "null", "NONE", None):
485
+ continue
486
+ v = str(v).strip()
487
+ if v in valid and v not in out:
488
+ out.append(v)
489
+ return out
@@ -154,3 +154,17 @@ class VideoResult:
154
154
  video_id: str
155
155
  audio: VideoAudio | None
156
156
  error: str | None
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # Document → event/campaign mapping models
161
+ # ---------------------------------------------------------------------------
162
+
163
+
164
+ @dataclass
165
+ class DocumentMapping:
166
+ """Event codes and campaign ids matched to a single document."""
167
+
168
+ doc_id: str
169
+ event_codes: list[str] = field(default_factory=list)
170
+ campaign_ids: list[str] = field(default_factory=list)
@@ -0,0 +1,32 @@
1
+ MAX_CONTENT_CHARS = 4000
2
+
3
+ # System prompt: the matching rules, with the shared candidate lists injected.
4
+ MAPPING_PROMPT = (
5
+ "For each document, pick its event_code(s) and campaign_id(s) from the shared\n"
6
+ "candidate lists below (already date-filtered). Judge by content only.\n\n"
7
+ "candidate_events:\n{candidate_events}\n\n"
8
+ "candidate_campaigns:\n{candidate_campaigns}\n\n"
9
+ "Event rules:\n"
10
+ "- Match an event only if the document is actually reporting on or depicting\n"
11
+ " that specific event. Match what it describes, not just its date: live event\n"
12
+ " if it's happening now, the aftermath/follow-up event if it's looking back.\n"
13
+ "- A shared theme, location, date, or a mentioned participant is NOT enough.\n"
14
+ " When in doubt, don't match — a wrong match is worse than none.\n\n"
15
+ "Campaign rules — match a campaign only if BOTH:\n"
16
+ "1. The document names the campaign (or something unique to it) or directly\n"
17
+ " addresses its specific subject — not just a shared theme.\n"
18
+ "2. That campaign is the document's main subject, not a passing mention.\n\n"
19
+ "Return an empty list when nothing clearly matches. A document may match more\n"
20
+ "than one event or campaign."
21
+ )
22
+
23
+ # Per-document block rendered into the human message for each batch.
24
+ DOCUMENT_BLOCK = (
25
+ "doc_id: {doc_id}\n"
26
+ "{title_line}"
27
+ "content: {content}\n"
28
+ "candidate_events: events dated within {event_window_days} days before this "
29
+ "document's timestamp {timestamp}, up to and including that date\n"
30
+ "candidate_campaigns: campaigns whose start date is within the "
31
+ "{campaign_window_days} day window before this document's timestamp {timestamp}"
32
+ )
@@ -54,7 +54,7 @@ class Settings(BaseSettings):
54
54
  # Fleet-wide cap on concurrently-running flow runs for the ECS work pool.
55
55
  # Acts as the queue throttle: scheduled runs beyond this wait in Prefect's
56
56
  # queue instead of all launching at once (and OOM-ing the workers).
57
- prefect_work_pool_concurrency_limit: int = 15
57
+ prefect_work_pool_concurrency_limit: int = 50
58
58
  # ECR image used to run pipeline_flow in ECS work pool
59
59
  prefect_engine_image: str = ""
60
60
  # Override compute backend for all pipeline steps ("ecs", "local", etc.)
File without changes