attune-ai 2.0.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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""{{ wizard_name | title | replace('_', ' ') }} Domain Wizard.
|
|
2
|
+
|
|
3
|
+
Auto-generated by Empathy Framework Scaffolding
|
|
4
|
+
Methodology: {{ methodology }}
|
|
5
|
+
Domain: {{ domain }}
|
|
6
|
+
Patterns: {{ pattern_ids | join(', ') }}
|
|
7
|
+
Empathy Level: {{ empathy_level | default(2) }}
|
|
8
|
+
Generated: {{ timestamp }}
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from fastapi import APIRouter, HTTPException
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from empathy_llm_toolkit.memory_manager import MemoryManager
|
|
18
|
+
from empathy_llm_toolkit.wizards.base_wizard import WizardConfig
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
router = APIRouter(prefix="/{{ wizard_name }}", tags=["{{ wizard_name }}"])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Wizard Configuration
|
|
26
|
+
WIZARD_CONFIG = WizardConfig(
|
|
27
|
+
wizard_id="{{ wizard_name }}",
|
|
28
|
+
name="{{ wizard_name | title | replace('_', ' ') }}",
|
|
29
|
+
description="{{ description | default('Domain wizard for ' + domain) }}",
|
|
30
|
+
domain="{{ domain }}",
|
|
31
|
+
empathy_level={{ empathy_level | default(2) }},
|
|
32
|
+
{% if 'educational_banner' in pattern_ids %}
|
|
33
|
+
show_educational_banner=True,
|
|
34
|
+
{% endif %}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Request/Response Models
|
|
39
|
+
{% if 'linear_flow' in pattern_ids %}
|
|
40
|
+
class StartRequest(BaseModel):
|
|
41
|
+
"""Request to start wizard."""
|
|
42
|
+
context: dict[str, Any] = Field(default_factory=dict, description="Initial context")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class StartResponse(BaseModel):
|
|
46
|
+
"""Response from starting wizard."""
|
|
47
|
+
wizard_id: str
|
|
48
|
+
current_step: int
|
|
49
|
+
total_steps: int
|
|
50
|
+
{% if 'educational_banner' in pattern_ids %}
|
|
51
|
+
educational_banner: dict[str, Any] | None = None
|
|
52
|
+
{% endif %}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class StepSubmission(BaseModel):
|
|
56
|
+
"""Step data submission."""
|
|
57
|
+
step: int
|
|
58
|
+
data: dict[str, Any]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class StepResult(BaseModel):
|
|
62
|
+
"""Step submission result."""
|
|
63
|
+
wizard_id: str
|
|
64
|
+
current_step: int
|
|
65
|
+
total_steps: int
|
|
66
|
+
completed: bool = False
|
|
67
|
+
{% if 'user_guidance' in pattern_ids %}
|
|
68
|
+
next_step_guidance: str | None = None
|
|
69
|
+
{% endif %}
|
|
70
|
+
{% endif %}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
{% if 'approval' in pattern_ids %}
|
|
74
|
+
class PreviewRequest(BaseModel):
|
|
75
|
+
"""Request to generate preview."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class PreviewResult(BaseModel):
|
|
80
|
+
"""Preview generation result."""
|
|
81
|
+
wizard_id: str
|
|
82
|
+
preview_report: dict[str, Any]
|
|
83
|
+
{% if 'user_guidance' in pattern_ids %}
|
|
84
|
+
approval_guidance: str
|
|
85
|
+
{% endif %}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SaveRequest(BaseModel):
|
|
89
|
+
"""Request to save with approval."""
|
|
90
|
+
approval: dict[str, Any] = Field(..., description="User approval data")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SaveResult(BaseModel):
|
|
94
|
+
"""Save result."""
|
|
95
|
+
wizard_id: str
|
|
96
|
+
completed: bool
|
|
97
|
+
final_report: dict[str, Any]
|
|
98
|
+
{% endif %}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# In-memory session storage (replace with Redis in production)
|
|
102
|
+
sessions: dict[str, dict[str, Any]] = {}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
{% if 'linear_flow' in pattern_ids %}
|
|
106
|
+
@router.post("/start", response_model=StartResponse)
|
|
107
|
+
async def start_wizard(request: StartRequest) -> StartResponse:
|
|
108
|
+
"""Start a new wizard session.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
request: Start request with initial context
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Wizard session info
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
HTTPException: If initialization fails
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
wizard_id = f"{{ wizard_name }}_{len(sessions) + 1}"
|
|
121
|
+
|
|
122
|
+
# Initialize session
|
|
123
|
+
session = {
|
|
124
|
+
"wizard_id": wizard_id,
|
|
125
|
+
"current_step": 1,
|
|
126
|
+
"total_steps": {{ total_steps | default(5) }},
|
|
127
|
+
"context": request.context,
|
|
128
|
+
"step_data": {},
|
|
129
|
+
"completed": False,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
sessions[wizard_id] = session
|
|
133
|
+
|
|
134
|
+
response = StartResponse(
|
|
135
|
+
wizard_id=wizard_id,
|
|
136
|
+
current_step=1,
|
|
137
|
+
total_steps={{ total_steps | default(5) }},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
{% if 'educational_banner' in pattern_ids %}
|
|
141
|
+
# Add educational banner for Level {{ empathy_level | default(2) }}
|
|
142
|
+
response.educational_banner = {
|
|
143
|
+
"message": "{{ educational_message | default('This wizard will guide you through the process step by step.') }}",
|
|
144
|
+
"level": {{ empathy_level | default(2) }},
|
|
145
|
+
}
|
|
146
|
+
{% endif %}
|
|
147
|
+
|
|
148
|
+
logger.info(f"Started wizard session: {wizard_id}")
|
|
149
|
+
return response
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.exception(f"Failed to start wizard: {e}")
|
|
153
|
+
raise HTTPException(status_code=500, detail=f"Wizard start failed: {str(e)}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@router.post("/{wizard_id}/step", response_model=StepResult)
|
|
157
|
+
async def submit_step(wizard_id: str, submission: StepSubmission) -> StepResult:
|
|
158
|
+
"""Submit data for a wizard step.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
wizard_id: Wizard session ID
|
|
162
|
+
submission: Step data submission
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Step result with next step info
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
HTTPException: If session not found or validation fails
|
|
169
|
+
"""
|
|
170
|
+
# Validate session
|
|
171
|
+
if wizard_id not in sessions:
|
|
172
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
173
|
+
|
|
174
|
+
session = sessions[wizard_id]
|
|
175
|
+
|
|
176
|
+
{% if 'step_validation' in pattern_ids %}
|
|
177
|
+
# Validate step sequence
|
|
178
|
+
if submission.step != session["current_step"]:
|
|
179
|
+
raise HTTPException(
|
|
180
|
+
status_code=400,
|
|
181
|
+
detail=f"Expected step {session['current_step']}, got {submission.step}"
|
|
182
|
+
)
|
|
183
|
+
{% endif %}
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Store step data
|
|
187
|
+
session["step_data"][submission.step] = submission.data
|
|
188
|
+
|
|
189
|
+
# Move to next step
|
|
190
|
+
session["current_step"] = submission.step + 1
|
|
191
|
+
|
|
192
|
+
# Check if completed
|
|
193
|
+
completed = session["current_step"] > session["total_steps"]
|
|
194
|
+
session["completed"] = completed
|
|
195
|
+
|
|
196
|
+
result = StepResult(
|
|
197
|
+
wizard_id=wizard_id,
|
|
198
|
+
current_step=session["current_step"],
|
|
199
|
+
total_steps=session["total_steps"],
|
|
200
|
+
completed=completed,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
{% if 'user_guidance' in pattern_ids %}
|
|
204
|
+
# Provide guidance for next step (Level {{ empathy_level | default(2) }} Empathy)
|
|
205
|
+
if not completed:
|
|
206
|
+
result.next_step_guidance = await _get_step_guidance(
|
|
207
|
+
session["current_step"],
|
|
208
|
+
session["step_data"]
|
|
209
|
+
)
|
|
210
|
+
{% endif %}
|
|
211
|
+
|
|
212
|
+
logger.info(f"Step {submission.step} submitted for {wizard_id}")
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.exception(f"Step submission failed: {e}")
|
|
217
|
+
raise HTTPException(status_code=500, detail=f"Step submission failed: {str(e)}")
|
|
218
|
+
{% endif %}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
{% if 'approval' in pattern_ids %}
|
|
222
|
+
@router.post("/{wizard_id}/preview", response_model=PreviewResult)
|
|
223
|
+
async def generate_preview(wizard_id: str, request: PreviewRequest) -> PreviewResult:
|
|
224
|
+
"""Generate preview for user review.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
wizard_id: Wizard session ID
|
|
228
|
+
request: Preview request
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Preview report for user review
|
|
232
|
+
|
|
233
|
+
Raises:
|
|
234
|
+
HTTPException: If session not found or not ready for preview
|
|
235
|
+
"""
|
|
236
|
+
if wizard_id not in sessions:
|
|
237
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
238
|
+
|
|
239
|
+
session = sessions[wizard_id]
|
|
240
|
+
|
|
241
|
+
# Validate all steps completed
|
|
242
|
+
if not session.get("completed"):
|
|
243
|
+
raise HTTPException(status_code=400, detail="All steps must be completed before preview")
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
# Generate preview
|
|
247
|
+
preview_report = await _generate_preview_report(session["step_data"])
|
|
248
|
+
|
|
249
|
+
# Store preview in session
|
|
250
|
+
session["preview_report"] = preview_report
|
|
251
|
+
|
|
252
|
+
result = PreviewResult(
|
|
253
|
+
wizard_id=wizard_id,
|
|
254
|
+
preview_report=preview_report,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
{% if 'user_guidance' in pattern_ids %}
|
|
258
|
+
# Provide approval guidance (Level {{ empathy_level | default(2) }} Empathy)
|
|
259
|
+
result.approval_guidance = (
|
|
260
|
+
"Please review the preview carefully. "
|
|
261
|
+
"You can edit any information before final approval."
|
|
262
|
+
)
|
|
263
|
+
{% endif %}
|
|
264
|
+
|
|
265
|
+
logger.info(f"Preview generated for {wizard_id}")
|
|
266
|
+
return result
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.exception(f"Preview generation failed: {e}")
|
|
270
|
+
raise HTTPException(status_code=500, detail=f"Preview generation failed: {str(e)}")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@router.post("/{wizard_id}/save", response_model=SaveResult)
|
|
274
|
+
async def save_with_approval(wizard_id: str, request: SaveRequest) -> SaveResult:
|
|
275
|
+
"""Save wizard data with user approval.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
wizard_id: Wizard session ID
|
|
279
|
+
request: Save request with approval
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Final save result
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
HTTPException: If session not found or approval missing
|
|
286
|
+
"""
|
|
287
|
+
if wizard_id not in sessions:
|
|
288
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
289
|
+
|
|
290
|
+
session = sessions[wizard_id]
|
|
291
|
+
|
|
292
|
+
# Validate preview was generated
|
|
293
|
+
if "preview_report" not in session:
|
|
294
|
+
raise HTTPException(status_code=400, detail="Preview must be generated before saving")
|
|
295
|
+
|
|
296
|
+
# Validate approval
|
|
297
|
+
if not request.approval.get("user_approved"):
|
|
298
|
+
raise HTTPException(status_code=400, detail="User approval required")
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
# Generate final report
|
|
302
|
+
final_report = await _generate_final_report(
|
|
303
|
+
session["step_data"],
|
|
304
|
+
session["preview_report"]
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Mark as finalized
|
|
308
|
+
session["final_report"] = final_report
|
|
309
|
+
session["finalized"] = True
|
|
310
|
+
|
|
311
|
+
logger.info(f"Wizard {wizard_id} saved with approval")
|
|
312
|
+
|
|
313
|
+
return SaveResult(
|
|
314
|
+
wizard_id=wizard_id,
|
|
315
|
+
completed=True,
|
|
316
|
+
final_report=final_report,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.exception(f"Save failed: {e}")
|
|
321
|
+
raise HTTPException(status_code=500, detail=f"Save failed: {str(e)}")
|
|
322
|
+
{% endif %}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@router.get("/{wizard_id}/report", response_model=dict[str, Any])
|
|
326
|
+
async def get_report(wizard_id: str) -> dict[str, Any]:
|
|
327
|
+
"""Get wizard report.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
wizard_id: Wizard session ID
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Complete wizard report
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
HTTPException: If session not found
|
|
337
|
+
"""
|
|
338
|
+
if wizard_id not in sessions:
|
|
339
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
340
|
+
|
|
341
|
+
session = sessions[wizard_id]
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
"wizard_id": wizard_id,
|
|
345
|
+
"step_data": session.get("step_data", {}),
|
|
346
|
+
{% if 'approval' in pattern_ids %}
|
|
347
|
+
"preview_report": session.get("preview_report"),
|
|
348
|
+
"final_report": session.get("final_report"),
|
|
349
|
+
"finalized": session.get("finalized", False),
|
|
350
|
+
{% endif %}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# Helper functions
|
|
355
|
+
{% if 'user_guidance' in pattern_ids %}
|
|
356
|
+
async def _get_step_guidance(step: int, step_data: dict[int, Any]) -> str:
|
|
357
|
+
"""Get guidance for next step (Level {{ empathy_level | default(2) }} Empathy).
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
step: Current step number
|
|
361
|
+
step_data: Previous step data
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Guidance message
|
|
365
|
+
"""
|
|
366
|
+
# TODO: Implement actual guidance generation
|
|
367
|
+
return f"Please complete step {step} with the required information."
|
|
368
|
+
{% endif %}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
{% if 'approval' in pattern_ids %}
|
|
372
|
+
async def _generate_preview_report(step_data: dict[int, Any]) -> dict[str, Any]:
|
|
373
|
+
"""Generate preview report.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
step_data: All collected step data
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Preview report
|
|
380
|
+
"""
|
|
381
|
+
# TODO: Implement actual preview generation
|
|
382
|
+
return {
|
|
383
|
+
"summary": "Preview of collected data",
|
|
384
|
+
"data": step_data,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
async def _generate_final_report(
|
|
389
|
+
step_data: dict[int, Any],
|
|
390
|
+
preview_report: dict[str, Any]
|
|
391
|
+
) -> dict[str, Any]:
|
|
392
|
+
"""Generate final report after approval.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
step_data: All collected step data
|
|
396
|
+
preview_report: Previously generated preview
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Final report
|
|
400
|
+
"""
|
|
401
|
+
# TODO: Implement actual final report generation
|
|
402
|
+
return {
|
|
403
|
+
"summary": "Final report",
|
|
404
|
+
"data": step_data,
|
|
405
|
+
"preview": preview_report,
|
|
406
|
+
"finalized_at": "2025-01-05T00:00:00Z",
|
|
407
|
+
}
|
|
408
|
+
{% endif %}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""{{ wizard_class }} - Generated by Pattern-Compose
|
|
2
|
+
|
|
3
|
+
Domain: {{ domain}}
|
|
4
|
+
Patterns: {{ ', '.join(pattern_ids) }}
|
|
5
|
+
Generated: Auto-generated by Empathy Framework
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter, HTTPException
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
router = APIRouter(
|
|
17
|
+
prefix="/wizards/{{ wizard_name }}",
|
|
18
|
+
tags=["wizards"],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Request/Response Models
|
|
23
|
+
class WizardRequest(BaseModel):
|
|
24
|
+
"""Request model for wizard."""
|
|
25
|
+
input: str
|
|
26
|
+
context: dict[str, Any] | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WizardResponse(BaseModel):
|
|
30
|
+
"""Response model for wizard."""
|
|
31
|
+
success: bool
|
|
32
|
+
output: str
|
|
33
|
+
wizard_id: str | None = None
|
|
34
|
+
error: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Session storage (Redis in production, memory for development)
|
|
38
|
+
_wizard_sessions: dict[str, dict[str, Any]] = {}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.post("/start")
|
|
42
|
+
async def start_wizard():
|
|
43
|
+
"""Start {{ wizard_name }} wizard session.
|
|
44
|
+
|
|
45
|
+
Returns wizard_id and first step configuration.
|
|
46
|
+
"""
|
|
47
|
+
wizard_id = str(uuid4())
|
|
48
|
+
|
|
49
|
+
session_data = {
|
|
50
|
+
"wizard_id": wizard_id,
|
|
51
|
+
"wizard_type": "{{ wizard_name }}",
|
|
52
|
+
"current_step": 1,
|
|
53
|
+
"total_steps": 5, # TODO: Adjust based on your wizard
|
|
54
|
+
"collected_data": {},
|
|
55
|
+
"created_at": datetime.now().isoformat(),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_wizard_sessions[wizard_id] = session_data
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"wizard_id": wizard_id,
|
|
62
|
+
"current_step": 1,
|
|
63
|
+
"total_steps": 5,
|
|
64
|
+
"message": "Wizard started successfully",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.post("/{wizard_id}/step")
|
|
69
|
+
async def submit_step(wizard_id: str, step_data: dict[str, Any]):
|
|
70
|
+
"""Submit data for current step.
|
|
71
|
+
|
|
72
|
+
Validates step sequence and stores data.
|
|
73
|
+
"""
|
|
74
|
+
if wizard_id not in _wizard_sessions:
|
|
75
|
+
raise HTTPException(404, "Wizard session not found")
|
|
76
|
+
|
|
77
|
+
session = _wizard_sessions[wizard_id]
|
|
78
|
+
current_step = session["current_step"]
|
|
79
|
+
total_steps = session["total_steps"]
|
|
80
|
+
|
|
81
|
+
# Validate step number
|
|
82
|
+
submitted_step = step_data.get("step", current_step)
|
|
83
|
+
if submitted_step != current_step:
|
|
84
|
+
raise HTTPException(
|
|
85
|
+
422,
|
|
86
|
+
f"Expected step {current_step}, got step {submitted_step}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Store data
|
|
90
|
+
session["collected_data"].update(step_data.get("data", {}))
|
|
91
|
+
session["updated_at"] = datetime.now().isoformat()
|
|
92
|
+
|
|
93
|
+
# Advance to next step
|
|
94
|
+
if current_step < total_steps:
|
|
95
|
+
session["current_step"] = current_step + 1
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"wizard_id": wizard_id,
|
|
99
|
+
"current_step": session["current_step"],
|
|
100
|
+
"total_steps": total_steps,
|
|
101
|
+
"message": f"Step {current_step} completed",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
{% if has_approval %}
|
|
106
|
+
@router.post("/{wizard_id}/preview")
|
|
107
|
+
async def preview_report(wizard_id: str):
|
|
108
|
+
"""Generate preview without finalizing.
|
|
109
|
+
|
|
110
|
+
Allows user to review before explicit approval.
|
|
111
|
+
"""
|
|
112
|
+
if wizard_id not in _wizard_sessions:
|
|
113
|
+
raise HTTPException(404, "Wizard session not found")
|
|
114
|
+
|
|
115
|
+
session = _wizard_sessions[wizard_id]
|
|
116
|
+
|
|
117
|
+
if session["current_step"] != session["total_steps"]:
|
|
118
|
+
raise HTTPException(400, "Complete all steps before preview")
|
|
119
|
+
|
|
120
|
+
# Generate preview
|
|
121
|
+
preview = _generate_report(session["collected_data"])
|
|
122
|
+
session["preview_report"] = preview
|
|
123
|
+
session["preview_generated_at"] = datetime.now().isoformat()
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"preview": preview,
|
|
127
|
+
"message": "Preview generated. Review and approve to finalize.",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@router.post("/{wizard_id}/save")
|
|
132
|
+
async def save_report(wizard_id: str, approval: dict[str, Any]):
|
|
133
|
+
"""Finalize report with user approval.
|
|
134
|
+
|
|
135
|
+
Requires preview generated and explicit user approval.
|
|
136
|
+
"""
|
|
137
|
+
if wizard_id not in _wizard_sessions:
|
|
138
|
+
raise HTTPException(404, "Wizard session not found")
|
|
139
|
+
|
|
140
|
+
session = _wizard_sessions[wizard_id]
|
|
141
|
+
|
|
142
|
+
# Verify preview generated
|
|
143
|
+
if "preview_report" not in session:
|
|
144
|
+
raise HTTPException(
|
|
145
|
+
400,
|
|
146
|
+
"Must generate preview before saving. Call /preview first."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Verify user approval
|
|
150
|
+
if not approval.get("user_approved", False):
|
|
151
|
+
raise HTTPException(
|
|
152
|
+
400,
|
|
153
|
+
"User approval required. Set 'user_approved': true"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Mark as complete
|
|
157
|
+
session["completed"] = True
|
|
158
|
+
session["completed_at"] = datetime.now().isoformat()
|
|
159
|
+
session["final_report"] = session["preview_report"]
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
"wizard_id": wizard_id,
|
|
163
|
+
"completed": True,
|
|
164
|
+
"report": session["final_report"],
|
|
165
|
+
"message": "Report finalized successfully",
|
|
166
|
+
}
|
|
167
|
+
{% endif %}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@router.get("/{wizard_id}/report")
|
|
171
|
+
async def get_report(wizard_id: str):
|
|
172
|
+
"""Retrieve completed report.
|
|
173
|
+
|
|
174
|
+
Only available after wizard is completed.
|
|
175
|
+
"""
|
|
176
|
+
if wizard_id not in _wizard_sessions:
|
|
177
|
+
raise HTTPException(404, "Wizard session not found")
|
|
178
|
+
|
|
179
|
+
session = _wizard_sessions[wizard_id]
|
|
180
|
+
|
|
181
|
+
if not session.get("completed", False):
|
|
182
|
+
raise HTTPException(422, "Wizard not yet completed")
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"wizard_id": wizard_id,
|
|
186
|
+
"report": _generate_report(session["collected_data"]),
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _generate_report(collected_data: dict[str, Any]) -> dict[str, Any]:
|
|
191
|
+
"""Generate report from collected data.
|
|
192
|
+
|
|
193
|
+
TODO: Customize report generation for your wizard.
|
|
194
|
+
"""
|
|
195
|
+
return {
|
|
196
|
+
"data": collected_data,
|
|
197
|
+
"generated_at": datetime.now().isoformat(),
|
|
198
|
+
"wizard_type": "{{ wizard_name }}",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Export router
|
|
203
|
+
__all__ = ["router"]
|