From 67b553688da393909a66a7221c150cef885238ee Mon Sep 17 00:00:00 2001 From: Yuuuu <1569752928@qq.com> Date: Wed, 18 Mar 2026 22:24:47 +0800 Subject: [PATCH] Refactor AI PMO services and harden ingress error handling --- .openclaw/pmflow-routing-rules.json | 10 + .tmp/feishu_case1_v4.html | 1 + .tmp/feishu_case1_v4.png | Bin 0 -> 30141 bytes .tmp/feishu_case2_fail.html | 26 ++ .tmp/feishu_case2_fail.png | Bin 0 -> 39141 bytes .tmp/feishu_case2_v4.html | 25 ++ .tmp/feishu_case2_v4.png | Bin 0 -> 119958 bytes .tmp/p0_feishu_reply_validation.png | Bin 0 -> 79410 bytes .tmp/p1_real_validate.py | 313 +++++++++++++++++ .tmp/p1_real_validate_v4.py | 330 ++++++++++++++++++ .tmp/real_feishu_case_1.png | Bin 0 -> 73021 bytes .tmp/real_feishu_case_2.png | Bin 0 -> 168006 bytes .tmp/real_feishu_case_3.png | Bin 0 -> 66560 bytes .tmp/real_feishu_case_4.png | Bin 0 -> 69579 bytes .tmp/real_feishu_case_5.png | Bin 0 -> 75757 bytes .tmp/run_feishu_case.py | 291 +++++++++++++++ AGENTS.md | 1 + CLAUDE.md | 146 ++++---- backend/.openclaw/pmflow-routing-rules.json | 10 + ...d_ai_orchestration_ingress_async_fields.py | 165 +++++++++ backend/app/api/ai_pmo_api/shared.py | 4 +- backend/app/api/ai_steward_api/shared.py | 8 +- backend/app/api/feishu_api/config_routes.py | 4 +- .../api/feishu_api/long_connection_routes.py | 2 +- backend/app/api/feishu_api/message_routes.py | 2 +- backend/app/api/feishu_api/test_routes.py | 4 +- backend/app/api/knowledge_graph.py | 2 +- .../app/api/model_capability_api/shared.py | 6 +- backend/app/api/model_routing.py | 2 +- backend/app/api/projects_api/__init__.py | 4 +- backend/app/api/projects_api/helpers.py | 3 +- backend/app/api/projects_api/shared.py | 2 +- backend/app/api/prompt_profiles.py | 2 +- backend/app/api/rag_api/shared.py | 2 +- backend/app/api/scheduler.py | 4 +- backend/app/api/signals_api/ingest_fetch.py | 8 +- backend/app/api/skill_tags_api/routes.py | 2 +- .../app/api/sstc_api/charters_ai_alignment.py | 4 +- .../api/sstc_api/charters_detail_update.py | 23 +- .../app/api/sstc_api/releases_list_create.py | 38 +- backend/app/api/sstc_api/shared.py | 4 +- .../app/api/sstc_api/tasks_ai_breakdown.py | 40 ++- backend/app/api/tasks.py | 2 +- backend/app/api/users_api/shared.py | 2 +- backend/app/api/webhooks.py | 2 +- backend/app/api/wecom_api/config.py | 2 +- backend/app/api/wecom_api/messaging.py | 2 +- backend/app/api/wecom_api/webhook.py | 2 +- backend/app/config/ai_prompts.py | 6 + .../app/config/ai_prompts_catalog/__init__.py | 12 + .../app/config/ai_prompts_catalog/part_01.py | 8 + .../app/config/ai_prompts_catalog/part_02.py | 32 +- .../app/config/ai_prompts_catalog/part_03.py | 4 +- .../ai_prompts_catalog/part_scenes_01.py | 22 ++ .../ai_prompts_catalog/part_scenes_02.py | 27 ++ .../ai_prompts_catalog/part_scenes_03.py | 23 ++ .../ai_prompts_catalog/part_scenes_04.py | 21 ++ .../ai_prompts_catalog/part_scenes_05.py | 21 ++ .../ai_prompts_catalog/part_scenes_06.py | 24 ++ .../app/core/startup/ai_pmo_ingress_async.py | 30 ++ backend/app/core/startup/feishu_outbox.py | 2 +- .../app/core/startup/world_model_warmup.py | 2 +- backend/app/main.py | 10 +- backend/app/models/ai_orchestration_run.py | 24 +- .../mixin_answer_entry.py | 37 +- .../mixin_answer_retrieval.py | 9 +- .../mixin_deep_guard_answer.py | 189 +++++++++- .../mixin_deep_guard_rules.py | 9 +- .../adaptive_rag_service/mixin_ingest.py | 17 +- .../adaptive_rag_service/shared_imports.py | 15 +- .../adaptive_rag_service/shared_part_02.py | 3 + .../adaptive_rag_service/shared_utils.py | 2 + .../ai_orchestration_run/ingress_worker.py | 265 ++++++++++++++ .../services/ai_orchestration_run/service.py | 324 ++++++++++++++++- backend/app/services/ai_pmo/__init__.py | 1 + .../{ => ai_pmo}/ai_pmo_cadence/__init__.py | 0 .../ai_pmo_cadence/mixin_builders.py | 0 .../ai_pmo_cadence/mixin_program_view.py | 0 .../ai_pmo_cadence/mixin_queries_a.py | 0 .../ai_pmo_cadence/mixin_queries_b.py | 0 .../ai_pmo_cadence/mixin_snapshots.py | 0 .../{ => ai_pmo}/ai_pmo_cadence/service.py | 0 .../{ => ai_pmo}/ai_pmo_cadence/shared.py | 4 +- .../{ => ai_pmo}/ai_pmo_decision/__init__.py | 0 .../{ => ai_pmo}/ai_pmo_decision/engine.py | 0 .../{ => ai_pmo}/ai_pmo_decision/policy.py | 31 +- .../{ => ai_pmo}/ai_pmo_decision/shared.py | 0 .../ai_pmo_decision/simulation.py | 0 .../{ => ai_pmo}/ai_pmo_decision/strategic.py | 0 .../{ => ai_pmo}/ai_pmo_decision/tactical.py | 0 .../{ => ai_pmo}/ai_pmo_intent/__init__.py | 0 .../ai_pmo_intent/intent_resolver.py | 3 +- .../{ => ai_pmo}/ai_pmo_intent/models.py | 4 + .../ai_pmo_intent/route_resolver.py | 81 ++--- .../{ => ai_pmo}/ai_pmo_intent/shared.py | 5 +- .../{ => ai_pmo}/ai_pmo_kernel/__init__.py | 9 +- .../ai_pmo_kernel/ai_agent_kernel.py | 0 .../ai_pmo_kernel/ai_pmo_domain_workflow.py | 2 +- .../ai_pmo_kernel/ai_pmo_kernel_runtime.py | 11 +- .../ai_pmo_kernel/ai_pmo_reply_renderer.py | 0 .../ai_pmo_kernel/ai_pmo_review_service.py | 2 +- .../ai_pmo_kernel/ai_pmo_typed_executor.py | 0 .../ai_pmo_typed_executor_common.py | 20 +- .../ai_pmo_typed_executor_mixins_parse.py | 0 .../ai_pmo_kernel/ai_pmo_workflow_runtime.py | 4 +- .../ai_pmo_kernel/compositions/__init__.py | 15 + .../ai_pmo_kernel/compositions/closure.py} | 6 +- .../ai_pmo_kernel/compositions/followup.py | 23 ++ .../ai_pmo_kernel/compositions/ingress.py} | 6 +- .../ai_pmo_kernel/compositions/intent.py} | 6 +- .../compositions/world_model.py} | 8 +- .../ai_pmo/ai_pmo_kernel/mixins/__init__.py | 1 + .../ai_pmo_kernel/mixins/closure/__init__.py | 1 + .../ai_pmo_kernel_mixins_closure_data.py | 2 +- .../ai_pmo_kernel_mixins_closure_helpers.py | 2 +- .../ai_pmo_kernel_mixins_closure_payload.py | 2 +- .../ai_pmo_kernel/mixins/followup/__init__.py | 1 + ...o_kernel_mixins_followup_project_signal.py | 0 ..._mixins_followup_project_signal_actions.py | 2 +- ...l_mixins_followup_project_signal_resume.py | 2 +- .../ai_pmo_kernel_mixins_followup_records.py | 2 +- .../ai_pmo_kernel_mixins_followup_routing.py | 2 +- ...pmo_kernel_mixins_followup_verification.py | 2 +- .../ai_pmo_kernel/mixins/ingress/__init__.py | 1 + .../ai_pmo_kernel_mixins_ingress_render.py | 13 +- .../ai_pmo_kernel_mixins_ingress_storage.py | 2 +- .../ai_pmo_kernel/mixins/intent/__init__.py | 1 + .../ai_pmo_kernel_mixins_intent_llm.py | 86 +++-- .../ai_pmo_kernel_mixins_intent_resolve.py | 51 +++ .../ai_pmo_kernel_mixins_intent_rules.py | 112 +++++- .../mixins/world_model/__init__.py | 1 + ...i_pmo_kernel_mixins_world_model_session.py | 2 +- .../ai_pmo_kernel_common_core/__init__.py | 0 .../ai_pmo_kernel_common_core/constants.py | 6 +- .../ai_pmo_kernel_common_core/env.py | 4 +- .../ai_pmo_kernel_common_core/helpers.py | 3 + .../ai_pmo_kernel_common_core/imports.py | 24 +- .../ai_pmo_kernel_ingress_flow/__init__.py | 0 .../ai_pmo_kernel_ingress_flow/mixin_auto.py | 49 +++ .../ai_pmo_kernel_ingress_flow/mixin_entry.py | 118 ++++++- .../mixin_prepare_manual.py | 0 .../ai_pmo_kernel_ingress_flow/service.py | 0 .../ai_pmo_kernel_ingress_flow/shared.py | 2 + .../__init__.py | 0 .../mixin_basics.py | 0 .../mixin_project.py | 0 .../service.py | 0 .../shared.py | 3 + .../__init__.py | 0 .../mixin_builder.py | 0 .../mixin_cache.py | 0 .../mixin_project.py | 0 .../ai_pmo_kernel_world_model_core/service.py | 0 .../ai_pmo_kernel_world_model_core/shared.py | 3 + .../__init__.py | 0 .../mixin_analysis.py | 0 .../mixin_context.py | 0 .../mixin_discover.py | 0 .../service.py | 0 .../ai_pmo_typed_executor_discovery/shared.py | 0 .../ai_pmo_typed_executor_execute/__init__.py | 0 .../mixin_execute_query_project.py | 1 + .../mixin_execute_signal_create.py | 0 .../mixin_execute_signal_source.py | 0 .../mixin_helpers.py | 3 +- .../ai_pmo_typed_executor_execute/service.py | 0 .../ai_pmo_typed_executor_execute/shared.py | 0 .../ai_pmo_kernel_mixins_followup.py | 19 - .../ai_pmo_kernel_mixins_intent_resolve.py | 66 ---- .../ai_service_core/mixin_business_tasks.py | 70 +++- .../services/context_cache_service/shared.py | 2 + backend/app/services/integrations/__init__.py | 1 + .../feishu_integration_service/__init__.py | 0 .../mixin_config.py | 3 +- .../mixin_message_payload.py | 11 +- .../mixin_receive.py | 10 +- .../mixin_receive_routing.py | 0 .../mixin_reply_guard.py | 0 .../mixin_reply_llm_core.py | 36 +- .../mixin_reply_outbox.py | 3 +- .../mixin_reply_templates.py | 0 .../mixin_sender_resolution.py | 0 .../feishu_integration_service/mixin_trace.py | 0 .../mixin_transport.py | 0 .../feishu_integration_service/service.py | 0 .../feishu_integration_service/shared.py | 15 +- .../feishu_long_connection_core/__init__.py | 0 .../feishu_long_connection_core/manager.py | 0 .../mixin_lifecycle.py | 0 .../mixin_lifecycle_connection.py | 0 .../mixin_lifecycle_control.py | 0 .../mixin_runtime.py | 0 .../feishu_long_connection_core/registry.py | 0 .../feishu_long_connection_core/shared.py | 6 +- .../feishu_outbox/__init__.py | 0 .../feishu_outbox/service.py | 2 +- .../feishu_signal_pipeline/__init__.py | 0 .../mixin_command_commit_project.py | 0 .../mixin_command_commit_task.py | 0 .../mixin_command_exec_core.py | 0 .../mixin_context_prefill_a.py | 0 .../mixin_context_prefill_b.py | 0 .../feishu_signal_pipeline/mixin_core_base.py | 0 .../mixin_extraction.py | 0 .../mixin_followup_a.py | 0 .../mixin_followup_b.py | 0 .../mixin_gate_dispatch.py | 14 +- .../mixin_gate_project.py | 0 .../feishu_signal_pipeline/mixin_gate_task.py | 0 .../mixin_intent_gate_core.py | 0 .../mixin_intent_support.py | 0 .../mixin_item_finalize_routing.py | 0 .../mixin_item_processing.py | 0 .../mixin_lookup_and_block.py | 0 .../mixin_message_utils_a.py | 0 .../mixin_message_utils_b.py | 0 .../mixin_persistence_lookup.py | 0 .../mixin_process_message_core.py | 0 .../mixin_process_message_default.py | 103 ++++-- .../mixin_process_message_helpers_a.py | 0 .../mixin_process_message_helpers_b.py | 0 .../mixin_queue_confirm_undo.py | 0 .../mixin_queue_read_write.py | 0 .../mixin_routing_notify.py | 0 .../mixin_serialization.py | 0 .../mixin_task_command_queue.py | 0 .../feishu_signal_pipeline/service.py | 0 .../feishu_signal_pipeline/shared.py | 0 .../feishu_signal_pipeline/shared_core.py | 31 +- .../shared_schema_utils.py | 0 .../integration_core/__init__.py | 0 .../integration_core/lark_service.py | 0 .../integration_core/mixin_clients.py | 0 .../integration_core/mixin_health.py | 0 .../integration_core/mixin_lifecycle.py | 0 .../integration_core/mixin_sync.py | 4 +- .../integration_core/plane_service.py | 0 .../integration_core/registry.py | 0 .../integration_core/schemas.py | 0 .../integration_core/service.py | 0 .../integration_core/shared.py | 2 +- .../{ => integrations}/wecom/__init__.py | 0 .../{ => integrations}/wecom/service.py | 4 +- .../llm_service/mixin_cache_circuit.py | 3 +- .../services/llm_service/mixin_chat_openai.py | 103 +++--- .../mixin_generate_db_structured.py | 129 ++++++- .../llm_service/mixin_guardrails_prompt.py | 40 ++- .../llm_service/mixin_openclaw_routing.py | 5 +- .../mixin_structured_candidates.py | 120 ++++++- .../mixin_structured_extract_object.py | 101 ++++-- .../llm_service/mixin_structured_parse.py | 21 +- .../llm_service/mixin_tier_escalation.py | 49 ++- backend/app/services/llm_service/shared.py | 4 + .../services/llm_service/shared_part_01.py | 5 +- .../services/llm_service/shared_part_02.py | 4 + backend/app/services/model/__init__.py | 1 + .../{ => model}/model_capability/__init__.py | 0 .../cost_optimization_service.py | 2 +- .../model_capability/mixin_cost_tracking.py | 2 +- .../model_capability/mixin_gap_queries.py | 0 .../model_capability/mixin_validation.py | 0 .../model_lifecycle_service.py | 2 +- .../{ => model}/model_capability/service.py | 0 .../{ => model}/model_capability/shared.py | 6 +- .../model_context_bundle/__init__.py | 0 .../model_context_bundle/mixin_build.py | 2 +- .../model_context_bundle/mixin_core.py | 0 .../model_context_bundle/mixin_verify.py | 0 .../model_context_bundle/service.py | 0 .../model_context_bundle/shared.py | 6 +- .../{ => model}/model_routing/__init__.py | 0 .../{ => model}/model_routing/service.py | 4 +- .../{ => model}/prompt_profile/__init__.py | 0 .../{ => model}/prompt_profile/service.py | 6 +- .../project_knowledge_graph_service/shared.py | 4 +- .../scheduler_center_core/ai_pmo_cadence.py | 15 +- .../scheduler_center_core/knowledge_graph.py | 9 +- .../services/scheduler_center_core/shared.py | 2 +- backend/app/services/signal/__init__.py | 1 + .../signal_capture_service/__init__.py | 0 .../external_fetcher.py | 0 .../signal_capture_service/impact_analyzer.py | 0 .../mixin_analysis_normalize.py | 5 +- .../mixin_capture_entry.py | 305 ++++++++++++++++ .../mixin_capture_legacy.py | 0 .../mixin_external_sources.py | 0 .../mixin_new_project.py | 0 .../mixin_pipeline_cache.py | 0 .../mixin_source_analyzers.py | 2 +- .../mixin_task_orchestration_main.py | 0 .../mixin_task_orchestration_skill.py | 0 .../mixin_task_orchestration_team.py | 0 .../signal_capture_service/service.py | 0 .../signal_capture_service/shared.py | 28 +- .../{ => signal}/signal_planner/__init__.py | 0 .../signal_planner/mixin_analyze.py | 0 .../signal_planner/mixin_loaders.py | 0 .../signal_planner/mixin_prompt.py | 0 .../signal_planner/mixin_utils.py | 0 .../{ => signal}/signal_planner/service.py | 0 .../{ => signal}/signal_planner/shared.py | 16 +- .../signal_strategic_analysis_service.py | 4 +- .../signal_source_orchestration/__init__.py | 0 .../mixin_entry_orchestrate.py | 0 .../mixin_entry_terminal_a.py | 0 .../mixin_entry_terminal_b.py | 0 .../mixin_execute_isolation.py | 0 .../mixin_lookup_parse.py | 0 .../mixin_planning_locator_roles.py | 0 .../mixin_planning_onboard.py | 0 .../mixin_registry_execute.py | 0 .../mixin_result_resolution.py | 0 .../mixin_result_run_updates.py | 0 .../mixin_worker_prompt_persist.py | 0 .../signal_source_orchestration/service.py | 0 .../signal_source_orchestration/shared.py | 15 +- .../signal_source_repair/__init__.py | 0 .../signal_source_repair/errors.py | 0 .../signal_source_repair/mixin_capture.py | 0 .../signal_source_repair/mixin_data_access.py | 0 .../signal_source_repair/mixin_persist.py | 0 .../signal_source_repair/mixin_repair.py | 0 .../signal_source_repair/mixin_utils.py | 0 .../signal_source_repair/service.py | 0 .../signal_source_repair/shared.py | 10 +- .../{ => signal}/symbol_index/__init__.py | 0 .../symbol_index/mixin_parse_python.py | 0 .../symbol_index/mixin_parse_typescript.py | 0 .../symbol_index/mixin_query_overview.py | 0 .../symbol_index/mixin_serialize.py | 0 .../symbol_index/mixin_sync_core.py | 0 .../{ => signal}/symbol_index/service.py | 0 .../{ => signal}/symbol_index/shared.py | 4 +- .../mixin_capture_entry.py | 173 --------- backend/app/services/skill/shared.py | 3 +- backend/app/services/sstc/charter_data.py | 13 +- backend/app/services/sstc/shared.py | 6 +- .../sstc_service_core/mixin_build_views.py | 2 +- .../sstc_service_core/mixin_fetch_core.py | 2 +- .../app/services/sstc_service_core/service.py | 9 + .../app/services/sstc_service_core/shared.py | 3 + .../sstc_service_core/shared_helpers.py | 3 + backend/app/services/task_brief/__init__.py | 8 +- .../services/task_persistence_core/persist.py | 2 +- .../workload/workload_alert_service.py | 6 +- backend/scripts/p1_real_validate.py | 313 +++++++++++++++++ backend/tmp_p1_real_validate_v4.py | 330 ++++++++++++++++++ backend/tmp_run_feishu_case.py | 291 +++++++++++++++ docker-compose.dev.yml | 24 ++ docker-compose.yml | 24 ++ frontend/src/api/sstc.ts | 4 + .../components/sstc/SSTCCharterStagePanel.tsx | 113 +++++- .../sstc/SSTCExecutionBoardPanel.tsx | 95 ++++- .../sstc/SSTCReleaseCreateDialog.tsx | 44 ++- .../components/sstc/SSTCReleaseStagePanel.tsx | 1 + .../src/components/sstc/SSTCWorkspace.tsx | 7 + .../sstc/useSSTCWorkspaceOrchestration.ts | 179 +++++++++- .../pages/project-board/ProjectBoardPage.tsx | 1 + frontend/tsconfig.tsbuildinfo | 2 +- 359 files changed, 5574 insertions(+), 904 deletions(-) create mode 100644 .tmp/feishu_case1_v4.html create mode 100644 .tmp/feishu_case1_v4.png create mode 100644 .tmp/feishu_case2_fail.html create mode 100644 .tmp/feishu_case2_fail.png create mode 100644 .tmp/feishu_case2_v4.html create mode 100644 .tmp/feishu_case2_v4.png create mode 100644 .tmp/p0_feishu_reply_validation.png create mode 100644 .tmp/p1_real_validate.py create mode 100644 .tmp/p1_real_validate_v4.py create mode 100644 .tmp/real_feishu_case_1.png create mode 100644 .tmp/real_feishu_case_2.png create mode 100644 .tmp/real_feishu_case_3.png create mode 100644 .tmp/real_feishu_case_4.png create mode 100644 .tmp/real_feishu_case_5.png create mode 100644 .tmp/run_feishu_case.py create mode 100644 backend/alembic/versions/20260316_add_ai_orchestration_ingress_async_fields.py create mode 100644 backend/app/config/ai_prompts_catalog/part_scenes_01.py create mode 100644 backend/app/config/ai_prompts_catalog/part_scenes_02.py create mode 100644 backend/app/config/ai_prompts_catalog/part_scenes_03.py create mode 100644 backend/app/config/ai_prompts_catalog/part_scenes_04.py create mode 100644 backend/app/config/ai_prompts_catalog/part_scenes_05.py create mode 100644 backend/app/config/ai_prompts_catalog/part_scenes_06.py create mode 100644 backend/app/core/startup/ai_pmo_ingress_async.py create mode 100644 backend/app/services/ai_orchestration_run/ingress_worker.py create mode 100644 backend/app/services/ai_pmo/__init__.py rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/mixin_builders.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/mixin_program_view.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/mixin_queries_a.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/mixin_queries_b.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/mixin_snapshots.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/service.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_cadence/shared.py (93%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/engine.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/policy.py (79%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/shared.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/simulation.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/strategic.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_decision/tactical.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_intent/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_intent/intent_resolver.py (99%) rename backend/app/services/{ => ai_pmo}/ai_pmo_intent/models.py (88%) rename backend/app/services/{ => ai_pmo}/ai_pmo_intent/route_resolver.py (68%) rename backend/app/services/{ => ai_pmo}/ai_pmo_intent/shared.py (97%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/__init__.py (68%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_agent_kernel.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_domain_workflow.py (98%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_kernel_runtime.py (86%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_reply_renderer.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_review_service.py (98%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_typed_executor.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_typed_executor_common.py (82%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel/ai_pmo_workflow_runtime.py (98%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/compositions/__init__.py rename backend/app/services/{ai_pmo_kernel/ai_pmo_kernel_mixins_closure.py => ai_pmo/ai_pmo_kernel/compositions/closure.py} (51%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/compositions/followup.py rename backend/app/services/{ai_pmo_kernel/ai_pmo_kernel_mixins_ingress.py => ai_pmo/ai_pmo_kernel/compositions/ingress.py} (52%) rename backend/app/services/{ai_pmo_kernel/ai_pmo_kernel_mixins_intent.py => ai_pmo/ai_pmo_kernel/compositions/intent.py} (52%) rename backend/app/services/{ai_pmo_kernel/ai_pmo_kernel_mixins_world_model.py => ai_pmo/ai_pmo_kernel/compositions/world_model.py} (53%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/__init__.py create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/__init__.py rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/closure}/ai_pmo_kernel_mixins_closure_data.py (99%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/closure}/ai_pmo_kernel_mixins_closure_helpers.py (98%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/closure}/ai_pmo_kernel_mixins_closure_payload.py (99%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/__init__.py rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/followup}/ai_pmo_kernel_mixins_followup_project_signal.py (100%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/followup}/ai_pmo_kernel_mixins_followup_project_signal_actions.py (99%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/followup}/ai_pmo_kernel_mixins_followup_project_signal_resume.py (99%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/followup}/ai_pmo_kernel_mixins_followup_records.py (98%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/followup}/ai_pmo_kernel_mixins_followup_routing.py (99%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/followup}/ai_pmo_kernel_mixins_followup_verification.py (99%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/__init__.py rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/ingress}/ai_pmo_kernel_mixins_ingress_render.py (93%) rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/ingress}/ai_pmo_kernel_mixins_ingress_storage.py (99%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/__init__.py rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/intent}/ai_pmo_kernel_mixins_intent_llm.py (79%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/intent}/ai_pmo_kernel_mixins_intent_rules.py (71%) create mode 100644 backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/__init__.py rename backend/app/services/{ai_pmo_kernel => ai_pmo/ai_pmo_kernel/mixins/world_model}/ai_pmo_kernel_mixins_world_model_session.py (98%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_common_core/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_common_core/constants.py (94%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_common_core/env.py (95%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_common_core/helpers.py (97%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_common_core/imports.py (84%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_ingress_flow/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_ingress_flow/mixin_auto.py (79%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_ingress_flow/mixin_entry.py (73%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_ingress_flow/service.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_ingress_flow/shared.py (78%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_aggregates/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_aggregates/mixin_basics.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_aggregates/mixin_project.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_aggregates/service.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_aggregates/shared.py (79%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_core/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_core/mixin_builder.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_core/mixin_cache.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_core/mixin_project.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_core/service.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_kernel_world_model_core/shared.py (78%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_discovery/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_discovery/mixin_analysis.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_discovery/mixin_context.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_discovery/mixin_discover.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_discovery/service.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_discovery/shared.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/__init__.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/mixin_execute_query_project.py (97%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/mixin_execute_signal_create.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/mixin_helpers.py (98%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/service.py (100%) rename backend/app/services/{ => ai_pmo}/ai_pmo_typed_executor_execute/shared.py (100%) delete mode 100644 backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup.py delete mode 100644 backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_resolve.py create mode 100644 backend/app/services/integrations/__init__.py rename backend/app/services/{ => integrations}/feishu_integration_service/__init__.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_config.py (98%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_message_payload.py (96%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_receive.py (96%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_receive_routing.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_reply_guard.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_reply_llm_core.py (88%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_reply_outbox.py (99%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_reply_templates.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_sender_resolution.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_trace.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/mixin_transport.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/service.py (100%) rename backend/app/services/{ => integrations}/feishu_integration_service/shared.py (72%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/__init__.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/manager.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/mixin_lifecycle.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/mixin_lifecycle_connection.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/mixin_lifecycle_control.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/mixin_runtime.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/registry.py (100%) rename backend/app/services/{ => integrations}/feishu_long_connection_core/shared.py (88%) rename backend/app/services/{ => integrations}/feishu_outbox/__init__.py (100%) rename backend/app/services/{ => integrations}/feishu_outbox/service.py (98%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/__init__.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_command_commit_project.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_command_commit_task.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_command_exec_core.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_context_prefill_a.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_context_prefill_b.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_core_base.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_extraction.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_followup_a.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_followup_b.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_gate_dispatch.py (94%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_gate_project.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_gate_task.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_intent_gate_core.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_intent_support.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_item_finalize_routing.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_item_processing.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_lookup_and_block.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_message_utils_a.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_message_utils_b.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_persistence_lookup.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_process_message_core.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_process_message_default.py (78%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_process_message_helpers_a.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_process_message_helpers_b.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_queue_confirm_undo.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_queue_read_write.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_routing_notify.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_serialization.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/mixin_task_command_queue.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/service.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/shared.py (100%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/shared_core.py (91%) rename backend/app/services/{ => integrations}/feishu_signal_pipeline/shared_schema_utils.py (100%) rename backend/app/services/{ => integrations}/integration_core/__init__.py (100%) rename backend/app/services/{ => integrations}/integration_core/lark_service.py (100%) rename backend/app/services/{ => integrations}/integration_core/mixin_clients.py (100%) rename backend/app/services/{ => integrations}/integration_core/mixin_health.py (100%) rename backend/app/services/{ => integrations}/integration_core/mixin_lifecycle.py (100%) rename backend/app/services/{ => integrations}/integration_core/mixin_sync.py (98%) rename backend/app/services/{ => integrations}/integration_core/plane_service.py (100%) rename backend/app/services/{ => integrations}/integration_core/registry.py (100%) rename backend/app/services/{ => integrations}/integration_core/schemas.py (100%) rename backend/app/services/{ => integrations}/integration_core/service.py (100%) rename backend/app/services/{ => integrations}/integration_core/shared.py (92%) rename backend/app/services/{ => integrations}/wecom/__init__.py (100%) rename backend/app/services/{ => integrations}/wecom/service.py (98%) create mode 100644 backend/app/services/model/__init__.py rename backend/app/services/{ => model}/model_capability/__init__.py (100%) rename backend/app/services/{ => model}/model_capability/cost_optimization_service.py (98%) rename backend/app/services/{ => model}/model_capability/mixin_cost_tracking.py (99%) rename backend/app/services/{ => model}/model_capability/mixin_gap_queries.py (100%) rename backend/app/services/{ => model}/model_capability/mixin_validation.py (100%) rename backend/app/services/{ => model}/model_capability/model_lifecycle_service.py (99%) rename backend/app/services/{ => model}/model_capability/service.py (100%) rename backend/app/services/{ => model}/model_capability/shared.py (83%) rename backend/app/services/{ => model}/model_context_bundle/__init__.py (100%) rename backend/app/services/{ => model}/model_context_bundle/mixin_build.py (98%) rename backend/app/services/{ => model}/model_context_bundle/mixin_core.py (100%) rename backend/app/services/{ => model}/model_context_bundle/mixin_verify.py (100%) rename backend/app/services/{ => model}/model_context_bundle/service.py (100%) rename backend/app/services/{ => model}/model_context_bundle/shared.py (90%) rename backend/app/services/{ => model}/model_routing/__init__.py (100%) rename backend/app/services/{ => model}/model_routing/service.py (99%) rename backend/app/services/{ => model}/prompt_profile/__init__.py (100%) rename backend/app/services/{ => model}/prompt_profile/service.py (98%) create mode 100644 backend/app/services/signal/__init__.py rename backend/app/services/{ => signal}/signal_capture_service/__init__.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/external_fetcher.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/impact_analyzer.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_analysis_normalize.py (98%) create mode 100644 backend/app/services/signal/signal_capture_service/mixin_capture_entry.py rename backend/app/services/{ => signal}/signal_capture_service/mixin_capture_legacy.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_external_sources.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_new_project.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_pipeline_cache.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_source_analyzers.py (99%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_task_orchestration_main.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_task_orchestration_skill.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/mixin_task_orchestration_team.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/service.py (100%) rename backend/app/services/{ => signal}/signal_capture_service/shared.py (85%) rename backend/app/services/{ => signal}/signal_planner/__init__.py (100%) rename backend/app/services/{ => signal}/signal_planner/mixin_analyze.py (100%) rename backend/app/services/{ => signal}/signal_planner/mixin_loaders.py (100%) rename backend/app/services/{ => signal}/signal_planner/mixin_prompt.py (100%) rename backend/app/services/{ => signal}/signal_planner/mixin_utils.py (100%) rename backend/app/services/{ => signal}/signal_planner/service.py (100%) rename backend/app/services/{ => signal}/signal_planner/shared.py (74%) rename backend/app/services/{ => signal}/signal_planner/signal_strategic_analysis_service.py (97%) rename backend/app/services/{ => signal}/signal_source_orchestration/__init__.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_entry_orchestrate.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_entry_terminal_a.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_entry_terminal_b.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_execute_isolation.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_lookup_parse.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_planning_locator_roles.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_planning_onboard.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_registry_execute.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_result_resolution.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_result_run_updates.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/mixin_worker_prompt_persist.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/service.py (100%) rename backend/app/services/{ => signal}/signal_source_orchestration/shared.py (82%) rename backend/app/services/{ => signal}/signal_source_repair/__init__.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/errors.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/mixin_capture.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/mixin_data_access.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/mixin_persist.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/mixin_repair.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/mixin_utils.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/service.py (100%) rename backend/app/services/{ => signal}/signal_source_repair/shared.py (70%) rename backend/app/services/{ => signal}/symbol_index/__init__.py (100%) rename backend/app/services/{ => signal}/symbol_index/mixin_parse_python.py (100%) rename backend/app/services/{ => signal}/symbol_index/mixin_parse_typescript.py (100%) rename backend/app/services/{ => signal}/symbol_index/mixin_query_overview.py (100%) rename backend/app/services/{ => signal}/symbol_index/mixin_serialize.py (100%) rename backend/app/services/{ => signal}/symbol_index/mixin_sync_core.py (100%) rename backend/app/services/{ => signal}/symbol_index/service.py (100%) rename backend/app/services/{ => signal}/symbol_index/shared.py (91%) delete mode 100644 backend/app/services/signal_capture_service/mixin_capture_entry.py create mode 100644 backend/scripts/p1_real_validate.py create mode 100644 backend/tmp_p1_real_validate_v4.py create mode 100644 backend/tmp_run_feishu_case.py diff --git a/.openclaw/pmflow-routing-rules.json b/.openclaw/pmflow-routing-rules.json index 0818c1d1..cd365b48 100644 --- a/.openclaw/pmflow-routing-rules.json +++ b/.openclaw/pmflow-routing-rules.json @@ -29,6 +29,16 @@ "api_env": "OPENAI_API_KEY", "function_exact": "classify_intent_complex" }, + { + "name": "engine_low_intent_kernel_exact", + "enabled": true, + "priority": 310, + "tier": "low", + "provider": "glm", + "model": "GLM-4.7", + "api_env": "GLM_API_KEY", + "function_exact": "classify_intent_ai_pmo_kernel" + }, { "name": "engine_low_execute_glm_flash", "enabled": true, diff --git a/.tmp/feishu_case1_v4.html b/.tmp/feishu_case1_v4.html new file mode 100644 index 00000000..35bf9db7 --- /dev/null +++ b/.tmp/feishu_case1_v4.html @@ -0,0 +1 @@ +

Case 1 回复

source_message_id: real-scene-p1-v4-1-1773760579
杨宇当前负载为 199.17%。根据系统快照,他身兼多职(FE, BOSS, AI, BE, ADMIN),负责 4 个项目,负载已严重超载,建议尽快进行任务调配。
\ No newline at end of file diff --git a/.tmp/feishu_case1_v4.png b/.tmp/feishu_case1_v4.png new file mode 100644 index 0000000000000000000000000000000000000000..92c03a44e88882be28b7a25c48a6fe57bc995512 GIT binary patch literal 30141 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 literal 0 HcmV?d00001 diff --git a/.tmp/feishu_case2_fail.html b/.tmp/feishu_case2_fail.html new file mode 100644 index 00000000..33861985 --- /dev/null +++ b/.tmp/feishu_case2_fail.html @@ -0,0 +1,26 @@ + + + + + Feishu 验收失败样本 + + + +
+

真实飞书验收 - 失败证据(Case 2)

+
source_message_id: real-scene-p1-v2-2-1773749870
+
最近AI行业有什么动态
+
已收到,正在处理。
+
模型调用失败:function=adaptive_rag_answer_normal, provider=glm, model=GLM-4.7, reason=LLM 返回空内容...
+
+ + diff --git a/.tmp/feishu_case2_fail.png b/.tmp/feishu_case2_fail.png new file mode 100644 index 0000000000000000000000000000000000000000..edce1b2b66edbb3990cabcc5f67403ba9effc6d8 GIT binary patch literal 39141 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 literal 0 HcmV?d00001 diff --git a/.tmp/feishu_case2_v4.html b/.tmp/feishu_case2_v4.html new file mode 100644 index 00000000..4af2d13f --- /dev/null +++ b/.tmp/feishu_case2_v4.html @@ -0,0 +1,25 @@ +

Case 2 回复

source_message_id: real-scene-p1-v4-2-1773760726
[行业动态]
+当前未接入实时数据,以下基于训练数据(截止2024年4月):
+
+**大模型领域:**
+- GPT-4 Turbo、Claude 3、Gemini 1.5 Pro 等多模态大模型持续迭代,上下文窗口突破百万级
+- 开源模型快速追赶,Llama 3、Mistral、Qwen 等性能逼近闭源模型
+- 模型成本大幅下降,API 价格战激烈,Token 成本降低约 10 倍
+
+**应用落地趋势:**
+- AI Agent(智能体)成为主流方向,从单轮对话转向多步骤任务执行
+- RAG(检索增强生成)技术成熟,企业知识库问答成为标配场景
+- 垂直领域 AI 应用爆发:客服、法律、医疗、教育等场景深度渗透
+
+**行业监管:**
+- 欧盟 AI 法案正式通过,全球 AI 治理框架逐步成型
+- 中国发布生成式 AI 管理办法,合规要求趋严
+
+---
+
+[对项目的影响]
+对你们 **AI劝退助手** 项目的影响:
+
+1. **技术红利**:大模型成本下降、推理能力提升,可降低项目运营成本,支持更复杂的对话策略(如多轮情绪分析、个性化劝退话术生成)
+
+2. **竞争压力**:AI 应用门槛降低
\ No newline at end of file diff --git a/.tmp/feishu_case2_v4.png b/.tmp/feishu_case2_v4.png new file mode 100644 index 0000000000000000000000000000000000000000..404db500ed3a8b8908f591a73ea280f2d2b4002e GIT binary patch literal 119958 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�DJr&{HL%sA7PubF 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

W(ffxkE>!2GZ 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 V=h0)UX7CM$yo~aTGAWby{~HWgvw8pk literal 0 HcmV?d00001 diff --git a/.tmp/p1_real_validate.py b/.tmp/p1_real_validate.py new file mode 100644 index 00000000..2c27de67 --- /dev/null +++ b/.tmp/p1_real_validate.py @@ -0,0 +1,313 @@ +import asyncio +import json +import time +import urllib.request +import urllib.parse +from datetime import datetime +from typing import Any, Optional + +from sqlalchemy import select, func + +from app.core.database import AsyncSessionLocal +from app.models import AIOrchestrationRun, FeishuReplyOutbox, AIProcessingLog, AICallLog + +BASE = "http://127.0.0.1:8000" +CHAT_ID = "oc_c487d87563e48c2f9566ccd5a758212c" +SENDER = "P1验收机器人" + +CASES = [ + {"idx": 1, "message": "杨宇现在负载多少", "expected_scene": "member_workload", "keys": ["杨宇", "负载"]}, + {"idx": 2, "message": "最近AI行业有什么动态", "expected_scene": "external_trend", "keys": ["AI行业", "行业", "动态", "趋势"]}, + {"idx": 3, "message": "帮我创建个项目叫路由验收测试,顺便问下现在有几个进行中的项目", "expected_scene": "mixed", "keys": ["顺便", "问题部分", "需求部分", "进行中的项目"]}, + {"idx": 4, "message": "AI劝退助手项目进展怎么样", "expected_scene": "default", "keys": ["AI劝退助手", "进展怎么样", "项目进展"]}, + {"idx": 5, "message": "帮我看看宠物行业最新动态", "expected_scene": "external_trend", "keys": ["宠物行业", "最新动态", "行业", "动态"]}, +] + +BANNED_PREFIXES = ["让我先", "让我尝试", "我来看看", "我先查", "让我看看", "我需要先"] + + +def login_token() -> str: + data = urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode() + req = urllib.request.Request( + f"{BASE}/auth/login", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + payload = json.loads(resp.read().decode("utf-8")) + return str(payload.get("access_token") or "") + + +def post_capture(token: str, *, message: str, message_id: str) -> dict[str, Any]: + payload = { + "group_chat_id": CHAT_ID, + "message_content": message, + "sender": SENDER, + "feishu_message_id": message_id, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + f"{BASE}/ai-steward/ai-steward/signal/capture-feishu", + data=body, + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=240) as resp: + text = resp.read().decode("utf-8") + try: + return json.loads(text) + except Exception: + return {"raw": text} + + +def detect_prompt_scene(prompt: str) -> Optional[str]: + p = str(prompt or "") + if "你是 PMFlow 的团队协作助手。" in p: + return "member_workload" + if "你是 PMFlow 的市场情报助手。" in p: + return "external_trend" + if "用户消息同时包含问题和需求。" in p: + return "mixed" + if "你是 PMFlow 的 AI 项目管理官。" in p: + return "ai_pmo" + if "用户通过飞书直接提问。" in p: + return "feishu_direct" + if "你是 PMFlow 的 RAG 问答助手" in p: + return "default" + return None + + +def infer_scene_from_reply(reply: str) -> str: + t = str(reply or "") + if "[回答]" in t and "[已收到需求]" in t: + return "mixed" + if "[行业动态]" in t and "[建议]" in t: + return "external_trend" + if ("负载" in t or "工作量" in t) and "%" in t and "项目列表" not in t: + return "member_workload" + if "完成率" in t: + return "default" + return "unknown" + + +def has_thinking_leak(reply: str) -> bool: + for line in str(reply or "").splitlines(): + s = line.strip() + if not s: + continue + if any(s.startswith(prefix) for prefix in BANNED_PREFIXES): + return True + return False + + +async def fetch_max_ai_call_id(db) -> int: + m = (await db.execute(select(func.max(AICallLog.id)))).scalar() + return int(m or 0) + + +async def wait_run(db, source_message_id: str, timeout: int = 540) -> Optional[AIOrchestrationRun]: + deadline = time.time() + timeout + last = None + while time.time() < deadline: + row = ( + await db.execute( + select(AIOrchestrationRun) + .where(AIOrchestrationRun.source_message_id == source_message_id) + .order_by(AIOrchestrationRun.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + if row is not None: + last = row + s = str(row.status or "").lower() + if s in {"succeeded", "failed", "cancelled"}: + return row + await asyncio.sleep(2) + return last + + +async def fetch_outbox(db, source_message_id: str): + return ( + await db.execute( + select(FeishuReplyOutbox) + .where(FeishuReplyOutbox.source_message_id == source_message_id) + .order_by(FeishuReplyOutbox.id.asc()) + ) + ).scalars().all() + + +async def wait_outbox(db, source_message_id: str, run_status: str, timeout: int = 240): + deadline = time.time() + timeout + expected = 2 if run_status == "succeeded" else 1 + latest = [] + while time.time() < deadline: + latest = await fetch_outbox(db, source_message_id) + sent = [r for r in latest if str(r.status or "") == "sent"] + if len(sent) >= expected: + return latest + await asyncio.sleep(2) + return latest + + +async def fetch_processing(db, source_message_id: str): + return ( + await db.execute( + select(AIProcessingLog) + .where(AIProcessingLog.feishu_message_id == source_message_id) + .order_by(AIProcessingLog.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + + +async def fetch_scene_from_call_logs(db, min_id: int, keys: list[str]): + rows = ( + await db.execute( + select(AICallLog) + .where( + AICallLog.id > min_id, + AICallLog.function_name.in_(["adaptive_rag_answer_normal", "adaptive_rag_answer_deep"]), + ) + .order_by(AICallLog.id.asc()) + .limit(160) + ) + ).scalars().all() + + picked = "unknown" + inspected = [] + lk = [k.lower() for k in keys if k] + for r in rows: + prompt = str(r.prompt or "") + pl = prompt.lower() + if lk and not any(k in pl for k in lk): + continue + sc = detect_prompt_scene(prompt) + inspected.append( + { + "id": int(r.id), + "scene": sc or "unknown", + "function_name": str(r.function_name or ""), + "created_at": str(r.created_at or ""), + } + ) + if sc: + picked = sc + + if picked == "unknown" and inspected: + picked = str(inspected[-1].get("scene") or "unknown") + return picked, inspected + + +def evaluate(case_idx: int, scene: str, reply: str, run_status: str): + leak = has_thinking_leak(reply) + if case_idx == 1: + ok = scene == "member_workload" and "%" in reply and "项目列表" not in reply and not leak + return ok, "member_workload+百分比+无项目列表" + if case_idx == 2: + sec = "[行业动态]" in reply and ("[对项目影响]" in reply or "[对项目的影响]" in reply) and "[建议]" in reply + src = "未接入实时数据" in reply or "训练数据" in reply or "数据来源" in reply + ok = scene == "external_trend" and sec and src and not leak + return ok, "external_trend+三段+来源限制" + if case_idx == 3: + mixed = "[回答]" in reply and "[已收到需求]" in reply + prog = "进行中" in reply + ok = scene == "mixed" and run_status == "succeeded" and mixed and prog and not leak + return ok, "mixed+创建+进行中数量" + if case_idx == 4: + ok = scene == "default" and "完成率" in reply and "%" in reply and not leak + return ok, "default+完成率" + if case_idx == 5: + ok = scene == "external_trend" and not leak + return ok, "external_trend+无思考外漏" + return False, "unknown" + + +async def run_case(token: str, case: dict[str, Any]): + idx = int(case["idx"]) + message = str(case["message"]) + source_message_id = f"real-scene-p1-v2-{idx}-{int(time.time())}" + + async with AsyncSessionLocal() as db: + before_call_id = await fetch_max_ai_call_id(db) + + capture = post_capture(token, message=message, message_id=source_message_id) + + async with AsyncSessionLocal() as db: + run_row = await wait_run(db, source_message_id) + run_status = str(getattr(run_row, "status", "missing") or "missing") if run_row is not None else "missing" + outbox_rows = await wait_outbox(db, source_message_id, run_status) + processing = await fetch_processing(db, source_message_id) + prompt_scene, candidates = await fetch_scene_from_call_logs(db, before_call_id, list(case.get("keys") or [])) + + sent = [r for r in outbox_rows if str(r.status or "") == "sent"] + ack = str(sent[0].message_text or "").strip() if sent else "" + final_reply = str(sent[-1].message_text or "").strip() if sent else "" + if not final_reply: + final_reply = str(capture.get("direct_reply_text") or "").strip() + + inferred_scene = infer_scene_from_reply(final_reply) + actual_scene = prompt_scene if prompt_scene != "unknown" else inferred_scene + + passed, rule = evaluate(idx, actual_scene, final_reply, run_status) + + return { + "idx": idx, + "source_message_id": source_message_id, + "message": message, + "expected_scene": str(case["expected_scene"]), + "prompt_scene": prompt_scene, + "inferred_scene": inferred_scene, + "actual_scene": actual_scene, + "capture_status": str(capture.get("status") or ""), + "run_status": run_status, + "ack": ack, + "final_reply": final_reply, + "has_capacity_warning": "⚠ 产能提示" in final_reply, + "thinking_leak": has_thinking_leak(final_reply), + "processing": { + "route": str(getattr(processing, "route", "") or ""), + "intent": str(getattr(processing, "intent", "") or ""), + "status": str(getattr(processing, "processing_status", "") or ""), + "duration_ms": int(getattr(processing, "duration_ms", 0) or 0), + "error_message": str(getattr(processing, "error_message", "") or ""), + "reply_source": str(getattr(processing, "reply_source", "") or ""), + }, + "outbox": [ + { + "id": int(r.id), + "status": str(r.status or ""), + "attempts": int(r.attempts or 0), + "created_at": str(r.created_at or ""), + "text": str(r.message_text or ""), + } + for r in outbox_rows + ], + "call_log_candidates": candidates, + "pass": passed, + "pass_rule": rule, + } + + +async def main(): + token = login_token() + if not token: + raise RuntimeError("token empty") + + results = [] + for case in CASES: + print(f"[RUN] case {case['idx']} -> {case['message']}", flush=True) + one = await run_case(token, case) + print(f"[DONE] case {one['idx']} scene={one['actual_scene']} pass={one['pass']} run={one['run_status']}", flush=True) + results.append(one) + await asyncio.sleep(2) + + payload = { + "generated_at": datetime.now().isoformat(), + "chat_id": CHAT_ID, + "results": results, + } + print("===RESULT_JSON_START===") + print(json.dumps(payload, ensure_ascii=False)) + print("===RESULT_JSON_END===") + + +asyncio.run(main()) diff --git a/.tmp/p1_real_validate_v4.py b/.tmp/p1_real_validate_v4.py new file mode 100644 index 00000000..1b5360d8 --- /dev/null +++ b/.tmp/p1_real_validate_v4.py @@ -0,0 +1,330 @@ +import asyncio +import json +import time +import urllib.parse +import urllib.request +from datetime import datetime +from typing import Any + +from sqlalchemy import text + +from app.core.database import AsyncSessionLocal + +BASE = "http://127.0.0.1:8000" +CHAT_ID = "oc_c487d87563e48c2f9566ccd5a758212c" +SENDER = "P1验收机器人" + +CASES = [ + {"idx": 1, "message": "杨宇现在负载多少", "expected_scene": "member_workload", "keys": ["杨宇", "负载"]}, + {"idx": 2, "message": "最近AI行业有什么动态", "expected_scene": "external_trend", "keys": ["AI行业", "行业", "动态", "趋势"]}, + {"idx": 3, "message": "帮我创建个项目叫路由验收测试,顺便问下现在有几个进行中的项目", "expected_scene": "mixed", "keys": ["顺便", "问题部分", "需求部分", "进行中的项目"]}, + {"idx": 4, "message": "AI劝退助手项目进展怎么样", "expected_scene": "default", "keys": ["AI劝退助手", "进展怎么样", "项目进展"]}, + {"idx": 5, "message": "帮我看看宠物行业最新动态", "expected_scene": "external_trend", "keys": ["宠物行业", "最新动态", "行业", "动态"]}, +] + +BANNED_PREFIXES = ["让我先", "让我尝试", "我来看看", "我先查", "让我看看", "我需要先"] + + +def login_token() -> str: + data = urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode() + req = urllib.request.Request( + f"{BASE}/auth/login", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + payload = json.loads(resp.read().decode("utf-8")) + return str(payload.get("access_token") or "") + + +def post_capture(token: str, message: str, source_message_id: str) -> dict[str, Any]: + payload = { + "group_chat_id": CHAT_ID, + "message_content": message, + "sender": SENDER, + "feishu_message_id": source_message_id, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + f"{BASE}/ai-steward/ai-steward/signal/capture-feishu", + data=body, + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=240) as resp: + text_body = resp.read().decode("utf-8") + try: + return json.loads(text_body) + except Exception: + return {"raw": text_body} + + +def detect_prompt_scene(prompt: str) -> str: + p = str(prompt or "") + if "你是 PMFlow 的团队协作助手。" in p: + return "member_workload" + if "你是 PMFlow 的市场情报助手。" in p: + return "external_trend" + if "用户消息同时包含问题和需求。" in p: + return "mixed" + if "你是 PMFlow 的 AI 项目管理官。" in p: + return "ai_pmo" + if "用户通过飞书直接提问。" in p: + return "feishu_direct" + if "你是 PMFlow 的 RAG 问答助手" in p: + return "default" + return "unknown" + + +def infer_scene_from_reply(reply: str) -> str: + t = str(reply or "") + if "[回答]" in t and "[已收到需求]" in t: + return "mixed" + if "[行业动态]" in t and "[建议]" in t: + return "external_trend" + if ("负载" in t or "工作量" in t) and "%" in t and "项目列表" not in t: + return "member_workload" + if "完成率" in t: + return "default" + return "unknown" + + +def has_thinking_leak(reply: str) -> bool: + for line in str(reply or "").splitlines(): + s = line.strip() + if not s: + continue + if any(s.startswith(prefix) for prefix in BANNED_PREFIXES): + return True + return False + + +async def get_max_ai_call_id() -> int: + async with AsyncSessionLocal() as db: + row = ( + await db.execute(text("SELECT COALESCE(MAX(id), 0) AS v FROM ai_call_logs")) + ).mappings().first() + return int((row or {}).get("v") or 0) + + +async def wait_run(source_message_id: str, timeout_sec: int = 600) -> dict[str, Any] | None: + deadline = time.time() + timeout_sec + last = None + while time.time() < deadline: + async with AsyncSessionLocal() as db: + row = ( + await db.execute( + text( + """ + SELECT id, status, trace_id, source_message_id, created_at, updated_at + FROM ai_orchestration_runs + WHERE source_message_id = :sid + ORDER BY id DESC + LIMIT 1 + """ + ), + {"sid": source_message_id}, + ) + ).mappings().first() + if row: + last = dict(row) + status = str(last.get("status") or "").lower() + if status in {"succeeded", "failed", "cancelled", "waiting_manual"}: + return last + await asyncio.sleep(2) + return last + + +async def wait_outbox(source_message_id: str, expected_sent: int, timeout_sec: int = 300) -> list[dict[str, Any]]: + deadline = time.time() + timeout_sec + latest: list[dict[str, Any]] = [] + while time.time() < deadline: + async with AsyncSessionLocal() as db: + rows = ( + await db.execute( + text( + """ + SELECT id, status, attempts, message_text, created_at + FROM feishu_reply_outbox + WHERE source_message_id = :sid + ORDER BY id ASC + """ + ), + {"sid": source_message_id}, + ) + ).mappings().all() + latest = [dict(r) for r in rows] + sent_count = sum(1 for r in latest if str(r.get("status") or "") == "sent") + if sent_count >= expected_sent: + return latest + await asyncio.sleep(2) + return latest + + +async def fetch_processing(source_message_id: str) -> dict[str, Any]: + async with AsyncSessionLocal() as db: + row = ( + await db.execute( + text( + """ + SELECT id, route, intent, processing_status, duration_ms, error_message, reply_source, created_at + FROM ai_processing_logs + WHERE feishu_message_id = :sid + ORDER BY id DESC + LIMIT 1 + """ + ), + {"sid": source_message_id}, + ) + ).mappings().first() + return dict(row or {}) + + +async def fetch_prompt_scene(min_id: int, keys: list[str]) -> tuple[str, list[dict[str, Any]]]: + async with AsyncSessionLocal() as db: + rows = ( + await db.execute( + text( + """ + SELECT id, function_name, prompt, created_at + FROM ai_call_logs + WHERE id > :min_id + AND function_name IN ('adaptive_rag_answer_normal', 'adaptive_rag_answer_deep') + ORDER BY id ASC + LIMIT 400 + """ + ), + {"min_id": min_id}, + ) + ).mappings().all() + + lowered = [str(k).lower() for k in (keys or []) if str(k).strip()] + picked = "unknown" + candidates: list[dict[str, Any]] = [] + for r in rows: + prompt = str(r.get("prompt") or "") + pl = prompt.lower() + if lowered and not any(k in pl for k in lowered): + continue + scene = detect_prompt_scene(prompt) + candidates.append( + { + "id": int(r.get("id") or 0), + "scene": scene, + "function_name": str(r.get("function_name") or ""), + "created_at": str(r.get("created_at") or ""), + } + ) + if scene != "unknown": + picked = scene + + if picked == "unknown" and candidates: + picked = str(candidates[-1].get("scene") or "unknown") + return picked, candidates + + +def evaluate(case_idx: int, scene: str, reply: str, run_status: str) -> tuple[bool, str]: + leak = has_thinking_leak(reply) + if case_idx == 1: + ok = scene == "member_workload" and "%" in reply and "项目列表" not in reply and not leak + return ok, "member_workload+百分比+无项目列表" + if case_idx == 2: + sec = "[行业动态]" in reply and ("[对项目影响]" in reply or "[对项目的影响]" in reply) and "[建议]" in reply + src = "未接入实时数据" in reply or "训练数据" in reply or "数据来源" in reply + ok = scene == "external_trend" and sec and src and not leak + return ok, "external_trend+三段+来源限制" + if case_idx == 3: + mixed = "[回答]" in reply and "[已收到需求]" in reply + prog = "进行中" in reply + ok = scene == "mixed" and run_status == "succeeded" and mixed and prog and not leak + return ok, "mixed+创建+进行中数量" + if case_idx == 4: + ok = scene == "default" and "完成率" in reply and "%" in reply and not leak + return ok, "default+完成率" + if case_idx == 5: + ok = scene == "external_trend" and not leak + return ok, "external_trend+无思考外漏" + return False, "unknown" + + +async def run_case(token: str, case: dict[str, Any]) -> dict[str, Any]: + idx = int(case["idx"]) + source_message_id = f"real-scene-p1-v4-{idx}-{int(time.time())}" + message = str(case["message"]) + + before_call_id = await get_max_ai_call_id() + capture = post_capture(token, message, source_message_id) + + run_row = await wait_run(source_message_id) + run_status = str((run_row or {}).get("status") or "missing") + expected_sent = 2 if run_status == "succeeded" else 1 + outbox_rows = await wait_outbox(source_message_id, expected_sent) + + sent = [r for r in outbox_rows if str(r.get("status") or "") == "sent"] + ack = str(sent[0].get("message_text") or "").strip() if sent else "" + final_reply = str(sent[-1].get("message_text") or "").strip() if sent else "" + if not final_reply: + final_reply = str(capture.get("direct_reply_text") or "").strip() + + processing = await fetch_processing(source_message_id) + prompt_scene, candidates = await fetch_prompt_scene(before_call_id, list(case.get("keys") or [])) + inferred_scene = infer_scene_from_reply(final_reply) + actual_scene = prompt_scene if prompt_scene != "unknown" else inferred_scene + passed, pass_rule = evaluate(idx, actual_scene, final_reply, run_status) + + return { + "idx": idx, + "source_message_id": source_message_id, + "message": message, + "expected_scene": str(case["expected_scene"]), + "capture_status": str(capture.get("status") or ""), + "run_status": run_status, + "prompt_scene": prompt_scene, + "inferred_scene": inferred_scene, + "actual_scene": actual_scene, + "ack": ack, + "final_reply": final_reply, + "thinking_leak": has_thinking_leak(final_reply), + "has_capacity_warning": "⚠ 产能提示" in final_reply, + "processing": { + "route": str(processing.get("route") or ""), + "intent": str(processing.get("intent") or ""), + "status": str(processing.get("processing_status") or ""), + "duration_ms": int(processing.get("duration_ms") or 0), + "error_message": str(processing.get("error_message") or ""), + "reply_source": str(processing.get("reply_source") or ""), + }, + "outbox": outbox_rows, + "call_log_candidates": candidates, + "pass": passed, + "pass_rule": pass_rule, + } + + +async def main() -> None: + token = login_token() + if not token: + raise RuntimeError("token empty") + + results = [] + for case in CASES: + print(f"[RUN] case {case['idx']} -> {case['message']}", flush=True) + one = await run_case(token, case) + print( + f"[DONE] case {one['idx']} scene={one['actual_scene']} pass={one['pass']} run={one['run_status']} leak={one['thinking_leak']}", + flush=True, + ) + results.append(one) + await asyncio.sleep(2) + + payload = { + "generated_at": datetime.now().isoformat(), + "chat_id": CHAT_ID, + "results": results, + } + print("===RESULT_JSON_START===") + print(json.dumps(payload, ensure_ascii=False)) + print("===RESULT_JSON_END===") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/.tmp/real_feishu_case_1.png b/.tmp/real_feishu_case_1.png new file mode 100644 index 0000000000000000000000000000000000000000..11c011ed5761044af3ab50bc7c9ba44610968c47 GIT binary patch literal 73021 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 &2)n!DDYoCa^9&mTXMNq(?f8*6;R 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 literal 0 HcmV?d00001 diff --git a/.tmp/real_feishu_case_2.png b/.tmp/real_feishu_case_2.png new file mode 100644 index 0000000000000000000000000000000000000000..f12e34cb03a2855e51b7e1eae00a1c08e943ca29 GIT binary patch literal 168006 zcmd43Wl&sQ6E&KI012+a-92b(+JEVbV+bkq*P|GRp?eV@b+QP3nLe$t8*823EZ z+(Q=GMK{-u18+Q9aF%Tw7LHt7mZ0Y?<(<2!XQ;`2LLLM8>-?TW9( z;Q#*4_D`5}l->UKMuN2C?g^*0!LBeyAe>4C7A`Z|(@j7fr#2nt>bt)i*I4?q*N%I| zD4!o~_mPyEIygk_ZVH#XCae>$tt~&wX#@HBHY$xPc&u=Givp&5?{bm^d;T;nGgEuRy5DLm> z*$TC#G@a|aPV0cDiSLeOW8W^vbotwH_Qx2%-ujM2@Zb)Nwio({hvAeUj-+`1ywi|| z&@i9l1Ah$qw*{|Dao0lnRqgwOg@^-#h2~;ZgWbu4iw6bxV zD;pc4yNRhzp{QIHLdDZSO5l03qC~n$7}mT=+l{HP!rw@Ue~XT;&+#o)D}gw>xYX9w zQT^LzyK&O6q(7l=B+T8#-Q81&>EYYd9tp*#^hy#>mQgYCf`t-48H9@F3Mq8$I7hOZ zghq!WprT6sG)V75V?YS|w=+^`A6~-+NlHo_92{63?3xSS1>V1F)alL&113THmM%~< z=M5^Nyjfh|z<}^St!Hidw6+vTmSK)DfgkGOYFzvc^cOO4;yiNbHc6)6!1s5G1 zj)aQHDQDM}@ZU#~U||OluyANujnt{$W{5z8gq@eEkjlQ+ty{OHSF`sjQnHJ{+=N(3J{YkZ4X>UiUfMv+*haQX&@1$qN+S zps9ho$pgO8iP9Nmpiu=*#=|xE%?3v>#*;y#E3=7&`HPB8;Ym%Sjm6`N-YElybj{IT z?Kp^rja`zLHxfwwypXhbIX%KT{*GcUEYy{r-yZA%GeJ8&8qIciC4JGNtgIY-Q$qP~ z-@XKi!ff?5c!fpcH>!4L1p*6AL9N_21(uYOaB%w<0c2fiIdVJTk>9FQg?WGVWdXkJ zHS60TB-TelMoDmVboBJpRP6JOL*uX!kP`@4_v@q&Yx;VF_&D$IfH{JUFr-mV)nh=@ z+pqq4#2=)fV0!H~vvm&GM6ZlY>#ucGTfzJGY`|2CTT^b{PG(ifoix%n5qNg5JphkZF zK1f-Qy3ff^aK{VaRyym^`r#nl6e)q{Zv?TRX&|3%Sbl>YItD&QLi-Rg^>2>%{hB}T zPJ!nJ``QNt&+<|C_COi`Tkdz52hI**GWfuVWne_C5ypP+G2_1}vN=#9I|zLALj#PY zwXtLRj_coQJgR?eg=$6LtvOTO*ici0?R(dP zs~vt=#7oL_4A$(h6b);BjXp}#C{(ba^wewio*Eh|5^H%kKmWrp{Wi@lHa7NMD*GQ8 zn#{D@h2rAk9;DQM`s4)j11=jk-^*MBe{Yx@XE}qPP!j%z*+Y^3D^@XxYQfnp#AALq z=(^&(-eQhYwGip~nU&8(K{*%q9a&zMk@*)Bin$2^z)Dg-bY~dlZ${(rx3#k9H=(yY zX8K48-rO{`+FwAqob>Xz24T7A8hOy}*FA%G>eU`I@{5s+Goy4uK7mA$FFVE}G zn3%?N=&xV`4-czy#L)5RZn9q;gWTMeluo9JZj+ePmCL0?JXV2_7PobfZQL+24|idi zYrF#v#9`*?;n}v;ou4jMmS|HfL2xPPx!%0~?ED%&WEESiyK9n!NvF9dr!7qann$BZ zBcDOj%%D!IT5I2V)wj2C?$En0zl$s2{URV(#|?Zu=ZED+FkQv;Z?jgs8k+h{V=EfvkVUw^nzc78V(zd>--Nzs2*SoCRx z-r=%JuOiok-y$LzDi!NFfaXV92f`hm<&N47jgBVfb0}Q&TkM%DiaDNE8S-U~pDAxB z(W~p)-#X`zblm9Ey_1ipcBxFC%g@@pJM5i1Pp)v>F`v#C1e14!hKGY^S|V=rh!xY* z$I@%OZYHUCsd?=(7j1MnnZ65EJ-dS+ndw_UJit2B8(l4~Km~Mhv{KR5)zB%wy?s}Z z!dzF3wm?PLw%*&Mq|}0Z6s~@`Xg4=d7-xMG5v3JH2QymCLL`0Mo<14S@!lc|fP{0l^@inej1&?E~ z)kpDAnay|*m%rH_oKTHvc0s_=w>S(5G3ys5-?*9Absi#mWrVmVrPu1*yVC06#E=Ev zW-fU}s;f(MO1s@Ejra4Lv-!+=H+&UEzakk(nAm@>T)V0DpGAdS#lzjivzll7F_4m; z0B%2ffAr{a6O7S|*iQ_{X#@T|SGCg0XAQxUix^g=iL#z62em*+f@3nnrS!R%ghwJ# zD8$6*98^jI-r@?HmZs3R_8jhQ3=g-@+zqhY3Hm4szj_6e#oJO`_%qa>e(wZw=IVEj zGGT0P{@zhjh!vOo>k!IJUOVh-H?TltWcXmLfE=g`7CU^vg}|n8OplRRv~#7*ORC~( zOgEmwYkINCsptB8QBgx*4(179OLy{F%oPxu#|Xxx)T*V%K*-CbAO!6OTM#!TB^J+U z@CaWley!z-)p7L2#lBkddV|%<%=u@=6m|(k#U|nuUMI3&Uq^{_l38@<{b@2=430_A z>~Fy^&K-^}LiSfZBRbIWsDzhG?zWfKTGSs?8A>kq8oeKW&X$=Nbl7f{af$YWHj3~# zo^izeQxx(riZl66nZoB}lz#7a%*ROY;evcHGnmM(19(2@XlpBw`*-(5+2d;U&?nn; zR+HMk-@3f@K%6joRm799x3?FD63fP6s|0W2w>^wE6*5=tl6bSk=SDJZH~*3-1ZsnB zmc3Z(0&SG3;b87L**?0h6bKn`>(#=(g&bl{=;8Bw2e=1*YI3q&Xl%95xl;+SU2YFa zOE9-vY46uRX#I3`3rRf-`mEPHs{2!vKRkIfwyL&ImDl;G!*1sWhzk8YT`}#xPTMTW zYUl8FA}Sf1j+?U#1mT^z;bENyn8-Kjp!FUsgAOM~w*!tDcp}bsJMS zt7gAG(>C1BesoIK_~(=Y7pnfh9#aqnH%hRY>`dcn@ zAJV|WtXKaI`D|@%?JN~V)kh8)!!}(bnLaUdvCU_wlp6U!T4+qe#UQ9xXyYx%ZC;q3 ziuzuhrPNz77?=sjURv!T1{M}$@LObLg7?npH#Nf_CrJ3rF)=Znc9zA|_85|fQDQ>B zlL$DCDjasa|EZ4}#m=_eSirApJolH%#l=Ohr9;H6^7C9^pjeMp7+vRzkpyBGyU_rz zo3#)4&g5f0Hwu-4oQ5qL5h^hcJyxu{)v=I!sNm_5C7HN5?e;M4@zygtRB?;Il2& z17|84A3mu7t9+J1cveo%OTm&MjY+UC+*&4sr*;Wpg0$;qRC=%cVQ}qel<8=2uNc%%PhRA0viYj&HJD;OUejcjrCT|lZ7eT>xn2;!(kPywS(I%hs z!Ea{N|4`pcAi`D}`R*yBhpq2G=|&%N3=ONjm^E8(#+7w01Y?LY7}QktQiRiG(KxRG zgf#_kKHmsVcuP@0@+X6VFWgK_tlJpg#ud!NVHA1x>Rk{b9W>^>Lj28g)H!~)kmup7 z*Kb`tb2r#ZL z-J+fnN#rWDW6vmVBxse`xOTC{m&ouRmK|i~qe^Wfnn8IP*XX zI9o17McCU+@{(UU8#H61DS3o`KR8O#I4fzW0=P%XW9ZH8U0jmekCw96!n>oG+k!(^ z^IV4dBDUod$B<>7?Ldsyqqb48`xwa2^*0}hg(+MQ~|fxvU?^;45m}|3*4G^I}hJ zCNUB-7>~^;_b3%{m-vI{N#Dp|prBH63#1 zVk*E(Fr|G!Kt}dDyn=w+q&&_yA9&R* zKgh^(;w=3_3M-oX9==jX5d>$8IHT^*z~lrcWVZ)OlJK5!5@W=M+k-|sC&?dyB&aX? zZr*7oQ%p=s{@v2SCno95c8urMC|dVN^TP)CEOGNcD>aWl^R3nVSZ)~g`PoOw8jg8G zMWJgHMMN#AtG4~F6##qU>1?H6^ocWyY^HpLy7D&k^75^5aQ@>?*jvDisi{Oya|a`c zX6N(~-@M^qWgRuL@c(hwD>vg^IwVHDyu+feh>_a4$Z?qmAQ$IG*NKIG4MOMlKnnGj zDgqTZw{^28lUIt+K#K-NAZjOHcCWTb#)y3iBX2mx0_ekk?aL4Gee)uqCU`tIL7nt~(7C3?| z&|iebbl;Egu?lRP7EZ5hpJcS4N(_EAbX_QypI3hWv@GI~G$Vlh!@|bq@rI~f2;#eO zrgxafq~-IX1Bq2`5aK)aj98I3C@85d_65>-lv7h^!I9}4wvg5Kw&wg8zmNcK^$Ha| zXGYy7!<$XeZi;|k(}5Kf0I;r>D`z2!sL05-cUyAK&co5V@Zwn?@z_Hj6f<-)xp^Wo zjXDBjyKhEPdDi}5nngAlvTwOiDLF#-mtPWP>MdVcrSinz3^v2KlJ662s4aSu9Pot0WDr=#8^6_-JyxxsS;}4#c zlnmqFqWl+&3V~Q8A9pePmWXJnGGjg*JRsc*WIa^ssY}1KC_GZwcbLO?>urzrQ`t=t z5_b5;VJ}WejkvvsT}SV*jij;qF7_ui+iV#IXJ8{VHn?0jNhixp9ggtPXgEL(wsTkG z=nIKfT*-siA@5JM zuG{A+pN|^d#E{I}8W|B15#8@rV}ebqhsz%l`l9xEd4!}I$4kV^N-z^|sS_CW`vK!g zfDY)>t65>8q0Q#lL=;AK=s)Iu+*=Qo8%$l4cs3u`f!_Hy=FD@bhhyW2MBa!NDNKG!G;WH z3Mw!>1g`0o=sJ?~lZ!@n(rYMqyoi(%MD5KlIp@f<&0rZDVW|Iy8g1MUdse=Qz)+!U z4<)l?UUzn%L$q3eq(EFz3?`6=y)QzJ32=diV?ng74}%*3hKGVd%C=>wsHhzd2g`dk z$(JO&%vlkJbGfOye03s;3kAkj3GL>_epdqyYkxT#qX?&~{IYBTzX?mNZ(^reZh6Fo z^^R%p-pUL@+)^BNDpPxC7W=lZhG=3YPe21sqE}XPo~+cuUK6drSN~-;6hKG9YPx@E zcPnVHYBr|d?sNOlRlu;G^6DFU+A?t*M%hx{4y^;* z-4s@nk74T)zcjUbd}H$ZA^g* zGm!%LJH~0JQhy3P!@sFSHjg8zQP_F56DHu6=*lL;}Eagi- zZO$ODXSxaH0RP0!r9c4{G4$SUT>=nLEKaCkD8VP@RHpm$+JLxy_i*Lz+nunwiO$n_ zvwnqis=o*cBgvzL2^c#7D5}G+jio@kILERSUhusy91JAV6Y8K)fWsps%&n-fF)#Ko z?Xmg*#$r%Wx<|h8x%phJZTyiKfB!?34h9RFMD*E-^p~C~4ve1* zrcAHUF9cDE1xuEGR(NJ%N0_Y({BiVj+Y5)#Cq*;!IGtj z;}OLAd>qI9`=9c9%M|XO9KP~FcRc&<_Bar(RyVm)6Nl3^rt(OK%P!?N7*3;VMX$~k zaei5mcuot*ZXnmIYg#$l8@~$VYON;9MV5Z79|k*!YW{S;=H-~r;;AnBoEoQ+3kwG~ zH>cn7#jiqp3qc-#Mjm@ioqj93b7`v*NUI?6N^M!(F1~lWop5k)2ohh;xt7pBkmeWb z7nvq2RG>42b^@%8`^mr`K>@y*G94JzfK6DQQ1@cvmUlxqW7AXj?eLx5{R+3J#OF}d z&kg%l(SZYnHamh@LOy?zI1&5479Gy|DCJ1+4(XIhCVu95bO?(A40PJ+FEJ6(JeQl3 zUxV{nWVzNubq7pbbkdO*`8c}aKi)jf2N8d>_A3BBzKL`n(^I`he#%D)=W3`O4LA@fN5QeD;;Qlei;^GO`xc|~{fT;fk z)VrSd^8c#AAiwQ`GA~OC3b}|#{CmaB>Y*$aH@&iSm0#hZN{91)t-3>!C8`9nbSYIU zJ82Ch!9gL(^vc|Bqt-%%%R6~15y`fR3`v(~moAR3%6FRfg=wY^)RqSKO(tS*=J3Pnn1I_@|ox|g0Ka~QbR5ud-4MOhrlKJ|B6AuEUD#3hd4afar(jV*A54%}Er2eIYgVT01SeJrlB!5)q~!jI|)Zlr0ua|IU*Zp1M} zS*S$gO!}GWyB*de^))zzXIp<_RS549cJ~`elvX0V0ibO*`22vj# zH)2O8zpA1loo4aadM1MXUGx6Tlo{LOOQQObSf(Lh|Dz^wUCbwXx+iuP3(9CW4rVF? z=25$QcuEf%zzh0qQEvx~QcHX@N~yx%MV|XeNJt#uMWcnCbrN*xNb>tq7r=mnz@m zyI6e+Ki^{3Yxmu_6w~h*oAz*;D%KWc4YK0TvKT&9#R0ff65^cS(myzg!#oPoudLh@ zvKsomq;9=;E=L@9hJzJf9_N0oOKj`5p`ZvXO&pvq)1_+XAz9&Q^*NsVY0C-9V9q#` z{9e;0fT+pGFsmb^_9rF(*^jypb91x) zOs;2hGdeng^Pm_NtfB-RofP;sC^Ob0O-hhOv)KOSlEt+dd_}c)Qd~IoUVPN71 zuFjXylany^e{zHy9}78g8OMvhrLqs;izATl{5LYVn-bS!q3BGU!%D!>D!ht5ITc3H ztI(3*`*vKxOREg;j6LMS!KmqH>Iuf}kKeH45dJc$#+@y~44>j8aLW;T6)NJxoA;$x zjMf3CYB7LJ3iW8J%qp#DOfMHH+R3P^t2>tQb;!5e5gtFmvJ0I{LH9f7&F`6Yr5eKM z#26U(JH@T7LSO-Noik4_g=oV>n8 znft`7?|C*0#I-L1KBL6PZ|aZ84v`|-;uIqbT3Q%Fok%$%O}a^q+@ zT{mdC-D1J_+Z&ZQLDg24*W^I4Fq7Zapj6G1s|7WWM*9qvSYRUGcZ6;OZX-~y+0x3u z;JsiF5GR!s@*LU?+xE@-pl6rzcOkTlseCz~E~=Cf-_f+@y+$EpWtR_mx$eE2dyh=X zh>sWLsrZd$pliAH-hTToam&i)yN#js^WNhX$wYlOKOG!tfo2I?x&l#gmulCm}C>GB%Ukus>%eBI_&=OgsJgy*CIAxdRHNyy6$!X)oAnThx_f7 z&&}a~1>JgF4kZjcJiOex>8(V9A0;iW=Ad6%0+3ecEy)6eg7LyANf))Io12nKy^iGH z(!b2=016ov-%eS-&0_~tsZQZ+HfB1P=fn_s*~{QYiG|B^lkJyxGgY!Y%Vh(R5Xv*#`_Xg{HqOdL#*7!>sRs(M}|{}PfcSJrAuqz7MstDE%LN!J5IGT<~eV7Z{Kb;BxHM` z5|*hm>(r^>eS778oNM-jL(*8e+Ry6XQ;4YB)he+BqRxiIhIdms&o6S!&KJ19KJTrE z4@he?H2jU}xn4f1T9yooB+x4}UMTdaaLkn!v~#QYaw@u;N|&c~-shrP#MSBojm3Q% z+*zbl;!QVs1jOe1!w_21cOu{Ylfla+1y?D)veM?Wfl`WApUO_UGX>kTGTZEXu}t58 zkf4q``Him{)JcIAesb~#RnbnIUKs>xlqnV9UR+$k8OSo*YYmfb|LGhyAQ_BmJ{>TX z;$kEP$EfShl#e|SlT<#Rnek#EWX8YVP;E6A0j(WC@%(yOkG^^md{B-ZO=*yYh$Bb-!o$v1{=CCBIWfh_ zr4WUJIpq)Ic)$^ECh_gnmfN9nfo!7X>~C8$7Vcr4U&}zyH=ROgax#s$Z-4Lo(Rp~Z zO-fl{P{IyKcFZ1Mcm$AXjF!9OWOdLk_ltb>K4JcpX zaEyb4kr4UT2p8sF%|?eyyuV0MU(nrM$n)%DW|saP3|fmp=_XGWU7m-qfl|5?G0A$Q z2Lr$lPSKE-nNa8R>|U*tG}@lqM%r(#W;Qv|RUNO|--C0Ef82^dj;F}lHxL05HN->n zz2EX{PosJ(3pyO*MWEu9%4H_87T|Hd^#^+e*GO?nPQ#5cmz@UngSxKudRY8WpUv%i zE&!RJ+mVs|{rk6TN3YDYH@@g-^Yn9*E4+n7!5P&;t$A@-S)kyX6rkeY?0S${l#Kf7 zM@UT8j|Zm}gnv7U7zigzj;ctq`6iJeK{W6vjNO&C+EkG!jQ9Mg1=U;^r@PhbIILOj zkbxK%i_z2b_G*wqCQ(+=IW+P`P|~d8Ltqg!N(U7|Qzyr3v^KVQF~Rr^lB9@jLOug^ zDA6jXWPuXxdmFJEHwjC70DFx3^eeFfJObgZPsQgB0k_e^bJJ*%f~dZo3O-arN^=;g z61t9WLx>;PYn-Y322=7hX1J)r>DaMq{rPx02IBd=UQGng8OQR#>|ct)CYUhE`fPHl z1VA9|qYYPjwqu>~fI>l$_yc!^4F2QSY#H{XVj&{3uEP=vj=nJM5A9=EOeaBz^g+ZMn_sZ!k1>47vB(SF`N2|x#Jlt$D|WNd z_cmQh*EP)gh3==hupE7il^bXTeV%)RQTvHQC0Tizc{Izbvvi2Jm*)DlKhIWSfr1hm zax3)Yoru^WYY&e)GmAb2Hru_c%+TF+0{TMPj2*ifTTFELJCWcNi}{sN8HaU9K}510 zE++E0gYD zmC*`8j#PxUixLYg;AOAfZ}vH#Q`c#?LGv-xMhWAzT2;%T8wTOScTdCp|ftf5(ew%m=KyP%F)vA4Ktij#ac9+9J4DG&IvHFBHZ9k!_ zGLc+se;BmN$EVVuCw-^QQVQuf0-&oH@r_;v=<7_}l_A)WZ{)u^8GG**>yA_UYEBel zmCP5FEc&lg-!W*f?nkwx$N{n^pgz?J$!=y=E!}Oqjjpt{Q^pl?@N74#koVsC8hR`y z4$fwMX(SZAbgQ_#zkhl77moG2pD?MdvNFg$t(rztN{VJw!`b%%gN1VR1-`ywtH?LL z6#5QD{jRRAF}=ospd8WE_0uS_(GpSGK-BxorG6kzQ{vQ{9iAvuryHb7k$gcgTtl}7 zAG7GUQK?`s^6#+dT1}N_s)G-WD9M$(UY9S_!e^0g=QvV&HUk2Oq$gk;>Z`Mu=O0&W zy=P<|M)biYHlMy1G?+mhr3^%OB|-VjCM;~6xB%v(v<;7j^fdl#(fK|y-|1(h!1X5Owtd<|_{*bKfTjX)a1IUr)IzQGN%z}{xOTlF=s^mo zyu1K=r&y^R0DmTPmRoa}taU(i#-{rcb*8uBgGW$gyPQ9|wy_hP(fYM;uepvsLSRk# z4}kYCCV9LzDPYfcO0-KN0g;MKo!?8xkUb}j^`NKocOp;s6^<6#gjR)Y69BG&kH1h9 z@p6ZAQkAK*3e1a_8$HzOnL^vZ+!-thckqIqH7ov`&d_!ipeaA-D| zjCF0LQBGB&Wh`)tNiF?IZ1eGEJvhm=n7{|nHHWGsdxv7yRDWTGXH(Wanyy~+8bu15 zRqCQt-n|f46x76R%XICPN3V`Twpm9e5!&F#v>H(rDi(Ld;1yT(Pyu^y)u74M)}^{7 zc!gDE#@e=)1_|nb1=1~@Ww+Ba7bLCU^QH2LTc^~DSsgtqfl_0u)RF-Mrk9iGTe3KQ z>ziy&50;UyEry<)j|}_S?>guzexn76caTl#?XWsr#Dpy|@opA{wixD+C8drO_`&zu z8z}v3v@P%)`w9^6lf^GUp4LAEZMwF=J1j4Yp-LH+{OIag+@r5-1|CWUs3%Q^EW?UL zWnW(?zcrV9*gXiD*So)gtzN;={(1U$ABS4lt9@evXM|wpotrXkZdi+IOO+Jn>Yg;W zQn6eqwbccTHCv5Oo&&ElhYTIpGI+J_+7Dps4p zJbqEpEn|3$cV44KuL1ndSSFJqxBtFH1&|9oI^`s)+2J8Nk)vLQwS9e}rthcuTfLEy z5_O7J>K{8ESdwA(T}1dfyHIroX@da$U5hN4NOzUn&0Qiv1F<%CmS+V})rJWh7^X4iby`F%Fnab^IgG>06{*2{SBusw?xZ3~)OMY>la0fs32Ki;N56Ho`5^5gv@(*G}D zaqjy?MZ3GY$g#o)fRylA4eQH9WMd;BpbHbICK(Keq3mb?P)d(@;z<7fWW3G1Hzg3D zwDUjx?M)q!M}ammG4P|2*^P}U`j8>VtmdS-kHLPz_U_{10Hl;2e^%<=#AH`X=?h`o zh7thT7j}4YEp-W1uAl!=NP@E*dGyqj~;Z(YHGb(CqeW2X@lY+X(tJt$sYx^yNU&ulaC&-r z1}HvvnCvXLk>F33kdZ8md9m&Mj|%{%?lE-P)_BC{e7EEkGg%U)*XkK%yD0vMO00)* zKl2JkOoaeX_9*}*kQ=HZ6If#?t2o2}u#JhS!hr&Q@8AF)4i?~`vlT1$6JrR4CJIe9 z_*UXxA@lOi*Azwq^(Wtxfz#PPPF7s==1Ak&5;%7f?yTNQ$A@#jHV0#mPQda4H&N1A z%r^1CE)vo+7eL-z&=Vg5@Eyo`9ms10YIYMtYCG_Zw`R{QJyHRB@6o2sC1!F#*= zC!uejG6F2bG|O3oZc_p`%k2EMdO@m;rNo248`p`62?Bmc3~;*#bZxRpZfhv%<0@S! zO&S6Aq<{A&AP}+OcU`8;{eMg?5Xk%u3sj&d#YSy$lHq z7bn#dUZ{%vo=4kDhA>{Ru&@-K1F=jIH)Y(PTY}QW=~Of|zqRk5KRF069|G%HYh$2d z3G_n5?Hzufic9u=M@=`!HIWrwmt}R)fAuy;4j{(PYRdHD!&0oE+dIde!Lz z#6-!-vBvaT6G)=Mqq|nd$Bz$wkq4cPW%9b74F?5E$xr{CrdMyJ^)wwzD)1>QJbMpN zWWPE**)OdC&JXWHm`XknY_8MspACe~Fc3M-217aSJz&znanNdf9^O~cOyzjhF2K76 zr0p`*Gy!8$E2cJHgE2|8pEOlq(e@Sh7TXK_+aKZ*3Bs|NX>U?+i|DlTv-!V5rB=B0 z89H1t;an`uA+6BW(PPKfEQ49SdITV|(8Ugh9FqxIx@`sb1yPC*o&rL@#dYr`K+E)B4FKZz zWF5@>{6@FnKx&J>35F11}BCV$epOKD*-ZcFf()c>HxaO-bZnh z#q0Lg?O>)Pr=WnpREUxiGEu~`LPL5;vQs3oiH?VNadxV{v%@0fcgmt%e)3nH0Or-J zL$kSc{*W(8yeA+z#t^<_l-h57?Iw0<=(E`tUHGZk?0<_s5aRqRp znC|NV1y)w-WgIr^(oHc!vh3qd9k zhnrWZ1G*aanEYZ3SKC?gwGt30`V~g2^jf?;Zvn#V&Ar`{$I0TB`t>g=biL4`WM(bM zx z+jq{X_Sb_D$iI7Q!b%b3d^SeP@M8Qt}+Jc?&hoG4rgJ?Zbv+|fY|d24h0$c z07&dGrOfC+?^|yU|Jpy~-ZzG^Kf?k50fZ&jl;ZE ziK02U>=u`i9&+-LQ8DSP{w^{Dx-pX zgN&%t#N3j0tAnK9EUs)v0+f%cQd0}7)bnHyHCDWKuL^Cv#NFmpdQWjEhV3$nRQa|< z-brJAXZx7Zrg=}7(goB*b`(h5)*(Uo>I||FKFtn`mSkxEx&&eg)cbl6IU;prJ8>6i zKN0k~{0j(eFOL?#GwaUJohMc3HS{hHb9A`qF`{0d?}(8WJS>_|)hUZ(QHe|3Z@dEW z@~)8l5VTrLW_h@|xglzXV3Gxt>mOGdOQNV8Z`dCdRy*J`u7yOWAI`R$Y=6Yt3R!M@ z1Z**Jx7~ZIxnWEy#HwL3D`Km|b4s?kbl`Ba1o4e*J$<2N_LTLJc2_Ue zpI==LL-+;Sbli5SRshx0eq@QUv60beWkTPn%Z1#q5({34-9)Z~rlxyfGw0%Rt5~#wxw%r6 ztIM@kTXVhC6r}<#&riD#7wGu-xQFwt7<9t%aCORVCWGc+d+-TR3eqJqxiI`v6V#|9AVEuRg@PZTle{=(I3 z`0Ap!NO|Ma*xVd+e_o!|-kx>h5Me70XQjb}%l%~$P`kL!l}E)IZI7k?ZIf%^mfG4n zQf>Vx--8x&JQpEM2C!nuE*2MtTvht>jfT3;<=QRR-;SDeG!hNx!V8?R&=D>={}u;) z1vGCB$IC;Qc`vc~UZJ-x4kcGL952QQzI^Eq2%Z=<>Ju8vD!u1ajMCr8-(2jK5(o7> z75(Lss`xydaWD{i5QsDN3S{5KlW>0cuvBl+3w^7Qfj>cEgflGsv8lfP(+y-#R7}in z#fLQb?c28kejs8@ek<_bW|z{4%)Y+9e?5=8ML_JxXHd@#-Q8U&i}3Vw*p^6OFqs@A zCUddOdo39{@WLX%8;2*zds-6Hc*DUGd0?<_-;T!i`yZTEVJ_tY6`rt%H-M_D^m?pK z+jgnl?XeT{x-zqp`4=44*gA9R7K{GfRESgwqfroIa%FZ3^y1d}xHHeuL^e0Z-~Sms7d0yNWFL-t%hjnIo^WC0PrMEhqL|_(CPOI%7_3~B>~U&s^Nnj$k4X5)m|o_ z+;Ka-x3L8E?j$-RX#b|9Uorh?vORHQ!pv+`4?_e7;Q_F~*Kn{o1XK-)>I$a`)>=$s zb6`N_Z82NE0m%D{`dupG4FhjU-^is+zcv&Hk;59J+W&dL*HdWs+SpziS#YJ;EB>4p zc4L6qhk|{prc5@!oXH31m3*e(+DHo7G-zGKj^`@w6W z_9}#?)^35En>$~piYy9$(n6`D-rKSdz>hrV$%^CV%#PkEzgrc&!-HjF(MA*klu38Y%* z7W`Oa#=LG@fuER{m)EcO!)0wfk(~zmBekIa1x4S-41U9J-UhMua}V=V2Mx^nO?vgt zZ>wq0$+7+X{178P!ol5~8KnFalTsk&_a+Crg@_g!2zgBRM{TT5E)lT@g{(L8>Db>P zk^HUlNizk4@v{vnuLC{Fxe2sR+aCdquGL&+e=6o+L`?aDc|2L5N1!L?)=Q;*v$4{} zoYfux30@L;fX`G^GU|WRYm9PBxu3?JsZanJ!=RBNt4m)1paque<*k+4-a48n=r@p} zG3$9}As=~r0j9s9@DUy#Z;=9YxiBF3#K#0YL@AVms%uAA4f;>C@yxo~&O+{2`8Gv@ z6u}U$)BOuTXGARI5C`vdGEi+4^eeZ;a_fv}zFv6z>t}<`#gbeaJq3NCsa3sP?n&@(&1ueQ7z?;OGZv}vtDd9en_X2K)~iUYk8dgQ&Xg% zZ@!ev>h#>LDa#KiQW7w7T3M7odtiJw%uA5Mba(fR!WYa2%yM@$fiteg%kPc^vj$!ReRj%1qinNZx?r)tDk&)wl^F89>8pG~!P>8A}1{{yh zWOQ{i0O38)=Qc7LEc?^5DL_~SB%gJbE9Zkj##+BL8=E?S61l2MW*-|{qGb${IPSy4 z%K`b;HX+Lc^AE)u2ZtVfr_PUGh4V3Nw7LGZoBH;}C|WCsLr8vnu)?tAELE3;o{I4? zGO_9NJJEX8Ge+&tOyfG1jR4BNh5fz0*eu`cbvw7~QUET~D2z5@vwXTbVbyWK_{J@d z%ZG&B0t5sI1>iNDWg6|S=h2>uo1`pyja+wJLA2YkY@xO!$1z~LiQ5Dm4|K_wvF`bAseo$1U1&4BR(iP zhx4(3+F`y@rL~M#2m#RgChjKvRAk7>9~2+euhr-9n=fQ)p`3CtMAIbX@#TF_d$q@& z3`BeDg=$3wnWy|W6eAFz=?cW(peW2|ib;L^h!no|_3Kx>8k}TCWvru<(J{R*BSw2$ zNAW!IXWAG94Tpcht|56M%Se z_vWL{b?kNA$FVh^%yvC5MBZ$%TWL%7B@Rdgx>gO};icu{v3YmFE3$lmHk>Kr4&a=a z#?oQYeIDP*v+KWPN@n_DQRC#7^)hdJG(8mUb@!s*38mFLL}x;vWfRcn?uKb@mej}r zh;Bx~2IRNgg?+_m(G{w&1P3{s7P-HReNOq_J|C--Kn-JiEYsRFsoio5f%fY{y)Bn> zPY+-eM=XQ__E+MkY@Aj4_3{~g=KGPbD9O<~1DP(MzjZDIb=WO5q*CA2Z~)Gl3ehxF z(ie~ZQDd}0qG3j-sORHwketS+in%**U`UVzwa>j;?#>Y(D$q!RvjiANdWNn&gK8MW4nH`mrQ88cylKDSU4?2(GbA1Yfs~(b(dk##)+f2Hb-`Lrm51lQa3WPL zj7+NsFB;Xri*yed-cKnir>^iScD}l9cC~FVJdXwfj+eXV(R{t6imq_2dv(`tv2q1Q zW(vDL9cyb->R$Z_!>L~7e8!h&B2ipOg5f=77$5krpPY-c$N!@sUANUV4>d7$3frAI zJ2~V0AV+ggnT%7VQlOFqJ(C~=GP|KvinM&oWt5hlt(YsQm?_)%${1x49N?cw`g(M+)u^p!FsuWn&v$ zUh|ByOx)^bejf?sA$~F~ z0b7@ds8#EUu6QtN_tk%pZ~Iyy2qU)jX`&%2yy|gi0e7E(;MCgP-fPpXH+v2E^Y>Mi zL45wfj$NE5YN}6+=Gj#Mx#IuVzuqyA0kq%e=T!jhpwFGo$_T;8T-Nn6;xb(lA)&r> z03{tnuJV+k-qdHFnFT$(!>)e0TjKP%*MtM8Xmc> z@GO<2d&f2Pvom;v>zUv}167$?TVoMo&_5%k@Ne>*)<1!p0nZ%j%bCA%Q=$U<8t%F4 zD51-M0y)icF~k1>893u0B+^!K+KL*`2tU%8oTp!0kR|(R7(Bvmoo}crPkw{rPOMCR zW48*=NU28i{=K-AhLliZW@ct37fa5QLqUclq@*Z}-oz-0OGx4o5IE?=-9P~eC&&*@ z$hZ;UD1(9|zJ2>9AtCjz=gDf%+$!%+|A0~!It&%g^8_#*0-s$|-0$`OUoudTA0s6P zBO{||PH1F!^mBc1cLu1bPESm5zLz%nVor)q1^+a;ERh~GT-@Bevb-$WG2rJibaZr{ zH%*^`mlt1NkSu3y_B}fnm(il|BXJ-cuuxa`z1Dpm`S=Fk3v!{l7Tici z%dM6ZDhn((u&lGb(vP12zW;tI%?pK4{kX|?A{sHdYCY#Fv(Dsp*(J>lDD}%iG(-op zLnA*XPp8m5iDi`${E^wMGYy}15ChclTi*Mo0jQe7N2OyC5~|dxT$gJxj_;%n4Z#jg z1C7ZY7H;IDMdjKZHh%_@cTdhPS;zkmZEqb_W&gGNswgNaDk&kLQVP-y(g-5m-6bHn z=n|DKgGN9=y1PqKdeMz^NOyB4KELOE_t^XFGsZc49RAaRvbfj1zW1Ehyspn2ia5D6 z?4)Z+Eib}52S>)AHTOf%gJHB|Sc zK=*SOW@2g+_+LkFKb+V0jb{RHB#vz@zrBt%QV6nD>&Hs#$*P0h{TH#LX%Y#i`tPE? ziZ8Z*)sTO5&4S&EfL8eRK&E2eNZ!jN6GcTtkT*#BC$jmP%Y0+XEpdTnZ^JdKZN%4E z@~lhGtGQzc=>t?_CeTv5)p~PH7H=h?1&HIJH`U>OWf=P@rLY^eOV~;rGc)MZI@WyRhDPWJS7_7JM4_ zPrP6*@kGpJcE1`B4^6M`{w$7h;5i_EkImAzJKrAay0c#WwXl6OXpEfKcD&drIVZ;o zp?HrhDEP?v>4(;|G~C$~Dx=mgmJ{t5wwFZbcPSF|U^4ZR=<;ZifATuxucADj?)k-g z>^M}UDFEMjZ!X$7J|f2oOC3S0Rj|Y#Q%sDKv=9;MqgW->_KwoyXPRK2F{4_I@lI2_ z3V5ktpwwm^i-vqxu11ou{Q0iQ<`AWVn2dG&-PA6e)iLQ^ar0+3$F2y(>oM1bP^JoQ z?l0zOKG8v=6ufak#;E?ds9)I-mk!mBo9T%E8DMFUz_Q%-3`+#5q+%9T`uV01v8|^79hYSCBlc#LK4<0Iba9`^fF&zVmcwXg(qR&YfQgNvWThSEp*&Ka@v_T;O_9_O|-gs$L#5 zw9#>KRuU4Ce^1pV9ndWHPIgB+SgXyB5yUuN;vx@j-b6olZXSi`miXeLwl@D)<0rgM z$M2Wa%DxwSdh!?eiM9{Tdf)plf)oOcxIagm=<`+7GTp^@wRYypPc_4O1>b$3~e{!<-@?0PhUGA~$K)8N7R5~O2rm);>IRmgei2MW1=9K3eX>$(o+ z9y`r@Z!m6R+PWT(_te#K=i-q8k_12E>t*e6&Yq@$BG>@;+v6Z2bKURcc-g#21N~Pb z0TEEBZyYS|z`KM#*WQ<@f%fmzK>&s|%%+OD%FUS3CVZPmOAagJkM{U2)md{!(k8sK zD_OloWcDgc89exw8@3b{MO*Ti6z0Uk>OVH@fy`&zu1`zbaFaYJ-^<&Ji<=L|U-WWW zc}oT`CP|pMZ#`Kf;M|*~hM{cO9asD+i0;IpY$EKXb70u0tRbBAWvu`X>pCy{IA6lX4J!M z>&W|M9CDt7(Renaz;v?;@Rc|^pL|~|57>`+z5gVZ<0-Ub`oAGBZMALHPv{ZDa| zLzxuE$L<*eWu&%f@gX&K=89}A(jj{IPqcW98?V?%wk|SierEojpRyWQ*OgV{=J9mv>ZDf}kKMp63PB!Q&-y3<)N zW*&a#&X^!U6hUgPw{h6K|P^flyPDrM(r~=PIbNJAv$`77-;_4HF z4mr;s5RvV6*veEhjT=5ueIaqSaJU=ww+0Hm_68=)sN#w)o_gkfTze92Sc}Y zEVayM$wUuO`X3E6QQc-WEG{DCGsCC6r9~Q>>aaFLBJ&47=*`y+{Jbs~(;>0zcYzc! zG&I!xjW#K`>)iVtf#QZ_|GA*AtSQ^$w9w3V2JGRohg=mva3Ltb;n(l zi|Qi_*er&u12&K|P3ZA_^L zWT<4C4DADV$&`0@vccjc=YuHv0)d0v1*66@K4xa-59Qm(_r5k`x@}DRtv|-9-6(u_ zJpsayTs5&N6kE4ne&7AF)NMPx>&baiSy3@$v3(}_$R}iUKYA7IzqEj*!K;EF$^uH< zPDugA(N(JOu`80hL}ZK7elfU1qihDe0?+_HQLQrXJ)8!)0~#>t*wNb|k^*KcjdEqb z9MG^yRg&e8u-DYnaAp_ION0izSKPMo=yI9jEMt^e(^N1>RdOfx_fGl?v~UnZIdXQO zVi60oo2iyUpWLb85X)?1SYD89+@;Nnkd0rEzCTB+YBkVXEEk8?p=(LU zY^s9R%7*`AiE@7F`zLM{MYay{WTdmM%6H^Zj}^ECTx`~V&f^N7ob?yJ8jce^nyASx zSg@Wd-^iHXfe56()d9zba@Hhbwv84A<%}R#`*KY7`VIN=1udX=y2H-;zX*!Kjpy3B0`3fbQ8*ka5~Sod>)XF-Mj{ zwRsa{uoGkgQm{HZdLa(KyULTL_)DK!CYGGdYV_oQYzIHbF`3)W^h>q9%iF^=%WEj6 zW@gDU@z`=dchv$O%Mi+?eP>(OE_kb2=E%m~D6J_spBRfG9~L*kty^%$??$JQ@{zCs zfCu}eoWxUy7<1h!>H2DjP&&CRRpk4BG}N{_K4M>*dFS26^tT6|#C#|NU6E;e=QBz) zt3#IKlQ4VXIN$(S%JK@G%#D@B1ELAFLe=KR#!y-%{non4)|*{ARg`BK>+9BVvJYg4 z8rU3c-AyerlNNC(ro)@Vhz3{%+_4!jH`@vlPml#An}Ag2TO0*Rxy{b{f}K>8a9?Vm zT8S+Rvv2xWF#$XG$@H-+AZBYhV+%RI7Z1Q^kWE##Lnx+e@&o&)F5&J!uNxikj=u6a z;4#@DaRVVQFVOEG9R8sxD!+ zCur5+@Ka3$gHDhvO>+p5^UwXPn6Vi7M*&gMDFn0mZLvEbGsBpW=gpGipYR}Q=Gf*VW z`=}S2GQ&7blk4qVyTzjeKsLFmsvb_9gn&~`ZY(LJq$GIvqCZc2tqrd|miz5=d&E0P zUe0*Od3p*0D6;VJ-h4ek4mKGc4lvxRsd2Y6SL!1Sf#BU+fGG6m`3R1k)4MChff;G>qW?tGn!hY%0a9liEVl}F4*BOx#^d~7vE5>-H{|hM5 zS}xc*iq%^HTl(ki)d_ZCy)B1sWE_2tyqgNTZ9BDED4&DV-b}Iuyc?!h9q$g{sD*fc>Q%7#AUy#q*Fh1)O5FOIkiiZ)6ErUQnXeuX=^2xr{r>?6udgJ znI$3mBPY&E5rIe$IwGGaZvRG0g}QUHd)5}=v^{qx7w_J^k4|>u`>%i95gp3aR!kS0 z#qT8kr}nB1SJu6mTXU}A?;nD451){ zlZbIlCqULTCk3YE%Olys2RUo={Aty1)gr{4bgE1fxLBxvNQR;Fedc7fn6Mjc)S8yC$B34?Uom?65!Yx;n9p|b4Z6_#`~k4Sk3~4z>My9at%JaqQ&G>A17)~ zbUQ1u<=>;?tSXfhZPSHi4p|;;Kl%;rEZm^^XfbSVRnR!yB-mdYrLp($4GjEQcY;j< z#SQL}7vXEeiB>oE$v&XEI$n1mxoMGJA~5-)rz<=Fh?W zR6oY>3d=LaI5G_>DXD?kKNnN$?i)Qz%6Da~B`;Z!Ip9jNrrQ#1kg1nnn>*!IXvQRB zIW;SJC#E6Z*#l&raEX;@Rlx1xN^_!(w16*cy`%E|Awc_S7t#js-v0FfAC61%IwbCzSz z1D*Jbl%ir$Ft&`gFnshco21}H0RJ8b|J7#zmkWROA5roDg#G(py5y$+mdLIYh-TvA z|0rmo@Bg)!y+KkW>PX7Ss3iD?UVcB#jRw^F>|+lTA719BYePK+Gd6E}{XL*)3E7A9 zMjqonx^*d1B?n?+B~m7pDg2d`{Jk#4LcKz1O36Eyl2<0Ecn}2mpA3(evsWFg)%W~H zoOr13`TfJI7`Fy)VPYbY14xPAQtpQs&GSiA4Gmg7_)-S+2QRY&^pAobRs& zSKm!&1*(Xi9xhO7AuI9sr=O{FkB*vdGcos@G(P|3b@>8XM8+Rn{!h3_a!>g4KVSbp zXUliVF>YaDU$a+YR7AjHo< z_2g~(-b}%~ykluU`Pr;6o_kUxaPxY<{}KoUkq=<_45e3)Pg;bY;((k#VUik*gMYft zmQ4E!@|Ow^>X?3DE^ z@vNRy@fI+5S6}@0Gxu0HonI0Fl`<-weqVD**go$9)s&oGa-xK!X?C;tT>Ol zJpqBE10q&bQpjTDC?+7j$-HPbV5Knw*E#}W=cs)-dH!!FZy~BAvDF_1W>aXB!_bY` zoPqjMI^rfl8b4923P!*}ssK4;6!DiLZO`6rFF|}8Fa)N_>c~L{6pUC-t6czec(ElH& zFpoaRPc4FR@1gB)=0^boxeC?L4S=z|p|BZ~T-Q{0bGa`<=})QUa0LcRWK@dyOrCL4 z=Pwola8>u@X?G46n?jc!!$Mnh%OxuGBz{isDc`Ua<(EINx0ne7d4&G26 zmHm^UuA-$L9!^ffqxZ{8g*IdB5X;WeWM!e@RCR0B7e!ZFEEGcjMBD5-P>~H72kU{N z`ue02gU4=s6?(mq30gcQ`?VE<#TsB)COgpUc{i!`!$Yrb^VZ*c`SN%Lud82C zQ4!pc_lbyVlz1?xI0Zb)-P>R7QA9kA)J}U|Q&;z$>FWZdg9G{JE!fva6Y#jXi!DY{ zM3c7q7DQ<%qd9VF4Q99B60AYuDj+UjXKSUChaw?isqCVa$@7*5r%NxGZE>A+w?P@t z+%nClyz%evT6=j*DZ?G4F+>-VFq~%>0Zz_hbH$p2&8=!Q8G9@Be2WXY>u3<>Zb)L( zE>F;q*ZNwZU*3zgGPsIN)@sIF;Mpf}Uf)J91^HDaT@@eMFyDtZufOAcq(6goDx*^uQsSB?+mBZr?q0EF1(xQg_PR{bjWi`k_@&9E?0bOIF%=3#NLw7yNf5g}_ZbR$`i!oy0rTuv};#68r75-#t3rf6&Lc z$P3sFa9k=|-5t+F@%XXvPz@D0%PZV>UZvVUwHQhRySEMR=*3t%KGgrz!9**EyGLEs zKGu`vAushnD5PqZR?+rMbV>rICfRWQ`2<=wut4-{A*Pa^y`$c=o{P(>YS`u*s`q?@ z0U)^FxLxuAQB6hNwfkq}Xgrx-g6Q_k;VhkK9u@3=%r6HT7$_JmtjqV505$<4iJU>aT>R!NgR z2MUNxd~uq`l$9#K$6+XlZ#mlhIJZ>3g~ZjGdi?6r14mIl`SIyd0OW7E3sj9?#SH$3 zfpU^JnD|gKc64bph$lRXMGdakHzwgiF*`=hnqx3c!@=c@MG|z?8*6p`c$AduJ$$}Y zy2)RBRaYwvV^AvszYfijWEhRUsV;a&_ihwE9?>dy2d^!LJ$B}xb%SMh$p9So;W`p3 z;?&G^bX{ODi+i+7#EVZQxA9@?9$no z#|x`Yl{Cf4T`=QX1^d*5f(b)n_xXpZ-2CV%Zfm9i3Lyv}k#pEQhP*}mRE3ej-}_-{ z8qu$9!=?#~0dBcQc$3b;f)rN85cDYG$35!XYAbIfX{ zidM>aewAP?2R_-M3f>SyV7Hdf1twCu{Bjc)7A~$o{g9RMvkT`U(IMqe7rw>OIAeWdWD;tfZjvk=`8o9 zg0-p|x@N-1kQ_J_b>Vdw98b2Zjg@H}9#*}M1(>nr{u-t8M7)1`)LK075VMnxmDFL8 z_B{>`b)jUtUGEtuc}+$pV(`bFK(0A$7iH9y>$hS06Skq8u83WF=>0@4>zUP^vp9rZ zt+7}@c+N=SMeFjc%eOfGkifvK?2;>KJDa=aqXq3>PxirmpHI&B-&RxRsEzp43)7+W z?yyV_q+GhUt;hB2=>KIIeaTceeuEykc%`Q2Md(WRfq{K~hqLg7O~v;V9C_25_BQ>oFS$2Y0`@o5QN-bw8baTi;A1p_Y1 z`NUPx!*nbq4BhKzg@otUF`sFn(jk6ebYcPQtqRw9XF&Nj+VGXXo!>k-D9vj%)ZIK|{R%WfY*H>K zfS8bQ6@b^)b}llVr!>1h^d4QP$8hTl1>+}#{6BO~*gNmMHI}?o7ZYJ!2edn}j!w|v zGYunuIkgN9{NGs?QE-s<^nAiod_Dof}X?l*lc? zE}*&N??~78;bl+bqE@2s_G`u~C`jSrO)E%b<>j?oySsx+wU#aXHsc@vAH98Ym)^cb zUnCXk@d?_4pPT#VY1v5=-?g&~7uOp5;6w9SvIxlRmzRK&63@~Ppg>G|t9duxrV`#PkIKQMFd(C=oW|BcbR6Ls2ZB2)8P+WW#u0ziSchV&)4tGOQD0NrEvXlX3pt=%ZVcyx7q%A_t0c!885byr|+|0}xb8Y!Uf z(lsi%odIJcU}t{|17l+}{poSHM%gtFYH31b`q(=Vj?>}=zWi;+axcv_^4#Loiyb2v z8{;J@ud>ba$*bQiw`H)!Ou@d3yw)GN@shh$Zi}94%R7l*r6T zUg)TV%z*{F{}RPNmaW;X102BYrz9sE#WIGJi5Pvk^oTeesj3@eW1?^GorSVxCZGDR zG>C|b2UuMiuyT%*pmmsW#S#$5zModxtLOJE`~9 zXo4&=$ahojh5VBuK1*?D|GP9P@!~q1vCL%Le_7G+U;KY(!@=E7)1*Li4@}lLw{GD% zP*EkZqH;RU&9$+`%vEZXtTjKa$@lW|0*#OrJ{?`+CfyqLFvB~7r2K(5X8i5NPk>Bl zhGzCQ;loEi0m%%H)rV)8jMnimyliv8A~A@TBNt$A(u3p6X5+_G>veh)X(cG5mJ zhjQ*%;#RQx$g11S(s+>(!5+Wkt*TE;`v*8nBM!suD!IVxZ{$F&`m2e)IiwZLV|~9 zw_g=2gd0Vtlrfxp@*DY;IXNK7;3y*~&5Wsps^!(RogE^KzJrV9aVr6W9kAjmGPzPA zg8GyOA?7eC^1P~J;QOww4#g*UvE#G!#aKXeOiYPIA4$`jLS-#bT97ThlshSQQgf+j zXkNc*-cf~08y!-ES>6}v!H^C;IrPFl5G+6UcuP0Cq|v-etr?; zLX*?GJ@w-qijpLXkgy;3_akV#Dik|Ez$^QP48HjvpL`Ysb1BLg{Vu;gT`+67Tk(8lFZ?+-&2KO78XU5}uf zrWRAf}%&SXvT!c9~+sFm65S=M(Rn(l-oz6 z$O|YGR{s2H=i9;q=Q9c^%&%rmWH#Z4-a3sy7v(zSl^bu#vqKkFz!y0jY4!$$vc>#Ip-NUCZSvF*XI z+=r9>mF?v|lTi7z$~Zm`wJg&Fh)`;mA(EvtQ@~^{Xjx_1GI>;(nie9cq2aQ>y9FQ3 zi99E?jHzMktDUpO=E`_R)aYDQ=-fY*T;#ta-Mi1JC{3SJxNR@xgpi*-mW~PV9>)Rm zR-YVGq7^R?<=a+a$`Qe!bZx#UHKM!vBt$ZTE+;edU}oL|bFfOS14g@X*zvjHer17=vO1{TV}&&I^~GqvipRD4=S!^BlFu1F zLYj$;&-87b+j8v2=boX${&Fp%)c4&HB$e#jZg3-rfK?X=?)DJ-rsI2Tw#5EIk{%#42Xlf5Mn-~1$t3&ab)Z4W{Nnp zb-??cGl@!(VJv-?v?K|sCnc2Gw>k7~-d?Gzkk}7%gUJntY-!uXQ4rdIrp6;{Mu2NF&*=^84bPL44Nt4)Dms z4qkeK9poZ<#0Bu?8DZ@P?~y@9SC+$eE$zW4am}NlA(ZwPWWcr#jyrjvgnz#9h}NXc zRv`BJdv8KoRHyz!q;P>zah3ay)y6s1ZS+8rK><3t3i0>^D2&8I^I*DcbJql2h=53lWHSh>V&W0-2EO@HyOt{fxWVU_)N*pB7PP0acG z4qPUcI;;M$)+?IFBPw=m&~$a{vLsKYA1XDUyeWvHt-kI7yHI{$isKAF2wdtl=o!An z0_h7y4}Fm`p|eBcAS&5sS0k;5tjGTXXm>R(p&_)P@MvF2zUo#<&SfvD_yEi?36o+8 zC!6_qqs;>d6iUc0H!DCQ4lF9MPVFd~KI<=eL&l>^2y6Qcu2wEXvCFI;yI&5GE; zui>%zPMg?T?_l3f4dOZMUGJF7)MFQROk}opw;`C^AbhmFFq!`-_^b|&Owbd=V%}Ci zUMsQEq*#9y_bd@IZW7Pe1zhz%b19sZbjB2~sHY%oSczq#xpURS&?S_5T0QpXYV2c= zF^PNbZWz@kD!DjV)mhbjMVM8;6z&*$xhzWJw9ebG!78c zK2Ag&#iSqdH@eI}?>=e@U zKj%H0*LQ_y{O3Z$4%0XYCviu!rHAxj$;CFSJp|i`zv}H-@lbEpMF>_2)ON3I_0Lt=tmtM9FqMbk)bo6uw%r0VU9q_<_AgW_4+H~J!O8TcfA=|>{9%Z(Ls z$znG@frTG3p-!!d$(gf^#7A2>OnUU;UC3d98OS*CIpa(eSuMx8`9SFAXmo9DnyLgQ z8}WP|L}pM*y^LbczzHGpLZINquBGqL=#Ya!Dk{(6LXKB4N&OL0;=C}_X*MzLzd>q#354p$HHOHM>fA(El;x=$M)M* z;pU8ZbiLlPY0&^!C-b6kz<2}9KJurOR7n+x*E@%PcNd=We~0~ac(iJ3&0z`N{(jlWVJ$9e3g?eMQvSH^Bjdk58?UBp2n9I*?TrB zxI|8et-n-q)+NxK?KdLT_9oXD$%Mm^X=>sx4Ws+T^g})j{3-GM%CDFuYY$7!W~7_A z>T|5tW7|uyF)-j-32l(YvRjX>O)saDv5r^UFn9`6D_T<=lw<5)cD85Oke`r4_fp3w zVo*Dkf+8*WH4weJ63vgE$C&1O!BfZSG|%h3MdQ4M=Ll{EOD z1-BN~&TN)^{piCx#4>FWFEQ1BE_OsdP`999ULkiuY>c|3Nw+^X&8U9!OK+g!slwyH ze#d3AuPv`9OERsJO3>`Bh1`O_uuYZX8`wp3lW%RG4?Fy(c_}9HQGb4=^K0|P?5;mx zczE1Reez!r%P^7`<>^$* zM>RdB0&hplR_vm4waTwd_t!U2gE>&CzCXfQbt2S7n+A9VHkbJr0M#G)FRv%Qs&rk)z3s zMij+~&@b(*;FS45Jaqq{`BbgwHCByHwW8n$PG*{|XH(A({`>3*{Zlu6_uNK_3JST! zu2_Yd0I|_}lrW#C9g&3(yS9EvTfFa;lV=lQ)F{_m%eZ05v#(aIf8I}o(e-VZp8UDz z!U*g*OFVPk+dhI?|7A2g&ZB%U(G>W;ge)O5tt!=N!7~?MxfdD!Rt*cJgeVo_Smd%f zwr>3m&SEdkKz_LS8)sajZd=fidHh6x?YY9NBm#qu0sA zL1Py?rmjUo2TLsX)`lYir8EY~TgZ*7N*>Cg_uwz>H$G1;Og z>nTGIAuQm4jT?|~Ei#+sD;4@#5jTQUw0>iH`OL!D8ky*&UF#0yr5lKN zZnyeQEw!ClJ7HmAQMAd&@=t$YLgaT|3-b<+6z8ObqT+6NP;a7BSywl{b9t1we3T{Yw5b`};CR=HZ4D-f0kXBf4*Q6<8BELWx!p7TK8 zWKb#TU)Yn%95S;x9wzITXng=q=1+|H57OmP6%i7*8=2Oihg$>VUbL5{+V_XR!>SOTl9Wm|T==4Df zG|Wr;D)Dfw__s4Q>{wsF;K4!VxBx*-5~f#a>8RMGKJVY0ul_Q1#pi1YA!Byk4HfI; z0eZu|`D)FWTk!%Ov`UGla4J;Vn^)>?Wn|h@_dkA6rU2s;-T8uYTbX;d0~Q>Ye%wT- zKBh-ycFV(Jyt{1!mUW+OOnYnJY)Qf%NS-1KGu_QS`5XHBwu_8Z>FRExGB!88GczlB zZTmeB$aupQRmh8KZQZ~fHggZ>tx_t>^FHo2URK=9_}1U!UhR|_k-U0_2MlXHka_>3 zeO42vS`Qouuv{mVglr%_{Jh&@5|OJZej}K$Y5nwbOzHX_DXa6==`vXUgwYFKru2^Z z&U-9MIRJ7nQexw^;(z}jQ4#HEs=^KDZHE3)FBslpSv=JI^u0XxPjOGMA?viCn7fgR zZ8=*;DGU#x$y)co?rXWh&#ukW^WShH$A5SO#yXaRKimttCl4P?09MCm=~WnASX4S~ zius@Y>PYIF`mSzgsJm-%kBhgzVYL1T&mMpGLzti7#m`SkFglq#Z=kL$$=-qoObkTk zw++)C`RkPzw0^^7YW!SC03v;p6FI-@rbwHv+2?YHkgiYLoA&(z#bpqzOEsd2g7iE&u(`c<(W;6XVf z1;&EIShzo#WLLwxVvVMdh|3D$>K`8QIV*11Xs0qjHINeIouTKbkzgxi=7%}1@b}71h2?x^x zo9B4X0jf?mr(@n9m|wP6K2W3Wu6dVeLZcU~b&}Nmi;8F9;H&rz7v1iK=sk7R8(vvz zOfuihwxL564~f^VH9MKRMRk0zyuLKY>w#>0)IJ2%hUa`%j;ZEc&t@gZ@e)6C`lZ1 z71|dv^@=xQg@Y5ao|*9tj8_k3ODxqvXjaXKC7QD7R#SH0#Y?e|rxKEVY(zxJ5-j%<`0%pL zjpZpC9A2C0TkKY{V7ogpLmAtGW7(ooV8k+igHfY+gEqZJZF@vXuP;1SzZ=(%*yK$e z2{gqY%Bc_PRqSAIwnI*QgZ^umm8#}PbeGn++P||H8bt+g=LVi6sN~L9k9gI6|9%`a zR#{^&t`xX$uIOzVA@+F$=uELKbVGwd^u&X?crJ>jZE>vbj_F@ScNV*SM(^xBq?2d; zkd*8;oc3#JeX{rr*freT+>qCKs*oPHhSYim`^|8ywQFC_u0(!W>Hh$10H}0U0Z(cA zLaf5m+bA@5pCEXlv@77MtahSywfk(>xEK9xj$s{MZ^xPb{D}o)W9UYo4^uRM_*Lgp z|8`zsD;5^H#nFVxkWGMPq8rSrrK77{uB-h=3TQprd>;Fu(1=-16_eYUrCOmd{gJ(8 z@DTz`!jDdekzA}#G=aIh{R8B!+!skZUIW2hzF#$KC#vkMm)rKx_FvywB>1H0iG2qx zf*{8allb9)8BY@%?TygBmC7raWIY&u-mV1Ff8Z?JKiMU+epV9!6s*DRM}<6yWdWP< zH^`!3u?-lw{z56Mb@FL60WSr3Nkpu|*fJko3qF<}O^UM~5OR|Af)3N|{PfX?iM6Y2 zc*uP|!wIpNAdA{Knd@qJ3m*nbf4|-?a&jz(!Q+5=&nTyKG;~)}E}M>pon*X>_JH$y_^HyrsH1=l5t@ILi4 zg3Qs6Q#=4jE~~%3wuFE$vMbA8yBo}n;~s~G6B%B%9oQ<|Et3nVPjn%TLE4bhRs!^$IGhJ(v4IZ zQTKa#4;SV)QJK!cbaI!IMoiv~0l=RM0_|7$C4DkEWTRThRB?%x*UtwV8wpv&*P~Vo zIj$xo==oAdb6DPX8T1nuZWSUM z`lO)wt6t2%YqBzso{)cVotX+unYXDH6a&BlOG5;Hqa%*JYbD2+06WH>a^J2wXS}$U zadEHtsF2Y5fAoE;y$85JVq)SfFWw_~&pt!Ms5z9iY9 z^7-fM|0UjGbK4jOVx<3Dcwk6PVl9{!Kmkmmdi*%W;R07rZQ>5HBo_?t1`kd1DjK4%k}k#98O|!G zR=v)?C*UQt)reF(^DaFC1W8;TdCi~NkH^-wC8DhkTN?O_lK20Z zWJPSM`Bti`@wuGKyIlc$T1IiSUgwjuR%jSfx>O&e*QDythp04~>x}1-m}7?6J*a_s z3y$mhHnS%(uJVnVo6@@}#ty6_mFMzLC4e3Y)%T|_C<{*qU#r&sa(gDje|+3TW()o$ z7~P6+q@dM4ahxS?_>EzU>=LXA(3#`(__2xMYxkKq1Bwso&Dor*7QA~pp0IziDQ;k< z(%Vn&f;KdWNPx;S{{AW5w84O?6a-`RSbjkO$64kji?Xs!7k%Od$2t8sLMS%6dKJnq(`Fw5(4k52D@5^w_ z8s74)B;fZn(JzjzBsJ++u5A!yieY_E+Br)AD_SCc@6b>S{*RluhG)(Wq%5|dU`_3_ z=4P|DP&)Ue)?cs?(YPyA&OZQX3njtW%%Ri5$L72UXkNxx2t*Fxy4lWme5Uw7#0{*Q z7huQRovN}*&&kmPTvrTsHFU1ulTJeH4#DXSiXV%uss}t$V1MB2k+LeK$~bE5jk31~dh6p{?Gf~`b=P(Kpy@CY#~Jml zBuohRZ`O^JTfxxy31@6iqVxf*i@r}x{E&BU&TOan;k*SFDVN#u&nWQ6sUo)I5{+SL zV7}6h*o<(5lcOV_ojcjo@FrIc!p@>MjqE+4tKk=B^K#uV9`SePe@XyU2mUJ|v^Lwj z1_n&7eac!r6qG+<2s^uxP_40vYrs-^@tetK-!fe@pQN|$@l$S{Vjj_iZfeh1=fI zyHfvUHE1AWPIz<#YQcP)MD^go@8(V(C3=-5D2eHg;Bai6Z6v^-au%W3mQV-BY*O~x z?(WHH{~%HfY{44|Fvn`=V=-oJIo4bF_9*R27~|Tq(f3?y^Jr~@0)mbx`2<%IspyCZ zoQxi5`-|6x@3$*Kv4Ff=h&Y`afo6h)sc7nQeOrP&@224IKaVQ_&Xu*5wv&`uPeEZo z-6fKp=qsxmJP%`gaKpMv!zQ_}q3xCr`HgZ*U-ioTZ1HE-bc-TzGaMbYag}D(eH23d z=saNOBI%s1<>xck-Fr`N0UGLVNH7uC?Exy~)|s3t^=4*vvr*o$q{_{(U$8))CA7l z-4HMhwBx7G!Wu0y_b;wbm5HCKJun^paXub(civMjbpJlGYRQX=*VTj@R zDlN=2!`)Hcx8#DlqqttI7n)3cMmC@|lNCVl8QSe?E9)3q18U3r03A`cw5GKWP}J}s^5*B$Tekju0vOOG%=S{cer-eP#>;;_!eskbUi zOJ>roB{cXUH}&hz&hPKKuQ4bDeqQs&eMQ1Sa-U-RlL2){W1D%ez|H|f?5JY8qBiBR zz~AXGZi&iZj{37Vtz{qtuNJ7?r)uhmXGwO6uqXcD060KBw$J!Vx zIfwJjjQ7NLwHM?1cQYKLq7RAOUAwhkXXt;Rir5o?;wx8&j(r?S_x^ZiP{}B}Mt_;) z_1jFG$8Ci8mUk-HbE{GRXeWY$;@2_FE@ZCa@a-c$K2jQ&qiUcd28*@H1V zyQO9>;+MW@|4M%iUI{jmMTejM^?G}E!AO;4(yHQAi0)yJd$ltnL$-K&6|M_-Zi*)r1h;A6 z94xifa(3<1DOg}&e>=)qbO)AF7RfgifRkSk8Sq_DzH47r@lddyBCV|lmyqyBRlo@a z#bTF0J&7z1vA&7$t&{CXSAHq3o;!Ysyi(Pc2}>1?Lg_W`;3{R}2X%HzBataecX{dU zqWg!@VKz%mJd^U07#0=bbZVB-r_+7pgw4HUl@~WBnHNIJETRf&j7h)H?&;-a>twoT zzAZTkzJLA0zMC92b4gPE%Cai&&@N@WC7vBl?EQ|E-WMpVi??GGB zteO1%^2*wYGAHqp*WomXt>%Dx460cYf^(c-GKii&_-)bf9fGkPdif?k-*e;912~*1 z=YWs1rX_+0D!BhsTiB!%>O71u?f?$!hXdPHFu(%TfZQ2njSlDkVpKDi8zVTW6@B>; zK`e$(BYf7!9`uIArEyzIks;xmmat5uj&KgMYg=@fT1TiW72AQCF98Z;;ibd`Zyk#8d4GRQftt_pnLu z$HXblV0x;`@}MG!U5lqwI%4qp)jysSG0uDBp0*BeF5)5yaZ#pcH40q$YED!G7oVsG z4_xzZq-z3hpJAE4V(!GGVh&OS0oBG0k1Z*>$*dpf_49mecNm{&jud@b(b%T6Cqp{U zs%AiBf-*f^{`$D83$N3*B4e2L0e=J3GM@xS?v`9`9F2IYa0g>9+_KFr0wR4a5kiRn zi>kAX$};M@wS;toba#ielu8Pc(%s$Nh;+AfcXuO5Bi-HI-R0T7=e*xI-~CsIF}R;+ zuRYhC*EB%(i|U>lf<4j@p-J+%pG7^MdpVrZFqKlR^$x=g$q#s2gF%@M?jNCo{Z?bf z)pfrg<(8e?E)a5iF|@0`b9xs8azdJ*Rtxd3AadPwF1I?M2^!|*$aFVEooXxpeVn$L z)DHi2Y(JlMAmnZMbaz1g_qal>!)p9{hTGM4*KiM?=(fMp!0SpXfEJU&I?oS(WOaPa z5eUROfG4Lcu0Cu6?~Vyu4yQ>| zOE+Ca*T;A^u(Rzc;NlwT@LVF%{)iWjfUtxKgkXA=g1o%Nug!KlSX`2aT;^dkv&Cvm z-wKfSqhMwPo}#iwNFw6y^~8*b_poy4Fy0*i|GeGL&LEV`k52Sl)}V*1(()q5^ta+! zZuyH7pTTq9!u*?&q)x&m+SWUm?1;Bcqq|nEU#ALvu6xXYk3zpDAhA-rbPpxA#`HP< zU?~Hu=m+>!VR672+{YHs(ry8;Y3|X+xY-`pt4AYXEbC=_H^Y)LC&w*$5gkP>7X{&LRY&25P<)k@a&(m-`UwYAa_17 zoFYi9Q3}@TrBRrvTc(FS^P8y{9AeGQzhcfni#gg*NV<8L=J@PHF~}poBpm7Az3Sys zqY_QSPU-wI7d4bsl^O4gq+`^o;lbv$B+nh_ac=IONd>Jh^9mw2+GVDCbQ95@RB{~D zq>@gW!ypzTFG5BkJV;9C@UzmY9hYV>9Qp^0|64DZ@Gi@;sVvT2GD#vz8~|_sOvSiM z%;ko@pEcj^exlrO4K`XVXQQfAzI3l7jRs+nHhWs2?^ORox%ys zT4BSau*X^UOZi_!o7!`?v5$juEw z9gRV$04wW!iGs|R8D^$K4Mwg6tJLELwSPBaAQ@Szwu+oBwTmWMwmOJYDt+4EEIpuL&=|BO3s4 zW41*6>2kN1T_VO7plTh$*6J#e9%5ou!0E6`U2IfnexO+s(r`B#;08phAZWCsK0zok za>~hhU#Y+lak(G5Ij+_I?G&n*IYr&%cDrfm&pD~H%%7&0a?h$jcD*kGKb9*!B%YMg z3oySsB_cCj3+dY9{hg^|cy~Y_S`%*>a+cqF*+<1NRl6JtQ(g zjWAPUOumvYW6B6-(!=N|vnp5B=ifibiX+Xs1}tWae(3yhtEK+)G^kbQu)pj@+D@t} zg~OzP=4P}4X#2iuDwAN^gA z*&1DeZM0FNqVu6xWzuNYM)EuH2aM-%B2Ku6-S~qm^dWsF-|KQ%8Z)&@AQgG|cP}c*_9cPkLWfRq+vni7qOw#ZL*@ z+jeK;LPn$E$k!C6xi5Snj3$y`w#b{P?1*(_pmM6Tz<2*|1c->|i_HIa^0>k#+ASC) z=T$%t;hIb?8F|0Z&F8#n@=hZNXxoZDaH-$sGp2B9xMcZ2W(PY1Tx3E*iBhrVMbLtP zohp7}kiHNXq0$*Lav6KsD>tAV2F?bRarB>=e} zSe0*xcm`j)*evk{y)Ww=`e!eY`JMV8xA9G>lq-Yft|<6Ec4sw4v2blkVzY#f6#P?D z*i{r6%hfrE`OI0(59GHB^jzD*ju$52;?4TXMB#MgLP0tnq&f5*_yKi@T%)0W+@+R$CSVffw)=1zn*k;YyAg-YY60IRzG82Hl%nVty5f|(3tFx(6Fg>mG4LA` zqOGN_IFT}AP;5L=JZtf4S!>w>DEVbP(*rNACG%~ju2!=CXfuq(Iob{fj2LY zx5Mu$r2J!it~&MNoVR^92l1|t_N>VC8N$Zn1(qSGN)l|k!Iki6=>tFX;} zrl#(rb(z>*C{pVO!YiASObHanIbCj@7!Qy2HQE=az91B2BP$|r8@H; zi64ujW<3~r0T!zJj)MmKr=?n6rms6{HvEU2oLoNVleJunAYkZ-BxbAn7NmUWM<&*Z z%{%+XLWTMFj|jr-@RAtpY6^R?cq%xHZC~IeT=N;22PGf|Pwl#&j*QD&Q1Cxk5B9|9 zyTC)}KpQ&m3?+!UQ9st1&Z0$(A?|+dRQdRlPr~m`UVN(^^OVK&$$&zc<nakTwrQbm652xLf;@pt%!&KvAyI%CoSyg>P4?YFzlio>--pD2(>F6h~ z0Y(kLgR7S6p6cOY_wsk~fR}Vt+;OSJD6X0UmMo?h!30R7IiaO$vUoXUSaB}ZA#sm$ zgy9Dk!|Z2vb%&b7 zY9zBKVo)b#> zE)f}TK`@F{DpOF}-00kw^zzEYr+HDtf%}IO$E(fq^$i+t-ta)vg@%Du`rbym6sTAY z3A_W7D_AbKTkgOZjgQx_Wa(i7e_XVVfz|h*9*2tq(t3CmqSbp~mQz1t6OK&3cMtCL z3zC&gxIggQ67;?n)@QDq5qhMT72bN?O@BP)iY;+>Q6y^h4>})WZUK=ZRNJoGw8J!t z(HvA9v z^&yXoea9DdgJ<)2M)_&r;b22*_;PzHMDDEldG~2q)Qvn()S*z{@{oY$Axs&Is5Eg5 zbK>2s6Yy}|s8~=>%uMwv`!_Gl+2FOWtJ)Y?o7Qod{@(Ph*8yyu-q)moe;_K~H6G~7 zuXy{3|6ICsF{w|fU%u^SCsYEo6MJDqw=55S-_dMEq_HQ~Mq_2V5{c@uLl~0`Fq(>x zSW=i1e(+^dv?N3#S|+eU{BIqE$5!qGdvVNy1Q(}s?V)Ow{kO5Lk;gxGRaCPR>fVDk`mvcv7l`s&ej zj1w0P*HAwVhAO8>YDmS5)ltrd&aN+lIQ%Eau%Y7Lq&-l17H%V9Y2ohbs#G*-J9&>@ zMAOSv1acxE>zeynn2%->dYb~+`TEMxWIs(c78U{J1`ToBH(&Od}aoC3YkvYe0R`wKR=nP;P0-TfXCY{(UN~FVQT2 z4enpGjoYn&iHO79Y+(1Ok|INY^7Gz#7PP)b04lj58Jh(PA z{M<2&6j~cb0Q9I-r4dHE&NSvTC{K(l7)I_*KR*Zg00yj>O%5|!$i|a-lN4<=HZF5H z0zW-W!e*#~LI8NbhnBZis+ic`ToArUzupc)A?9^^{qgIH9Y;|aw4EAI5QO~6rf4_} z3a0qhn!SjgTTlP?qa)R5#v6&0VI?I;35lYUWW?aQXhX6nVfM32B?5VNib%D3XQ|}S z8`du07x$dwPD&Cw&;M!x^Xm0yA<^g?KiUd~LGh8XG=Y%EYt4EzTJ>7%_Ybuox_#JB zl^xnJSnkGwoub{lq3-v1H;}~U$D`CUxpS1lQ9%O}o#+gWZ2HinqAiFA8ynLfsUu72 z%b5fh-e9@3()PuVJ|RKpr(ci^AWj3q8{Ue&b+G&V`0w@lB@LkG!{N`CEx9W=Im<>k zJdrU*msUcP>X=RzF}I5#CYFgMW36o%!}EFVlF!0N;LmoJHQD|l9%{%_-^&V3{b1%; zj3G)btMfe`!D=z~@@s?#jdW_G=LI993~iEpQV7EMP5siJZ+jpuE@UZ@N$eH)jyZ|s z2w@ofEL7%on!!k5S^X5{?v0@27;- z0;g{e&7d_HO={x~<%sIWz1^e{Wk;!ee*i5;Rc|Mx5*3ov?R!uZk?6Ibtod<|8px1? z-W1^khC}D)lF;$JLeksM(Y`uv)ioHfjA?m9yB|gk#W&i$@`zfbEWAry%- zP9*lLh7WdxVuHVfxGl;>v^nedJggd;4=~_MC#y>D|#ULXi0Xk=x zRTkd2KK)j-TF0$LDlqmEzb{g8b?wQaDwU76UTD&Mbc01D>ZhI_G;*k>3;ZD>dt#WZ z8r{>&DF@G@S%a`-J4F-qlO$d6>1gzQ=)eH57U>UZ==>#lxQK z+5LJoyE0p@9-MD<5PCCTq}6KvDUTfEldyK_G6*)!%3F(DY#oJ1tP^_$k!fMT(((#; zMapJ>e60*)<{GKKKUr?sU5~+xRo@9$f{3lBghf_vwx6z0drPq3cC{<$ajMWd>c#fr zs^v0+^6g(^&t;SGi?XMm{q6vl`SUeE)zn*0K(AR`I3^}miZ1&#`mC>C7)u`k6E>RF zK~eo965&jqo)!mR6ll^>i+cp6Egughr_ z2l7s`C&I6P=%l}UuPjAEu*mzx7tDvV#i03zSCTV6_~B}&6ERy2x7PiLdPF!hI&uS9 zKfvvNz8Hi{B?ixFHMNuOj)g*9LbS__@Spk>;wYO+EACZ3s7YY6Hi%voykm|h(E%Xa<81*`4Agug$y4x!uZ!3B*?V)^ zjjb5)F1xwibvsUD=>tA}r3lpd<;{RC!>Fjb036q6ueqvfA>Gp#?=acE0h)--a!Xhi z#1(92yDH92YvdYBahe2R4m!)r4CnqPn7fmkR z^JxC^t%9N>5Zk{yZ~!0R@it)TIR}C^86wyowLjtoT)Y$e9prTBCoYRNwo%V9%EIop z$Zw~h>XdWxOEmR#^Rz*lA-s0~JSoJ7qkoV_^wt8GhNPh8PdGN=@kiFl|4IbK= ziXo+QJ-)~KGXX%FRef?9iQ5{d;nQ!{h(snOTB}GmwvxCkHZ~uoX2hwS7cmZ?jBD=o z-gN{5lwrSHDCYC4cB6{-&3S9Bn$;DI`^~<;Xpz&Cw&`~C*rso?Uw|7(mu)h4T?48` zyT{o?gHtVUEj5562Z>I1k)x$@3{ae|_=F$dOd7%ZzhX@M#RSF?$pijhp5vFv0^+mx zFyr&N5cQZPxnNlxSge(dK~O-uIzG&Nn!#(5_;I|-viC8z7cnID<`fil`2#LgGs-v(uH&SEo1d;f!TT1AQdISsCNieQaLZb!jxg*9 z&b#+Hm-8@c5)Dppl#Jk8Zs|WMX}C$cz^4+Ka>cl?1tT-5&%=D)I+;#JWP?;(!Ot&- zv24oy1mhN+A8=V05kX*v|kvbuXUp<>xn8mUaHlB>Q0yiVCx z*f%9W9lXrg@Nlsj$Y3gcZrQZg4=HA^z6d(ZF?dog+8j3wt&#IWb;DW(gvK;Z7kG0S z%gk)fR}khiJ?Kp>m#a_&K&NV^$j-X@x{12g?DR{fEEurmc#T|v9fyvHFtc=NFq^59 zDKCNlDvT|)Q(a$F#<947fhsGa{jFJn_r%8aY0&pMUcl9*Y6}bs@q_eq6GOSU6*Zv8Xk_xi4M;Iva;Yy=rKrD(q5r)=2Q(^Bp^mK(kl0s zAnA;~E9H(P5!l`aX)Mxe?cT)e&hoILeoCVk1N~Zq4nhvvseC#E5OoW9cR-`0l`dfM z6QqDm+X%7WxZE|uliYtd{G>o+@f~I(9B(8lgD1{E3u1y*`M7Aa!D@|wxZ>jtm9nxb zSE$!gHqw#Ugl|6Lk)c%B@LWf4>q=`~SmdKSUo}lbhq!A`^UA@ zd(-WqAEfFfO1TL7aCR>HS%UQhbrlMrNL@y!%~OlKv)164&$Udpk~#61P%Mg&$M_k^ zQ1+hupIjkz2~NfuRYPnO%Xd{3MXK%L08iUHG^<;U<$K?F5q z==e39(0x;ivhsKJZQmNz(CkloZN;`ui@CGuVl8R!1mJz*C|T=pBfBWPPf$$mYMK!K zS)r*@mBmROzC|;pE2an0H;2tY!uA!$f&JN7I-??O$@CD=30ktZ8%1%T)?4FPGG_v$ z01wK#)5JG{0Gl%0WPjkx-Ko&w;=$2@&_9z&lJ+AuJQ;ImXz5wjL_f+R>8-+FqvMJ4 zZjh6Cc}WObC6y~xsHO8>!Ej|c;#wT&?+g*9p^hMU?e@Ae)Sr`atTUfb6~wX_IEM#B z=acb@0DfHTQDAnfm=I2)p|vCIr8Gd=9Yj31mWz zz})SHx2mGGs9b5wUBx6wq|KC@{^KS2la~JMPxzY0bYpepm%!LJPL_&_iv1NY|I%JX zpNPIVP10GmS+CX5mmUtr=g@t1!@Aq9Y-M=zQqY#F0YTDDYryNpXW~~Ow9b-I<#C~J zD8lDDItSg#mJ2m$g6;hhR%=O<9nh;C1MncilV*8<;pZxMHuQbWM zT0iF@ZS^GixVrl=Mr@WrgQ2;%#$OhDhhuSb97#LtqhOEpWi;;J<+nqe%t05YE3kG7 zNR04NXIg?@yPHxf+>F~y8XR4))l0@q?Kq~R^$X2DgU~xMShzy%n!s?*s8Il2GEUY_ z#fC@-p@+++(8Q3|hn)@b$C0FLyAG9BzSI${FOGIo>KOEM5H z`>UI~FfcA>(75f7hr4fpprlh_?C##O8T(U0m0BjfbqM^e$`Z)qXiV=R8Ieri^k1T3 z7Yx(Z8dv|OC#R&`zFv@m)0Csh{c<%s!w#l8Cx_;s8no)g)$~CsH(&x+Z4voa+eRYi z=Uxxo`pT6dm+HJKCo-X9GQT<7Ah>Hi(pwl z-yKD4bO)&teFCu2Q$ljGy~$yoP<;g4puX(&Bm)wm$B?agmPIYVt{OPpxx#ExCyRaS zN!0V@gIs{VZ;$%EiEN{%FL4YjFf(Mh=odRx^ur#Y$epog;9y$=zC;~mU_K??zsRe#W&NYY`%JXy<%m@VscDV zLPFs!9-PiCDCkgZ0k+rH#!6w$8v6xJ#vqC4-ov*R86XpC-Y6<+|-dLeZ7{?Hbii!0;Xjfr`1k=LUbbDV>*{ zdT>*yVTD-Jw2OxoL|LxF$4Wc_wNI-q>qUKonfrhLad`7JO;7t~;UxpW3Zs|z4gD8j z(2~QABH%O}x)mtb;1CP*N)NQ!9Zm}#*>PNRzHR^4>jUs)IGk?=kt~1keGm|y!d^rb z6}{W=qfvj)#P~ipfqG@^_%U+C-BLbXp6>={v2TaLlsaSn(ew71N_`8Jr2gjWNwL83 zj}gP1YzFJ+y5d3B0DFyJ00za?wcUEqKJ&Y%MRuOPbO)R#ISEh%U-!4Rww7{=FLU;j zc=w}D)GaOahv1iNiz?)YvfZEwxPg_k+3}XOL7raGDmYQbbL4q?;VrwvD)|ly&U|!N zf#C**(gpk=)U~n}2SoW4stto5S=~Mg?t5%ZiUxhE8Lm>Nn9xHLI-I2%A^%C2=R7q~ zuZj_2Id3aIq_HAqb$6=C;;bP$O|BEKtr@j-z>Z_*Bw#6kqoQRhT*uMwdb8NQb+kG_ z8(0m$e}97JLaW~Nv8z=Di$VST0@K7U{Vhal_+}CEC+mnFSQ?!S>85hXO$ptH5=_-;^rPU;svBOXT}Z|vt?5N}su_>5m=_BwM&}E(^PixRAq-pS z=9d|z(ny2b%lmL-b_Z~!+LQ-rAjuj0cTDQ0JCnSJ(x+#FuB6IcZB|p&+z9KnG9#C|gf&9qwdrRQ)5>`w=#H9$MA#Tu*TWnqXOf`{LU;zn2@-CE z6)PC9&jz>pEBa^WP9*l8Qy@=8-+|U12oC*ENc7k%fPNcV%OqJcDd82HmeI1@;-CYK z{BAJulU=?yQX9;Sn^JnrL2z?K$Z&EcRt?NBGQU4fpufNO<8c%V95rdX4YnY6awzp z7*YoSymh|hZ^Nt~|NHlEuwmQ?3wJi76j9aCCk8p1xZjSo% zGr9DS0TIY9drD129#c0%m66ZTYNqRp*C1{fC+&fApOp9P`c+ zs5QHX+TXB^b|d=Rmr0a8c$1CaR>RYIzGVlOsT8*G5JHu{U|S`4)adrUcYB7r&}4*{ zk7`saZ039KJarC>;Cvz-1gx>kZ|B)th2k$9 zrM9orz91d`%eEzCjXKZ%{*iRNe=o9X6_|zWM(5BC3W5#D(3?o(<$XRt7QB>9Efapw zXHZ^|xPU>;{H`-uj44hg((3p?{{yD1hEj<^;+VU};jR<(FIRs*Fg+Sz_&bl2%gHG{ zSF0h;VzZNA3JfbOT9<`m>2$$iN&q9~a>AJhoQ=Q%hV$~TnEmWn_}b?`38|t84d+c2 zJZXo`aHCE06Fhd~q;h4q967aGYlf7ks&s$%H%o5wX}tv*^AX?LJ*SJOaH%FyTY@Ba!_I!-OYz74cH2D10GfjFYu~#@J6St8}G+*}~5N`*O%iQaG zojWc=!(SmTmC9Jn>*R}crV&z)frm6FJa99Alf{>{ik3Hq)n6C}wNdeHQi&b^lz2u}T^ltNc z_^n#hG7ZqBz9{+RtPrG*+IM~z^H|=7&=%@o`0(1ls`gvyqRq8twC^i51P{-Y}P>qhSO;j zP@a19nn#ebi%e8Q?6YMLkOv^z&#_{AJ*Xx%_9~KBey7z{CAQfW;fQ?kNFU2+l@s3q zw&xB26W@pkb~n_0jwUBrQ&FU1BuWu)-G z-9oMtkJUo6!Ie7Z3{N$J;b5HjZJYNN>=6hAb8N1Q?VWgeIB}ZdsZ?MNifLx?W;O8p zyiS@QgR+fP0lukUwp?T*BBM;JLcfyt2^9(?J_Vl4Sz0a?sD)zv$)*~oY}y}SiwmCm zeDjAG!hB*b`@+s5o?ShP+OEr|ijY=cRtm9Nbc@ThN#r}&B^Fc~rTmF8IQ_v-P#{%Q zz^py!w-ClF2R;DvAzTo9J5nSK+(kMJEtfc9TZ$!e%0q7)N1fL6`~zS8#1ZpRwL_8U zjem=}#ill%=_l7abx#~r?vJy+G4C|Le?w+Xey!{MqXC&Qk!)AI$@{xh#XwAtCCDHWL2*?$%%d2g-Q#QWEIdoGNI|Br- z75aw~@`(mnX3MQ^GJIq|h1HQ9nmliIvt9*y9ECn`R71gh;SKJzp7?~x>1ysOa48mD zuhX6daCiGGAX~_{<2}nNz4hcWd_Q~ADs%MnY`HcH7JCrog=V+TGN60PWXwc{K%7LQ zQbyX(*J{3IZf8BlZwd2YRUzb1glRmcg~VY)Z`=7ATH!5IqPS|Dm=ie4Q@Ny51eWGR zM~Ufif8ania$S+XuR<0Xb|~!XFF*HOYnbYNNz(iAPF()2uz0wTvuS@-r`Pq?S@iHO zQx(8Rd?4uh5CJFpu`M!B-mj$#dY|TCWV0_~tLCigWn6DG= z*J!y%IgMf!7A!1hvO0pL9qnGPoB8`6CiA5kG$HA|8izWYuM%^}4pq`bKmIXE(TIt@ z&kVxCr=+BO75i9dMLj0yu8jt5z#f1oLoAGHMMcaX&TMw``p%<5{5gE#Dq zWfk49Q5YV~Y7H5o?6~ao{b^R{5el6F}HHKd0Px=t4;m4;x3P@WC zXe?P}v=rA&@!g9`VZotO4_Q4E*k$6#=p3TXsbrA$QFIE_K{C@;)7b39 z@3ZFW!}@#UVQV7HI@?VL*o|3$r3$WrP4_%$zlO~F$L-y|air%$g_FZ49(a*le9P6Q z662e}0jlZu>V54isp}HpqIoc0L1i+#oAA-BZ(;cR(l+PFN{*Xwu9Vz&6WJ4SdvlwK zRCjSZ1B0gqCJu~ICd%iekJBiNgeqt3y4wgIP%3!@czV-~BbY-ElS`_J8nh<~QL zt2$5GOc)3D8w8mFol#-+8NzMmL8SvXz3 zV!-j*3n%d|_;{2+nYJ2AeMXt)32-H|^{<>Z+RWCvy2cBcHQOKw#2+@BLoP^gGCf9Q{;f+!Y~ZU5;lDKmBICY=Osb0%t`n?#Fqq_pvs;)9an}hmaEr3);yRL3SPqD^T{y2Fx)>^zx(%^w!Tw00kZ1JqC zcL-}}v>axkQC#H;T%_UHv@H0JufXv7VJxQm(PGXhvjMyedckZiMY!A)tCTl~GpR{e zUu}e^d$KUgxxBeXGHg5;^qY482Q(`?dVe}w`fvNsyqF5}nW7rgSB{G`(V9=ZU6W0Z z1x`cn;6whp^}s)4dbX-ZIZr?QX#k=}N~pxf-LE@XmqoBn{!IiVSy?)@wE4?8t|5i~ z)}#7vn#-wRmN(gnSa-+UChMQW$=BZ=i%m(^=C(CEG}CzAjU;!hwFia0h2QMQ+3RUB z)sJBacBd5TvNTP_EF9HpwU-~RP1{FfZAgM0M${F>!aCH5?FotaNx9x)I9a3=C^GLr zNEIoMD(oOU7hJLATk=U=-`gXHSfT>IIM?^1;0={5uBPWsofz*n%`vNWS`%QwO>=*t zCFv1EQp>q7HX7{TS9O9^L9sJ#37e~Y{wnPtpTJ=v%FVdR0&%IF3sYY#=on2MakQWs zCHKm}Y7UfPZRRAQ$_Yqj{$5BZA#1#syxNY9O_CAGGS5_QLO=TLv>J;i zT$Z`$9rUxlYsVMJaAaZW6vpBS9-h+}b_)g^T~Yq;(LANf#iU~Q;;`>?ZDK)lsnpfg zGuDeICURiCcN=lPH@e#UKFr`@acx~CRDKA_H+E!!oYbz7aruT{;;`wHSO(E;uNz(8 ztNTQf$t&l`wgZTK_K$}M>wZ?tjVqeR4aTh)Jbgorn7z%>&l|C3RCbVY_nT|u^*PP;+CqxJa{2TuF^UOAYBl1N;;`tZIpLUWjDUo~Kt@<5tTU(HjWU4G$#}$m;w0T;+ z=Ir1IR34)KJtxcZI3rt}13Og5BVN@9VNxjYu0%dxc-^Qk?N#4R0g!Xd{o@UTTiEl% z>kD6pHy}U^>h$jYnc53|Y`*t&htv=F&d4~aFGaa z$guzGn|MjGepsK1Me1WP-~SR0`#@O@Nc{^AI232UiR8agf&_-k!=TfNaJraV7q|B- za;np~N@L)Bd@2kJi%lt8Tz#cMpR`ff1bk3b4Llsuqz#^sT>-6t09f#vvb!~4P-xx~ zJp$&R_s|iyH<(LQUX`y&_#f9d49Fvp?}$wE|2nOHZcTQ;?ziQcIcYOei5F@0?E_v``?+e~=fxWhko+6B;dS1;#t)KaBdqB(14C!}*_uyPL$Yr+eMd64=&x2( z2;Wfi7K;9_p{KTsTk9KJ@wlG?@YN_rB38RixkJ;3tJsl!A!+{REG@JcR8C|7uUyU? z)y3^eYf>t)Qky|yDF%h7r_K~qwCn3874kQ%&k_=F<+5w(2y*z5N8DlFoRZ@UmsMF1 z`gN_h$13C>#@5%@Y43zQx1n(&PAvg(3rOPr7!o;5lzV-#_hmFyt4Mgre zwp@}4u^R>8KeACpz$6h99ew{7)m{X=;5}<$+}_^(k*y%ilIj>G*bTrx3i)3@JuZYL z5-rvf3fM=_BX6oySQd8$TyBl0@w71*4Pv1jr49Y0ixKqt(B$vjU^1rN?s`ya!ic}w z6GDLjC;))KWa0S0W--;~r^XQDyx`;dN8#!DLHdSoy*&z_tqKs7HjltL#nf`ssKrrh zA5b%*t~OnvB;CQ@czeH^-xGql((VwYBL#L>4c&xIiO}#E(cpzPN>!GoSqeu$Lee~0 zuj{`V56yRgMXlWx!}5#1@b8dHtb7N{oWx!XaJ9>j8eF{0vZ-A4Ig<$>i5vs2T*gmX zQG-Hn5q=m|lC6AjF zhY;o8vt#rAalcS&4F7nK9<+LA*5v<5o#Iyxny%m`t?$1+V9pVuRC{3!v*j+$V>GO5 zySddDb$1Wwzr6c3$+a=x)gp*EMZRPpb<&DnZU-Rjw%Q$8Z+$1t)%Y%u!FN_Hf~skY zJ6iJ&W%|cnf(2MV=}{bx0i(5`H&W#fKsqc`jNs+^9!(3wpaia21Ur>>TYVFjs!$v= zYk#f4vSF+LskfH6HNcuFiCxt51^4>kI{9j`a*xIJ^GQ*$xe~+SQTi@67Ma;fvxkm6 zLBM?q#}|yJohwm%1%x{a-{3eNdGdXZnZ2{WDEjQ3?$GaI7}X}h8L8WN?8kF+y|Dw9 zI<6ie_ufo!sp`>=RY}=j`eK(5nM(9wj2NC;q(PI`;b_` z&i!(_`O!L>e`@1vGH%8CaE6JL^aO741;Pyeq3mR(OC#uMrh{SIJRL{h8cVYsi56VR zrl2WNWL_)#;)k5tkbcv|W7b7m%`S;;_}VA!M`$+NAX=fxD<^|WB2b)e^=Nnm0$7aN z<}6rj;Rx)QoE|TI<;CpSw($+@2>@bB5L1v|ARRX-?Lr?-E_#WuBN?aK?Cxc1N?Q^% zFEb@ZH}1@!(qK4BxM2DnhgnqZ7S1l<+xy&IeF3Te95Df%);6t%J`R92VA zbUYJt3`2<1VN^ecWp1X+@)n5zV*nG6Jr9RK$mi8^wSG-ZoKH1wK9)*~`b9p0{wD!% zS*G_;Tfoqb4udl7QH_N`|EO0QciGoOu866Y9m7_=DU_z^=M*||d8@JKhp*YKq4OCF2D6M;XRYfq*EBF(Jz5)iL6+Zq+5ce9dO3xycacMu`K8|dmPlqCgSuC zAH*FHU>#q(c3?7XPGbSPAy@J@_vP+L`NbE}o74-O1RaRQ;ru^B>R)z3jY1sDHMuH9 zYf0)L-Y2uzWY9(hAfj*hTSik`f0<#_;zx9cN3T!jC%_N4i3rVR`4Eh$v|J(Wa8a+3 zh_cDwVj0v|vn+=uL$nY_EC0E&Usjj;>5k`{yI!^)H2mI3hN7mEGbUxQP5d4dY?Sdx z3SRc7rO)G>27!)xYwd46;d=VIx;~T1SjxsyvK_nk(u-GXJUW^+TGkXws4OsjBs^F( z*$3N6%p`(+h>|H#w!t{O6e92^7K4x9{W;G*f|@+ScB;(o1tdBwf`sP)y>GV0jlwlp zurXSLEeE=<-h6?dB4!89i#PVwlsFojSFaGQW^YsC311tyS7Yq&CKb; zEk*t25jienqtXO(IA#)+&KSbCr^`N1r$nBpx0`!=l{DURntEeET@`X#6kZy&mb+jU zfayGBV;Z)(cs5&RaT)nVkw!NGg@7>T^V$VV2kqTtc7?(PGa2hANX%;;T&hwcH*5W6 znnF{~2YV!Su|W6^;0r)?7_I%Bq4C;tm1x+aMp9y$==;IfovEi4OmJHp4` zj&~i-mP9U~rHMb8s&-%}lYQbnQx=y=VAO`bh?H!z+v2h((AQTrHBDcf5eDxSz^Fskn!SWAop z5HG$X(hxt0{yzw=auJ&MDd0}L)TC_0lCJ5Muus6AwTq3rpCzfm_Iw2p9=PbWpMUn6g zUtQJ{wce^l5tkT!j}8XdkF)sseO!3ebOnyk(S7+oX6uw6mJPQW z>S1qN@n_hP-bvk`I4S}mGp>MQE3?@=;xy37uxb6&NT!~^D8GGk#Ett5&Y3{e11Xr( z<>nyC0POD3SuS=AvL7D*)dH?DwwD^uTt6NuRkal~13Ja??aEWuZ_*2iUfgyVpWfef zmn6K#v45BDLcz%N7xw>srv+ahOj)lO`uw>{d09lZRli@t7pG}p(W)*0{oRiO8=2J9 z7yOl)sBmze_XA5e-!-=1guOW)>S{a5yO2j>jvx_gV;}y>iAWNI?QQ#DpAY{|%>s||{IZ+TG1~2ecDYl|5~3j4AB22by2ixF_;uI?4T@R9=d1ob zKH;;{F&ZJ^S6gC|Q^4{ALwNl$C-hDitq`38E-oqa@#X{j>it7+7!K+gW{P^QyAioZ zPukI3sai=7YI=a~3UIbqYw;$q>eb~X-czp9WHbG65~*uzYm>rak;JdW2@QKn;{68W z%ziX&(K?0yZAvp{%oe~6GrR)fYpM+w4uLF!smr3)ehc5I)njv2)Oic07(7vFAT|$W zgsOK`)d1dH*oh)Si^A~`@=m}Kzy0yF{St3K%85Vm?Y;n3K#>Qx!|B_|i2@_c+fJ?w zezvMya31}nf5;7P_j%fFGun&CyDDDr;2ujCaPf`B5qd{wly&-zCV~vr7DfWSP9!z` z6Vq>2osJ8NE?)1+VQ_ctCbS(SnjS2^9$hERP z&3^oSR;-kXP&QtXQ!tGJ4tA=6vh*HSOESvsmnV|k{%QdUisAExD;%A6=rBB5*6t>| zE&l7v3e{{R941XxA)_v5#Z(O!Z-lC`v|7Ux5R7hKIdX>^67t@x%aIJKD~OO;>XEhE z>?;TR&>UQUn0@ZDrizVHh`Te7k+fEkxVM}KRn8;k{ z%j?gM_?lW@c_zQ3L7Dk0fx%?aIv>i{Zk}S89kkV;9HncySWzx}UEQv91t6Jg0UPwe z9h_RGK(kNyjILwLkcy2ZxE{{d*%9vvjOny7j=HUnSA)sHMs5gGKKuidb{X8tC`^!1 zrOuNc?i4lNb+FRy5pP;-(CW7#&Fdm-a;SGD$Oq~zp3+A@rL#7xMnJ$4FA!0b^h+$| zrxG=V5=X>37m>7utDk|1t2Y{!*&8%@ug?fHZr0Jv$`0?$(NmI(@Y9zpib|GL(|O8EQjzW7`IXGvCsn zSM_W|_!WQcid;3R-tp^a#+DnGB^&-nS$;{hqi=Z@ z?C|_QYmuHP5|XjL{=0W58jjiaqD}+D!yYl;J=RV3MpA?JH5Vn2G`~mN-&(5KK_m!4 z4@{nu2}PKyHW}vsTy39n+9MW*MrGlZDhQ_xUYM=?Rx4Rv;Ne^rB!l#f-mY-ID2_3X#G;BGJQ^a7y@mI;@gsZ4uDmhy>PMFtC9nep+DB%oyu+X#wb9e#^`l(XQo8) zc)oq+V2gPcvmlXP$72OwqiZ07U{=7rOF%Nab7jTPGWuircMbat&@<(Dv<&eH7Q;!4 z2+?a}(}>@7)3-;e{&(VsZNWUbPEV2Jm8jDI#%lB^5{MsghKxhVUsH4wxNT0WF$=cD zww5uY#}e52AQ7&iBC?ziyLEj*LNQn#p0yElg@9&m_v1@{^9M+#!{%*Bip&OlAqj53Th4CBl-HmkdCx@pU6g|p*TMrIx!bM zQ@=O*nkke?mr2B@HL3$=5{O1X39}^14vS0_#um)`{^_1qsG=^oeKTcE!FANJldD9E?%`V~c_yHmQmyGyzeq>)Z(kdp3B>29Qv zPU%kRmhR5?^#0%X^E`X@`_Al{{b7IEe9;+aRN&&gj`LV+{g!MR%R6_ZH-)eO9zk3W z3~we2WdVRzZoAM(=-#=D@&+Y45}z05cLv7*)0!SMgqi5LPQ#nUPz>t4B3gm@g>w6q zA1s3pB1xU<;;FFThA%K+o4)3gno!h-H5Wj>@196Nf{d-!ssBpPl3CB}P!QhMR(Yj^ zW?H00{#ADXTzn1r(c!I5ee6QNOO{7!?nJ0}R2ddv&%Wk_gL|kFfXiR_slw|IRl5EJ zMC0KYe!Q(3**#P#sNk9d^~JWYdy92_@5RRU)w(MUSF+iyGY!$-(1OA@AF+O7$V~QZ zh`|Upi8t9z(&4zwio?MJakP{RV$Y8ShK;<}&g+v215-Oki>>Adk*y=CY{?vM@QVj* zW}83Ur6UhNqLp@2MRU0vuej```C2nn>*PnEqh&ozYPka@buBLQw|cg~9pa{JwjjJi#)jb2dk z`5gASoF0+hc@ zN1EC&E>8v;TstH)5lWO*Fxa`uR)=#T?FHphr=OrAM98Hxmfq{#KeUg;a;s!QkRml% z?OZ!Z&fBq^6!Piie1DE+7HtB$hd~va1tC8l!F=QFM96;bO~H(rMcd!O$XY5}SVUyQ zwnu!d6f7`3U!LE@7fTPxGZ|O$Q|-nHJR&OnJhX@8jM?2-`iN%W*fV1HHJ9&CSdsu_ zgY}U*o;77p-U#Ijy{qm|0o!$!r$jicN;6ViP#Bj$EV_VGgVpZ&APoY$cIbzv-jW31_*qg(H6fPBMUeC|29eI-2}2? z_nFYs-Ock1MOx{pYDpEErQ$4CTwfm1r_;rD!f__VqCGMMy+dwuifE&=1%}hP2Xc(G zVW-T8cP60#vEkHFE@jVd;K;zFtN*Uf}br=k;MHxQnyT*Z9D z&qVq9eCc)9J51dl`d-JEJ0rZ%3ZEPY!|%}hK?xgR>XFA&Um{L7RO+=y5^@*G-1tgH z{Z#obMPI4=nMU*dD&T_m?qgT#wu05*C$W~*+q>^hEJZ=n=<8>?Pw*<^KR+98`bmcS zk-{XbC;9Li2!;w<6cd|3oc`Xe)oE8H1{b(6)Z1+s5Z$^6?0Ejv4N6_8d%vjZY}j-z z4H(1VGOF_J2U9RQRuD9Q3d5u}?zuFK<2Z+( zE!yl8e8%)-x;@ZP)yjRU^6&5ws+5fC<;haTl;}4{qmUJf%VhBs&{*1P@w@(VdTOr_ zMS5>!&TAMe1IP>^uB6&@QOVwUzHwmZsYWNujebcs=j6T8;jGh7rNoK|_9|8lF$5D>x}%`vPaFy9;hH0Z z-!SS5tbz_p39kV=qGST6Nh{eNDxA_QuUOhl0=#RpB=b$xr`t%*bLg4%Lr2WO#f}#s zBuut*uoykfTY?GZG48K?+7h+vPlS1<=@gZ)>d6)*4nnk?_ufM#XQ`jIxE?;$iu{}e zEiueX`jdIUeg`AB#A%pr z4b4h~Jp{OO+2pyu_;J$pgouf*_@Q9~U&(kB4c}lch^(teNmJJ`% z>LcoI4?(sF^Ix|O8CO5_rSi#@pfh_B=Ijy!w+4H`Bm3Kam>A@i0OYBabi7j(n2gGg ze?a|*TTxiIyC~slLK2;JI*r;O@L-*OFmezd{q>F$CAAy|0c?amBn%>(u`@GwqF~|C zs?kYmA{q4W!Y`cwtz@o}nd`Ek;3pkRt@U(S_u%J?BKB!W)i>DHeRndTypq>$SJTsJ z&Cf8o?io`d3HzimgjG~TgeIQR^Z1e6A4;T>3J%9h*PYblEBo!ioK;d~3a`Tk@ZSLV zh)8YXD*{3KG+uU{h1wXT6gC6Z(5(R&O7#*=_2AcrVr-Yvho@TuHGKQ8almHCJ_i-I zh}uFJ?;z3yBHvdjTEbNs-m*VI*$WLbG4MT^W3A4CiKp7Sgi?7@4rw()DZap<$;zR$ zF*dGWE~7Ixn0oH4_Z!gU^_hzg-Z%u{RsghSOEv2BRZI)B>t$7^VT{h_` zD^*+1MrZ@=&qYQz3C#uA#!Bko<6c8+VD{VM;^UVgP)_*0=fg#Vk%P?kMW?q=M9_~S z67)npSot%S6qI%F%s-mu_L7}j4`)L6(S!&Y8QOkhMZ3<7u3&_A{}NJfRbyeU)_lsW zo3qUusZjTl7>UpwP)Xu3U5@U{F|H31=`-@jFmB{ws3!at0PcZd=FhSz_EH@naqhwy zro5`n0}&@!Y4Ps7Vh0Ir1k^BLttdhZYgVHh4oNk}^Kbvz1@Iq)d9I*484gvU71Aa$ ziauzyeuaS4E-S-fG`!@c_ng`xKwm4J^9q35anbxE%dFdOkM|Lh8X@4@1#OAz+(eGT z3=M|K$Wzt(0Ch(-3l4sVt{Ds07vRcUs9c`!?FtVxG2+kBXh&gN0WG#W!!dP^J*Q~l z$SC7r3&L^K?80Q3K;dLT{HZGu<*ig$qH=-b86cKS=zz=4i2HPOJaz2LS3LvA_xm=1 zQ%wZ?A^r2MrFT#7boV9=+Kcz^m*=ZgK|=_Qsvn2#6P@xQP!$hIQ*#C#=}p(G z$1SOomRY0qqC(+Vj2X$*wO&)RN=Ai;OB>_OFTtN_@h@fcA@^5!{m8waaL?u0ksR?S zl`6=?olP#C;pJ&5rl~CYN{i0NlkKOFHRH(THmX<4WeS~xmLi)o1%i(4HqQsZ%v)i< zWZYnQRJMn}Fv`T^~K`_pAsdlbtbK=>i zByX%dm{eZi|E1raalRISPolu|d}+gy6@oYJ-6L(Jl#1~(7kVA&+M3%ZM;@uc_^k{V zVor{z2BYI43XgMCFp>#vk0j`x9_W9ej~QJ=yGB)Z4Kj=%sNG39!mzcHYrSQ>faOP=y>X;-VGRtxoh1{{z=#j965!sCXH#* z+E0C(ZIFE?>(v@hq4Wf_TC}2|dgH-(Uz%O~Dl+3kX16Y?DL(EwE*>JTud-q#KVz(U zb&zq_5*gJXhMtDqlARNWaq74)5ccO5nmkXqqr#ZexD_h!8)i_>U#pl76S4f@EVF94 zIwcE{2zWw+4cD_p#pX&Hfb4@rOyTKRjH{WyO$N#5Z9#s5~%8mfliB zI#1n+e2g4kUYxsVa6KadV~fZat)m|>_cv#olw!2OgGr2fQm34cUvzl%Jr)#Suo%BS zI_WuV4_Ck-P{pS6)cdNTreWpELt92wXNVw1%=T`rT!;zY`*FlJ3w^Xn!E^athXNa< zlTfV6s5XdhmW4(!vQZ%p|M8XbpQkOHOOF_yu)#9xu+Ox!T~Jqko`#v6qSw>;v^@2; z2nK{aU-(GUUFC9rMeNzpH9Ib~x_JBD*AdLwO1PzQJ1x~= z)AM@5AXaFWqhN3vE1pE|TRQq%huLEW`>|=NB0`#Xos~Qi!k)dZRrO5ujAo{t1bUrUsf4P z$0V<}7`)Yqb1l;!SQ2P`*HG^`G3+baeI;;2-U*23Q!BJTsYJ*NH7eDc-L^h@Ia(g~ zU0a|z-`~{X3pAe09B@)9u!i^J@f0EPzmf}IM0&V3N?6U4jw=XOblWXL@$>pRLoY3p zpVQi}aLd!mh(o4;r+gew{yDGD{!)u4)@W3>DO>j1 zm8BqKWgplV(W)}FWBwtd*t?q7hD?D$mP^&2DgUGYq@<83KuOYSybdpZwF!q_?%4lX z<{lnuUB8#6FABvLd8^P3E(u97XamjlaAvt{^Bd(c9n9-W0m@(43!kM6SVtA%kv#29QRZmg7J!b76n*}* zXe>v5kNPs;KPG2`rJmH>IMA>iOyX?Ou2Aw@kW^^YM!<`vw={8tH4dZ4)1YquXcOAh zV+1x}wjf}}1Ys(W)pm&<8N|IkoFe*iEy>Iz7NuIOoaG1~nryBRj_3vbU3r?E60O5o z;8*Q*PWub*R`@$4B1T=N){|GIz`j>!V+&)ji^WVsoGA3UNHg0CyaO#}Y(W*#BXIx! zEFEe$GBHdCv`SVH7%I_)*N)ImKH4@5jRHNgWnIVwK!X$Y>dCUS3jBh`c@nz2wy?&7yeCOptScEh?*E^iuUCqgX zD502^zKz8eQff9)%gZm9zYcV@H7hD|JWll(!+WA(LarGilq3{zZ?U@5vWLDjU)s%1 zYz+dr;bi#^=LuPB)4~y6>w@8x{J{8L%zi=^l^XRx7fcH+QM!wQB7;+V4px|#ScJs@ zRtrw6H>Q>=(@cqChoYZ}g@EO8XLB>`RulBKoEZgmX0~TG2V*K0=?hp}jNM0Q1GF2m zUhj1}1LQ9h$9_#f?_Y$sF{CwT>T?DQXJC~RzErL5JM4SUV9b^XyGh8rB zX0jTQB;@GLZT_yo5^hq0*Ha~sd_yqwt(P`Bg~#bgVT6YH+W{05B<8;=OlJXJj8>yS z_2=M~i;WX9sle{RI4*#2>g2eh47-8?YpGw>TI@53W3hLA@Q8ydbXld60VRSF_EO33 zAq@RF9KSt?tQ}0(ovc0c`?W(-N)hR{0C{l=G20gqkGcD-SG_ogQH8I8yr)*G3LSi@ zrH4?98;-Cp)P4qZ78p|hKtMXn;>9M&&&0jhjky66&p*0%aOq#e*JDBl0VP$?$zp!o z@1iP|uC6XX4WI7KY0YWFMJ9B5h$M!G3)-9iiDcgB0@QUopB?(z(vP_xfaYJPQfE1} zvDZBmBiK_xByc`@dO`EK9a7`d!LDpD1sPe-a0`~X0PW#i-1GI*1DHs^dX{{DCt$My z|8=lsUnJzmw~1L52DfLnJIox-HmhRvfw&MP?bt$Sg#$^0#>_Jxc{cM;QC!}URv)2f z-8=-zt86wCDHSsIB$b0w)({w|Bi_8Aju za(8Zkez-n5PdKn7Nbk{}eSf6txC!MXrin2|s1-|@jH^?%RR0M{69R4FF z=T;FDPpCar2C@DlU_t-m#7Da-CqM}wb2NSM9fVos0kKszh%TrefY8Z%U!c-h<`(3> zDSs8Saed?wI+zoZa^U^fnjAQn1$f}(p~&tt?R0O-w`MV11~0M>cB<6<7V9lOP}SLY zy9Wy|@QNu`J0Jb_&S;YPj!P&aK4)uK;7MW%)jR?OKKx{)mRk3jP{dNqsd?`Masm+n zAC=$bj!U&}k|1;o6e~Z5P(A9nb2VJ(8cC0hl?aI|yw2VH`p^Zt+b~BK&(Jy(`lVvw z&+|KGwIYPhvjNN^IF!;BoLyY>d*}05w zx#VKQumo3pMJ2(jOL(=~X)%L(zXJzM05%a}6|3map%CSb{oEv~&n*SxI+|6twKl)S znq4fv??vEFgGdsjT=-7ziNz0ZpjK=R#Ib7E)3J%>b@YUw8bG1*eqhjRIC3-yLNqC_ zz2y$qntwCna=j!j0JcUbf(SVCXb@71N0a%b5hOf`Ir~NVK&Qr;p|A*kHoBy>J_3Eg zi|KQqP@n=NWQg@div&LSDS&=YFNOM>ecq)jAH3cKGU-^F1e$TUkIqM2UoOd*pCca? zcg!lcf*@n@LF;Ge80bm#cCjLG%X;j6_yqammr;;&PK)RRn&c|GI850@nKj%!oBKjn zK)41*wxIB}^7J!K^Zs-;g2sf`XHw^U`Y~?nbIsL4PS1=bO*UJa`a#wrAoy6`t5k>4VLY&?SK6$XK0Vfa-n|Whn`oV0f0iE&f!3;R1HjIqB;H2FT%Me zORWmGt=0zx3-6WL0zgOwT=YbeOF!hFn-}1cIE{Sg7{;%9#P0DEDq8flmT-eZM0^H= zktn0BwJ{{~46&a(+5p+Bp}tw0E1Kpzt|j(^IvTa=WR5V1Ag}HMYr|K)9bWuY(#(PN zk`0ke#^Md~^FYaC`8W(Vjo4s+0#z zUuj%Uvwb)0RgzpK^SUi&#C&_=^`#7-nv`?3@_{)M5+)3TS`0eAkoy&eb#?cfe(_|XQWZ}7$&JU%!Q6f}_o)p{8Pf`S!(7|1SJLyMXr>AUyaBFGIj%LL%rhWav7X>M@9I zWwf8#uT;nJ&Ws&PXrbm$12-6;X(LPg`V{a(MBm|L6v0pmF+kcAgZh(@AT;*}Gc->9 z^V?&GoyDe1?n)v|;+1JvV73A-s-uPddE3+7&>8q{>-WB_({GQAgkA&^E$6ty>>rSA z8}sMoM<#cv_(iHgJ~Rs%I5S~#^@$D>cki1JwCQ;2<CQF@u+_^J7(R$q>Q(f@dvGal*s$IJKxh_z&M z?ZV;weq9ZADM@b}JWYQJ;p zu!SV$S-pt3THjhU8$|sLArq<*n$odAYtvugvC>5(9)6+{`wYw}6RIHHMFmzf$=sM~3sY z)^}uUB`U4r*OF7euIlv6f&cKQe#a)oXdbs-yZ!gC8CQ)(ShN_TeP$USIpc+Tm_$^jXTD-2{Nc1xlsvpsZatVTR#7ietk*i)VQA(EEf&?p z8bcrSm4E-`xjYbQ+(gMd3LfI6;iQ&R(rmoa2-fUym)$GI_`~!snT)v}G+&84qTKb( zhfvCIy5FVZnTVraIbQ%f`2oelS)9paK1e^M-ArE#zjWESDdr?=E@DeW%LTi(R=jE@ zg37(B;tPnua)Y0!F}RziBe!j~3ml2(2D>ctxdOM+od`KO2ZP}vCy*b{iD*9NwOYxp zBLN4SH{8E$^tX7vkv=um=B=GdziLhdUD=2m5l!f`wSSaNV(g2EUmvZ+)0#$tshEkj z_q5=Q+;y0XEf}$98(?H5$Y=&J&?K}KxW8WJC5JARI|QZDJgH3gZh;P?(p41?y>zH+ zU*}od$I(^6U*e_$^n4BpL1)m#cG`pl_#X zS_)UAL8T`_*0Ej6$spO1&g|L5=9+)nop60{`R<(E>(Moe(5+GxOXN*=(6vkk$z8`Y z|5}==VaS&{b-Hw3{r=>RtSHK5XVa0?pVGNaCh6`Wo!v}TQNlz9AMPD~h2U|r(aBoT zDd)ZSva=dQpzyxBA7L-WG;fah&Lmp4cB|7c95gXW*w1OQ(~8VeOCN7qSAtQ0GOrDq zv%~Ym7b`#~I(=>%?yJ~0M1E9Vts%NJ`}%2h2Wd-D$GsPU@w1jo^6`9ie(2xYn<;Rh;RI6Hn@7u-3UbD(VXT0yU2~S`!P>82fyToXS!Xu|l6As->sQq|0 z*V~=_m8^MBQAiByD;RvRDET0!FeK0Ga^knF^qt$mq!K{?0f_J6dLMSo*Sf-a-jQXJ z=e;AM7LWGqu(AFkARl7|FnyKM>!&y2n6+T~os0io3z*~4EE&GEOHp1AVNo%EJ2qVY zRo)MqNAH2)8ykVgS_Uq&$@W-qtf_e4Mtf_5*wu;1qhI%0@*1Wz_=@%k?XT$@#s=h! zcA78ckQ(AE4^8;-qN`nTl~rWBZto+Y7I_>^?@`A1!dv{#e{+fYGqLDPOBL&VYQVOSwuXGw3$TrC=}39J zt2zCs+N{&ncPHqHDIZv4G7!G6)MRXbiNa9papDVn_LG`)9*6}h7E3=OHK9<`V>m=+ zRN6&~nTv*VtqO*rz0t-~6T)&@s7dxYFpql21A$j@N<7ut*PUvXH0x}#<05s3$KkrG zo`4~ThSV<(pWh1?O`Ho;+xhUz?_D#>0>^9&9$QW6lquejIZcvp#v^IcL&H61q$tlI zb+9H;i^31R9nbW|qM2hPwPBAW(FBN=pPH#;wPnyQTozwMcY^uq#M&)H2ji)gn2SC% z5tJ*9Z_%n*hF3@PYK$)I^RNU*X_ig6U5f6z2A}Ra3LZ^Vc>REKscJMH2P~O_^DO%g}5Y>p__X5l4>QPbdHoioQOBP z^e>V1nvYEd$x9iebDqbD<<|<4#W(bz*|D$xZ*jWzD%WQJ9->Yey>>@)m8UP9{Vh?I zm9E*i>*(EhB&1MG-CnCLXMI&uj2H|;Y!64g-4E-4Hut!Zudu{r@31|H;-8Pnz_*;F|cZ;*LWpS`E6>2heGHUE(sAyVR#t>E|9wYwM!C{fyn5~?gW~O z6GB$oyYL~2CT13jY2YNW~rfull7L=+nODUXIo!bacAPOy{W<}mhN9*fCjd` zC?R(%U3!$EFKF3LF@ZB{-eIA+#`5U`luKv*&@c!yv+p4XVkvs*f1$5>-wLUPitmW0 zS2MG*p(DN${*;RWwIC`?WJ(5|S9_Eu`EXQCsA@&pD2-=S8<>6bw^1jRSyF z8mBcox|EbnD7g;f;kSrJhg;8gn+u@5H}N(l0bDR{z%hne<#b{YTa%)QBn%spuR#6r}Ms}G3bJXG5aAUle+Z*!*noNK#pvIQ7EOw^M=8z#dV>>l&EH(=>Z5WFwOac zf3pmCyAS)TZ~uVxqFlRvi6OF{?t5&U`?ZsHu1~5&W0fG~B32<_oJtY^E9P~&Cp>d; z)1W&-DpV?Y3&IDAs+)N=IR&oj${F$8CJJcJxcE5@n%9M^1wEIn)Zsol1`SGWc1c3?&RX@)zYLP#aNM*0Df&`q@qYR1+ z@L6oUyz6Ktfiw6g)#eQYl#H_|;zIeqYX`7+I1ws=|L2cTUnoIl?tkRbz;FNek+grU zIYs`*$3~PeEW*G4mjC?X4gdc|TJHbQsOLE6AvZz@k-t-ui`X2tu|tRQ?(CW(e{yAHt8L7nHx zDY54j-fFAI$>u|<*eqR&a)E62KhNyHo?EwS@Fh?}L2j2k_UEc8$wXk0VbnanK;D5ton@s>S#rJbG&5nE`U!_@| zU;&*@_ajIV{`=$hM(AIs#LMfRFjEp#oXNriLjwv4=8knVa#^O~L5WBI$hAK&iIt-V z8f+Gdg%%00&QjC9`o&%p+w!I=LDJ*)me z1+BNSRh;_7{hwWc8&F?D;|DVNJawB??U}V84tec*+=fOYBAsKRU}0fJ+;@Qpa}dMX zK!+(`~=0gGMKVhg6RStUZXAf zB&`4o^30&wk^_n)$z{;Xb#g6$fv;B~m|)TQaJj)lWed*D%?+5TON(Cne_X@_L4<#Q z^t9vrbMNBJcCG5ll|*DYS@?@-4S?HL0AJvdt>0GCAniB&u0TG41%$^EN;t3EPEwdf z-CM^`hlZH}oFhGSELLN@s{Kk5W@ zU+;{h<~=HO0jmS((+PG2m&uCd#+jzpZRv*Pk2NNU2nbzfMSbs z|CI@kj(+f$feb-!6c)q6Lb>L5W}`r$o}RniJo^njcY~)~yUOwJI!IJh6r`c9xAq*w z|LfJc@xrzh4BojR-Z&zn-GNcnd#iI_&XgbSoh%cnbqu$w-GDh^AItIa@oor+iqi2k zdWB&DAHgRjMa1{DMRTHS;|Rl@OeXO%`T#RGf}tX4RIjDX6Bv$|-QVs6XFq}B8Lzwj zh_$uK`#H@+!QDi*5Cg(@so#ptE5dOmFV+u0)fN%IS1h%Wd$tSq`oI(xxJ#-0S8$!| z`RSh!OFN8SyB7hMqZPn)S(Qm{^=BOI+PnBeRjuWCDRw8HP1!89@EaVS23}cqUlY0W zKi{+God}hJCS8*K@SlpfSk1H$W$%64ONURI%hgA(vyV%eW<}~QH@t^b~q)K zy*NmHvN9?ktQP+LamM&>ypLQmoApW(V<&jDVvj8h3SOk!+cuX{IABo8#@ljO!f`k; zVppdo4ePpp0Y`+5gI4*|{+}02gRWf>Bq9O%IEsYpDhoqv*+JK&&wsYfPwj6Qre` znE-8MD2Wkt>Ed&jwa@1i!vlmB2_SC$tpt{&DDIezV9DIAfeNjl4GD!`NQJt2{E14t?wZj`Eusc2{ zPZo>KldfgQim=<9O&`l_CzDC71#h&}`>b_Pa$n%~#eCn#SqeD*f8POq(wyxQz?+v% zC?P-pUD5VuYJ?CH2|(I$bp^Smi+-1gJexppjbU@&4*S<9NvKL>g66xBEZen+azufyk&`Tsa+{r6W674`r1=zPZ5{cuCn zbc5665a#E#3Gz_d9G2*mHfa&x>qjyS(3Y#FOzAB&+h+kO|9pD{1eH>Doi8rDAMWdj zLIAEgPbx$9?t#f{cYYMp1eDC)-=3)m=|?{o8V|&hDVU_0u4+_vi*#RV%WkBAiOyF{ zUvV6?g$dxC-T}1}5N@BJPpBXS1z*12$u?^xn+MT-s*a^qFaDHK+s*R_TT2Tw^zWkhbGBa3U8Z0Ty^qMwHgGv!j%ukfKmG(#K!`91o=<=aZ@M`*UHsCd(Q$v< z0_aE&Swkk(H$&;nNH$;!kjMir@!9e}Yuv2X{;lR*&P)784hHXP65Zq+1ijBD z`)=x<3%lN4P|8`jYA@cLtTta`8@GS;`h*q-W-i=xVx=7a-cWR9)v)~;+nzG>O(PD< zhFnT8+kI+Oe8#?pfqQU1dG5w`mI4P5l_|&z055(rrHef9#V-GDak{x2Fp@r5sCnrQ z^0Cq334W@Y1f$WeUU)e%XkFc~6EE)pKRaS>y|-tZcLYEw;gH!0zq?4W(B4Im z&)_j?HZzHTc=;n2g11$XU>40}&;@c&=6XM;05B5ac$HPHB;GJ=88#>!XR5(`M)m8} z`S~;Q>82I>?aV=g?8O#cwBX5Hr5>ehDto89ShcZqf*>pn8;E~hcC;^DY|E^IUEy9N za8k6~bd<_a)+FB)b`!BJxpyyorl0_BA`qPdT!)rBL*l)2WyBu+P zUvCYku!y;Ngk1Fw+)2q+GBTXyP4(BtUsS{`E1@ zH*8W2&(~R}EBzFQ8IIDWJ$HsZ@-Awi^Z@g-lM!PUi$<|tb#I*TI0MvJ>OqB0}&5dow)Ghrn zI-6fl6MH=n878kg4MOoaBcB6CttRwnO@iK--YjzZiP}2GCi~7o8~K8_P9mdT)0j^T z<&raL8J*sCk+tM|Y<9!<_`_aDzy1#A5#*s*{9fn7Mz(L<5Ml3&uzP~@(D#5rS?Wa4 z&N%KO(l{7RI?nF)=|LAAr3|T^8~AkY!&cfH^hgQiwVe?NrWdJ7%`gu*t!COlA!}Ib zr_|`)J4jJ#kV#BQPv}@9nwB7PYI(mhX|as@lYzc zE*~1X^KFGl=~Lf|f+eS!TvvKY4aM)P5_NXpHF=b3Zlbi{3dZ1X=7!+mA}|Og!9_XaUIE*TBHD z!#+|s;pTBdnC|ViL%(JqXdtlG($zxQ4#GS%p9xkP_12WFs<=XP;S_;Yb zpjcYzwb?h|ehBd_kj1AzzKLEu1I@nBo)IkLgLdGJr+EJf_?9cHPdZ`(E(&H#RYdRP z(i{cnvf8qb7At?d0;F{sN})IrcZ+RlmV_$gVOZ0t=k{V*g824_?b_})Zv)QGMjWu#J6=Kex4erUC)6ey@N0>32ZaU!qmJa4tu=wr=*Pz4ctHG>(($KS9;| zGs_z@rE}PY$kNQ;fI!(*1o_odZ>CK5BUL&2-u&5AgRQQpwV{-MG z0R@ObhfPL_nk2yaat+b?-JkO2L2JpzSqx`>gb?-7o(jOWT}1U?=wp{gL)xR%Cpt9oz|B>x*X9g3DOSKDr9qQ zZ5}TMeKs2YP*y&K^HOHX4daoM<87F9(MaMjL(J~3ofmr%)GJ{4y3=ZKb^^2;!V(ND zB{MlZ!1#Y{9Uu`yW}nmlCBf`Rw1b#MZf3zkI0yR(==&T2YdUkhpCcd$gE5@xGBPN> z%O32KRJk(lHIh!-2Y3+TYWX(=c2o5=F`13OU78+VFY&hdN?n*`iD&Y79tT?3t~ZRA zP%Gc2IAGz=+7?`N_Q#R4t1bcIz4-Egv+Jg31#BgRH-L}`0pUxwi!354T3iN~R8gk6 z-5>EIMjl2-Ldo(`TT>HUM&q$9>c+K~BQsK)^{IU!2&>Z+(^6>9gV%*~$F5Uqu-a%> zOVukn)RYT96c1Dmp18gTW|JEN{h3xv$&B{iLF^l^QVlxv5j~L&Z`^-wax%V;jF0C- z0HhjC*?74f=39N=?%Ag|HZ=ETt3OddM{5!WS`8mjr5Tc?|~~5ea&gX_cl-%-EA49XicE=vF)|pH?*@uawVs zk@C5KeH!y)jjo*7;dJ(@8aH0?h^959qu*_a48A@3 z2EU2iD~r4c=X$++4XRUEJU}$0ft1qz1nTm@X13yO)hA~~RZ8VFqS#lJyZRG&Nv}`( z0M%36=|jn!xxjCe`1y}YejRq)^p5x?G`zRUU*CIWBvs}r!s9Iknbof@Hay#|3V_=h zmo%^_+0Ox4Fonf4g9|yd&hB`+!zZhl6)VdzIHK{#*Kh4!7i_qT16{!l(if$v4S zT0AY2MWv*uj=lOAJn5UA|I$BD`JGSr$6c~8V4OwaQ7h)bw>vm3j9|XaxYJQMlZttM z4eUv(QmjWyuW()g=RoPS*|&5)Z$~*Zw5=3<1NET)>;f*=)@MH)uC;tN1%k2o;r+#* zv3^a(NDv6cph;FZ9sbXjCIc;0cBSRZ;I0+q2qF~9+>N` zByg~OJ6t20=t=)jHXV`OUsW9fJ6MbOk4`Gzv_G|O@8H(~%15JOWp}mkdvmko`PM*U zIdsD#T?%W1H31>cY%PFZ;x^(VS~ znw|DuWvY(xW>oCRHEY__-Jv;k=mlU9F{CMSRPuUsgkT?|IuN0eMj zr@iUpvOaPxsupIjlLW{E|pLkCHpI<_|G8Xu5ZZCyfRdP(k@c@Ix9rUwH8W;9Ee}aDe3jX12SoMtBYYrkn-q z=Z_))>j6Y-V;$4KW(}bO)VVWut}67LF`TD zpWS!|eYx4&q(rsK#^23tA@tA-8K>4i!G<}4wikm}%_iJaCi#O?`38d&I6>Y71(sS# zrS)+CwLY}>5B}wz#mrWGWVhewIa;i1c{xQPin>TLvp3}H1wwG9fOo-}{P&+eCJg>i zEW<&CrjlU-@gZCe6UsAT7*ECRB7#Q?4&!-e>R`Oqc04%Ie=&NLI^;*LN5W=F4K2vB z^CH0uS6Tuo4R6GMABz)hI&c|vWT#`*p9lCHMmGveHt{e`l3#-zqsd`WCorFN!f3Zl zXDCkA6L{F?D!tPm3Pwp`G3l6C@*bBff^UR|go;J z=ql>v(*BeJ`U-@J_c@urvvro}9^vA!7HH(WGl0dfv>vs5e0&Unci9sdN48ZpWwZLS z;@dpXn#Do|#c-+9+6Qx639|4v7alUi(JvvJl7QGKf{lzjTd6}M{{W1khi)?LM?yY1 zX!#oDAKs!wD8bt&$LU73lxjS6*4`DH#SlS|55j)STaBelF$lh`Pxlxg{_F|@Dfywi zYQXsf>JdXcg9XuQBIX76zz#hq96PEBLe%Hg9B?^H$~$$0H8u&v3Hu6HPBV{zB|Nzl zGEpRC3>CQ71BP8NBlU!j)!HB8B*lmBa;xzK0Q-1**1@>i>>%p4?;Siw%QP z-YcU)?0Tjm&i;NWTW4s3^qqNM;4@$5+piDLq(RKs*u$o?wP)aY*^xiXW{zDYSdIpT zRIGj8ikASq92~PgVg2s2n!wkal{foTO;d*5s+VBHB{h~|8rERlFbUE7?n`MAaVHp# z&B?$8&rXsE_sw>}_J$@LIdqV25oP1h?W~9fODx6@wf>Ul{qY|@KA95zn`e02KRiyd zRc+!)JKmaYG6@Y^-IoObLex+!H>sR$T{p#9sY!Pc+4lyV`IF(F^>6OAa@2(Jytc@= zxMWO34{=f9qH?4-Z5QPZu@Z=FKB4(TrNTU>to^}BM}K|MnFtvsA5RsNoCUE#39w7j zk+{4DJpR^w@<~G)w)eNU5+(~pe2F?d;F1P)EVi+cUwXAa>!YDjrud6qWCNw&2DjJe z>lVhgH~bQH#9O$au8I7PDZr_MM}WJy_M(v(CCz#2EPw>kd;m`9`>*i8#ey*-nzz?n zdsGkrFS?%71aw!B?nzqYB?P;Ubsw&5uVA&`Ag?5D2MOW8feV>Tjn*dZ=pBd!K&bLJCqy2<`g(Vc9BsQ? zEe!_w7@Y;jU3ncY+vR}0^Y|1)*1^rL&rfRvu)Zu4QMcn+18zvIEw(EyQ!1@X75opj z%S}T{Mer096zFg3!Vd$+J1RB1K)wuf^t)D$CQw8ONvM@ST)qlUI9l-Ud&6Qnn3Y-J ztH0dhbjw70*kdPoQAiA-_#?+EM!%$qV{ywY2-_d4YSISSZ6+p($m&7%?s&fCdbIVS zmxM5YFPTE_d(aOldMvtV4W*fP86i6a3x`hpQb`Fk8^mF_;#d={-?{hsekuO>QzhbP znFcWteZ25~^`V_!^@oqwy9{-F@m$SnD|S6vjYp-?s!JhcWf+Yg-KlC}v$hNBKMg-V z;giFJys-QyS|;80d9ecc)GcT8AeaYu>Po|y<3e#C>C~G7?KV#{p%B@{GUCXjU(^1m zNguhu`^Dn8bqfrsz&JMEQfz?Y8$GUwQqegE;yORiAKnY{t;JA6ArN@%Uve{NezWWx z^p}T-!Q7-Yl4x-~(_i}vAhW#ID{g>$$04$iEsNAj2uxUzH;ATEN~GPvh_p!~rHU1` zhROAINVf3V!fAgt3l$hpmIxbujjd+to2=-4ezp2m;q4p(3+cB>|2;e7cZhIE%aC7K zg^2*nn5u2V1cuvBQ*LgQ02aNR&B~4ly+c_gM25 z8$8e=H^+rr3rCan+CKP|%a;;)2;LP;de|IIjZL+zXCCfVpD4CUSg$*Abvjw9^%YyM z-2jRqgM{7t~icnos{_xcB$_sw&6Q-pSeywh!e zNX;LHNIZV@(Sk=6lVGcD$~M}7MG z(x{ZbJh$SgjqWMvc@1c^d{T?%a{CnQB;Fc?fjna0oV=7(+%R+~>auf8CfNH3p1R}v zjlcS`Gt5BI-Z4fn#|xeykU~D);P^8IOZn#EDoZMcZ=SMg8y;XDzFyQD z6m%@%a7-{uBNV~=^P`;aGLIu767lb~oE2v80Te_~-UQ8D`A4mG8~TSjyi9(N;-@Ch z!lCfu&C7=ezMlq;Fw-PuQh6{#m0GRBA%q|#|DBL|U?T|`a2AfyiJ_`&fpJSUfqAp= zEhOZ=DYMxv()ZXL`R;7a+x&4R+iNm1z8C@bCC|Nc_U3vXi}wA#a}Y}z?3Q9K^4h*R zvL`Y4ZFzc1{1l2opNA0O_ojP*S*C}=(*)UcfK~3*+lcoqYx6xRcX{#&p*iCV`dwl$ zeUZ4|c$i!|SXGXeAQyeH4UHx8Q3ayjMEs1@4SQ09t<-rf0xjN-HYBj~w*+D=6WdW@T( zTLE^WzbkD$NJaoizNBPLBA;IuoOkvz?1PgG=CIIOGM}eF1FQG7v47NYYhYHSJzQ`< z7*)Y~u=;rzNJ8B{g`=OM0bBF38F)N*z4^U6YfiG1K&q--u}yZp>#bJp9*h3zE|@JN zD4<{=A)$URl&>@!`2iI66>eSk2r^x|`(I?}DC~onc)l-VwT{zckNGqa8PvY#-p0p| zEI6SzOLsEpy}`s!iq=%j)&X!3kZ^>WAYMVi3Sw)<3g4-rEE{&`)#(U{6Wxi8zW`pZ z*UUG__HEx&%ae@sfdRl-2Xs3NHW;|91!#KB|GswOL3rvk{7TekXQtvCV#)t7Uv5{V zRzt%q^kj3uVdZr*icC$8xzKLw(ioI%mQ`dqH&d+E3T%pb8kNgbzl0x+zx_854eWh6EKyki4^L%>|1ifD7&<`MZH;wo`Nw|W}*eDtxjcY{J zM5&O8Vjqe%2I-~Tt}E9sX8M;?j$m$G&r!OroR*E@|z85MrspGG*a|Vz#!=9v}H`Z@Kr0FJU z@htFU*`ufjH-*&{m!+ud3a_Cz2Wpi>-P>OoHjG4~weRXZ(Iil8%9M-wRvycFZ#cx@ z=b5{}LYmqxz7k}9!u_FM-&X44F+zRJ`;p+~ADI)`tnL-CO^Xos9ts|Z)C6M!6S_4t z6Bs~Parw^gBZ%NbKN&QIYGD}|WcxEn=MG{}DKV;6Q zAJ+*7pr(C!E%e@rxg z>rWal^4q`gg}<3;X>$?62V8pYD zRulKUhOOkj4@NpyP84kbJ9v}8|L9sYR-y;9#NonI>jmQNvS5BlF`~^!MLE2@7I}F_ z>syp3t2v~gve4y&pNtgX{X4?xVpoePL^t0NjxGsI_3Do-mj@m%$UzP4g|oPCGNw9b z-s{dPM2rLx@16B%noiWczu#$zYP+vGx4UH64GQyTG=C(PH?YusCL$n7X3jM`wwvR% zlAXcC#*Q!gcY80bm6rjz7y!UcZjziWC5|t6o4z#@IRS{sPKGV{AoBE~)nYLPY~i|s zx`&V36W=qj?V3B+FVa2e7@^D1(9Tf~cN`znIhR{+|~L7a3RBAgw5S2hCdwRHpOf|>~8*BRTsl**-4RhZXJ zTCcF_7XUf2UR?UO(IChM&>RTGJomKp^E2cfb9mxh}8aw-6a12fk(!o&g1R`v~v=znm5mbiUGobjzGd?7vwm? z(F*%o3{QD~hZ2_XS5t{0N;6vskMg9ZcH>(m)jO$cw3Z+|YD*hLxI(m`MI`xULtmbq z5zShqc-aJ71ONzZ=)bi6#v`L@2u;ShIT@P01G-CLcY-54oSL5ae*9ALcr z^y%3HYjBh>1FH*T*C1AP+j=;rT(}0R8vt+)L~2?>cEEiRccM@d@Di?R5J$1w4LH1w z<$l@IR1;@2O;ASjzTLqxC

4C?Z>v=jM^c2&$CP)s7cG&yGw8-_uxkW7^aO$)ghYdOQf%d7>0kBk&F zJQ3oF??wsQydQ&Bxpa%ym;A!t6R)uetFV5(wAK?nU1WLf$XbH; zZRJN_4>+jpVuyP=EbqK>n4-^{&=~kts0y1bTLx|!AW}qp%0D9*2#}rzgahLi74(Px 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 =b~?3j~490^w(lrk_efdmfz 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 literal 0 HcmV?d00001 diff --git a/.tmp/real_feishu_case_4.png b/.tmp/real_feishu_case_4.png new file mode 100644 index 0000000000000000000000000000000000000000..0c6cab08e108380c16cb59a8cbe28a111933d5dd GIT binary patch literal 69579 zcmd3NG)Tjt8|jqphC_EfBYwVr!1L}m z*X5hTICIa;-fOSD_PRsmWyO&ZKnO2hyg>deA)@%=1+4pv7jU_6UID+fgEtCayg+;L zSwv9TJ@sG_RvYsO2JSeA09LsTDsTX`J%Il2V6axO?#_O_OL*c_t9DfP^afo)A*1|m z<#6%Pb>4RVj_+vEtMO$!LTJdpgNk}bQslqmbATWW`oDu59}fAyUt^8&*wFs{s_@-l zRN#Ln2mODZ{GdHHhVgtIa#~GIjIoiKL?5N1mt<}*uGH&AwDkyi^D`p5S;NQ2qo7~5Qe&NGjq|CWl5uG$c&( zXn_QJ6pg@pzQ&FjMzAuI?X@_w`StewN+kv}3qn5j^L>3t=r5iyQ?S2;5+t(UTj8hGGloC#l<e?1TkFvm*5(<^T$3hTQHEol-EKHxgdZi#d{MTh7R9LG9S%q{;_TtZxM=JT+S$%JB z&;9P&NK;Mj=qcOZF4dg$cAl~(lOGwi^=ZC~m95ify}M|L5E~yI2V`hsFdXam`~fhK zkrCKvN9zSJ$;663w4iT%y1icx2EG7!x<7>>G4p#<6jVeh3)_MJ&i6)3#x~c^3`PHq zf4s(>LNwxhRcA1IEEhs+B2R_Y$i+pN@bALk15WV}h=|zOT3D?zTM_(&TE)K@3G~FX zNDPeOcMM5Ke)yX-q}d<7Qow;gMg8i(f<8gJ(ALU3fM6*&35`<<>g#($L}WwE_Aj75 zzmY*=V;fzN*9ng*sW#e1RnU}o7DHYr zTs;E9uN*xs)3BykAtq)a?Z5RAf5;%7hk)S7#6(a=b$ zsfj-?kyKLA+&r@Zy>tmDl+#k%x?ZVs*$;R_^jzs1Xe1ZrHa4~|o^Kr6H99IUiug41 z;--}|k?MZx&+xt4PoHl$SrpHYs^>fO%?A^B6T;S=0%E;GhyI=Adil zw82z@MS}@6Rfp7ECfdq<0PwoO)*@8MeuGJ)N&buDmZEQXq+|Y8k$wj8n zs8B^xcxoe_A0KPiIdD_lUhO8c1bUR5rE)nL{H|>tucOmo8d_M8mh|=YZF9mc(-%TV ze=j-u>gbP+fWH1{qYU@fP_p{Fj5Ho65fS6n?^8^bE}g$L*=dXn&!!g(Ms&km`VZ$n z7AqE}dVY>koXQKhIk;4oehmw|jxH=DJY`3(TRob7Xw`N85F9NYox+g+JC-JXd5NLS z#M9GTv)-7UA_!?GG;=j*J@jYXKY!GAM34Urt^NF2+rJy1TdZA^N*nI z5xon1CYCZSMx6$~aIw|Lw0)6LCeQnb&Ek(;F3Tki;k|T(e7PgX?ck;6C-;qOi(Rd) zp>!(p_?Rq%;w@yg(nZIk7Nxws$VL}iR-=K$deauKW$LnvLd6E{l1*&dSXlz`xi#p? z=UifzE)oeFHd9tsR%}cK1%*yAneV@YqmvyER#fS04saPy$FT`$;u&j<+npl=wVPZK zePm>0ei;wH15<8Oj>x5QWzbvY-?~_>(VT|&_5FnE%=I)T^cKi1&AT0nizzjk#w|4b zlKLCA=j8_R$I*YIGSns^$Sy6JG={}w0L@A70k?c9m9yYrRa}cVuwhI0t;NKgL};Eo z)iHMOW{P-9Z5URIzHww!Cn<*?tr_%aZ#I#QRKE+Ip#R z{?8u?>FvIBY?&Q+RFYzp*pMUrU{jerY73bC>!}{I5j{gL7n)+yz|&sOgx=_k ztV5!G89~8z5V>hS1qTCBTsebgqv`zc!|udvSTVON2?V_Tvg50N6Nhr))cICvlh-AQ z#oW&_o>m`$%;J=$ggUvwUM`nQ(nmNrIBZ5Ospf?a((Xbs?mYK29NlZ!%_vigm()EpAKkMuW*ncjTIPgZ2PnPIf47}dtLsu(bq|?mJ^z(a}FDxVk zUGVY^#pp)Z-`zDb?CX1r+d`b9P$vCWdrdPhI675y6b&Ce6q4ii6q5mgpc3(_6p4&Y z7M6DL{Ky}UY>0jlk-?*tv$4K^_gl^8V4Qha8>Yfoo9SflV%|@|gXkl`%2785(2$)(7!CB+cJ^`?50doPIg)`HSZ0u}4%9R-mzwi+LMJVLu7{ zzprP91j}t)z=stM`8?_x-y4{+am9sA*3?WlT&g(`}LdD>x(~%4q~tW|Izk+Ty3V`#wep|5JkR zHT-KHSMPky&M!elxt&9cotuYVCDAc4tC^3B-PR#5hV>djH|+21Az(&Q78a!*=bOVr ztgsLuEu|A##1{hKe6~?atJjE|D3p56HL6U*E+{xSP$PvYR~GN0udQNO0?%EZ!ksN_ zMm@!EeRmIH!L`sQvK;(|NDsMl~X=e2ViIiDyt;A^qKVHD(iIw&reik>6% zkF5SBi_4T;pG`jpSo{#{)@*U<)gAk;rUoP@T@tHte=7O}6q+6aqrJG`b&mhhXRIjH zDA;^(p;l!gY*VV2budd>PEJlbB(^1_RT}tpuA) zzD&2y<$9+dL}}=L2NiCT1#cE-KP=QJ?eE|yX1pZp#vF`iKf6^yJUH8mUvqibveC|c z?R5o(dE6R^LG~fxSKu41!Y$7q(x$TQ7B(BuP+n)CpTGE1=^=Vigl(j=Ldkek$ciIT zGy^Xus7SQJWd6rpy4q^0+C1i!LrDl0K7J_qZ?%L{ZALjG_Tx(;{%RQ%kZ!a0eASWt zXZHG@K~AaX@_!3YZjK8F-9G-yq{?qX3u*}2Z8ba07?g_e7yt|mPbe+btJ`W$G20r{ z3qrZ6s9^HQ6@Dl$6ueJ6+dIFw*w~GEQdXi0#F_hIYp+qZ|acJ;T-C3N&C2TMTXaOi$VthpP$CHoc;HNlS8`!w~B zPnOzM6o`yPw<(LTu+n|30|@_eoh3sqsrU#0n${}2l2`2f?mfCg@P2(PO-|);A*Q8{ z5)Pe$h|+d5nJsC>2z{|I))2Sl8eguFx8b*!n>>mcV zD4qt#5Ke!8;W_7n$QqmRzAUSPkN-iCO$5dxJy3IP`I%KiEXL~%D&vn9oS3$VkvkeA z=X>SSBc(=ciQq2w$0~IYtG&8kAe)O)9^c3A4Gzg{?qU~z%&+;ZlzZ&+aAtn&b9pjV zA6}l3k$8PSi>u*#ejyJF27?^}L=l{vsxO_4M{WmqX*?e%jQhen;-pN&C$tz;_NTKYb*?nQJReKnt=6==ES(?o8-b zRCui7LPP}8iELV{JGP*3*4G=R$PU=>IBqxRqXkv4T2y-{C6Cu5#y*eF^oI-Uz`rOY z5jfVl93j|ALchw{Q7%CUq$N4H;QrDT_H>rl-ch&35{AJn`bg}|Wpi$*o;m|eynC0F z@{k~--RjY#nYM+zHw9uf)SspsFk%~B8HwQ--f|lopPDKs{^U%ED{Vbr4>7%vbXeR! zA4`)U$sf^mKIobSkUW7#!?}G;#lpshtJ;dsW=qJSQ5?+6>j3IIBEQ^qKRIQ898PXh zM#_HyhL1|-ve<|*DyXihW;K?W(b_o5|DP66p&`z!{J|E(OT9#|=3RPsJdMKS?#T~n z{0Xub9g?IKe`>D2onnXFWxjP`)NN_kYvSP~pL#=Nh#Os^@i>od_>fN|k?>(tst&3A zh5vIebW5{(^Xf+kKb}9FB#T2$Ub#*y_Pz#jrhBE3S=5nO!R__us);$xZKcSLcW}6a zKe&U>AF8XXNBhLZ!F9tnsTn-2jzbC?uu2q2M^nu?u~e1m(t`1{X0C2-a=A!}(D~xC zC>?mFRr#i(KM8`bk$ZZ?vNu~DewVTD*GVobLd>%oSSdp+_I3k66a-j_XL|w2 zXE7V7@JVpA3-P*2d-duS_@QMm^*wmbJ-*fRh7iDH8=FI#n&h<_&05Fi5anc!LPm1m zPgP=3)*8oGHQ|o-l8~*_B*CKvFq8ZBYv~ngfKsSDw*c8bNd7_mD(8&6G^B!%X8vAH zVhj+d%6==A9H0dXj2=SEaV`u%V zprQFoB*G#dzd*3=<|8^lJnG8R#uJ|&VG0;{;f>Y?D|M)q9+&-K3`yWUeEZb_;*UbW zuA!KAP-PrSYQuHe!Yc3 z%}>o8GJ^PlmdmGaB#NR;+e!L*5K5d*DlcE4`!(Ie7v{@`GO5J8g3;i+ zk3&VO3~pNso2MpHYZOF8L=H%ST;=6nTE;`k8mTTqXzT0(l2ab!Mu8r;D1nIx3lGlh)~$68y+b82n(H8qznccg(8>KD_+jl|U($MR-XWh~07MBB1LL-B_oQ_G zY7_jrqR2!Fx0kiqepfRu%%s+K+3W1Gd32O%A;8gtaA;)(?Q>15aC6V0VT)i+@Rj)J zyL>OVqY!!FFtxfR;mdWguJ#~4E(>` z-u~^j9VEd65MUDh|gVL7MT9gN55k*iACbAJX0(0V|tT)_cDLm!UCO+rdD-- zMyq?Al8a|wz}d+Ii-hr>ay+x03j};L9=A`NuDh1${|Iixq1TpA9dgiLh60J>?LDvv zPe)I0xOCN3cy@w>o%NEsCp*u|cG=@ySs0k>o5_k@X|l>UB7k*OD)`U60hMVt(11~jx%4zFY@ zZ9yA@Z|e5;_n)c1$oZAws6Z%Cp_%pjK3!l$7J~o+=Nq02dh^RIGr1q{@d_>)5R}CH?&3&B6 z7LTiM-{eX3mVf@-kT_gZU@L)fh_3eIEyu2yQ$?Af?+P< z1|7w~w1pC1rvS|>cr}UD^rw2(`Fiij>BewYX-?#KMBMVvMf&7s!zFzbin;Q{mRXMj z^^Kl=)WTB(n-h6T#Ha6~`hIE_E|^=~COIWgL=nvud2|xaR)zV$+oHHKMMytsDN;z_dT~)c4ud&)@o5& zT3m#x_Hco*FnJbWzA@XJ-lrwzy{hG!V?A7vxr9doxRVL~AqGc&WJ+dqSWejn+Buqe zn__ZDlM2#Wu>B8Ol%?xe6e{;4|Z1EK=~yMj@zHun>$Fp}=IjJg@K zvA=InWmJ?SlLT7@LETOXpP6oxwkQJjPnDe0ZmF3)HgmG+rQ>ET4V(Xu(F`0;5-Bwc&J>Fby46$;&-Y!-^2-)45Ve|dhlk1sXUFpk4vZBx$KY)y=`lqGAWc{iac}G~Y4ceey)*aA?az@ zH^h673eF!10bzQ2I?BT|fOduuaidDf=j&1iSW?ZtZEtfR2|RS#@c8nj3#dBAu2i>X zEoz9C>0~uW>oGe{#9&WZ)x5{c-5X=O2u+r7P8Xt{C6@uYBH?QBoT0|^WhKyK?gHHHewyxvVQfE<3N?`(BDrqC^I+z-)eRYy0tjqqu?u>zm1C|Ut?oj(TY64h_+<*XNq(=r_o=-2EYPXBVO=u zB0MjNMN3DnE+DYdfT%XCTd$a9UD${+b+P_QDvyw@o~Oyg)|LWHS-k}40qlknB*MbN zfuz}j6DT1BXo{01s=xzKgkX1Br_@20nvd*fWNj>!%LCX2Prnk1EKCB}2kVr-v-emB z-`)%#?F!{v*~YE-a*{v@A0t!sQ3uR-XDX3_BGsnz(Ppt!zUykWRIyMye(=dHERs@A zZ}R8-D@^(ncK7vD@1>H^-HC_gM-&MzP|Z$&fOx<^Jv#J~-Td3cD_|~y256#18)$58 z^KK+8Cej&gNn@4eOTUnjksr2huuRg^b>A-VKY0bxVr4Xgt7~d}#!r7__4P^cEUUNH zK0Lr6MIx;um^?=(MhGy}0r-3dP15WC=2y(XkR!o?N#dF9k&;TX{OgT?)2aHI%-{X{ zIr{@JjC#I8NXU1je^eOw%m1a5y;E?@wWT8>C+70UsQAC!YeQ2toQ!|dkrx8ZVSE~V z3P+KVq2UK|F94(y%9aLFD8&H^aWb-d(|>mp4$R4LU9OHjH}0h$D9FfitTkAA=;)Mw zF;NH!2^}r?9Be@~-szwQX0A%88@Wii66)*-!}>Ln%<0e5S=uB!qSwOA z!Ah?aw>cijbHL>eVC0!STMCqeqoHg1B;?&TdYQjm7*!4RzqH zd#9-EyZ)KW$Db_tA;vkE4RF=(Jmw)05fHB@2jO5#ACb|D?Y6hCki*EM>58J`-nScG^F3K_%qVdW2MCNq?}aB1zw?bjAa8}NCW zm#ZiECh>o9&ZyCM3Bxkw{AH@yXo+a}wf&*d+~TvyF6cfH24X_9o67CGcs$H&ndcBpzRFsia z82YT;pPKv~^B#OVMHT7MvgJ>&aQvVh#q;O45m zp+z~QuJ}$5s*cF)aDOi9)s)K1RoBJl7=VAK>V3U~Gc)Lgnl53LV%n7nP4vElt{((i z0azd|D*E08Z?sAHvLxw)N=Sv(a%E+wmyh$AJ>(3HwLs1c6CIt?d++O6g6(9X0(03Z zVuBqUZ17+bmyn(ws=;U*`owW=kqQIZzu`d=9-BO=sHkYph09ZxlYqW`)Ik!#OtD|Q zx^uPj+Icsknk^h`pnAdQsWKCPpr14tKAFI|JeuW;i-U!R$hxrAjkaku8<=UPu$e11 zHu!-46p(p_a@g>%+@I&g#0a)P8Y%>UPJ;i**Cpi&;&9{3u%8`x%5J5sB~BUNv%Sx8 zbq%%O7g=}vg`tw~%Zb}u^Wwf60M)J9z4^Hj7 zb#!2>ODt-}ooG_4J%+7UD&Zb4Oyt&DPGoye58@v)tY~cX#~pU$rS$X!ZT`k8SE$j} zF8{()y!tTfwjK{|ZmiL*vyi)|)vi@mC_&Y{BQ=-5f;Ja#Qyw9hNOh?savE{nurn(j z+Ugq`63z@B@zpol>z@8k3z#a?6^t#API~3z|Me@tFL!qLOn-m>9E(ezikhNlFR`=e z6@7y9PDUWBtV}lRmB$GmseDNB!scQx;VjF06M=@PD3p&YZJGN2=xB5vJv#Zord1t- zUejW!QTPej9SF2zx9EWuSTLf)>2fI%9g)HgJXZDAsjCQ3}nbU6(j+Z-ayULL8d-$2! zm(XH@aDt3qE_UoN45zWqht__7o%Ek z+3w5f9pSFA3hNchxc{2BzwU|L4B@|{)+5;+SLxMqBmP*TMznu_ABU(pH-JmS6b01Y7>-Md|yt1&vIn#}Muo=vt#3ulx?oIZQc^MvDd{(c&{H+LrB%ieh#4|90v_;1?oLkEW(D*?2YKn)quZ4u;fX^KZCm@4<{0Ce0qYL(aE zxt#`4+L<3+`Hrh;BZg-LC`#`sZ{DW4$4~zXg#p=_uNi92p4eJN_I#Lb@4xXV31?wrYfBx`g@|D&)F>vsY~~gb z7wx`Ou`cwm5{Ptq&HEqv9l8g;oQwcIlGwepzKUAw}KgX%u7%c zIF&q&NfTc>#Zzgb9U6=wMd_7{JalZP0Ohh9HhQW^gSYw#9XpN7EziCkLRBqmG}V^X z7#cbjsdd#Y#DuyO$U+{Tx%k+&+ke#(%6&=5&cRWtQTp{9CbkKC!AHA6sL&3_(-Z7{ zdXGKL?p!*G_l)zxU{|7fg`gMm10fSC+&DBbON*#%Q`p(ZCpaXWQ zdb%_~_Xx-Zd9h48&H~l<_l$L?>9>ter{^)igCvjhSBygS)h`AHt2<%*&gXQ=ndTNb zX*gI|-vJQxNC%31nCxRc)}S3F1q1>srwk7w#63~_PYkDUeSJmApcprQm?Ha6+>_QmyyYED|V_)w-igGEc` z<%w^jOw4UrjtX$ML!haiuI-T)5*=PB{}glQCcwr~-b(^jC<3QQt&mF1&(94&CSHxULoS6+IBGJS2>s-Ve12yyR z$cK*GAq8lRP4V%LtUcX#RY%u_h(LmD45pKpkze1!(&<%U%Q?ws$ZAwkjd1x)z|lV&G(;>cA$=)f zhQ2I#zV_}oJb}R|=<2h9?cO%YC(kwQizYIWL>Al39u`2uTlbU$xLIADQcJ<=YFIP8 z+&fHWR#tlTwlbG0&$Sjt-R5puI+nly$f-MKFVcQ#3;fF+^Zj@0qhr(QRrZlog2DW! zfyEq9BLYX1!veng-fZ{`O+0(y!5^PBBtsVr z$^}`llt!LbCpg5d>!h?A6Rl;T`!|JS{QG<$)U-?yPk^F9x~+gQZDeHRfg?3{yo-yo zWE_?A?i~I3!`j}VS2P+!ER`x+q{sPXPB@;7Ia?WnUihXyLJC{q*atqxH6Ca<6iVb3 zB_bwjG7^tpy5KK9vZ640soP-Nlw)~xE)wFRfFRoSHIHDa*-ccK*{!T($AW{23CP0H zb7WMdjo~D0YM5!C0!v@GpQ?pOnp?kB?L;N~T8qZ0V#nU5ADm3!HbcPQ^awvWf_SIY zt?!G~>IIg5&)BVpt~*>2agig8-9(G5-$*66L-A%hVPS*c{c4vAn{oOb1p~V3U=X zge(Xe!H!Cb2Qd3=ncsZ<1prlVWP&Q8~D$grH=iXYo0;wV)YrbxYi&FSL- z0SM%}b}nj-loy5_Vf7X$#=tb?aI%)VgPQ;8g*GW!PDRf)gD%kj(CK>6ZEqy~o}x|Y z9_$X^=pBzr{S@qWyt!Xj2jx0~h268u>jqm@Bww39x=1`|Il3DAx-b6Tr6<8|*=s%mox-HVoZqahzEuUU(pkU_HcfgoP+s)S)ow>{X%G4eK24g(lr-pwL zlj_4GU5X|P>By(bkY%IOO@4Kz8<^bTm6CTT1_ze`rGr0y|RL<_VC&1uVwaY?@ukK3a~d%V!* zM+fX#t*wI3_EL@+qjo}UK^qk*Tm&0X{(~x=H*rKz>ouFiL@Sp?v^Pp#aJOyMP0bW|x1*H(4nw^dciujSGFD9feDLA?65%K--{8Qw*_6q6mv&x z9D=hrIh2l0>_4iQCp3j;7l>_I_-BQy7j+UA#7-mO^#gxd_#;IbR5wU$U~YHj_9@|M zbIaGW>0Q)EAtBE+Do(@E;{r(v!On8GgUN0y4yYwo6!F{ym4Xq+eF(rFoF8!+llj%6 zN`Tcb0;ZxrzGamt%hwyT#XxPgz^b)W)9Yz}_k5lI1B%KH2O`Q_KsZvP zA|=hq{^t&;ajvepICn-yzKD!OBG1lk1C$W1L)CwJPoBO_Sq_XouD2WPZ*FUK?lz0%(wmPj&Z0Tz{m3W_hIO5{(xzySlP+3Z`urh|$rrbnzwt#f?HX#qIXqB=kE;E>c)XNG$bWBFkUaM~?Y3 zG0QVZ5>#aQ_jcGB82yXuN@+cZ);8nT+BN#SMUmT#I!diBw*lyljg1zHTBY-lv++0r z`jn5Oktg3)Pl_LFX1z+r5jZ#GE(hv%i1O5O#KA)2B>}uli=K17JE| zB{kpv;DtoOeb}{oW)^i&Q6pRBx*i1|otna4R1C24CI@bwfifA|F5N3&be~W@OBe5P z(L+yn-16fLxwq*QhTUr88?E@qQ19ZEZ=)5*_V*E=|EeajgNZ3g0GE0lymO3)Aj$R@ zkB!qPutA5>sTu6nv)wnlXB-SilGuz9QkJw*(q4DhY!(wXcTku;x60pXRWjjX^)-Uw z!vI+bl_xXBW3Mi6)+y6+gN7IP;W};)M6G1%Bs>C~3K0Q;nAgD6*|{3hhHb^s>h)Bq zS|fQ{`2eJ+6^CuFNpHIU5r2JhA|Aib4QQf1YzX; z$GI}k6d4ERJbXXv{QLw3^uIMVQA!pm(={Efoa9Fm61Gynf`B_wMiW`u+PYq~O!#$D zCX~%(G7Fam*_`06cKa^ru$W3DkShf>YpJ;%@Qsq1gf-R_>$G^Dt;G>IDx#s`)q8e+ z{D6Kq&o-ehd-IE2i`^zu!Tt7rvl%{0^1KN)IACvQ?>OWw0EOAch$`&Lf8=e}Wsze) zmF&ZbzvTL#7NE42*5qE}R(I(M^d4}LJyUf@{KUK-wZzz=#CV|+gY8{9OV!Gj z_m+#D^!3rQgQfAu3;74~XgTa-s5B6ekvZg>Tc5wLbyi1^B>}*03I8^fl(Kp|V?H83 zf?Q1H@PB-}0XpJl_Zu!|Ubox&d}Jc~7`rq+v;F}WEmel}q-DNnDhJPd=Yf*IM0FOo zFyYc70E)z-GRj)o42<%_LL+u3EM6nm6shfvx5V`z9B?~-*&hZ29}Ph?ppx0A@^5;6 zXXoWzTv?G1rBe7Sbj8sqd=Rgt0|xp--xzF!woris84=U0@C^bA3aT29$D>dSI|~hl zkFe0=9mDSgj;mOQ&K~LHDYIiddYe)}z{_C*Tv+s=nIGE@5T~b(&3$H){p-TBdWl#% zQ8CeeBg?nrZLP(SP;k#7<|m)Js8Wa7nxl$8nC)jj)E-RJ zHnb42D-x7n2Hfh*Rht`|>od8H4ZON^&F)$lWb@(U_JE6))2-xFg=)`QsTAib01hmF zAVZ&hra&G1tKY57Y8G&M*joW3T4pifYMRr@@o~vMFfj6ObhWOrneofa)R)fOSn?P5 zetZe4Vz*E)aPB7sJSTvL^0~-?blw?yHxvjD9}LV|G;@xBT$%j`g1`vDFS z*uu*-P?z51F0(FzLAvRF^+?80$ye6W@}J3EgdZ6Q4@gDDaio@8N=h;gLn5l$vX6bu zujo7HI>K5muIFCxOW;n?i=@y&Bf26t;m0Omzn`o zPS`rD*-u*WgZQkui`8Je)mN7dP9`Lrxu|8mPQ6KI@hXM#ruRPu9X}T-$A3TF3GKb! zAFY3weA`K@Muka4)aph^YM|)!o^YGijJGZGvoWGD&^;H9$i6k_0LhPJ3LBS+j8<0E%3D{|Jnjp zl?r}YF5A3d`3muHTxxX2vhj{=qQXM>9EbaflfM~!m6cK6>i8CU{@p7oz1j_b@nJ^2 zrg{(erA8-`O%B2%KJeu)Ano5Ub}feE4Es1AQQTx#-pgoRt=Q#w)^w==#+inRULcYh zT(;YThMFuV?GELuXa)ekVdxv-XVOKQo#6{OAHHY!w2l2R8;cl8p_~aSZR(Xy4ipN2 zeS@x9eRT2YZ>6kU?IYm(d;V(gip$~l)gw+tT?&h>`;U){->4;uj2Oaj=_e~}Wu!Um zW-xtd)#$=otvGCYm!oWw``tL$nFRyHF>5D2mQ4ud`#6RS>#SKIF><946qD0Y>v^f!8#6pPcDf0c$;Jw$IDIJ!7NiI?m(A zB0U0|s<%qpqz->7EWOJJXGal0T%H3!tsNX1ye!mozI1KaP*T96Rg{;LvpZR!21-&V z-1%VQdmZmP9{llwZlw2FS>f?!B4o&Cxb(5hofG?mH?)9}SA`}n{AOqVicq`RJ0+1p zH}~D6p&t;{c8l5`-(bgI=OV4=4=S`GPBQ^=_XGtJzw?1}-f15Bt*}VpuqkN=*-II# z3w&qiSPocN*kTTFC9}GE?iaw|+4Teu&j*Z3&FGyWV7t?6bo1buvgIP72F24-Tt>ZC zk4=&Mh+Sr#{Or!+8HQ|#Qn2a-MI-_e5|`)wc}IiNBW!l zv-hXdhwG%L0+xd}h=?3)Z1wiLbL+iw-S-;5jFXwYViLU{*RY9yx3#^Ppp75w(b3jk ziYwMel^S(NulVDM!mdi6LR-kv!g-dU(+vG&G@!1dL&UcD*SSVSgpH2xu{kGm;dMCc zX1pGs=Ha*f!j=q2B4rAUq$L$-u>Nm$5SgTRcFC)g;}YTpKl#{ZM+c^3t}%)jXj!b&_RTO0W<3xm)jmr z;Q~dV-eib0_l1j;`lfm(}o zKVZd>|IRLzPu9c{qUGgkqsEkxR@@W}u?JKSQAFIf^{xIm7~IfDv>gma^%6%K+TZYX zj3ELq7k4!ru~jn6hQd5hYKnbMPj#!EYp6JVPWHAgqz=!v1b>tCC!{nv_Tn3nbpjor zP00k|pQ=K^AFjLB&!b}UXu@ed-N`gB)J=;ISOw8X!#`-Z<87LN!mzr%%{W}Vc6jLD zYQAb@k!Zf0@8R!RgM^HPMg`z+JdNJn*rHUTs$S=}Z)hWOy0kOad$Xm&=?k0;X}lpF z0Hhxv#CKM|0U4|3iC-~TKu|)EWYahqN9^@SdU}Sq>9radN=py3$Ogy~x!g#oFp_}W zTNU;*78zWq@IG|NfsEt-``&k;%Q`J;&72{1h{m!8PtPLRjfEr_mvgh!1V4uYRD2&$}sK4ofR0`uNZriU?vB#3usGgr#hoJEp%QnJwScc)T1X_w+8oPEIYJKUzYxmr2iPB@t8wM`S=m_PW6d&go7lW>^p-jmz-qmpT)j4yzWLAq z9fe5$OuI0pW}W%L`E~yZrkUuduU<-t220jrlm)>)+w3nB949V*kEIfo1Xjw+fPjGP z_T^07L)X$2WFlr}IVj=I(sCpC=m9>OX}&kwHUk{zPh|W%8$veM`Ecj@g9I4u5?zSv zwORxhhV}!lMOqcAD!1N7V4I5D!SoX}l2xsodpNmDWU$J7*K&{|KZa<PE2bvRlZn3eUu zYzg9Wb!fNqcSIqGwmj=X5ODkG}aq*}OKF@>d^+PXlRBM@- z4novQdtfuod-8BxW)_y%^6X{e@WpDEgsL;v>drBJU=GMlF?mH`2|x&MaIzuSr%$<# zkz!tMWj~^W(0VVrjAK|}6~8NPS~c;s8l_Rxc#a+q2~!U1)i;8-`L+v2aEDUj-#3>= zmy;lnaPuQ0DZ>GxSSotYpZU!m&!E6SfZ|qL&X8$m30!}DJ9vCMq;=2hax0WFEtA4t z7JR)mIR+Sa^U)B?_vOi?ww{J%Xgl6~W>@S98T%oZ(cpf6>`F_E2%rYAlO__MQ=TvR zbM2QYxckE~vz=+pQ{-GTdDgeld1-O_-CGE z3{Sf*bR}IuxeHO@(I!H&9)M{GpMKvT1OK~_@4yDK(DirOq&d#YWd6=a<0uKYGA$9P zhE`V1-WMHZyzp-3GuhU=7%;&R9*=7($&-4n=sfP%*8n)%E(|p^gB~)!`K0{)vfVoozlRQ`v9Iy-K+?H5Svk>2Y~^>iU{BY#UwuRi;Ihv>W|t zRe-fPF|pNr^W=va>d`W(Yi1&wjV%CEp3aB%=hsPwqKE_Ke~Q%@+#Ar&m8tYRkB?6l zn`%c=IdkePt-}Y4Bsw}sv&4t~vg5IgaA31Jxl(Xcx{!2*%bl*n#dW{a2|O|v*IZ}t zB`@IM4J`5@9`a^98p$?@U;y(j{%M7tpPzHNn9l;d56s&PBtEuMT9?T6#%5`0X{76E zLgs2s_@^kq03<|bGdCFchHT^Jgz8$WNXUjxl3}6Bs8k((lZcE=TW7KReBSM-JIAU7 zb-F=5lm-;|@}=KeYWZG;EfB**e3pv0=N5ODdut?UiL7>?WBXX^o4ojZPUeP&7WG`X zB3uY!DJ?zDuXh1|ll|i8sC~zutUbgM-8`WJl+tuQFAX5e8v zcmVK~`6w`uxYJWpKv^Eh`#obSOF(I=RpUC5`N&&)#5D@+8FO-Cg`^jm-5SgeTpUgf zD?QtDeHeE%b^fOXZ~*#MEaA=VZ5p>vgJYf}DhaimrbdPD!xD_RSTr8mNQl7L*qBoO zP%+?@n@mDH#y!;1(z2>_5;QkAA8bbILPy)VL`6VI=QiqCH7bBEfJ5vB`TB(tqHMad zFcZobzO0`@q2PjPdV(At9_{xiQ9E-DGQYQwH(}x6gOv+s-Fvs`b5siDB!y$gUA2qIG%gbsgy6x44&@rduR-wMIq$!-dgCMrcc&?wAQF{ zKIBx_(kix11I@cZ=bMj?A9eO)%=^e>7MiarfM8eGbe&3!>4dwY*2$6u3XY@k2uFHJr9Uxy9qQ zdfEab9-YB+w^tJ$9Bc+K5x@J(?=3%$&A`rngzvr;gc3X?(jH8L~&lr(SpB-%z;KLA%Cly7wC+Z zkLOjz8M;4gl?HDYEb(%^XKyo_D%2SooK)75@|pp*xC5gy*4%P6an43#5s?oTFPrGM3NagZ1epIPEU}6R<)j_A9I$(g|0o#3fAa>zVQI~xN4`ZUO9{bQkA}y%_ zBD4-*OUzQU6t^@jRu~06x0Z$`5+0AK{1Y)H$`RE!{&GmdYpxsaS|wA==38W+J7{NQ zirYXub*1wv52;|6g z$?|yNyzk=tbYGpPhYvBc*x74jM!lBG>U)+(L1ks-8Df7l*LUi>qh+EEEaZTx?gR_z z{Gumatnv_^!NYM?hF^b!+xsS3NNdB zE%G2cpE3eDv|Z}J2cbayy=HM~qH8OGKF-BbrCUa;?r3Ukda2F|CV9yGFdD=mvp_^| zu)ELjc7~BCh0XiA!$WD)fXH-ML%YWD2H2Gtjn7ZyASN$=j9!z4roRn^0X9xN-Oo~L zO%F0hk9|{u1NwM>io*pP{o9=r*6VqI0VcE4 zBrvKWpbP5+?*TLx1YpaJ(jBXbB)`izm5q%}(?SDa5i%Zn&?Brc>_7NU9+u7n!KC)j z1<8^Eo0_S{rpIgRMwId#J%HT`z*OGOCe8Gsamb3pdg9IVcHo_iJZ!X*Z#$^DT#Yz>I_LgB)b?@Hjqo{y%NrQBQG%Q+D zknR?c?rxB7kS=K?1*AbzT0*)@8W!CRXDpxnf8VpuwXf@ZIPc*rHRoJoj&a|=x)lJL z&lwzkbc$x@>^wa+)t^SCJa|kzDd6JsiapZK<4)|$XH1M9QJO6!stAjPfzH$TiVD)X z>o{opGc+{$b7k>A@947Le*AbgefW(3`Jk<#h5xs%;bGF!WL8l~lrcoW;kRY7_I+fI z1_CAutF;x-pdF>M8a%J5bw=L}Ev-LT7>;KMK@g;ztfU9B^%9-pFR&)b7n{X)nbe#m zd&<--fA86imi^61n@)^Ug+ezgsyctCqheK0_KJLWw%>z78}(m7ZZ2sTh=z#@k^2`J z!}$LcI|IrG*w=pxw5g#Y&()Wb)aC;*6)=b^_aib6m&!b4uGmU}s90M-aeSPxYo^kMaR$K_>0$}AK=`_?4(#}-FH!$$ zVQDqrs-oa+KCVd7DvY?&(D$m zuwug==C5b;CPkS!als{NVrOtX(X<%%Bv((6P)89F5T-^aSx2VF$Hyl(=@MYIx=&+K z5v>@q!Af{vs1!T9(ZPT)2)%*BxaYdU@1)|P1iULh4-W_1e?0?z8A)wIUBMoS7m;|h z#XuUJyZpWj9Fh=^VCe~5iUdn9)=ObzSSV8U?1;1@3(+<|gVo7fC4o0xf{WZ@l$B!6m{Z zctsMO^{-PX4&RX-OTX>YA9P|8QIW8Mf43EE;<7s|ZFi?Zb5^otaUB#CL^;cNY3Xmk zupgbqqx_yvz$ZML43HS+)@AnR`}1fvB$?p!zCPJ}#r~dvoznE;TO2)x#t(};DSX2c zjq-`$#8b|f0O6vaw7CJm_83*TjdmO5rbT2H z$e4>H8k1ZoQK0oeA@aUi_)0WewKw~NK8SbzN1I>BmMIA zx+{w)h0%L6bMxYtt#^;-P+u&9af?;jf{!sc-qMsnKn(0ulQN&5{~^~GKYKllj!6&> zBLiMUr?~C(qbTxZyCyF!&RLpUf^-dohq%c;I-s9=b?Rrc?EU(c=l8i|1*_i z&pu%ioADKszvx?MC+GiSVgKv>CUmvP?lREL!Yuyq(9A-r2kfH9P+7qC0jRyrsFN)! zb#ya1Aw;EFtpAh5L*LJqUis9f4?Ah>TPz^UVbSx5*0p>u^o5dUH)h%apA(UZE1=+tL(u7pQJ z?$WPV762j)Op?G)4n#hB*mXN&Xc+B{d3=OC6YbqXuSa%{l_^9!b$F{*)VN*I>>*(w z6bM*Cp^4}mFxK>J#3wlTMXX}K9|;y4Iul7p|5(Ru^>F6B5adt z4M1@s!X7iN&WIo=0SSWSv)U#Q2;RScPcT(htXK7Tgm^be3r;X}EWS2Wb|Bn)%vds9 z{%M3jP-Qw0xdSIDcd?Tq)CO&`G?|L>e!orTbu{_VtTEC!-4(vMOglzT^^9#AFmZ~iEk3Bsd z{8DeV-JWSE)mJ#IPiEXM7Vv}9u)IC=r?+0K1CWb&+}glJ?=6BABmpZ+Kw&}&>Y_N| zN4<>d-kH3W+5tYWp#a-P*=&RN++KGIaEfV^nxL$w|JH4G0WHc2CqacSSD|!w7459= zHqQf>U6Et&N521H0jd4{N|{VSjhZ`{;=sq?z1}q|XPqve#gnVOyuvVT&Fgfg^7E-X z26?mF`HSjF*CNFxkByYwus4RIU)}-Jngsu7Wp8$iwi;RAXIO;+UW%O~O2>p}VA*;< zoYeMg+)JvTyZif#Le7a8m4d&X7OF9-pTwTBbH}hyFb<}0I0zpyr))1+_5re(oSeed zmv>o=GpZMO&yw;X9#4Hs)r;-C9yfD?PK|h1^KlSRgHg75Elxpz z=p6a-mV6)2K8j(2Ot%=@j0k65S%Rsb;;m^k>d^w=0j9WJ9t411CCZtAIOB1(`l2j- zHMcU5F_o>W)=pZDfctR$tdDmp{k^QL9%LCCT)+@?xv(@gb`(TGfG*MBzk|zaX|~1E z^Za@lq(PasBY++OBq4x|2l17LQPJjR!TGJBL^k7atio2?_!I%(ohqE&wI8(~p4?)E zxPBmqK(rG0-EUx+kIbvzpJkq1*U;4;F0}#NG&9+29CW)c*M}MO7XfYpP`z%aDR6DZ z?;o#zRoB#U0e>mIZiz-9`P1WpuhsB>WeK^sjaBvpY0H+N6zB54;^l1 z$~6Y`TO5iNFWcA?_ZEHgjc$_=2)b0y_SbAd{)}@gDh9-U;rrX?rx|>Wwm08`fRyKV z2Cx0*m+m}C3ARIO%#_fQHEiGNm(FO5n1G}p{>56OQjNpJ z&;-8+H4vqi#qaI&q3vMVxk6M@F8g{hH??YLIajv68>zr_@H5Fcv+XjAPSwp^cOKZJ zf$vVQ&O!81Q1@#VonryAJPECQ%IX;i<51Hb&YS@WNe~W3e)6{l7AMBT&)so@*|(Tc z@n=fSV*NoUe!sWI@Uo>vXh5dZVty{4m6f%wss`J95#$vBc$gRukHh<-Vjl#-D)= zq3k7JcwG^Tr3){ZD(9B6gm78Zq!$Z&lTwn~i zz*ZprpC}MziR$>L76XfOFr^v~1!(QKAQR;eJsZ})iDsRg6GiPj9Bf@HaFTdm18zx_C%?6z36!jKIYw#i~LvOx00_`t2>yj)9c=AWs%3-=P$ z;?OV$W0{*<_`NyDf4!&DFJPGANg}K059W-yF2-r~eFSu65SoJfJ42~hs}p==;4Ex% zDmKjJuh1>Wkx8JSBN6m)IDE(h>y>DtEu^llZmq0I3A530_?_T-j$MuyxmgOA;|h4M zZ_oC;whjVhwO#Ypy9c=SmX-i$)yq*8^TfL;L%^3;CkG_;E$2|sEH%4p1Ey0(2C4Fk zl_opsTz#YQ{6W(Nk=0n=3V>gSv|Nc+J~FH` zTpt+g$ThqkLri6-<-zj>`FI6|g|K%6qyW0R`9NIk()l}!?8oo!@0VL{uWp9s1>q#j z-f20S$|l6gfe)MWMZiL{^(9ClvQVptG}qQnCzzyvSCaE8Q^1$1pzK?M-`mN49B`By zn#WeT&88K~>5phI0Bi+qZ}#Iyvrk)z%o$~Vem?XJ-&0tf8JSbansmb=3eo;h|1DZ* z-9G?!#h3CK{1CykD3$;RX>CosV_Kq{X8~ULXcD3Q-Q6rYBM%(~JaqIT1oepuZRJc^b#?gRw@UkQe>OIB z%cP%2Z2&oAiGGXcVNO%X){wSqON+N1=bJZXeW8d21^w=?hO>fS|Es0BDdWMT#JG=z zC2`>67nhdKH;81AEJFAjKnY;J6Yd;$vKMD-I;2aE3-V7IMOw4~F(#Kh^#ZFQJ4ioA zht6K^#kRWHW!ujhl@INf^%p4z)|*Omn>S z+JAc??cl^DoUZL|lYw2+(-nFI2dUQp|U{3)k zihwQ)WGpMaO zZH1tV9bu;U<&x;pwzU@L4qzn&GUk#2ZUf;nU#-Hd**Y~n{n%fd4mc@R({z4=z^~%cfk=FH-!Pkmz#D70 zN!)Y+Vk-jg-VSnk_FUDKrS)IVfhdr`U)6@pWgw-j#luM~e6(pf6xJOa5Kb}1!3sOf zZlL6noI@%)$0wi*>75!IQ^N7Oe%zgWq~<7?>{5LHiv;8*;RL+65vAfcyJDUxA>|dS zm5YbSLLcW#78m##8I^!mRZLuh0M9I1|Fw%_JB!BVd;IROHvk-b*t?ct{cRX0Jd+cu zu>L!dBGdhI93DP?kB73G8&59$I)hl%A6BxeY_U`b)uBtT*2+;`s^N;WI zv&o8qPkd+F?uY=kfI*+)MwI6lx-Ily9g`1wz`qf{|EIkC>^)ecEC7xo#`zbU`rr7V z-8fmUzO=l&dQRxy+L#vqf47V1qb$Z|MWnCF|HtoGeZ<4Z_?7V&CXQoLnN% z08pV`Xkx5QXsPJ1dKRQ^t7D;Bn!qVTO-I-4wO?hwd*JXjLcKysBzOHKT>$v|#xL3b zKD#)0r^jrMO;Ys05`yX%HyE(862OuzmRSbJgKymTM(UUd2h{8LA>ZA)J>EO*jKK8E zqVO?k*x1-sHYOLNle&ZP!-6o6R*FWA*W$h>+E-gT%^C$DH`^#3H(?QUJ%*Mwq1i08 zlJ4Fm_9ruIm!ycb0qE3B!;ME7_TuDz>{}S@nr3;;BNo!DppeUImQATp>hTpo4wl%0 zpfKE85`|I$U}%@?uuE)Q0yX#JZ8@A2{u@@86D#*R6oxHzM)j9i!(gaAz@i-bCgdOF=2+3@m+?c zGwU{1+E4obq@XeIxao>LIYMCr5GG(DEwDHHlEm+!^OvOaF8eG6p#P4Fz)lD8%4}v= zEg)S2cq9KpAJRRr4FI%KRH$HgW!2KUay;sLr?Z-6e;#aoNy5*@%6h9CdB{q#DS1giO|5*2MC<^Ny- zdUUp#IvVjHtCumAPu%F?G`ZqXz)mH1SvDv14c8{iE7-151{x~km$llO{^%;6+1aQw+P+t@!F|GPJ9N* z$v|Ww&vJ2aU=jr-4{Pso?&X_zAs%T^m65Rox>nF4lb>v}yG4rq!sJ2(Y}>!fpB3hA zh!4et#jeHG*AE94J7V!TR(3qMP%G7V-L^PMSZ`mE1Lr}2PvG;g*W}iH10p~^ykL+) zqC^F013X@LXTa!SIbB3l&7{>V^%Gbv-j#?@8NERHlpvC+*ZSc{PZ_Y|Ecjjp{w-~R zDjW;kWj>IKEHyeb8w@amq)sFgIpJR==h{GLti$1{^g-eC&6npV8$*fggi8PHZmUEj zR}94Q8m)QMx*Q6zIh`fv5lW!#PnWA=sk_D;D{txjaL60}!`dlyaWMO}>okRd@Ua`B@3DgwJHq*kX?Rc7u4FiM6qRBmd0tOP~QWb-5}s0^3&_K7TqBW z98Qv_NWceCDq#R*YXCT&kO9sXR+BMC)^bV^hFhsA3U5ukp@9Z6z&wxo@KdFh_#6KN zGMeLYjKsX%_3r741s$Tc%bp{!cR-zcr-les%TxfQ7SK@JJDu|iENDrT{VgEk0TSK{ zz9o@@H@=&Jz~DnWEIgVmw>UTHYOQtrkf)tCp~gzuF)=}@9&N=s!0G=&dzS-%11{#; zxf(0>%A3vh?)8Tw$ABvN!taSe2j?G#!mw#P6!#8xvVaXY3yjsEic7xS0-tH?PWo%v#aIZpv{9aj8bF+Tf@wh+gV(~>0%g>p`4lG2h*unahv$soJT2 zHC3TKz$`D<*flwMUpp{g^;PD>O?>j@ZABL9PY~(vZGr+s;=p>A@0skPv;YE!V)I%& z?l(c7iqDpBc~h3>2NWkibq!m7%|W8d4}1$P?LF3;y(W9QPYZX8T%J1st@(IThRmzM zk%no2c@S_GDGS$DV-oSYNH>})kH0uU24V}Kh>2hQ+&{*qT{jp9{Z$}kkU4$uc)|V> z4b6A&ZjVH;%!b5B0yRP9@WE7H-`*1N<;N%#1;?!*b@_7kG#OOcaar8~Au*uYfn?iq z+dB_l?UZS8^bDvrncy%nm8CF@H#<)Qg-g$Abl2fj#%&Uva(E$}=LKB0 z?xu+e0ENQO^+IAYL5Sz^Ol-iaCR?jY;gHTJT>X0#|IpArPqbCSLJ6gP%_L3dL9GsA zA@^4-x1ar5x+V#^Z7Vcd%sbP>SxqCNla37gsiaaXe>Qo#y`s|*_WliO|DBy(=Ut-P z@2wg>X&lAJM9ZL`AI{lC>E#2ui;Oq!Ty>>)Lhh6qL-Q_K07E#Cl4Y*$h*#2oI)Hp+w$gi`Bj`j%Xc^50vja$xK@&&m>knj$l{TGq zv+O;VgTX9CAih@a4)O_1_E3ot6vZ7(jg(8)z>PRuANg)~j(G<)?oZLXLG}>3pDVkXr#sTInP^gIfskT|*RUl}@iG?zrBhAQ^FyUi)RH`D;$fnOX_yzRD zOt|86+nZ#}O^!UFF8A;`N#l1uxZ_pkXq}PIXgoU3Bdl}Z@Sw-uZ#vU%GW7YTPmOp- zOLf|dKU|DOD!TEsm8T;AcqvAB^?{%%K&U`CnPuNiQl$Y->^ zE;vpPFWB4Vwq?D)>t+yB&xOpCdcvz%uqu{Iw=^W3j$IeiU7#oSxxKj0^Xx~#!HK>Tr|-YiYVopJgm5nTUWG!FZ^lNxTg_J{$AGCk!mqV> zatsX2de+4IQrWdzo82e0`_#)9oqSH3RZk@V<18`$L-|%Mm!93E16joy7}D||LpF4H zT7+E7iQEv>xn1AkKjQ0mgaG>`h$78aVlzBj2L|>Cyis6&Gw#QZgPIj5T5{D|F7CDA zUMEHXU(rpReRA>m)_{6OmLNAfJMi(9HE4ZxxCWu9hO2q$o=v~K3*?B*Ei8UmZFq20 z9z9c>?XH(D#uPsm_Ztz>*Y|}EUV}2U!}Jx?%5P$AQa<~sp&{aG1H>Y$L>6^=9?Gtn zzU?^_$>v5kRYikD(wn&`ZJ?S0bS=K#g`NOJy2J^NrRokO2729tL{?~pdtNZA2O=tY z#+NUJLs|{CTOOnQJ#pfj=Ld_D=MqTJp8~fJ++6#nq(c0wd6M$$nEVw`RfZB4UR|wu z^@?Sfg3DeM{`>?Cv8cp)s9=uXL?HnAX>=QL9?lk_H0gFTDIw7*8Vw&>`xE;O*1!d; zSx$a#fgVj1yw6(BS6obwb_HsFxTpSJUS5YnUV$jZV#nm^(=6Q*;3Otxb)MvtRJBx@tlSr_;{0!7(3`6$VgFm^UOyyYA3@u0ijqI^KLwNQ})D)!RR` zRCv&Btir)!ol2_^=6e?#U1CcCL7y3rYC=mmU4|f?#FQ3>Sc7j_erFJx8g9~U(|{cCgjTF2;xqEDyYh(M7+cIw zF`b`&8oe6K@ZUW6HNHhPDUZ{t0i>VI#can)RBaO$a#<}`nw<1?pXv!2-bgaV;IW$Y z$L1AwaR>$gD5_hXL#yv$yvu_5ZXpBm~)k&TB;tc2i_oza8Xa_6p6T|6A_t>EqZV=;ZYGCmiRE<pE6{un0idu%8PF*S zwh)Isr(_bi(Z@Q&Usj6#{f0?8kC*8GiZk#1#@7JHv}yr*O(-dvO;${JI4Tm-<;8`F zFH*m9!5c{d9OwS~CN2)EsV9kC*tzEnuHW<6SUnavSefWINcDu;m-X(0b8P%J{%D2- z2OB&M(nzSR#IT?=l0=aZ7#wMD9~t?lmfR_Uc&?te$C5hdd35?nK&9m{nTp;h1JyL! z)^O%RmAh!vHB#c0Qliu4QIXDC+qA{lL}Np!`Puo3e;5~2`g=S+Y%Ffu#qMR1&Rz;H zk>#|z++xwYoYvq)50r8+Lgt1U?I|R|;o|@s;656wspV4NJRQcRlgM~ccDTr2%DPpw z0w)&3t+xV-8FFvo+OcJ!4`Wv=hA&j~JkN<+T{gU;_*NHxCJ(0JoR&_TZz>c5F(v>B z4!i{%QL1Pr5GpmA&B!GHjv>KMNu>cK<95jsLwtH|PRJZ$rVPW?Y9jk_entINU*(Y^c!HtYw6?6^5ugiK8+T&=|Mq3zmOyoJ-~bi z;L%Bv(c-ZF^lYVTh1da+D_!SWx=g=?wmgIFsNbu)(r;-RoJt$j!_{^>Xc( zzFZI3VT>cBCo!kRl6gaRv=uldiSLgdn1Wue?@pBYwu$5=Gi4TEW_Z?&wRKkTq zU~;%}XmY#x)Z;LyvfmjZ_dE3YMh6G)R+iz z?o&Uza=qh1w5s3dyA*#Lg76QSKLSK?{^$W>oy=EA#PNEwN4Ya1*!+$;fmu_>$ebeh zth-15tI3-7v|L(xI)#}Ahb_Giu9oX|qj6`|IbnTi$RcYFDdAyIpF!ojZyFHoJ#E~! z{5i@%%Iijaw(q!^oPr7v2;{5GD?&(7^3_v(A#-*W(jWi90$#P2!e8t@=%UeQJLa10 zK(9~|vj|#0AKwf445D9C1k)C3?LWF~x2MnN%5w8|z>LN}RQvly&nY!Ufq>AKj#SJBMc0pG9L6Y!-V}z4GPu9d?}v-jmKwbHVYF>li&$a#0_{r#lbB$-+DeT$%Y?X*?!7Z@GxGpFC+spvkvO$l+tjtz<`#kQdvtw@FZ{i>Ppik+N*oZYzc*Jmx5Ug% zpqt@|44AUWn|hJ=Ggs>6OTfy^PZ^mTRq1v7?S(AgMA5j_N^>z#RK~XUF5OE;Mn+Ct zzKh23CmeMpz?!RHqaMN>pFxeC{e?nfMK+}6Y`t9PPM|sth9gDW3p|^9BZYtDlK1F7 zGGae8@8s!q7$;@1>L0_NHjc1y+}K(8Vlmy(O~ELU3S%TgemTv8i**?jx7S6&`WV9CGARPPF` z>;%4vXi7_9a6>53<5_;g9#wG*G3Q34+wrkEvVP;-^Au$Ka+H0oD1M>`@Acezu7qCz zUu(}nUHJ};zL-tZi{pq{JO4ddbhU6%hFjdRF8KHHMtk3XFJ;awrv z0!gM<6w^r*(s8p~7kK@C=zZ5EzZx=*?lm82V|(bRGUa5f@=TmQWVuV-EToR9>x02n z@!O8jtx&zsh{~IesQyHVca6Ah95V#77*uZ1+~p;-gY8EjL?B??KLL|00q|0$ z$%-ZbXJ(mGZMHsB3}sI!B^n@^hVm)pdW5v?j2DV?VsAK{Xu223X4Rzpx}Io6Dk0PE(HA z$G+Bz7<~gA@+IlRw9J>q0~y0m=4I8A>Mal${?8#n=GED+yl&Yd3h14$q&HYqz^bfS zkZ{rp#B=TGRK?MdOg8Vz)Ot=JCA4@BY{Oiluec1FCKIO-uK|F~pp- zWW;N1HShpo?hefHd!h&7n80|FjS?9KdLW=L5gXTaSzS*S;QXE0M5#x6mI;cRJYr+> zST>+{b~t@86y{XNb-iaqTJ}$t{}zkh<@TISH&hnbm`Hxu%wRlxR@Ol@FFFtmQG@a>9sBCuG(gmii%3r zz^|tELS*UrBciljhBBMP)~LO%ziHQI2WrLnmz4uy(}}8+SYE~#8dp>=^|Qlyrp#gN z)~4^&VCEjMA4p^eqaiG@t$(Wg9ceNXK1q0FzdeyNxxE7SSwEY-O{Q1P%orAjCOZU7 zM_|UHfS=JvO1m}gHyrIvF?fa-&0oPXlVCU)U85n0px0>E3skrgjMd(}^4J+Gj=E}J zwS*TDwE!*m1E=BfILfNZAM#Fq4{-eVe2r#7%G{#+=Ff`eXw*{g13G5Y#E}M9_#JQ%#Qu z=W!Pqx8Y%-HN`P0gcTSFIZT|KosyVS{IG?YCEHjZfXo9CC5HmYX0yqd%d|P( zRxARE9XSPscueWGS%-XUK2#dBSBgcW`K8}e`U*o_%ZU_RW<4sFd|>8;4#146`H%)- zrRPM<%!eZtrS{*>zocTlZJT1TU1*M4`Y>9Tpwe^`fq3Tgg|87!_Z0#;y1@F~BUUZt z$9g3WOL-iK4+Ey|_m3E8A1xK6IgjOs`@ zEX}uuJ`>Y1L_#gD%PXM$xZIg_?=8P~s|NEx!#$Q(jV&V*%SZELMJo<6FlSBy#K!Bf z$Dwj>aCyNAO!R=`3;6i5A~Pg}Mo;*C{=4`&piH2h9IflCrc|X3(4#Y&#H(KtY&|Eo z=;AMWhBMN1nNd&_<><%lF>&;8qROvXLmQpNAF}s;Z`Hui5V+Lbo3Ef4I3*`YTXM0O zuQ?cX?nms8j*k-yM+KTpA2E)rUK52WMSVEAxQFL=K1&R9kCct9MD3OT(&%wN)p(kW zar{A9vW(!c16rG2IIIG-r5+ZF5^O>x_j^#HbL=SMv*5m{p&pFN#WMW$6tDUv3Lt)dZS1J1}ER3lk~Zj_aNTq3ws+F?ZVw^x9+`j3<(WpP&*$y z7BDlioo{6EE9(!my)z@o5cI@I-`H>r`RqO~5F^EOr zTj#e%nWIJjP@2r^c$au#jWA%wu4(Lx+1q!hx4m7y;*>(h2@idGz5hLfq7iFZ9btk* z(4l88mr&oSLmrRsn|UcSdqHIP<#4bpUl)m8P`D~d>6{QY8FuR=z}1w>q{V@Grbh8= zph$Sg2)$*}p#9M2n)E=%VjmmB4)~A1PHL~biP*j;_kGZ&!?eO zfc91xM5NV!;Ps(fj{~h54vQ9^zFCPe{iVRqw6)UrN+^scM=NW`B^in69}MPOJs%Ci zR)avWz`*CFss8?4*I_y8o*p+z=)|{hv;qHahEeCy99<;$l@u%i#8n9I2c02`!cka!3fA?ug8+bMJwzdN3s&qY3 z%C(yybuYzmwk1SE)2yts}~4^N)l6Hn;X6q7tIFDsq?woFeaT1ocs&_B-n>^WV`oY`b+NDBO7S$uB{ zkH;Ny8Wqx}9G%K*kh%Y+L+}g>b$kB)0geV3!$(>@j#oJ`Lr_X_ar&}w7YlXy>VHnk z>n&~jcgnc#t!s8E8A%|&p9z=2Di}OIJDaOCR^>k(Ol4I>-Xvh)DEd6|ZW+;9h(pUT zX&}pzlG+VxP4g+2tr$~^u*_>90}Sm1?2G-$nS1j9Y<{8l1*W!bdXntwvi;)p^zh)s zvzDptgI2fAKgHv?tGpC1xK2PL+kLJS>N(^n_8%;Ot1jUK;__8xSTr8{^Q>Yp0&A5V z%`fZ&5~;;G``+vQxM?9awwFxI_LIQ1_>g$4Is{S!r%l`YKnO`{YC4(OXV>5uI#Ar_ zBZCx~cGD+AAG$1{Gz+{gcgPPnS8C!eb@r!nfdR3x7(f*Pv4>h^XsK?@mZ;KY(T(?m z03lrZ56eY4qOs)9OC~Km*4x!;m~feNmGf*IN26@@HQc|M-Gv19MHBG_srwv8J0ByD z#?_>Exn6G$4-Hql+^eyUeSYK7CG1@b=NX$5Qu>1&U5L!7ou%(Kro325v)|}4Up`q) zTbsdhYB1$OiFj8X(8$|_?YQUj*qf`=_HX(UOSuc>Nm?%3V|93|xouP~2WKM;$*!n6 zzM98-80NpbOA>IuAre^ui3I)040KP6Q*3p>3$3B@wzc$_j~V0M3%t^_jq&oC;lD{i z1c^9NGE{0K}SN@TiK+kYW*iBMd5UJJa2sac)a^3 zZjsvD#&0?nrtk`uv>ee^FeB5!QcNiWNm*$szfGkgOUO4-R#jFcBqyvS z({C=N>DD)-+Qauk5tXQHiu!$GKXlu#OsN^n{ARVx++<#`u_1w~A2$F5H!-2}d)Cd_ zFNUctpNEMD{zk`d%QbnDhdE}{VbQoxP-22k3v%Cmg2s9U`hU0X)mxCsQxJyN0^q(J z>3n>J)N}X2=d758{C37f|?} zlz|z4*dZZjP;Q+)ZJ*n6hqg}ystmcS5rvxHHH@1NpJQ=lqTZCd9thf*9h#h@sk}cH zy6pM|Y}w!DtC6$xNBN_}!(r3JZ)Pe5vM=%OzpTPBAJ(pK9w_E&7&M24B6MrSNo^dt z7$Hr&SsQh*fx*D6AC6LRWK7f3Q^{Y}DBMc*oU!Y@q+5z2UAEoMA{8>}1Ce*FjYQ4q z&=4u7o!AkzaXPK*fuu;a9&+ss*?P6rc*l|5B@JqW@U%MVF-v@?LVlDDwmu&&hhnLhO0zMWQ z)E7yFCr{rw=l|Msm$|gkFSRcP1Bd$E=6PAIm+A=1q!njdw%p{Fval+GMFXY?*azVI zh#(GCj=fU=o>~G^VG#&MJb9hE0^#L;Zwq1;Hj2+b03XnHED%15jFH9MM5CSpk^#V$ z&06~=wwa3(>S)mWd2R;6&Nb@au;FunhsZw+xuB%alO=$3L7cLZr?)_t9U6#k_vdRm z#6BN{?3nqJ8-ZZAfSbWBW*s2qrYS^@gEsR9+jo)17Y=C`Kumw0&PX5vEr<#5v5c;e z%9maN9|@S6?QwEs5o{GivfLE;y9F4YNM>4|Fd00vlaP>5o^&~RgbfyTXRHypMFoZt zjn;5ElbT`i5xc#S3xE!yk}ec#C5G=qF6nX!qJ|}$9o(#pPM(Myw+}Q}>~G#TaZ{FJ z{64bpjcByrH>@G-Mh}=-I%19?WyiMuaF$nRHYdFHr}&aoXCPS>b>tI?9P11Xg!lNI z?Yq9KS0Noa`6_f>UvP(SUK@jYWD8`sq?Kf-9;-{2WTk^BWiU27pcyagY+wr&bau!`8_SG!qNSK8 z*;Sy>?&^J$sJgc{Z8cj5-$t!WLnm4M{ks&e@oFEy3lEm+sQ0H^2A_Gk_wd6fA?xSd zx|G>Ye4|*h&kq4vkRU1Qq|yq|9Rv`iMs1PUj^{@*HO9A@$t?O2SshK6Uyk4|K?{8+ zxH?U$51Qo`%m+nsX+a<9I$5!^fD2;&xhm_J;B(8o=Wd%X`W} z;OR@CvdL9HePlLQ@;+4rLXfCe#6DMmyNi?oJ`%++kv3*6q-}QJ2c205(R1SQ)yM+` z1dlWP@Mt=F|56YNns=*ykbSkcMw8Wko@XKSeBvv!UTZYHOuSkKi*9SMO}IDTblzyF zbR%WhI8rvN%QFEQGwpJ{rbf?0vTco5Tc#qUtvr!JqBl-vj{V7C8X_S+K~Xa0$1f$= zo11AB@=z5IRym^5va%qVyIz}?0>oocFT7{*`r{2<3{Zk6@OS?OD$$-0bNAPvue{E!ixP*bvI4+x2x=5bHYYHPzAW-DMO z){ZDsr!d0;q0coAJ48+?GWMMpv&G&FjhyoZs#qNHN08w!)p!}6AY$-5G>;TrW&} z7FOLBEpa*6c+H z&gDuTLkXkd7t@K?rBfFEf<1HR+UJM!)(hK@e91on(H$WA7UbA~`rbe7xLZaYB(W}) zIr4#wxeMK!@EeY)eJH17h}NWGxeI^qhhC%J130uVB$n6~durhY--@Or?Tcqe{}#me|P#=t#Uef(5beTM(C3o1+wWGBmChFBci-y zGC65W%K(#<2Huhw$rjbTZer|M_Zy@NI4OVDz)vGphL?F zlRd}G0>6K_==)4Srazt!jKn(I{soHDv|8v5G%=!Qgrthkl{9H6hpsE;`tf>vI1vdVH2RDYNd(zV9!u#uK0amWp=sEl4_K0QTi_oR<$-GLrtve=S* zvj9=c;It&1C{-QKfyJtdCx81C{51ml`tI^Damy$^{inOb26lyzUOdoZk@3yfTGuw~ zzc_GyT%owu!AwcvbIiq~A~9G7E%LF0^A0P*d|T6PAt)PG1yq*{MLR)C*>U~hr$?7s z;p9ItS)?T1g}YohyF6I-r*c;q%3agExCmzQ*sp@%=xZZZZCOCy7zK)eKTHPg&>i>)K}ba%hqTdvJ9s7BSS{@BA%?J&F0 zF!d~BzV98#5oPzDdTV@9Z=kt~dN=xKZ8yS&O^xj#Rt&4^*fP?x6qO0W>vm(mCV|qk zRb%&VqaA+x*I^M?`%;Y5AL<;`HnG0m0RI3gFOGzxK=>MJCwn23nZ@6iHur1yZ;w) z?*SB5wCsChLQ#T zZe18GjD%m_M3zq2)hrs+8oNl>&&>}kwd4O(0%E?PJW;~--b&Z`+|CYzj7poi2*f+8 z*K|V+D8$7wSFbkyqRjEQ1&VTH=(gVHo2$`7&$j+F1%~A%%N5XwTJ{>(J1n_;8`{_~ zhp9SGR3P^@-3|Kn$y5jg9v+TINCQ*+)g6^#jg#|T$Z7OfQndTl3B}Rc!Or>h+KV-m zfJ!ZNX%uA9)Hc!}zUuigt=c8CaqL#dKd<2OYGoMeldKg5ZFH*_ zLMA~8&|D+@N4H4CK+wJMQZeIq8RsDkCm;G&-mE?g{JIS~3HA?c@mux*;Z(E8!G5qF z1nCi*p(VfA#GA082K1(8wX45}?(Qt`cr?G~q)KS58eRNx^e{AWv2!H#K$*wcMzwmhcnW!2 ztgW6^s;-hm6V>fBS!Xx?TMm>R-(g1Gt^RaP(yTjPKPZ_n!kxRzAqBps>$Q zrH{EcBsi53e@1?-ASv4SX1T z;1QV9xRxk|8UvYt6Ty~tZTSc5(~^}=2xcxPex=s+dbtO0QiE{q*J$bvfqXr%bO)C2 zEAKX$ocKKZ5k&MHWY++78TN8Q->+wJiD|Xg?f3<#S~_q6mDFy_<2I+ZN}*gE9h32h zeStf0sU@1z67%#sEN|vQpT?l4pBHGbhT|RXF(kKY$XGH*Jr}L?4*0#9`6KZlFA9h9RP$Ojjc{}6-`v;d-Lnn;op)&qf>$iuRI&y$9H*CO#JP# zi@~tUqjZtH!rY>fpkRNI4Xu;03KM$5A7T-u&h>;-9y`*HntVu<4i5h!^C&6sPD?%M)_(=vMu+j$oN|-E31b4_~{|kqBQ`}HNf#cHlBhrs{<0* z@NtQIT4_mrI^DNvXc!4eYu#3S!p(wLE_B6j&q~HwyS-Z^?boKd_E>%U;B`ECk2%j3 zN_&tf>|ng?lM2M7`1S9<3*%a{w~_m{^)o{HPm6ws+5RDH5wBpk zdkiu&0E~1}*;!%p*g&GV*u6XVedzbfvi|23W%9p+M~H$^djnd%u>Fl1cT}UecyiJN3+qwEs zkoDSEd|t;p&z^;2>DPb1{x@a9uahx-ddd0VK{?>g>XeGe%dL%vSMaTID9?9e@uFL3 zd(dqH3e?}bhk&9$0L771fYMYI zx~X)a{R2wAK$-VPtG(k3zx@Mu6BEdhZzLTb5FgkCC41lPr~H>vS}o+sJ)s9h05>s+ zbVcq}mX{9?t&9m^d)Uq?bX^%gy<`LQ0Z`XR!qit{m~RLhRgzu+;SrKXc(O?Dd{-~# z?(KWS0NV@vhu@4zpz^CcDJm@NhgqgbyxhlTG{EeFa;bv3vC|F&(9Qr-6ZrV}n-J^6 z^^aFTX>vTXXA~&A4had-?dTX<70lBq^qYtQb#s=TaSJ(xsR;=QwaFAasS*YWD-l*z zyMxHyqajcm25OrJkw2uWclw8VdaexdsF1v~S~*3u6o_Dub|&7@sj%uXv$b_u+i8h-Gd5PD|5^ZFhvlmxwTR=QBA-UA zXjNfkGIs-j-U1znB=7xl0U(_NIs;1dnvOt8{&m948sz-P`sK@~2<>7+<9AY^bq1V! zgMeJvyE)8)eUyk!Apg}9l;5$(w&)S7TK$dYkGM*cCL#g%vP1qf_r;}Q5gjiJv?63? z>ME{ zGG>5)r=)~f1WHC)T2@BJvNw)iTzt9YXKZDgWQ>Jlg~%s z60ef>|nWGQHu42#JD^Aa$# z_kqWd+iUni6EMi$tpH}vEI`2+N~7xC@)tqdMlWc|e41csXs{Vlv#)|aO?vR$Exc{-)Ld*zBJ7Mh7>uV7@rza+#M2Pe5Zm=vNeR*&2SwSkWF$@$S)idvxY(z`SO;*TPj?0@{qsUxdW_0M;*fC-nDk z77dM9+#_3QSy|7m$$*{iaG+eWGwEx+mJyir8)RNBCH)SU0V-IVGo8<+I}Vr)faph` zg!2K_7L#gQQxN`>btf;X!cxwfLEVT(y>+%t1=(^>Y$0e;krR;oVq7mJc!EMLl_ri^ ziT?tj?x@4`#cGd196oJ-g=6|ANE85#mu`^$G7z-$S{Q~Nxdmbk}X4XyMB!T`G$ia5@KMok0WQ<5G&Nr%f_x(6vji?b&QnvNAKyid>wQ1_sMEQ$k zdirb-TJINC{3fhJ(hgP_T<@m4efx!t4VZuvVBUjZ{eJO1fqG1WgD;a}@;3VLba|h_ z&CRRM3~Qd{$*_bU-Y8`M7rC0sFRVQN^uV_avY`5talbjVxdi0!LEok$kpp zgQEhdU+qlf3?6?VEc`64OVaOEPr;y2-p>tCeHxpff4DBoanO zLB!zH=@3CA?7sRGphLp&xiQRKyI_VgJYcvI`1Te1>d(M;77r|hoj0dJ6r{=v?fx*j z?Q0Bkj`?d-{$PJz-qgc+1Wc=xtm5xvhABWn8u*Xsi6k|Hw#rq4l9C-GAAD*dpRk&DNm-8@>uZiy zx`Z~Rl-o2xbu%E=8>I5z;5|+kURqrp0Ih9tYkgMCeGIs%zk zBta81AjP@V``jsQ11(SbRn~v-AjIc{*gF;R0h@fwSDJpcxhmIG%SbrIy?dY`d8~Ns zb`T+8lI4s2I+w2melWbt8ixU`aa!z&ed+AiA0=f2p$@Raf?PAZU4r72P$@WH5c?Dq(;D#{ z&lTQ#4m!U2QY1^1x|m!RK$FKY=$HUT6D$e9&!)rN0ZsNxZpcMhooY~|w*iOSP21YV zS{@zL9%$9ymyWobCStX}QYb{$oBja1KM0kPJGyDR`8FWJa|HXLT;ySzepnuIi zs-|XnCM^vV)ab#|RZ1CV9S%`5Y;s0{au+K_8mhieAVawWdBtYP(p1k+|L`F{H_HtY z5W8${ZqDZ{oOUQsGhPDd-UH!VF`I!osw`%LCkziBfH>R)l*S)PF$aOdjxe@i1vDD{ zP1lu=4@^-~UU_Mtx`ZwCeKlN`-CD+D=wl`mb*1`+ricr`^KV691>oAU?MRz~(1E>Hm}NCb+n>kq=Q+>u}WdtN+js%>oE)N>8gSx}V^dp@Bes z)u*nm5qe2H1aw$~CH=S+e}qqSP*p1yAf4NZ89&WkfxP%hFZv&%uRcC?lj6bHPa5DN zCnXIIqvtRrrXaSlwCwh$_y;l?7h+y>6=1*c2?`oB(;_@xo0=TzV%~O9UgOrrNzXZ?rX-602 zKDUuR)m?eK`SXePKlq%oKd$*9jNU@^*|X1zrhmwaY6q`(=rsG#SKg{>nU6k(yaf$G zSSke2vPIpeflmLnQ^6|!|4tH^0Zwe0Z8rEIY6}m zSxxxDLU=zt8=L5;?C`)qM;v{65@^$|0|twU7s{!olVjU@xM44FQv%{mBk7_4>E-?V z_s55ab+I$;)60%u<2OhJ0WilJ+d%q0GV&&ASrc+z4M2D*0MmndVY;;taNa%zXM#?9 zFaYwlPXeP#lmTiMpx@pET_*XYYG5G7&wrPsaLfs{$^c*!6{ahGiucX#@w+@=VoP8b za5_6la-D5N&1c6}$e7kVrB~yKcV{j6X z?euHxi&-#x1KXWP1}YhiKMp4lI>t@jV?VNGG~I@$r3-yIF;BEn- z9QkMAz`~cxLYQT0G)X%$|SEG8ON=gS;P;j%u?+TH(EItTEDj6Lfa)N$`gdZe6*8cJ4i zi5q^i4tCM}4LFt0OjT7sc*Ox?_WC+>Vi<5`Gfs~r?OX7dcbCuH{cC;AvP zGmunU_9ajRVCP;}p^y_X|8Sv3gV)EmpiF{?mtWiu-3?MeBW#Hf|D8LF_|i3(ZQj0l z)40aG3#3%r>Ox$+=;6a9wg05oT*A_8fN=HF1CvPgYnj$)P;{nY4rKORjy6XdU;Af( zjKQbR0e!u8LfhQDygtW^S^!IAI3oeDdPTY=nngsKAfEH{b~T3rUQqY@tZcbJQO{km z{I|G)4}uSBh38&*BTYe*qWW}-pc{+6@E=`c@ad52*iQyBoS+D%1ZkB?d1SCu5FMTQ znKCEaVeEp^Rnq^$WuhRGC-Ht%?rlfmSx%F$&kXfi0LA6)+qWn;_^XRS0YgrW^6_}P zs3dNlQ1B1$t5&m_Xd_OdhGTmUfjc08oFp@{?R;Ifjk2 z^M&APf$(kf&2DM%lss+6E)TJj^(2WnQ81kyZ-x0S3^6+$od$!6J^$Bx`v(pwAIykQ zkK)piEG2L^iQVag+3k<{k)HO3vz1Cnpg~RgJ5gxVx54k6Y4tj25f5l8`yInK7_2-(cvrlbt{%wUj!>BSz79ovw|a2Pi_j>Zk- zYviVg+AXZuMWyO+xj~PgisZfAwgo7(zZ}T0^;}b69qy8n3cMVCC`8n!ysZza5Ry`o zupBnb2j}M$MI4+iE5A3r)*2I$R^Auu>+3)YFp)*y8gne*VwyNBweQ(8II*eFcIW15 zUuyF-K5JmHJ7wr3@RGSS07wLsAYaGNeEpIh=u>m13e*67B0!tsX)@ZOKB1y5sc}16 z1vK^O49yJl=8gR!LWk5-6gyE7fVT`SlES}9-r#yQCv#*cRG`xZI)nc4>|(H+&%<9g zt$IyZHGeebY-)ZaX~)l3&G}}%Otdow_@$mF<2E4mBGlq4nDZHO~H@W16Z^u4!q7U~Nc%O|C+K_f~JtPWL)aEhdg&gL_{D00)mfpz6McKPy}rwe&t`O>W>H0 zuT0oyO)e7Q3!9_|!fb0r6`1^c{OJ4`ss6`tHy{Gmk3Ynfz6u^{_gw2rl|8!zcoyju zFOp^YU!j*LqKr_mCf`2+-}x0l&;Mitz=j=zz^*FkyY)Kid;(BwfR!#x^jse#S_zid zOy&#F1F~H=&(T-d#;lk7vjo2S9@4(s0O79r0gmv*YXpdGU1s{08mf075A9>>1Eo zeg$Y#EI>o4Hw_#^x_wXT{UAy6m6JpL-3G1E6aZyPcpr;vv>nFShTgy?Qt!QZ11K6i zk$8qhMI3+0V($o$SWNQo(+)l~V8y{|^gjdj3W>P}Ojr}p9RrTuZ$i)?w4@gWFspp2d$eHXlVWfxaqx7Jo z98a$oEzJ&)dCD|1`Cq=cZ=V%Ah#iN!JR<@pnr3HU+n_}9a&rTvoMD#XQSF_}WB^>s z^k<2&Xcs*qQFD1|YpV|G%Rxp4R_hH?<7hxTQZvps)0?gYBmhu42)xPN8BbBfJF7?k z%mujasEc=X03GUAugbt42q3^@Tm}q@i-A$ zDN~h;Kr9c`PJyJTDzctWJcm-2Eh%|=DW>!*+LS=5SNfvO}> zTBP47e-6;_HsY0_To*K=T6G%-i2NhNd*Humtk7oS8X45Q(^maG2 z7NY)NY|e>nZ-hdeaLJBwevvxUt5txo7xMHpprYcbe_IE_{Os&(LBUaH{Je$Nj1VO&a^9I-F{`yMPt#Mo5e3YJbkRJr& zZw$5iXaE&I^|xQ-(l=q9f3A&X0Av>bOy&RkVp#w7mj}SM2c@TR*aiwje?B+nKNyUu zgv96SAZWfR9rR~CPvPwyvm%aK6kpEAw0QuZ8;AshHjz9eL3!5-*q8xO-;_r`negE%uP+(*Er5*f3 zz)`&1*y-Xtf1*cBm2-_)?Jn&0l3CWLxA(?p_I0}L3`=Kb-kGfll##+zIiCH{zG`#z zi*H#ZDdsw1FAwgbR}fF*Tpx9xcb{)>FVntc6QZsBHsx#5EaRD?_lJiYd@j^dmvB_2 zg9s|hyvYOyo4@K`PI!X|yUZ?ga-Bnni<6mIh*P52G7@z6A8ID5UdGYgk(ZZ$YL=v- zs%xm3mY!~7f}4=uk5k0Gkoj%zV`^e%rpbrYR1-t>7#y4zr}7F)1Wu0QLn?Eha#9YO} z>C(}+wUv?aDb^R@wHuRvjQ{jXS#!{@53#YS2^rcI;C#liczAgD&pYMhr&(m@YG%C+1^s0zEk5y2F(<6p4n3i77n%o6E3k!IFCne&tBlATJgDBhAIgZzpN!+9kl+?q!*xAQ33QB3j(r;}SC`{_ zCmWFC+wUK>hdvoY(b5P(7dgj_d`_#X9lv*k(0I;yr=}lE9)|^TiSHhM%xN3Wj~%I$ zTk_oru%8YJ@95`#IUWX-HU`%y&Zl;X#Lo`(Ia5j}>h&hp`ihL~X52@W%NCbcSNd34 zR4Kqy%&MEv8?stnUi~5x@>ZUNGOzwtX^rV&(p(p38n63ght);(R?Lw>@erbCEoSKh zeU!w(yoxXKd?)l$B$WUM3r7-PZq_v4-Mf#%u0aUz4a0}HE)ctqI#T`5pc1|wjtzAa z=Iu)34TuGl=Z+h)cr4C;KHthj++}#(rH{ds4o zg9DlXldD>AQ~Mu(!6V=(l-+?9f3ETRmB$0GvZZibU0IF1Cs5a68>wSZtXAXQnM@vM zB*Gz%?2h#fn1HaeyC!-}d2aSY@%rkKw6Ng*y^GMyhfa-{h1>;%$P~t`AVoa-qLqUI8^U50eaM8z)I5=51|9-f2mP-&W+is(n z({n=olB!kk5IqAHM)sUn7}w&Ba@$>_NAte*Q_z|CA{zEiU-za@?hJZ&4%?7Qx_ZUL zMoFNDE4WgGZ8}GUW+@Vapw4qGDj`1^b!OZw6G_G=Q()2rMsFN+&6|>z+%|s6 z0{T~m70MPta>v5*jQkc(mVDRm=Y2grDv!V1Akds$O?A}N)%E(#W8~q~G(^QExxaNw zA|#qJ{iEcrkXA#Tdn~-tra`Y(Rsw-8)YD5gaQwExL{$udHh(VroL3|4?HCNEQLLd= zl?*}4+FSq~#IgC;;B}_H>)%?xQgQAaiM#dnEFh$HrpL>m=gTpY=Z%D!p4Kp>gVmIt z)ES?13q0*SU?~1t04?3|7q?O!@bd0vzp>!8|CRS7Fx3BiJ39JecOtbEb_kXa23Zbo zKG23gIxZ(PaH#d}GtNB1oS|~*8mv4Vje965VsjOz9LM~{wkCIG#T-voQFNZuj#JAi zCQ!7@iMZY9R?kJ1Z`idyNoOaQ(-@+3;q7Jf7FR)o1{+{L+x?@dMg3kP+hM`Zmx1LA z$Uu_nF;O|yKzB1Fq9AAetQxtb$&J$&z@_>IN!*uFU7H(gw&!^O8BE_>2dHOgUBqXm;tXt&CQs&5?eg3TaI**fbW zgpfwYAO6ifo{$r-6Ud?XPAz@jv-d5ED4x0?6H2lwmX9LGt7V3z?~ z*5j3zzCS)4SbW1X(fb-*OVc)pRq{ zbIPK9(3eUuuc=4d!0*I)7i>bRR~?%c-rm}zp(MC9 zvk>Q|S~O>yo_U>+u#m=OUec)vqm~mwa6ImxSl(BQh(dVSt|Lc0 zJ5q_nd3R$H+G*UM+gRKfwH`eTZIp1TTU~T1T?^=|-)njO#;(emHUsmUq|a|@n+U+3 zfVsrskUa($(x#Mjatxzu*z2T{3XR4W@^q}8aTqh11&cjea>E}%gFMC*d8!#F=AX>vyC&45Gru5C5M?=%wFuQ$?-0x!{CdR?em?~y;6tC!<`8RMNIXS zL(@csYfDUzP`c;mmV{KNv!7Ibj%BlTV68XgOtqMH3`I{jSRyP}8hx5do5T?HT`^0` zKgp$5P3jg8ZuQB@MAG(2_{_Cne0N(^WTPnk*U)RM?BcooWiF>h$i!>cf@wUaPPxW7 zSXnowc2h5139xB6Wpcn&xi30V9>J5wX1#>)o1J<|q9NE7fmUJ_sSU={ev0Uex~CzV%Obf`8XZ2ue^QQdg4EQ5+3_?IantalABdl_POR|U(_~yxI@0|!Fw7= zt#i&=%&R6t(yGoK*H=b2cypD-og+HNiT_4&m{VI{< z!E*fAbPP2Tj!|lFATnrXrx3P&?$xYNTF>*o<-Xsvd!H1TtXU6w2XHji#tZ{|jiFE^ zW(H%?ONeHi_Vm4m~++I)(gOANv(u${ld6=KOV59^~8vK5A+e-SeTC_%H6 zMJk|_+^_ITKabSeg)(E7hqxX!qR+T*D3rs8<$wtw{U&=wm2_pT&wDq}*eV^WJ{92L zztaTcw;ScJtzVPPRL{ge%*{Zp{k49r+VwS*L|*)B2tdq4q7VW~~YYdg|2H2Pk=VWS<&W1+-%$M@xO-hi}p>zqGk zD6~P+pczp#^2r<6thoH45F)yf?F(6jM5hKV_=QOTa;Psb1fS+&8m;gpp=#-Psy9Ud z)(+oQ1f$)XCejgHTs_Y32R-jWTCR4pakw?mf(uIO2Q!E~BS3ps@_e##=DdmAq`?B- z6N4h=*DKzyP?0#1&b*k~tw*N7zt7q=F$AC->^G!hbRM($(tOT|7Ow$~AgNV=FJmN@ zNE=36Ipa~-fShhvG7jCTM_o94pRS)ybQ~?(Py!w(!T}j{z46qeZ@GCSRZwKDNaIn> zateXq%+9+)CuQu!k?ZIJp716P~#eWre34dzJLP^+pF&J+=Pit$zm%DlR#Cf4695jS`Bi=A_RsoRz7FEv$B zgTdnepTv{kKK|P%lu6)H8TFShUlZcu9)iEs+>C25Zm;n8%SrJS4jl@Ljo@Huu%-*j z%6^gwPTf&u1%agDNP(8Q@)Og`IIr0_?8}0CzF*aR*N?-YlU!v-N=`bJe+eh6^Jc28 zX&|Tlcg8IBAlw(;APh}QPfY~YLH_S6m*N@uak>ohmIMI)D47o%ACi(_!}F9G3SBbr zDVK)}Q{z8oNczh}nX`Z^2rDxs6YOi2(I3}T_`&;%R_#UW4b1D6)h{c{%U{3bj(`B+ z7`%%knF{>;an*f(FlIOdHweK0kNKZo0pD&%_G5qcjNyOq(vO=7 ze{uz?y1JVG$`$ndeqO3oC}X*;j~DjD8b>?n!#x)lm!ZKS+tJ^@yNlveQc|#41vXYz zJg3v;)gC-5%ZZ6(mn&V$;LYzT_^d!=ny10cclQtk`s~lWDsnA*C>I!(L-Z;TH*ZD2 zM|$Lc@Rs@a@v^e8q)D2$-OjHh`n z8-%xtWlw`URdqErn~XicTqvNgKPM6F1FM)Y)mHyX*Hh~k$$p1&yF+~qFY(yL5xUhL zQ%(jxuD^bpNHIN+GJ$TVBO&O|ttoc)H5YXYmg%B;yY+MM5??y+EVz>I_BXo?soC@S zB|E1&_+B8Aoq#~&t71bjJTP6fl8c=3V)s1)b&NS~ggIJQDR)T_3Hvy6*_>;`5B(6e zSJ{;vjW5#PUXBNI@n%74x`4k7_(_U63s^00ZcOf0`cIvb&L_Zj`Z_zs3%l@4(HrFc zEtL>Pi);a>`i7}cI^-kR@*=wvhp%f6!5BLg(_3<5Zw5bF5KUQ(O{W;Tj6X?d_aP0p zdnAU*Bk2gXs24-Cxj__%mIG5a#$%BA!_ zzM9QT9nhU^`f9fYWA|;9vSYtnui~jSOZ6&VJ9VQkDm`Y2+{7ONqr**u9(-G%_nb!T z0qdF~;Bc@kWmHiCk1K)r{VFw0=f05{8~CEBK}`FESJ1F&#I6tGVO1T9+#0|5#+-?& z^qku#VTw1f0)fL-c1=fPsJ>4Mf){K|dZrqj*wrgo{HF6;vC7rzY4?DW=TQL^;`Xt{ z81X&RooR^9Eem=suBJmv{mFU-{JUhN{`)Iq*}Osfz1!b?OV`>>%XVjJK(s)I>p6pZ zb>asS05qP%KTTittK|l%16`tPu<5>ZG306D+N>tt zkXP5xz~+QZ|7CF>)F5VMGrY59%`TQ9SKk^TB{|+RE2kJbR^7e}Vm8Us`aW|N$+_IQ zv5w$KO2KjaSOVGT(+0PyUSE$q9U&A5cZrs0CLQ@4(Lsdi(d#~N0Xn}E8{KmE)E)&o zVT;YmTU<@1d;P?TXk^i0W#yD(%>%v)kJQoBp7ekpZH}<5njywD#?|dY$XccAquD0c z+$+c#zSD6~a{=}loRjM}2%g-pE#NcY4!o!HO{!hVvt?I{)Tg)2eu9fu z_vrXa-B2cZ!b0BRMU0PLT>>@~2ZIB0B;{kY zuqeuLPfKNGM@Q(nkGoUT+D~EzS%}N7gXwn@b@hX#*H5Z!IfW@PU>?>ToK3adXSbVm zCy+|5IXdeSoUjg*&U7s%Lnx1e<7A&!>9wGrAZdB8v6Bab+~t-$!bGw9ItG8A`tE%k z?u+riAPuFhnSYyzqINNya+tKQ+po`cm~^Y!t8FRS2G>3aVw|bUS^J*Vp0>!$y`yRf z6L!$7u&JhDn<3B1r7Q`LDUpEQG>{Xh@$93`<~=++j6g!pUhYbGpccDIqa;?+dITr- zdJ-=ZV`9qZBxz6q=SFJ{qFeRmQw@aT9_xD_of?M_4H=nnzP)=t_CC1W3P9VuFoR|b zFMc@hoT-wUa*#yLj}&!Jnu;Hc@igJnj($3&RMNJ)>nP|jDC~Dpj;RJ29piiN=^*>S zPND7KoY+ts>#wryRte3eN{Ce}rx#5_9_gR%4k_bjFMKZ{3OU8#!mDp~yZ~#1E@0HE z+Q_53$OzS&S}Nb&M*>XC&ozX~j;`{Q!&IEXojYyCMszm3;JmrxwaBwP||Q1<%lTx=>7W*Du|F5lD-GFCHM4oQU^=1PUSKOZ zgC1>zlwcefj(IxsXk4~@l_n<{u)si}iCE>#D%frD6lhQ9Drh{XyyqIkLiP(0iPSzX z*7Pb|uj5mrZFU<$(g`}ioGf5>jD!#r#l};lG`n&&(I!+H+Vw0O@ma2>L3;zL>Eo<)YoQaIXNYzj1?Z6VzM3sr$#xKw0J$(OGi0v?bUP7nHmMy_ML#0 zLMP~rOyp(_{D(Rm$G}D`^0hqf@O%1U6>8g!99N z$Gh{;BAF|wxKY1q&B*CdZI1IsX}~nvRn^h3&5TTA1&@Heu;4aj)*Uu-%3e|AVt@l| z#95Xu0eFVB*a$yV8HfE%{*Wbqo(VQU)bxG|0$T!V{+HFt*W--_;Y)oa;fStBr}bl0 z@X~5WkbupT{}V&L3OouyPYa1$Y*&Ay7xlwvm3qK-P@RbAYWrGk7w?%ZQEdGF4tT)@ zES#KGl?;b)Ob;|&tXL9LndJ`7dnW?V5jDEC?5-QX1AgOAu` zV_{i$X$t`Pnc^W@SON@QS@CFnY8MXHqiX97g2$vIr5GWaV^KGdd9cd#yU*Z1>ug+Q zJ4!8Srg^U$M21N6^YYD?j38^v2F}h3KG6kG)?nz{J-5ob3b`a_%S;28n|o1P&LBTg z1dcNf6;SAgVP|A#|Z-F zmdeg*YCbX{3Lw-&=lokN=3#m7^XJdqCS~(u5XTD<7NJMxJQ4W^hVGa1K9LpUzrp^( zV^Ze-K_~!*>^}t%u_-vZQG@Cl3i8+(HF_WP;<(mIjyEq^)rh3cDw(-bK9I9tI<95v)}8Z-H99g_RZX-W9+rjDKE9c-?u2 z^7RVm@P3MbZFhGvc$iu5@ThD|HF|dNIc{ZN(!I1f;@k1N{6BI5ozGP=B^laz?t-g% zHqL!3)ykBC)4qW51%+@lOPN_vzz-M1;J+f6-_KRt@fdMluap4G^Dso@T5zj$D+pkY zEnZCE5&XDGazSy6uGahX^z>S7?P)6`BbaXqrIo|OdkHF*+bMUhe7gbOmC#Lqorn6j z|F=&~!ASqNQLc>j{kI*h6|!VXR3>SBu+P}cOp$%Lx?NF5LW8nTV4IB|p0422D_xs~ zB2AI(DHGxi#&F7>W)l@~AulXQW_i;z#bGKJr9FdiM+4WMs~myD;9oQ%q%YZ^(tJCC zlaDeFoR5{o+@wO7#!A|chxVY#K4!kQxDO6FO?pl_85kXwD3!2=tm?MlZi@_bd|2hN z1u(gT%Zn3B?<@#nMMWV~{=>$(muz_|wf4Q|j;?M^>7L$0XXQqLWOYsl*Rppi< zT(w=F>Vp{{)%+DYJ`c{IYBsON^TR&>n4kjzkN(ZnvqwfMAZh$xXWoAox+>XTzK}yU zO)U*&HgPqGxHQ+8@6ak2L*site=$ZSoV3iCL)!P}Ie#VC- z8x>aZQK8Y8^Wp1`25#x~Z$%bLmfQ2fmlRC)CZcDJcBZ)e_j9K`?bPI)@0lDNO08Uw zcPB}};igR-r{*%rI$E1QQn_wHLQ%8c0%%D&Ffi=QzWjTAXDi5o!jZyEYHXMiV?~>i zp6NNDLDc9oaKy^W+OguPhR9Ia$fi7#XzsOR6ZYR9;$6EZ|w zzck~73eh`6%^h+1hrm=MeT&hbVN3knyxzv3qA@l1+t+t>w%5fDxLA#@@wR#)XOI zHJ7dcY60{IgVGyBdbu_guPS!u-;jV#fWw73d@8?@TPi_65A{SM;iC%Ba4X64`8A%L z#(Revu+*JGqZ~_@p2Ez4#jW1!p{WZ3eVoZ+Fm6mo^qg4L5Y!^mVfgdD(VQQv2;s^D zzg())+T*M_Xz|C4S=*!4jup%VX0Ld3CTqHDUFhX}+NWk8J$F$f*9!!u!DoOJYzqY{ z*7KP$V$1QSr!`$$6`$=xom(2C+>uTDNgBHPE@Y2vPJWzJ1w z1w2mah0($rS=%174v6B}n!~R-JM|uLZK@zHpP2{@yuOD}ht}h1Xknl4N|#FPQMRU+ zill^q1KwQgcevBx8KV#_w0&ejx)JHnRJL642EyN2B4#-l#Dg&T;b>q_^Hj>lV%#PD zs04Br$4%6i&bNkU-Z5&LNKt|dC`xL1Y}+jq zPoT&NAJhmHzpXAa>`;NORwL9dN<4on;?!lYPX~hWa-oLapz}j&Z5W*fyEd;0ogsUR z)C%1eG7PF_K3NoUe}QcF+RfIXNB7E6__TY7^^d4IH@S&i{~d@<)zr&~LLvw~x~3UZ zo+}B3aHR3Oo*6mS@AZ;aOC0=6otcHMG+&99FG1VRsi=%Qln(CPE|5Q=R#!fG6r<3L z2^NGG1+<4-T?ksZxSXfePcM1S!1tqPOER_AvH-IWJ!U}6Hm5vJ;* zO{nRhdq8aWn{29sLK7JP;mhfwy)_FBs9ZBZ)}J5fR7TROWd3Sset-G>>{@XEBEtTlE%yczdAIzP+PB&|AHu^X1?(~=ZJL&&sB+z=g}O_I)HPZ}-T5l+D42Rc zFQ`oXKT?Mp4kA$Ae8-5)n$i|B11n&uJUnJA-A_i9;gy&?yd_8Y{ubkPIudQze$t~J zs^SDg`e&hgB+q-7ccJi=2qMw5>WFVZ#ERmK+dLYoUN{==&Lfs=ZrB!@QU^6suj3>7e?SKfvtLI-+UH!&y{aLKN3qC~fwFyR6^VP6yKq4VXlq4uH!EA+`VR)N@3Vr_q_gmn zL@nm}o{P@JOj>8uh!R|=W+zW$)6!CMe?Tl-EbaKmwrQ0<1%*G9;+j!s<&%oIT$^fNWbjB<{l_EKO`HA|bdt|T2s=R!6E zE}6yEDk=tBv;+$jK8NQV_{y7QeU7Q1b|p@Z*$uW|m!m=$ztB^9HhprS(NF>t6Cog+ zGfxW5&HX}GXO60%-SO!*8XL*if_2fNE(E>Jx|n)m`bItXe&p=d9q(5>GWI+Y$Utqi zl)e11PJAAMZ=YsVy8Gb`%)ZV-_m1Vf5}a%N%3|OyN~Jeh?f6JBg{RnUy2i1gy|<1< zgbNICV6bow}6yvUEdW4dPP1GH!%-0 z7pDD*iVei^_5f;;t!$#%8|?hQ3x?JbGn-}ac|vwOiwzvc zSzwMyoysXZ{(Z%h#?IsWsc6*rlbH-c#K;qKng7+^bw)Lrb!+CE5tJ4M0RaIKkP$)$ z5fCy`6afKghAtpYnt+s05=S7BVHA`Wq#23@5Rf8-77fy?BE1=DQiFuh5|VqEb?5%L z>#n=L@2+*%UF$CR6_UgIp7-SJXYc*&XCLCO1;C{x@6RSyd{m}^Jw`bYC{w$#F?&qYdJ(6n~_elBlJb?Z}!~OLyguNbj@06 zbdTq*P}e}%!!fX^*a-99jt-q{=uug4JR$UvgW0&Z2Bax6=kagaDoLIds}aIt1JpV# zQ%%{Z_&Q3aVR>8H__?srqy7l$=-tYmx@GBk5!4MN3Uh4W~Bja9J8f?s01;YkW=f5#_m3b+b**D#az?9R}d0rx1UxPHsyhN!rSUsekGxz z4cD+~P)5GWDX0|aFqNf2!Z0b=8ziQ0?qwL4$oBXXtq{Xfl_)y7Eu@hYU#Tz}nBS9Z zT#6vY*T@s7-(HGx^388*Z<`%NX2Q7NQT@qc1YwVtw3^BDCiuPy+Iw&#aDIYz10i}; zyiJGxA#=d9uWs)lFE0g>9X>w*v8!<)|Dl&COIxp>j?m~{NXFcZ)mVGfc2DEoGF4q9 z^3Fg6m!>~{6#%eRk%eQ)oG%U(2y;E;EzB8VeLaQgQ~ z-3V=cb&|Iqn_CQN?k44~Ava!SYnf)NQ5HT)5X3Z~P)OhLKbi&9C#PMj&xxx?X+;_C ztUVo+;2d)dx`JI|ZjBUoxG4#_57#%)taB&B18jQi5~FvvKuF=Rww_e5-pjP^(-t+l z9`3>Yki3-_0c0xtNvlX?aRnpd?E*VyP zR5rGcZ88WEUQL=AnlXb|?yv7DM9<^;&*{)f%jvYJjW780blTF(mA7O^KweIkT~hy& zyH0m6?m3|qGUAu;hWIr*V6Umae)tHRD7Ec1Y{7e@4I#?_{u?+)^2bR3h()50UKe(o zsifUFW4#ck$*XC)B{q5lXinpga?vfDM7NAhot&`Z82tWn`ost=J=bZOw);AJtG9pC*o^*str!Pr0+wROYbmUtC(CZY3BCpWaGY{exwuae+XPcpu(;Dvcg)P zJ+kpZ?T$inLXkmvO*5^T>7pl+?ybwEzuXTrZ;fL+ZCjUcIT4vFo&TOgz#-T^apB$c z-GOX%zi5k^Oxo1O*D*(Kx#*pjKQ}=JR!U&c+V#kQejAQ{Iuj}!5ZF#1xXpi(FNgUt zn%Bcb%VoIHs7yU@wpWDs=l)3Puy({yOCjy*#Qu;mwXz~Pk=-wE*lZe|dyrC%cqb2a z@DXb$KSIK!+v=SHP22VQq8X%Aj0g-QXZ=P^B1)?CkuGFdC1oMIAsEA@-eqV;0Kt7J zlJR5R4Y9mo|8%=)s%(3vamw^D+oRoTn@2(*=P-12kzRe*$M74EyXuEsTjLHv@gUZEWIsMOU@PzYux>o zM9Y=q;x|?dY~5s=S`8fGuFt8ZO#%KO(vdbjGZr>Rc3yqD4P(DI(a7&_ZeEIBKwck3ol%Wqe;R^QG&PzjX0$xU(t zS$segwWU~KvV0C9J=D`3v0lX#4O-@_5ka+ns=o*HNG!6MUw)yhn68zZVcXKCI8~<3 zQHAN6fpo5`xf>l|Q|@}=^-ekMu@l@uQMeu-LD41h7?Bb$rmCX>Id z1~fU8*;358cqkA~4gX>7fQv7>FtDh}{RUZO9|poNE{Qb)$+XVb9ECJ)M+9{|77jr6 zhiXZ~-r0r4luLGZ`xJOyLiD@(q0AWN^oaw$l~SwDm{RJDn;W`NnIgnyZOTxdF9T66EYl)3IT6-ByrX?awZd z&DKUVN))|Ys~*j#XPaviIWE2#U`$U^!xxJinc1QwN;^W}UAR2{AoBqcrE&pAk?~`g zwB116P;X{~ZF~?Ghh$IgCf&WcW4Jb?;{@k9w<=;EFu;6=5r}>Y`X)M5JinA#dUkdTDo}R|`V@XXFx2oB zfIwnvtDm+jp9!iF&mfNcVId*?Czr~SfI{sBJfYc)z-=Zfp$MG(bmHlETVj#wvFd}y z(>I+T99Pq!|Hw>|M4nQEHiU@fkmhGhIVh_VSzCHCFkoIRFV1^1*>+ZVokfz;Spc=W7{+7&>!Puova6u!g^8ClrSODxNH@+7LafZN`8e=4=+AU3>iU&1>Cfk0rTY^M8trs+8$ zzl~@ARW&)MT7HR>?)L>yA__I@aZ_R;`-Dn$cz%oj&8*5<3 zpdJ--oq5sh_KpiO{Q7W`G_e+h^tsf#!+@AGN(6*1OHoNm)&%hvS=?@9O5Yo=-3f!X zj>|yrrcR**4TBTZ^D1(3sd{bQRAx~1`s0Yu)sk5?fwSz>M1Uoj=SfkNsL$PtD#4!8 z)6&jXrcVAHbhp#WK0`+6c%!4<65Z>`TJ-d1N|8399yWI}M7UV@x0Xv98n}kwl@syO z$;5<`Obzm03htt+&&aEL`11YmmeO+Zv=UwqN~EW^4obc2Tvghm_+-rkR;j=`9@ zZGZY&l)2#V@HQo|+KyJ=`LYu+q5u+k&OmXwYWfdb5O#y`xoVZ@`Tdk)g#M1BAban( z-k$`K5^-J?UFY7&?xT*g+$R)zuJubSRhe210bQO?&vX^dt!Cl~A#JVDJ~V8yVn&(2 z5-Vfo1-E?zJy6cU1rHQC+pOT_f7_vD0Z4h<*J>W=CndqiJJP;O7%s+v7Nq>nA9qu+ zg98=Pe0>Lc!9mlJFA!mR{85MJLDrIhuU{@u-)Z!^a)8L8e-rkgl5a6nRpPN2!p3KE z;DiSaN7^C(LGqUvxst1^{CdHcqT%hZRvfu_y#1It9$A+aKLKr0(+u3bzF^fsf0JE`<%0b`uH8eKv%Sg=x!yZbSC=vDZ zAJ~}x;O4T@)77g^sVFdTUo$Z_Q;?DG0PR6w5VTSef7J3%#ZNoIpPriD$UQDnB!zQv zPu~#evgn+C@)Z8v(k`NPGo$kKZ{W*;<}1EAnw+56f(@HVoH-}+of8#}W~I8!ol~{a zGe)?kg8N^;-t+24aA#G99Z)5jd%NhKc){lq^rWGD&bdcl=9H!1Q7+k}!3jd91eq)}gz;vHgq0969Uxhm12fLxCKBs?7=CldV;g z7e~>NJH-sFU)eBVlWLJ_X^C6(emxqZ?DM4^>@*g7D$qw|R0bs^7=Qk(%&0AJH!@m` z%${T8;Od&(NhmD21O)6(qc7GMh#zwUaVwg9>S*OK%+T=&Jk_K}htU7~2`1rti9J?MQm53+vG~?4clYMb>=!HIz~ROWUx(W``v+!Z z8sFhUomP1AYY&%usJCGXBcyVD`X33q8(_vpU(3vn;lJ++@#{Qwed{jnexC4jQBO@= z)ryxd=bO^o@1|U+#zeim?zcSWIkNBLq5^-NQ%wSUHyb*}%Wn(DCl(k&D>8}Sw(Ra& zcP;68y<&8Vz;DQ3gU-X_VKcf%Xv!%}O0LB`65z_5)sMG4d?hz@P8UG;ud`h(;fX~r zxdm#erA*b-s>#;kt-xpmF%Eg~V3?qEEPQr~ARv+!9>iK1e@EX7E>!@ny)*aJuDL;k zx3euS1)ngT?y@GfOCnb;0E>c}p-?=8f<|F}ZL!;Vc^2lE!I>mBCxCTV#AXXID%7C< z*Ib{dTu#$*$#7;&b^Vn?Ww0k^!tV}OShF6xW6cOEub{ zh%bz-BJF z0!RXuVsz))*YbX$V@0w!oThO`I?ud(VBCgu44qP{pTYZ^CJmr>$r1xAT0kGXWBgVu zHe0Yr9fx~HTOLwm5`sJ*I?P^UoR;@cVEYM-EW)H11jzaE!RwIEm8~Lyi%GA*>Phn3 zNxwZK4IO+I-{KSwhQX=OXwBn-zy~$UxO6O_p*0>Eys9H+;HlwRg_=frD|<4{GP+)H zYiP8OAsz7mUBPDbkQ0!ulr|fv^A!`oZXfpnZ{}b-_0%ep)$`!VjgPAI049&q? z8y1~eAeq74Azf2m#Q z5H}8S;}ACvapMp-022O>n=TJg^bkc4QS^TwMXy}pZf0TOet%8>vejXnbBI@mOu`}M z0(|d3buhyJKOeo$Tp0oV;eZD}gId0}0{s*4s1kqmE9R=TXpKxsf5VMh{Aos`t;__8#r+>A%!<@AVF{5K;?gU2Yk})xmoh&&F44b zLIO&l^uuLHEfh@%$kQA_en<$IHsC`2`wwuTaxpMe|3K?~*GB;97f9M!=kB!PB^f+; zk(1f=(6jL0wzk6+8e-pf3R8CopkK8@s%S>-dK-!QV zd-v{jtfa9xoE~u47@X0)*}h6#kby9-%AVpcPRw)7Pp%8rneV;y&in*(N$h)#gql}>hJ45 zzP^XgPvy{k6$ZLNdMG-%jzoX{iuiTBt3+hi9aZEK#U$}a z#rpmynX=sw*v}wfVuq`-nCl@JxkSXoc|>`6xp{ec`uZ5OuY;K6`;2UAaquUr68_mt ziK_1oWmOz0ndNZ-Sh^adG3a$J)vr0hazaA8yQbKfy9Wmcd;9yQ78a8KY-BZC0Es3J z5ixY)H2CzCo@W~8^76$SN!h(X<)mBENM{ z_kQ5|6!L1IyY8Ra-a`e5hzMh2V3;24n({toY@mVEda^qaj_UKu2m+p z!0UGW`k)M}#4NWgw;UEy#{1Qq9WdJ9TX>U+-8TwP)=En8F}|Ju_AS0#Zj#?=1Kc4b z0>@pvHr}1R7KIpBJZbI%*awLH-{BzTQew`1`G|#7nm}^?_PztKQRH3{6vp1i7ej_~ zTSv$G%F4+9=ktg~I%tvF=9-&Nvjh1cfca=hz57MjxzDh;xb)_AuPIV;a&tn_o~z!S zdn?k*-{#e1-Zja+5M4-0zdoqzO&Os#IWX+N=2qYE5cgF7%J1T@-^8a+%chEr>>-zz zK7Is@^I9KiGA^|Lqdd~9MNHD28=xZN-JU`Nr_3buZUyA!`f6Ugx>w7K&=#cLRKQbx zlKlssRvXBZ|2e_QDn!vEV)+4PvCA~zNEL;KI7Ey8=NPE$W_UpwH@ZFoJts7FJ9K@f zQX_Sl{|pvAxD5XMY4O7s>MkzesoA%5D*w*ICB8wxR@6ZDn#)ZFJeEFwG79o)-Rp0H zW7Va~kJpjnn5%!Dy8|y14HY*<>8g93+vRX?40G=l{gVJNv9tAOjSH~U>lZJj$)J-u z&hwzDgx#iuXt#(^Ly3jHpF)S}jkGj=(wEl2%ru^kYw>ZG(e@Qi+&bA;>2U58&eB;$Eo~au*rO z$yuw#wew5iobPu~mwSmn3{QC48!cDEe9sI5F?J6gydRmZwtv36yJ)PjPGvA~dtF}& z*PP!uSL5D%*V7o{2SHgvlt5_WlYT7X>J$5gO9yg&(CTz!y1wmri1IkS@P7U|It*6b z$N`3)6N*?XqbUYvAtLOKSFK&gl!@NVMv(RxD?Pd|7(s)d_5o@srjJp;02(i!A|UsS0z%f)hT zxAzVn^!oOtF)(;M-h2-PN8ViHv4^%rU`@xoZsjw}YHXl4678Dc3c6zS@+QI;G79V zp(S_?E4l&dKXrl7hEHB8h}G$07D_Ud$)kB_aHZSY(P>&_xhAU3(~z<)|1pJ0Q$oBz zj^dQfyOX=4)g{~p3qK1XcM(62vu1N@hfc9@yh^o5q>k)r%k27)52?`E=SSI`P2Z17 z9_WaOh+f+j?iRTKuglwzeW4J48D~>)t9pt;UmtzxlLgx`==mpV&)$@JJgqwQSP{4D z`C||C3p@TNc=$;S1M}GZ>xQZGOZ&LM-AAGcFhhi z6O)Qo6garJ&l#Bn4u<2bCfn~- zo95z*=%PivZvT)&ad&u5A2iLQf3?78c-JNB%$yE;&2Juhl2K=K0-c3s{Ee~XeSSVRAY zIc5!9^a?S({chu+?&BCtv$Ie?e+E6TC(LhDJ$m23kxHV3@WAcYx7ceeH#?ck?^32P zsmo+A|88~Na5>=#jLHlZ)n#85%D{x^7ZFid-!1$85rfybRE?^=_i%56n)@4nmvw0rki|zoE`fO78f6? zsE49b=F16Hf}1_>5Bt^$m2TagPKr^zS6LP+YPW|Hc*cU-+dY_NGJKj&xKG)vQm216 zZP3*kHnw|BA?`s0!k`bWwi?AnN6T%pW3!%oHJ>Q;tn%cVo2$|k3|&2b(tT)Aqc;i7 z%(UmDciR35*w`4ZrYeOhr!RSCo9&h)zfVub1BSDcLlcL``s`5~hRBeQ9`_uTCK>_) zUHINyTUq<{zi@K`MCWnjIO;f(?xhsu4V*5M7d`nn)wyDUqjZkitr5KdJsTU{-nW@L zD^Ji-+Ku)b;goqSWXHd2q$A7|kaoA_3X6)!p@fDe^7XWp9%yKoie>8@4ydl$B3XV=lvIthb9cytkT4_Bm zD?-F;W3VqJAOUgZ<~EJb#`@9oTg$z={f1Zd|K8XI5J%|s^FR*L03#2|AqPRomwLXM8ypD#8+hKl`xGjy%ZQI-o zTF_t~3IdMhqe4RL+8^FDTqZ~w2Z;&|MlEL6 zArH{O9?l8oiT0;8CGfB$eo>TARozK=z=qu!F+;_{A<~hg{`ibWr=oTlOn9(VbJcQ@ zR1>|<9nVgq-684g@dDQXlNw$};Yd^^TQW8>B7~>YWhZJp z3|k=iG{qbB>19ARizV~?ZWtZdiwzUQx4)YdQ7^Bq`Y_Q-Mn~kp<7wn`4n^3-!X|?% z7{qMlIwZ68{j7PmA~}g*tgx_#iHN- ziHQTkq{p?^Lc{yt$QSiy`L~#unCo$^9#>G`17k@;^)ZN(sub{+CaYA@?I?n(PUd7d z7hUdl=7TKeY-|==p$P{=`|9dJ0)~H=OaJmz6u@Dck{*$#Uyy=In9wEoiq&Gi_JyIj zZEn_DZ#XZ}F5n4WIrSuJgNjXKzW=N^O@{x*$<>-J&6xsjxDxt#G`X$0WqoKF_5 zkH4d?+eh8Ixveus^GV=Lo&DZABQ^UU7jT98OsiEZ;#l}bP(&)7$>C=S(@sM}eVx(p z?^-K_No5i|BvCa>a5J~>f(o^6|K6`Dz^fs?(IZ4uo5$hJ(SkV3V$L}(IIwu6QKHYv z-_H@m>5BfcGbczm&9!*ASYj3P^RB0dSfz3y{Okj}xl)Dca&FBl?({C{-GPxJ@-l$M zqFCrvP3m+#kV#i{^|3dS)T!qQ7P&HGLN^+VuPH7?hnj5p*8R@406%EfUg>;NZna6( zB&mq5a@7&VwHt8JP}c{9q#+&*0qk4jNdsUoIweG45bR;Y>NW;cDsw4uq|s*qznYqu z;=7Z1t2$RO-k$qV0%eO~wck9r|F9x8_7{Ssvt zi2w%^#$(^TJ`)s~sepem_^UwQ$p}?R%L>uyyl#iCOm?DNyn_;1L}E4WOA#v7xQK`d z;51enZYF{mhG5iCD9gEde@>IkET`?T6nftdmfZNrV>E;M$)rUK-}9LZRF05G8<7shk%Iw%EFZ-xpQ;0i1)g4B=ebjzty%I5;@ADovu*tIKml z(MYAL!t#-}F4rPC3ZX{(m+p-pf2ul!Q9=$t(ZE5}Cum5)%p@wQ&DkIyA4|?l?+Otw z-Im^$3?Ue+`hlxpp-@>X5h#Mdk{ZgQZ59 zic<8W!!qR(pCCs^$6gMU{l`vRtC%mU@OCpF1uo~_m?_OAgd-PXGU~5Wf?+W5*xXM0 zM~C8gE3WUMI4}^8nAa^!nbTrbm%fzmrXlK75kpd#^qBhgG(znNCLIY9w^NW|;ySiF1OpMSVRs5HT zzGx+FIh?sxL9kIER#a!5I(-Y332v9cWj{iU!irTH)_GJ#@-Mi5d zsXD_kf6lwGB$ou9sSxTvqmatE;f`_|A29~6VF)KLIZA-VK1;0`jLHtG2-4#JP=wwo{K zB7P4%V8eL_;YZ&o(lDg2?;)~&{u%%Aaf92PRGTyGa*w)K(7rnMO#l%R9nCv}GrQ(4 zSMo&ijDVKrH#@(kc0?o6BHvyeSm=vax8}d_{d%>R;Ab(wdxYfX5(PzNmhy^hR0dw4 zSN$O?;e5wGAqON{`lWc7{(t^Nx4@`|#tSHD!|4qUif@P@9D~aw7jF?Lf)yJ|QG0*< zPQWgd#f$J=xJYX2u-9#{v@W`yUG?N#1oL@5gTf` zf7`6~axj(wJCqA`oi9_tJ@eIb6y$b4NGX`1F;inPO268hJL~=%1ZCX=rF5_rphc zFN=X$X}gX1F4uY-}LJLE1)z%1yD{?g+m<-Zar3t-%5k2B1kmAmtjXg<& zuY(;#S{*HE{$e4-N&LSoCW-oZ#>C|0(PBvjMr63sA&;$+f^uC&+RM~T)=`UOE?!uo z)o?f_iIF#SYc?;w5NuFXMkISRxW;SmJr)IB{DS!He z(CT9AuN>kcATVgvf?`UQse+jp&!1mn9?!ZN!)USn0hqPBOTpzQ1lgwiptG-tMjPMd z_-h^RV6N5e$an)SqwgG_uO6b|V0U*nC@S5KP10EMs)*T4YkXk~^Jjnzsit&BF*j=t zkhLG%tmL>wJr?L+^}l`Vi@VrpIlGy4pc_x4_|8r|z`h_1{fptZZv;P6!!Z*UpkpDq zm5RT0F10w6>+qfcfFPEtPVR(3Z?e4vB&Jn=4kUf&paCufD7Y+wS*fVXqjBx#@3tnB zwG9H@gmpzxlGzB5stuAd{-Q#`9Ghb)f|&%WGGE@~PZ`s=0SRV||GmR-5)F%o82&4_ z@%)wBW!)wh72wi$+4A>c8ObWo7;1w0d>&C4KBHm9E-+?XI&gD$jHNedHrchY`{N}% zJ~Ytsx2o+Dl-_%d`%JVtP->iK-N>#3?HR_I)O{w`d&}4vbERCJS zYL%+gFM>|1jrwc2kBPNF0xc{w^k(CG0uc*~v*-g;J>FN)1*v(Q8Wp z4G&5NpyXMV8RLRA!~FZ^=1w|-ObiWCs7~Qsj)|Xxm#?-j(5;CMf9P+}nLhvtopRP2 z0l^HBAU*KNwm5Rh_cHMMVFrv<&0a+A2L$(bYao&j|#4Eq)4JB*CCrE5C2A-~4qu?tX#2j!(1RR_wy>7nd&- zC1XVxEbZQzYS6eie6G%iz3REo;pP{lU_XWRc1KU;Ha%>PpPy&;*o&q=V3EuSpe-&g z)|BIu(eA5%$z4xP1i5s#JYYg4^wv*($cM#Z3yVQIdRWcDzZL6F%VLKUU>4^#dg7ieGKIy%_f%i!%0ZY)Xdk8 z#h>&55@HnitMsBZm3w}=%U+u~XfmRj5yWj1ox2A32U6Q+-zT^rWs4@Q`ZnkD!O9`j zgaJqp$W6I?C0{Eg_GppWMy)DwcbQ)10}L9SZj=3}F&Nn$I1;3!8sEM}FDD!%Rcck~ z6zETpU0rUpNEF~OH}iEF7#mYP5lpYhrt|W7U49c36bvA$6;3-2#{R6pT&!6pSu`7r z+H2T$jl&JvG|bA(IG8I7Ar(0Pjw8f9U86m`6=qU4*MrY(&ue=+pG4<-pyD}d_j#fI zXlZ9^V7$1-03I|n#siHS>fhPkV4*UavhhM&b9T8fd@l_)$QA^nRV}a5XtN9AloJa6 z)?4l*e}{_*_39=`7n-sKWMF_b5&pX_3STGQ*sOGpTPLu@Am{1nb{J!$pn`RO6bdS;PbX|+%g+4#**P(K9yzdoA1hD9V0xPd{Am|N z|HF&H6s!Ob%%tP=FY*Nk$HMtR{Y^4}OM(>^>ot!D>R$^$1?%6ZxrROe{q+A=H|M^L zNV451_4lVR8BJh#|DLVn@6>X2*olr+7V_7>@!dEo$1ajEA}LU^S#kT;P#jgF(vsfU zsq;?u?CH{@_y75oXK41CG zH7<8SQBhd-+D6g13@zQJXon~7?*6W$`5gDPm?88aiZPv59H~P;bqb^Pd@G%_d7Zcc zR6hr_)Z^^TlwS-*@-n)!u zv6+aOVg%A@x2WgK@gg!?f<13*rs{qdgx-4Hf`+c`!HIA+^PdCVi zdOib0f?wL>?#NwP7&^`89WmH>o#s%2Z0{;~43S8B@4dhqJRN{{zP()-Nns4fUyxP6 zb*M4zGe(d-0SbWmuszUD<8fcO`cD>JMz#l2C^w)gw3;B!gy74lbyp{oeB2Sz2O@UU zXe`KuG zfuWLgz1TFJDd#oUXJGif0Ha!^F>fI_A7weBwzh zAC9M9oNbMXjC4K>aJ{-;e(ou%H5p#Fy@@Fau?u3A=IQVl*RB<1=@iH)EuPj(WjAr! zxbFCJ4X^g5-jc0Yv%;|Et6gUmP&?^>mTl#Bf`fq(eQ;RTP^M-o?BT9jZFTv5jkIEU ztfg{yv3-AeCt1`uN`D5tdhwj@PAwYrR*_|nL=7A-@)$$&6Q64PLGeht>#PF#vK|78y+0f8aABV zrx!2&tku;U8qud0N&e`r7jnz5AMrip3-mFm5zHTm zOyzE_Gm1{2U-p%U2H21UeA;EI3)XB(mHN-5AFn%9^W0|oZkf&E$$5cl%dO{QeoAjcJ2`b7Tb{)Se+WqC*{sgsA=LMfL4ebdC{;YLqSG2qB%S)fn<1yJU1!JEPway+UBpyc4*HQupjU-W*8xL&NKtky!FiZkN^u;ziAqG%?a2IemB4UbnPWT*4 zB?1`Z2H=NMcn(q=R8J%6y;gG+$7FRb4;P`T3Mwi(JVqODXLGV%2Iz*a{8X$5xNh*8 zEL9MOAAgQ6zigOng?{?9(77|XWzocv8Bnxj5Aq46MFYtD?{=L_exkGkGTN4t`N!py zJ(JO$X5hE}9~5GF72E0F6OCferEohFnD>EiaQelVC-J$>!Sw4orpDh zGsICm+{OI~&D##0((m3wN?L(XQls6TDHqz`f>m_vn~}bUInz|r3D4HAC7lid5Xt}L z(&dmav9X0kMfqGjBaHVeaWyo`H1z4TJJehB&Fanajs+&_*~2p*7``ii2W_Y4Kz zNMXvS^fI!yvdU0zZpehF#O zOSPE8oormKm!}yH-#ayqNf0a5fdE{u>_)%ZKhmVl@;F^H5|Qjd2lxjrd*9}(T!pUU?j78m?ZL27>)ktU z4+XO!2Rp-@L-+j#UX9?e-VadY z>E!0-*~KY&$^z$}NY<|T!t??jyDKW|RmYIa_f9q=n}kZUlwx1TG?dTo9;r0D*eowI zFjVomy=BwPey1KO;_-}9zC0S^^g?yEwzuWj=bqI%SA^4Gg^B^m>6=oTJOndqXr3+v zkG8hrloAHLVg@@wlT6-ud9YmeJ+8Y-@f1AvJe<|`MPfM{BV)00g?=T@f_@jBxU&0F z!^y72%?(kS55Tg?>b~2A3)OHBR`uE^Tp$Ur4^CoND{w5?MElRPc{v`2Ugk`9 z9&eS#HasMb(-$t4g4PFa?DwdDs34!Y;|cvRBK%IbPg;M}e7%YxS+(DALWfL*1lM=;ZN z1uPt#s;*CY3KP+23aeglb6G)wcAj~_MiE_yql1g*)%FC#=mx?4&Hm@rJAyfzl|D{t zB?6mw#5Icz_3>#8*3bBSvT1d>Vma-1=bPa+@W6X2!R^pC4x4{6TkNT-IDOf;>@I&8 zkdNzj94GfsNrv@1H6HP%lSQQT|NM4WLpq+$y`<$-6EZM1&SnjcLLW6u@tU5{F&Wbq z;104JV>DO*MqQwd|j$&fFA2qqAGmK?%LrUksE_kNe=u_?P zCjp-l(Dof-Nyb#6@pbuJ$nghl>r9i?f{1Y`y!jgNwFZt=pP1X*X;82;3$Qn?!QC1|ES#XxRKMDS)- zJBS{+-nUFQ`0Orh4h_w0L@I$nw5rld0!`JJ!W6>A*)@hdN%`Gy;EW$9`TkLWvML3? zGQMj>JONKf)d5Xs64?E8Q&RQj*9KG(E7Kj)XJ)g0q^lxgp=2H>|9)<`%d3qCsa1Cn zI%WQ&6fqG>P*j#MnZCKXLM|vr#wsmjr_<5hkA<2sptl^@NWix%@O^*dSnqV&e}l(8 zS!>2KnIQ@YZ$QOg7mxsQZ#vn5YNNT$=7*NNQS z*|h#2{4X6vQ6I4M$KT%lOb@W2p_!6RqgPsahnUB{;yGQS?Vzn~L($n=$RK*Wk&P?u!f8Hra2?YXGW0&VnNT|N= zW6Q*E>u=vaKX~q$T8R38k>e8@^4a3c-p_u!KU*H2dbcR;{#oHqRVmviBggM-7Vc}M zSuf<9=s-vVA}on<+Ua@%mCpS0taiIy`*95NPM;X_Gw~#7Z9nq|@`iiMpJ+KDR<#1P zthO8cDG=HUCDNw|Z1Y7sufo1C)cP)0Xp4_C&rCa2`mStgAGvXT^IDl5y)Yqo z{2SOIY{9nBl3Ool4r*$*0937EP@>i(>f|`&u>Gp4+GLW%`k|=v{rhz^RMZI4#x}Qu z{3FtG*O8qi;Rm;d(q0`rL$-NVoMUbUArYcK>_!$rsNY8^m8mwCp(FX6y&|1hNbSWYfOs-h zYr4;b7!Z|C52CW3{CTH;qn=$+!BQ=|GlT8C){+dBu+x8Kk*&{HZLwAhdhXqK&^KS0MvPIRcD=2(uxhH8aJnB z4g)ee6SI?*s%=BRn4Cc|blt_G__ z2ETxpmm9|H3bz*gr_VufyPYQ!zs{!Ohrbz`(r3F2)Yr&}OlbMNymZRl(J9bZfrdYZ zAI{Z5AVrutsECzrLfUlif^$CAkc)isfioB?H#$$w)YeF%l5bXg*oKDB>U7*n%x`}$ zvbVS|-rW@NvvIQ2^$AGQZsyZ}NL5?i9Iv2FiUZk-G}iP|&CLg1Q)>Psc$(fTy4UQ@ z%jH!d=_Od2H)gPog^%p620JK==+g65kWsTR&I^8D%vYd$o!{ju1_&1zCGrqLWmT?= z(;rn`_&nkHJrNodBgk`S$;ayWH*62b?&^$2!;8O4yuVWaXd3$RXD6z)U#t zl|Or5WAvhji1^8}Ok7QJpV#|iujt)!Q(!}BRguZ*3%D_UI_mG_DFQAoubG$?5@os) zUb>#?RH3I2AkU@|>+98o?s@(rSk~sa9G>@qXu%>PgT?fNLfn(>F??czp7}ZHiz2o6 zik6IQ=BoFt;o&K-o%8DLWeXU{E0{%`Ey2v4ov{C{F;KKJhQ;zy^|qRb&TV%+uv=K^ z2JiR|adc!( z^#C$3d3q)m7ULy;z9?X>Pi%mf0yGWD$r!`}Nq;B1%lEw>4?xmOfHD^{hzdo%WK=<1 zh|l*ZDBclJ41M<#M9SO_eX8hS72RqB%2JO&srK4%Kqe!t&UC8-S_X)cF`|H=S1;Uy zjer8GlY-Bqtb=NjX}BD>o%~_ADng-(u5xg<;9ku2tV*)3 zBAyBUNEV5yAW1-={XZ@sU+x*m3q#um(LZgD`~AtbIMGw4N`!-lPhs{Hkdg}L?zz%* z?(p21thScETk_(2kp-sJWA&1=0Vv`CN$BIxE7U+cF--wD zATooy$#}y#;v-h$;rFf&bb$!_RtYqcV}}dyCkxYgtHJeV3T#$NbI*Z204cqdPe4y$ zI(6M4M%3zLUB-}Qu1w`SWM+It>8M$wQH4)G( za=)v~WyMWguKKe(mjNXkR8+cuo6U-AImFz^J@@*);%hdJB=wQfm_G% zCYsU!N^quAN>&%ao6Tl5iP*gnH!Ts6viA##Cedi;>filFz~kt;x%`WEV#v{0SzEUe5DXKOL{1^CM6n8$iKWP;l`=CUjbRe3l*ui4o|d znd!-UO(z`9=F2J!?S?DUtTbtV$oM#I&i3InU2N1Eveja~>xWOpnbe_I{`j#^T9?LL zzHxM+_p{!0p9zP{H4>wGC{>u))Ksm?_{18Q1?|t^M=%d`q}ZjdA_Y-I#9f#$aFpY7 z_xAPz{r$y-#D8Y{#ufwP2S{3O$4kdX>J(%qPv90cP;;|{u&tPwkpKE#Skif({G|LX zHrD-kXSwOxySgG^!yMNB$j3F*Hvl|qV2sK_ZN1HmRA&F#O3#<;CjG9&qr2f|@pvL( zVbNhv^S{Mz`62&z*r8TP^07c$2Z@B0ajU~cC|N}-sr`K?59gY(!)87^QumFY?nNWd zmZ&5e9Z|`<4_8Hw);3nA->MC_0@sBevGki_uTQ-Os^`d`j6H`oa_R6YDl#~px#Ix=us&t zi3Ez!HsJheBp1!hNKcDwZ5_q^V$Abkt22Vy5~IU^yn4C)c!9Vfo7Lvu2Dd*u{aYb( z1|JFO{%m8%8;#)b<-SN@6A=^h>O7`nxGpJ+*PBwkeekz3HXs#98p|S!tOIQxo*!ov zy?S~fp_jj=={RT23FP9AM-x;oV>l`cj3-Mj%}QrPkmoxo^ltRWFKWRL@0OjM?i=w6a#6_ajfP8G- z#?OsKv&k}@)PZbzZK|klch-fS@%vB6i{%z)EGMssA_|mG;tA;NE!=JHFK0>PZ-+SQ z55qrXYb1P%O4ruXZE?T5U1_=bykWz&;(fi;037@ebiGr<@l?+Dr!sei3@;h(@M~30 zb%E=ZKq)8IRguYP#CB&T@2JgkpY3EiUjR2ei`n2U(La0;9Q+LVSE~SoK>)RWoVf)? zVzcUCdp;6?5mNnz@auy$#A?S)cx2?q53r8j7cx)kz?tt}1?+h=le5xkr*ja6WQGHM zYbf4wq5f?k*a_FCtHE5UdzFUkX3ZvQ%xbGBEngzw0|XnfpkHwChoH6_1_e=7Xzz?n zZb2De5+aw%!}a|TU;*1bPZ%ur=e>BiHk@j{B`g107e2J%%xnWG=dQk2C?Xx63(@0y ziE930eRIsGkaf819%VpoNiLg)j)KDNIh!jQm4(9&8d-gbyJrp^6ypw!!Nb$Bwmt@m zmxo-ojw?S=J)t9!ijE#93~TcS#z#_{m^-&_E-&#)_i0dPt>?WiW;1RJwVLd>bmS%8 zKVUbV9n)8vtgf+}?Aq=;1C`m$Hnm%exsB$}hk!0cwQ@P!)4|?ZK5GO}pFv53fuLU! z1q%by)MnXbKT1aF6_!*nbDKQv`fxj2YJ-c~cVPTCLOz*+_TQ6EMu3O*Bb+WoZt%G? zc}+ed1s!20ZH=dC=E#MoFuV7+7Z?{7iptFErxFF64=3ePXtw%iX0DWy)+u6QN8s}Q zpwdhPOw4#V6%*M%FrY%qj3DsI* zAsL_p%49U~s>uzg&Y&Pd8AGVdY2~s%oVK`biWV>&imy@MxSx;C6alXt1a#02xT zo;DYSa0Xqw_tISLp1koCDr%mal5=ynXf~=eOQb1BM~%LO0hYeU&vv|Q!78v^rPq;K zwc0Ki>4PQq#L2R00#NcRKlpVWF*vGi*sd_KFtLtT+L0*vrvja#xeQ05j~82d@G@6g zIfo$7!Xx0IFOR;Zw{qAO?MxyPrmhRO{CBmoO;OW zHvwd%L-ACRb2Q0(bk;~u@P}i#A7Nnu84FqI*@dyJOdKGTyaK}=%xE-%oG)gzxf)cj z+w@FLYRI656~j|mdCTYK(4M~Foh)cwLPM;EM`hh-qWfG&rM2(^{7b>0zh zIqZN)`gFBzrxcb>gX;;?B2tfQ%<4glX*=p+fGdXZVx zR^U=h`#3Yj{`wm0(&utvhazENZ=Ov7O-<}Qh|GX4-Ol%+8xjgdtUX~wNwA;b(c+Gl zy-9#hf@=9VF0TWBQb1wyuR8wx25`W85=qolPyor9%d2zQ9!w>)pwP#X8N09PXPf>R zlR!H9gs3Rm-HMrs1((e}`ccmJ(RDKt+wdHcE?QctLQ5xib!l?L`uzIr?1>)e zRtmhbz{LL2lm#7?e!T2m@3dhLn}?)X>VNJ7$^56w?T9w=4j}!V>|v8=mCH?IIJyP8 zO+yDfa87uCf0fxRj!dqq02RrXOn4Tm8DyLqrT|i#f@epC#?1d)K(;n~z z3Sl=u$YuvGEm!yx9y_RAqTpOioMdYfE+(z%Xm2^M4u_dQq~z)a8gALS+5P0%fFYeX z1M(|$S`RsyEQW70IXy-5*E_~!zZeY_B2(&_A(pglS~cIdu1CwQ&QKhKBW4@D+JFF2 zHl5RDZ)uGc*Ue-&)u?yE>lHdZQ3C0uyoA4P_sBG$p5(k{vra380#JCbppj%!GQIPB zcFqRE(W?DLBk|%9!|$y<7;eY~|NMQc%Qb@?CKI~QJ^2tC#a5EihV{y(rdpHF$jBRj zb>D4G{K;CTDoaUG*C!j!q8o2fga*Xsq{_uPazgrmz_D|0O0(X4^9B^O9$$eFb#poi&K8iA9G;jsy{zFO4MlITp35^lBN|q< zLdo%I1=MTt)bjQ6ew;~_^8=X7^Waq#J~Tiv_&g|t$K$mQ+UedZn>$%V*(8p}zA7i#R>Xl-|d$P9Xi$K)JLrhMHxd#_>E|}eD6HjRv*DtC>H`G9^750? z*mDDL#9*iWsRjowyZv530Sz?ew0%2P0y`uDCG#&z^!V#OZl#JK=i|l}Qx~hJec|FD zi}u`rfR>sVd3`b%7?|;tuJC&?$ufs?+fg@~vm4(YQfh~~{;OV`Q1magHgkbME(!=2 zknj-oOuv0IFXgrb4mr$~?R3Y^i0<*u4xja9G~mfFF+F*7zRjeLUhU8FSL*5KdTapI zQuPimi`5rCAF*c8-J*{gx@%0l$@k=uRMzL3I?0)#p+f>r4c89TP1teWW_=p*GtlNH zqviDG3J;f2?Z`y74$YGBh|ct4tJ&IDNPvz48nsgy1Y5aX?Z?vV@;J)TmrA7`QB8kZf=$)2TSBFjk&ulv}(MQG`8 z7FG7X2sEbD+J1RI8x~!my_pbgxw{TQdt&Ldw=jXtHpp724 z!_D*4nu}WoL5rDUeITaRxox?^s47(`5PEWkNgpI?b?sFfB*A6&y-KBaU0%-w#WXj# z-dv?kMcE|Dq!~aMy?~@JX;*E&w>KXUp_=X_DVd0CwL79BFS(!ImOjx;-?cfKD;D$f z0su--R0eClN_3JafF6_T!hmdq+seJWmt+QTW)5Blho8R+3c6onk_94jU3f^1c89(+FkU)ZC$?^UJ;*VsgayIDpCm=x%JnIUY;naUHqcb2?B$ zR}B-odYDeRI6k&qoSiITpCpr3V)7Ndz)|!=4;wg&WpXVV0Iw|hz$djmb9|mit3JZsTChnc2 zN;SH`lVxwmi~T(1HKX>klQmZjDjP9bR7WjePGdroSyj*rsd^T+{Gs0 z>AM&LsbuaVg}z##8!r-*quJR{h1H$a^YQlA@z9lP9a$JAQB<*J8|U{BY0VoCpv}@# zHe+$7a&eWqP(pSZ-Mu^g*S^7L3Jh%QUmY6L#VRGT<74D988%a;B_Mg%A>avXC)e80 zr#H7b84!WtJTCkE8)&%LY;u|Yu^e?qfB?ICq_VQAOo{5{u>gDaG?+oLqP*6Wdfp5i zR#A~gq>@Z;Jb1YfhRs%faQLWnT!WgGk=bdl=X_k#;?c3-)*A zDh=KossU1uYOkrt+5*YxD$P2*Cr;}s9hV~x zcFAoi>^W*$`F61#JU^(*BWt~w*`6NBO~jA7p~8X>tTddB77Z3r5%+_2Txv7O_`jF)lFQD)fzNB7n(9tOZY22*7}b`@QlRTn!~UMI0K#N0sw*y4=`Twz#+u40eAz?Mk(=S(vE- zI;T^gJtnT(fE;}~dN_f`OfR$0I34sODfs(ntgrcZ5LuZW3-ro-n-5;F!EeKD)P zuBb5Q=j><}@O$yKMLJujiSb}9gmcav41tXbA?g6Y-~kz?WD=8n36r*jY@iM(v3Z zYlXp35xdUf&``eEnw~foXdn++#mRWx1;}tslXq$5zAx8quy)y|>kY%@&2$*h2g}Ft zVl5Q)K3I;wAfJt&E90!oL^oZtmQ9#?dNfO4^#Z{YalYf5fhrbrB?iUI ziA*{Q3igj5Kg{Uf7X7FMnU=&vlZN6q5{~*J;D?+s{%&fu>J(dk_#<%u6{csY4!^;E zcie^J_SM_VP<<%qz)XLgEs*+zydO$<1>&6m2mtUa{V~8d0KAtVS5XlWK94OwpAt1D zAH*=3D(wESvTACt0=e@h-5SH3am$51UB! zpsCM2s>rw6a@`-#0;__$N?8dv@jeeqWNYqFR7^txeD!S9-m&MuW78ON`!6maO6w)! z<)wE+6UC)h&UD~RH&e0#H^}ybeE&s?q}{)?qBdlp3^S&4{!2U&7XCkkF8T+C10?Rs ziTI!(FG4)aBanCf$2?T{ggP&05fc>cmx6%wsNm+-2qTjF&vHYF31dM~`>zHc7P3R^}PrS>O4tyu0Hs( zZP=5x5T1|U+I9c^9GEY**ycLi1=Bws-^Us?DjuzsF`YNA2rtAUc^IGXSRuv~xmg8d zNRMx?h$V%e;UB*F`|ZDl3i$J99}ql%X<+sjS@Pv0wrK(@kSO5vorscQY+)foq)}$Z z%l7Jt=?1HdmV}vxhK7iQgocV!{htq}BPFJRBg5M(3KfP)(Ew8H+0ThVIREl#?uh?C zOSS+1zyQ6d0@hOb{Mw?qy^`lvqYM|lW^JF)8IL*eHxF1(&d)Am{*?R03Ls%6k=0Nt zzO(?cQQtr^p^nLk3(3n6-My#Tm>PqDg(YCNQ?RiSuNg$E{5s&r09;hfUlyhQu8SH@ zyk&EWIbEJ-%1QISBCiiwXZW zmj3q+>K$gD*`@a(%;Mw@{2nb${YjW`!kU6i^p^a;yGyogG|VJbMx2y|+AUs8w!Lo{ z4llN_zC#sTs4*H31_3v%`TG1!CT_4EI7Ry3lVZenhhn4@NZ**V9)F+|vS2AvS#H%! z>IE2uKgr>X;9V(S=#kX2q|&DyW@;Zlzvd9z2Y0ws>Rmqi_2;1E;S~>b{+QZix$*A% z9OzInaBgi!ilLj=8cN~X&EmmPOsOclCpbNO?EjD1x6OenK#0YZD8<(?wKO)~j|l;) zb45rx&(BWR8Lz|jSkA(JqDDe`N@fG&rw;F=I8p%pTZ#w9gfF*%$rsEgP|#u4e+UDu z@6f0&=u7NRQ;>ziVB@S$B$zAwQP9Dt=Ff>&@%6U@3*&X{tk*} zcuIvrV}GRSdX#PqWF2jvv!;$ms2v?00U7Dk#9?^O97|YKBpj0sk5UMDD)+VJdV{AK z@7}{tP0Z*G{*OnTyTB(pzzBRyz`p*%^n^vH5;IWGT;|> z01ODjc!gzXl439DCWse7;F_^m1%BI{UXTJ=P}K~u1G%^5B zqe)S$QQ&~^^aWMZur%(zh3y8H0@E%|kO8gGA!R4IXxB3mL!?F@vslV|e(E9zC?Tqd zOi??Y4(`?a%*=a9#0uuTx1pzl8@a(GizvZ)c1#T~L$=%r+Y{Ge2vE$*tS+k2kS2D)szN+>T~S0*2q-gPX8xt+A#k zj#`Vz`Z$$-MSIQrk7lExe3-}6IocC&x{31P1m_}3Z50W8WA!>FYnra`dLOQ>by5gD zC*6N$W?-PibXfEWuj^g5L1Z}(Z6w<1^3s7vvM?^sAkUTCx`HvRxDm{?_;-3`OmLACAo)sk2Mb@VZkL<^t z)d4B~9d<UIzgP5AXm_Tc$!Dk~=+c4ug+SOfZ>R}}Ek@PLeq z#ayPtCFC}ujp)I5lfmmO;VGRGF9Yt(I=us{a7qS+$K#_R9WGCHue)YUOxCX<4+V^g z^-ekRE$R!EhVij_e&xN(AW)H-nz6sP7e}Kl95xWk&;^zXuvNw3F`X@eq%J9`oGn#T zep&NgzU=FlFPkTcIso>Kz5T$PWBDGSRH#s6vB>W^JP~7jrfQIzmF0uUep;F`5Ss9O za*i$*ks&btgWMSbj)2S2O8gmrfA_(9xzTuGU&hf~d8yhh3v()0J`H5Nfm8W#n0K&q zbbkkjZNEPr0qm&%;@`zCgZ2tzVvVi{&@B@QS1KW=K#xt-(t9?RVQ0tGifa|Dq&@v0 z9c;1K>b%!%3T*bEU;>VYW+(3ys<^Y6s=$kjR&Dr+u@i$MvZEhdH(sa38db^uuIkcN zx_gTZ#85$Wgc&V1LWAGHZ*|b8{p~CVs;RL7n65LWC#Q;m&W3}D0{?Xx41rJ*-SNw( z<-D6Sdp+)6Lw+{_2>;fr71qbu%9v%E6&8K9v|E9PWvk8Z&$cyprL`qmnoXi;VH|Fq z=-wL4rawkIHPdN;6%JSxR-DTqCZjM$ceFEtx#rwo4-yO+>>vSg*7*)eyRoI$a3p2g zTB%&+a9bF&!tQK%=g5sP{m+E=*7^z#lHotc2C(1paEaoOgaXcYtBq%#t^4kJA+L;$ zxXIY-9O1!VUl&rbm8sdAxUbJ|7rQzil2AZ!B-}?ELd<%lfsc`D|M%R1%=C;4?ZH=g z(wPO68;x=&$EBPm;MoM+28F78WXI(4Xzx^u5hjn~Eb78_sD5sE%1bKTn{|}>v;ZX7 zuHMga6p*m*;O-u7rBd0ra~W*G+IR9qOhg=~bN!RifTuzQYm6JOBs1{TgKS4hAN^k4C%p3Lq$Meu{l@+Fcd8Oqq(w z|3@~IhiM0CA1qvXJkKAWgdmK@V1YR4Y@w7>e(();EZqZk&H|< z%=PYY`!-Znr96>75@~Db=W@&Q>2XV-i_W=F9o{h-VB55UK-U0P6?*VGIO-YK4c-Ck zaX5*Feo?C!DBih5$Y(<}QhonM$DgrZmhXaHwlbY2NDT%r$-3cg?M|8@xa@qW;2k!$1lQ{*?Ls6c66eAoaqsl)|s^sgJ{E6vWBbJpduV1Z-ZS{8=z?V9=$-u|k^iV-ofVxwB z;j|rhZiAEi<9OBQRL;Y#+1JSN;Rd3R} z|JC7{>#Z1ZxRB1Pp#7-_xbn4l_nMsU?Djvy^wyE0qF`c_bRso>AnMhdI6DIU1Q5vP zt4>YvTUc=|!^6O&*O`d8)C>Om`LX83AdvFp z0C0-Uit}k)5%}H>&d*U~;rp{dX_GG8z?`a7BZdvFUgd2CZz^i7qMLs)5m!$wyTHZL zRMrY4TLAPo1OlG8_P1bnXFBy$AoddoFbtrGqSD0@IMXj^Y1F&0Ky}M#%5P_{u;fzl zGG0|?=lG7AZY!1bK`!#WP^wFg-Ae$@sA4mfb$Y<8&-{P!sw1W#cQ#kSkce1rY9 z4>mExjGOfIC+{;VrNcSP<%wMR7Vy)aZ;zA~7azh*uRWs#RDB)5ML|)lbr>Loa3>~a z_dYASKv`Z{!{>EsR30MRIL_%C1)a{|b$}!q#9&UUGchtWRBBt*+DwTQ%t~Z-q=Ule z;uB%F0|APWgZPlZpR!n07TZvU9~8;My*AEE6zg7lJrOir;QC#>jYOwb_+@p3jai_c zCkVR5BUvc_Sv_8CA3CrTce_5sb#HFn=l?c;ceP)sKX91rsG$vN+e7PCc_BtUH=X|H zRKm{$3NXJKzRHdBPD)UQZg`zw_4zh7{09Js^>A}K z$eh61DrW=ADZ1Lpp2Y+g8 zsW=9qJA*(ckP`UqX*3W$(9X}72AP}2XAEPN_)#|NT$LWTOXxp03Aor^QtuEJYs~_v z+`hQnj3c1{vdRwvvzf}hk_IXJo&I!sYb#g78Y;BT&d!r%cT8;bh(?}XK+F|?{waES z0IFC|)kpqg84$o{>wzbRfbc~{3IPsde+=N{{?-yN5<-4>csQ6XD`OyFQbf4~?F8%-ewmbl30Re0vFKX17EVo%-i#gK| zA(pDODJy7eosV>q7H>xZn4sB2i5?(j05VEQZP&sp_yz@Piuq8Nuz+v*PBuoSG+I#$&dnYS{VPriZx zFaQiKDX&}P@tWP=gDlCoJYJ&zJpFguARP*xe(+)=;B{&R&;9a1_Ths490vW$`POJK z)B~h_zP_}H$v+3NYTYv`Hn!^_j0nI&Tq{w=V7`&fU=-ER;36TJ?wW!{yA=B@3x!DF zy?fjh)pd9MXI6lvF5x_tVJTe02@QZnv{KN|XcJGM+khBJz~l8j0X&*8z0I!oyfVr8 z=onLjQ>e5G6fs6nHIQC~L1Epm;ya$FjPuE5dt87^6{d(4iO=>3ej{+dZD?KKFk_dm zHW(aa`MQHwRe-Sw6_Mkm17IS{8%pZ-X zQnLWIDTq#(E;L8|b9}lmH-!v1-9Ko{OT^sHsPFCT#z%)ZSL|N-HU48bevc$uXJ~4P zK20RKTN(=wkH>a@c}TTPjDp!&@h2G>Sj_&c*0gX#Ii9564rRLOWPe%bU?K;AWdjoA zz@5^j2plGo7^B|N73i;ZgX-1d9S^tXAG3fiD%RTE0O&>x=En*^8}^6U;<7bNNL0>v zofCk#7rN->0lNc|T41rrdkc?0U#y9>txTm_zI1+T-+q~b0Lq*d+;9%K~TEZfKb6GV(I)L;$9EOp!Pfl|vK!eH{}29R4f z_mGJ8P;lfiA=jm7~LR z8sK(eO+K%w9BpsMuv%i{X{G%spn!lFd4lcML%4w)idvD>d}qHD;R*cU5HIu?$W8>2 z+-&}*=J(RRvEBXspGE`0o%n(0BO6j^f;gZ*G`854Yu99Jb0_S+@D=XpK=#q_CGQW1 z{fF(69uj_ll!{7SEHG{EMdy1t@fr{S%K5YIKj(Gfix{A&f&T#i{?Aa+wHeZZAozbp z#Q!TI{_k28+wOOiF#a3BHGVIxYi6czcq2=;zkOtXSE98ERl}(E71dx=yME(pu>~Zf z8|=O&MEf34U*owWsFW&|X&>z)=P8L}p+-dk9>70ZZY*QwEp$)Sx1Xmi`~T4b+I3N9 z5?RK_bjSJmwn0;a*SC-CQ%4DDr39y~WI(CFY-%7Ko>S0RQl0b|!ZAZom@; z_N=rbSZ$aKM?U?sTbXS_#FtuA%#t4P@oXA!-lbDF0?GTd`$*hl-Z1++z#334{GyWm z{8`)t^xoh8m)zO^KQzd&7d<54(kz}{1EjD4of;9q33}c#&M20J1^Bdp4c67-)gL975cA4sikJTK6~zo>1W( z{NQ9vrAmXcs~CNT3on`cWBe1I7T(p+{}BF$u_;Wy%;p=8H%mMgp$(aaIXJo2dX5R) zT7fuq?FuXApUGBbp#wT`+$&smBK$_D$1Bio5tB&_UUy|8>tA8(2T^2@Krdoo1hUk= z^IG%YKa<9Zn5}y@7;KbupyI1Pe!BM3NEn}sd0B@kAccJ3djNzcmKiL0yC$aVV`qxH zHc~E~GE3LIE?cQUYiLJ8iDDd1<-Y4ZYMPjs0ECz2irq~1Pg!Ppc1Id)p^wY%i1^pI zpCs)AiZSA;XssB;Tqb`)+tZV*bKr zUQk^8kB(%JFCq&MT9zSUVbfEgO$|+@j*28mnDyH(*5xA%;bOo@Q=v^NEzTh(itNZC zlZqwMiy5Dt1zyd6fA|0T)3^8D$-KrAMC5cDMJ~RLEFiw*uz97$Z9aVZ8i2M5_`I}h zyp~=f?LI(cAwe)_wc;~%*E>9Y9lk#oqJ=@L+9*6F>PV`bA6?-K#XtBFv0Q^!c5~uy zLK*0N|9gJ>uYN+dCP(S9R-NTSma~mb|2gCo!`IDPo+~yyRk~y>RNN9?BjEY{-5m-? z{b(TFsC-bnGpsjLq}JstCL5k~ad{XZVG!VSG`~2tWW1P4I#;%a&uw68?~39E3Ek>F zddWsHB&8E#vUlI*!fp8L6U=?X%mzRDfBq=Y5&sxNLI+kKu=2O2@TT?LMEPnsAG_}~ z<3)qbuGC9pSpi>C?w5N$YaDfasa!2^u_T231a))(WX=`HTv{QBJZ2ew$h&6iYlK9Yo{NMIJjR0WyVp zU9bOqpntAH))lXJuOG-UtlUSkue!Tz#v9~2%>rJ_g7Yam{?T)3F{GB(O0YoyCy#5b z`P#Zh1gIX-XngYQuHPG^ma8^du9oLXNjq3`bphdLqBv-}~!9rgsA4Z6)E>oJ%~gm{Ue3GDWx$H2FMb-DuVQ($}1 zTv?9!TEoG^OXG;it2g)FWbK_U(N;@u9~$c0UVufEEoZmB zyK{S2mFqnPBmK=H}+>WuTA!B@d+XCKA`HlVC@tepU=83IJ(z=K$IPvm6nj3W_elDHA<^w{abq`+}>2-7&j5oMnz2M(Sp>MyqO}EZcH{d=eoIL zAf43j{V^dverQB!@u1VHYP^Q}h!gUzEPE))5Gd!iy4I2y3YC`p_sPke|{Q7;}=63S*T(o0m^fhrdu()p6wx0PE+Tw`Fxa znS%aeFs)9+Zkz=H%*G)~`IFZRF z^GCJ8E~57FmYA$Cw&#K|FGRUauGV4?IZq0mg^{^LC%^Z*TFeibRCfFI?<+OR2X*VU z&P)4}+e7{IAQgy$g@(4vSfDe8d;QQZJ$=t^WGdp4OgtoWG*X;b0FfLM)6l=hA^Vf zVW_^G?Esic(OsZg00a&BRfT8DF3d~S3HdZ%-^%YG;I(GY zwMNf!$6|DSd2k41w4Ch(e7N91P)T2HcHMp8Z^+7iQY>+Rzg0__r8Qlwe*#hH0+-@3 zc>hj19w?%8ezeD-gm1r2ZKez4X$KPMohLdWVbO-K$BOFrzH#z{7!ycrX3sBHY4n|* zo*r>shl-sY%NcW)HTDI%nPj^x)+lPY@|Ia0zxMIjx9?Tl z8P*_M6Db^>o+i!pU!0PJvev(+$kSa^h=32ddf=lZyD#g z2p#xWbVt7%fvcb=cGkV^T8^J4=W}s>-fNgB)W8UE5HRoISy&kxU6+tFPSOxDChR%TWr>p6{$^ z_?%SnE2bz$%aP)KG`+5*#!(R@W_<+2UBB~r-i0y0&Jet{%X7BW0KAi*&CkEjN?fHt zblBl~Rp@q)B84uUPYdq;=FW1pHs5K)Y`$VlqdGg&%zeJ)L@q^TWLgL5((f$B*kFi& zp*kZlpRJW~60!5n`()ieBaxugh;Uk?*#RC(?zQL}{X5GgSU8y8d?u?6X}Cyy4hql4 z=OO1`A*TYuJ(tu|!1)NL&w<0KEe`d~ZCRhOaMTIsazmTPjOe8qp9+#!x;`HR1^fK%Kve}Xv{u{tw;hVZ zKnA<}GKdK0fHwRqjuV)k<&e zF3&XTy^uYg?}@Nr_z4Cy)UUH$W~cEa8*j&E$wo%dCeH%qC{g7qdjcVmpzY-yZNv0@ zjS#gb4^MR|8eL+!c-8~pDF29gCmoz&6YB;@jUMmc=?{aZ0JOY;{f+QJ zyv%q}K*p}jK|=Oouw5KQQ(cACqG+k_7`Oi*7!E<@M2H+7v*L!B#Oo@ZzRUp{ZS|H@ zFt=I2QM)+id&9SMpz2L~wVuyo{KU8~yVmSQX$A|nBbk}LP3uTV1)RK%?i`$);Aq84mHxWGUR9il%7b}8~* zai)?(oZv&++qZ8=Q&~T2BSHj*EmeZ}5K4uF?pT1Ca!ape3UgWDNc&OEy{~n`kic*M z)OS@q%8Zk0YS+RQ9Fn*8hOo`MRzE85(j%EUEAVoK%8m}?CiIM|}b_d`=KMUV7H z25%QCze;S`S77ET1>;{~iol@#I6x-g^X!Et#}{NJDU3L8XOrg+L4@^|M^8|Fr}Fu+ zk%hsAFg-n3-l%l#;y_X~WWxItli6e_Qh)w9qy^Bs!?=M!Y@a^}JSH#?1eX~UzR5*t zg5=077n#3=cZq&ZX4VtFt<1}H(cSpG0v+@lYCyMc)G|+)e`X_krDm!xZlv@wrFtlJ zp;G6hY!XrM&Pn%Lbb8Ewd(dYK)lqN@76hn8M8Ao;!0AE=&4;Y)Wa@2>>#yZ#KzAnt zQ)DVJvZ3J|MF8L7<`1PSiId6vCHSPW{`ujKRd4rwT!=vgS@bYS&#z}b#vC^dm7K=x zAfg4NZ2jsom;eTR)KeCJB`uM>sOuP2ZyD^o-$mJCteMzu{dB>Yp9UPQ?aYp+Mu^sr zvq!NU25HGN!oLg3Omp0BOid;l4`PFfqwYrjwM=iYI%Vg9VOp_<|A{2)-76fu3?JeO z<)9|e#OTZaNGe!qfqe6}!a6Eaqp*OgHci#>hPF^Y2(1Q?erta)>3efd;Yz`leC?m0rLYgr|WX@JA_jZ zT2#vxlp6Ha9@r)Mh(>9lD|T)}RSCEnn5`ExZL)GN*5jHX(Msll*}2nSh8u|%gcn7m zkOi6*^r+cpC0K_s6~mYxpv(0L?ypwdEldwsEtbCCjL~`CZm7+FL^1}s;irBdOArB$ zn$}&I){#<;bmYIAvn9?koT{)KiYVHNFhvm?B#y!k`pqv(>XV;Cv3B=dZl)$kdmoAN zCJ4lp2Ashj6byz^jHl7g-J<9Fp5WCRwV59nkw_6RflY}`lI4PBgr|FXep@g@;$?-y zb|RbvmA4*e9-GB$wsJ0%Kbk7mgp!;q$ABIOuM}^k%@e*SFE1=oXH~kt@!O_4M73A} zX&VRiB)Mka4$P`ZvX65#sLl*gD^+7?i`%cmRqL{JU&39e;sKBZC)$Q%KeOQ8?>ugY zG?cQ;H(}?J>(R<>w3CnzB2jNh4J4HPq06pX_2OU2WJPrN8zn^|R(@}01>(jFDndc8yu zjBK2u%(aD1X!Hh1ui?zn-A+JY`2i;iG$y{()mBc-FD=ahw6S-F6*!A(x4H=-V9T8e zrB39lVtCAEBYz(p0A9`v8#zBLRd!t=idfdAMbCb?xhfq$>dS~3n&!J;Opt50cfZ7b zjUI3?T~uq3Ad~R9R34=r%IWyQs5t@5T-kV;+mU^~00b4BI^6PwwaLj@Q5m81U|`}D z_>-Nsez2cET=7e62ebXOAI0EsSg`U8DQ$5fwZv&_=9KuP(Q7BZ43#ys}EMM&2U(2zjtV`|G z1idO_MQ~_3UUg@7o)r%7;%tRQW4f0j|D__oz{`UL$H7dSQ%c$ zLI5NMC~DSE2z9%3gViNmRrZ)MpGl@%T-yJNH=s~6v zB?4%teh_`pM`W^|2syP8>4f$D)#Nzz6P|6l_rPkY0x^T7mE~qd;y2|$lSH-o3a&Zh zXy7;v0q%K?#xY3F%f}Zh-kZxG^}yD-hN?v1pRsgw^Xe$ze=4Z588FMeMH@^=;`Ur^ zR*L~{DudT~i9I~E|})wmsWb$3VC;RtZ$}|y!NKA zVLOxi_rn%eYru$xb4xb&EKAjeZ**PecjbwHOUt^lp*oz0UrzBW=+jtD9l!*jp4Y+P zh;p-3lYb7)Bi1@8{x?d^zz_@$`<%|PR_ICX(H8&zswy*G?Lyo+$v&bO%YGnz0i8=` zsn?i%XC-)<*pmqw6Lg42;bE<;`Xs3uJpqgiPa7B*EXvbO3f^3XNO26paK-UsIE)@| zHoWCK_zwiiQWqe6zkjkwVt@#l(AP)B8cVBNswLd=cn4w|z~a8r)?re0e%h}vk0Qo1(%G&IOTc|jA zRoe)rt7Tm&R|?D9J|IwK19bXArIEl0%8+kJyoX9>lk*^YgP(5zAw-*(;`rT4wyBV< zt`u^nFddr<)Z4ec>6t=Y=14i2Pi@Z6NF@@s?RbON(8_pF5=Bg5KN?6tSskk*#Ln%! zT4rVvoKN_=6Wo@5#(~~6 zMqmj6ryZ3~=4;C#;y&q-1p2y_2I3pd)60%NpJb@c?+hqkl}ct!&K~wEn*IhM@`}k@ z3$Ln!5sMzdztlh2?U|VKxopu)(zTDGu(W6(I2(^?gg_~mD2LxBCMO3O7=i=OXxeR! z6{~d?qie?%*)frVw2L0VV2E&j!@dOK-%U0l-rwtI)U_fMW%EOdow20J8bzGLqXT9O z&xxz?=7pGwo(N2)OVH}Y{JbT>>2gAw!g6tXk=|yq4VpqT*IS9DnC+6s%M~o0T)9et zSC$=L!*;G&OIulu5D9o7y?d}ID$rVLhC97L^ z`;Kn}gwCyyZ~tx!tojkiT;fHH*XyN8q74;Z&xCKsvfa}pF%g=yYaPzaS~5D4By!pmmKMyu@+!k?k-2bY%tf%#XKnoL z_W<^~RB#}I`!fg8WO8T*Mm+^(zP`Wh&IpMH`yrB>4T?`C{9r;5L>JVx$k)Xp&#@RW zCj&9s!Z&gc-=#CS8=rqO+z^7p@yQHqU(uYU56{eh%KEPlVD|p*8MsTL(wnW6K_@T7 zmoEmD`}Pj4W1#E24gRC^GAluW0!%t?2=5=#SU9GQXjTp+i{OBb2hsl;oAw_S`_Rgt zvDt7(Y^#i=`Desc#f(SJZ^d74iIp!>57mGzfw1NJ^xj`0lOS0zK8Pamb4tyJZJY_O zP)0Tw(d@Op{(kX^AqcMtB#O+%lSdV`NRbtno}%z`=`CJjKcV|N@uJNKQe}a`l}B3j zU=~hZ^=4hSFJJ00MPo1)MTUSGL&P^bZtY)xA(B&gp7@fGNGHJPqD6i?l}6Gh#7w z?Or@A2+TKSzfOYyg-{PTyBK~_m7N7*x?+4Y*2hI?6N<|9;ZVVHbV zEsy(IZz-3_li)fI&Mt_oouRW@F8E{X0x_#tiB2cSZTrF1ey(7y5^Smf00^8ti`Dbq z6eZvE|9Z@vjxcj-vWB0w(}J8y1PLK{ylD;ILoALVVsZ>hTT~dx))a~3L(4(GD7Dr! zqQK_g8_&*bF)l&bAO4z#De=+0N5k0)i;9vjKizrJA* zeJ{1(M`(VVgQw$FDqg3G1^1+^z5CH>gP{2#A~a&gc#{}8O-g=(s)s-qWRtn|ntO=v z5XlT{a7|#_{C(DEjZ(8bhRu8+ld?sj9e#&A-+0uY^Ko2i5IPWre_DvU1$(AIUnh4a zCbsYA6aUcym`c2Dy_OAj^Nl3YfE6+d=GXKX_E*K1E`JNzn3l-NsOLVIwtU6<{;*!1 zRWY4I8Bh`$Ys__Dt4>s@?aB*jhGctI#IP( zn|3dPFQ2dC_%OY}hRt@!GhL_mAj)Q{SuV5`zrtB(K2@PQW`&nfzWnXvon7O_UTgaL zcb?#8CR<1MaH@-TuazDxkJm`d{p((-8_RZg7yFDZ^Dd~gA6mcY8Mhh`AP0hTmmV7h z>1RfCB^V5KKy(u6el)3Vd2Gyk`48c{aPjVwRVdya*JDO?#qxh=N;+ILM@xhmiz z0uLb8_}wwX?nsO5Ie|d$KCyh!!9ed z(UkU3Oa}Q6-kRk*;?xM#+f0Au>TSM0Rlq06;v`}q6Gb*Q$c_?NfOI&H-cSQ$vqV>1 zdJlU*P_(n#1hTU!Mivp>vbKDs?TX>H^HLdJS#>7Sx0xi~%ov(gzFkE4!DVX=Gw?R!8ERBttO0%SBHK_Ro*Dkn!< zOy1nJF0s#_M`a1PnwmXsVS>A^WT?UHuw~=+0x;NY^B9`%8N?HR7^iVKzgI39*c3CN z836SF7g{AoL;^0_Mh{r{dmpFHy()~H^IGp~QA&yAx~JW0dqZk)HSc^Sb1se;2Qbl1 z<>khol~aFVyZ1TnH$Bw_NLnC*f^s~vAN>0iIVB}#R=8Sc^v{dD_m{3kY8xXPq)F#IN3Z=x<9E z@iP7r+-llT6n_QSnti|6`ZMUA>6IPN{?_gG&Vp&hDzw;r&ONDM`n!|04mBzEs&z_) z=LeLFlO?<^)oFy*_F1o;LlG?=Ekk}_&UWD7@_qditZ013kOf0K$Bjgk!Cnb#{vOS} zJJh7rYN>Dxush@obpDqVFclZCa19fKl)sK2lEF`+Yw3@gpRZ&#fZA?%QfoU1Vm2ts zTzp>W6;NQdwfCXlk(4T1v$43#fq;zb3%e3`f~M$X?niD|8nNwwQrxkBN!)S z?|P+QJ`x2A_Ilp_JhjV~rMa9jU@rV6%(kTSA?qtbgra4nqF!G=F1#T7p+d#t#c=9; zFlOBDdexVVmb1os$?l*Rt-@P-GY0Hd9xj@_yi}J(;N)A(L>|19OJ>Exgo&XmX&)?t zpVUrSy1zP&JdweCf(VQ-U;Y*0yZIP`HYh4W*(R{K$oYjFfb-hpSu?GjAJiR>=heMv zhhey~&6GD<(;?*8VZihYe@!Bh2(7?b$I|`0!+>=bQatxG`mJYdoSS#Lk*LI859;GnOF2P8~^y|QC18JwBso0u`*S`&A*D>I$rkt7-1+%`gi<^mLgg|Mc|i0u1yEPtqF^W1?T5j zaGE}x66`bM@6Q%S5OGaXC=t$Df=MA}q*03OJgy<{K6-M(k{}F@3!pLgmu>_4VSx{q zCSNfxb@Lq<3g@fFKNSYtwaHLSBJRKGYG@9-}9{lgl=t7IRyN7_Z49d*6TAP7=2D6{d)d*uObCMNL^i@lon<=78O8Uk zub2sm#!=&lJT~50-67)ty*Wd<{he?V)FRU+kRxy0UiAh7!k{j>=>FK5$utC&&gQ(I zZrTIXa&&4P-Zu?hrPQ6QG;JP8Xm^Rpq%#;DI*+wPD{=Weh&)@D7Ku5&7lM5N#EJrc zz&q+yosp7Ct}pYYFabc-O{23z>t#BPpKq_IqWAaT&);swpzSK?NuVOZgb7&rJKX~^ zqP}q`?kULE1EF6o<9%yfAHtFFmoRIqc$y?QxQi(@{>5ASpJ2A>cHM1Fq0Pg{2;(j&ybo1G{2XN(LRnFDG@XUkDlDs+ z9@?Gn(J%=Zq1|pju9L2hdAfPuHh{#`C{L3!7`**5;bKeRmX4(Si zWHTg+sxm&X!4dGdb|v?$7ky$_KPKE;AYk(XPt^>wh^R|crk1DT1hmPxU0a>j11cl3va)(F zC{Vh^8E#r+^DM-5INVnml@urZ=;{JP@q6TaMF`O#QJH^-?x3P-h-}+mo;U1kQE{@b zE>(K2_11)LGrZS%^~QYcXvTcni_L1m$U2|<7FT2)C4Y1vt)dWzM?cf;`Bt#hZFb+; zdfCndNLIV@uWO}Ilk&EK$uvmY(G ze?T*kKll1=5*Q%EfKTumyM~#p)%G@)9R`XT{zw-H?flf$@37?-MHh~vVupZ{)m`KG z_{f)4&1uvrK3x#y(4Q}zGqTpG6}fa(3${oie^DjZr@qoH*GZ{mWN;b?EUAphHOC{v zANiCnUVGf#h}JHXW^n%!JGk?sw2#1Bj$ou9Vggk+jE_wE$ziBf70 z!}b}d=Fhj6k-y1eE$vVEE_q*QMuPw%jujUyx4NBZFZMm^|h@wrW7^!+?sTmnP(A`FE?^Cr0Vhn<`c{n z`BUWOJokvipJ{WPEOoIqK6{|F;V|pY1+IKw_WUVI0N-DQTt>Gu6mtN0SIk@nZJt^+ z5M$8;3U{Q~v*lCOww_V4giJ-6S%=YR2NhzST%BJx_qN07TN4>0i!vrU{b6oyZ|A}t zGvnPhn{zqq5Y{jP0m`l?;DcubsHX_e%(a7hO3!N*TfxEcY?(SYNAt`P?$#XEC(`rs zPpw7e%WL|I7y=N;(Baz1KjuSLHGbd)xjB|%D~}IbRXL-ux>PJ2;QXnpwW9i5&Y!q_ ze!}ERj`@w<|MNa?!e*4S zu){biBZO&iJG*%hp;j$l$)0$W@p#JnKKS1pDG|*Fw1%*zdoByp4QXhScVNF&s8m67 zg>N8=P_~j8HjMlS;{`(&M59qpGLZ2#357OCDJhF9s0S!OTBs-x79CSeo z&HJ*_wLm8+C0{^7L=qw2k{&co#E)%XZIdu((IhfKlA3nE&MsZ*>VlP7NMcmWb_n|R ziuMP9Cml{z!D2T?ru`BzI8V2RcLUa76#Z|iQCU$igfj)aTD2N2bQ<&*Iw0Ke0JW3U zbRq#0`JKc};Zj=DwY+dWIGBNcn;a)nIHm~$3Zzwd`WyLV0rM|JhztB9SR;`jJe&PR z2WVum!*audO{o;+C2Q3Xo7=EF{ANs5^OpT?&)%L30P&O!N$K;~$J@jzdf!4}&)s%l z;jdGHw|<5E7#531`&hL?wtj((CB`jUe)fLn?*bkCSD?YsDz~q?-ZgBDv(*OwAsy_M4l% zr|F)7f$+yE{E@-_u;?F*Z9G(*B)vZrqo(Na>we(%4>kx4HW?d~Tu2-2w(Z(Tc|Qq_ z#P5NzPgOc?$9#WKSJC$&D~!`H6sYy09($nFB8E^5%95wulVE;M59>$XLS=JSjW3CP z&J%;ce=^7sBnr?P2TT7%^li?ZQ1}=iZyS=>2WKa@{yBV{iKwJbeE_ ztH(JCf%56VxnI4`EFjIyj7gDZ5@ds-Zpl1s#E3}0LQKtz%@k<6W{gw_boE*;{L(a# z>>ipQAV%#?xEriVv3=4d)M2+d#Vq2ERu`qp8z^F7}4(<0!3|=Vow3a70LiVAm^^*4(>Wd`5kuT33>twZ}$M z0|Cfzcn}%Dnb&a@LBzl?A|HQ;banV8GywX+(7-t)jhm6angH~({|jIyy1dZPm-*VW z$>88pE^U;ukPe?7>2VGeeB@#LCC?+Ypyguv#+v^WH{0de6+pt{F)-+~E@y{|h39*m z{zVLN{C`+`>#(TXZ~qrXQKUo=kW?w@?h+ADknS#N7`jWP1O$}sROyl!V1SXK8>G8C zhVFsijnDpmuf5OSXP@hw>+lEH^N0>JpU>yM@3r1*t=AjXewtyl1ZJ2|ZR6U2LhsUh zxqQbjJ1%akr!fm^{&~={P zc5j#`ftR`*=}36}n8AYBGl42lWvzFqXB)K}c(=5@?H{K}a~IIt#j^#IrmXWH2OWcP z!uIu$)_2_Ut}g@S8aCb|9&z!}@S`qXTv%x0Q_ioio7%kp(pmRinBqJgT4RlDm zgP4B;Tj}+&N-^yVC7@!>wa=q;TvUr9EJ~QPzdudtRa}wk<~Hf%zw-`uLZPe=h&D_Q z#-YWjmXl?X$;mJ&zvqbf4=)QJ)@;fs*x9K<`qHMT@bSy6`@~i8`m2N4BC3<}9+D6S z*hPQeP+?~lCO0+aXfV9;Hl7El!V04U`R{rbW-%%l!khCZ=Ab;D=gFk`%P`2IL`pa~ zPKsKh2@n8`!;X%x41xBnem^Ztz44YrOsa~_Y@M>GHVpV8j~#Lo_ZWcbDPvu;p-|ZC=om^%6ML-a=ekRJr71RfIBU#K`~)N z)uh`_Th+%S|GUrs{fh7>+C5GgWF_Z`DfVpI$^qW+@Pj z^pGtQh)c>V+XiE;%Ed-W81u5@0MBct_+D{H*hxgY5@lq4+nU4RH{*1H2I59Vkdbic zY)J&+?xapLl2L}MzTVhamy(nwX4RdWrx5D;(s|kb?-@$|ZpIfcUbqO~yCvej^u>n( zQn}$LBK59;d|yGPn+`zn-Xw{7eT$EWdLqfjng|&a`aoxH_3vBHm9`=t{s;^V+#aiA zWaP}Z?CP}$MNSrU0d{mS&W3;R?g34)$g@HIcFCX*hTQ?{=DqrelQ*K3&-@yM$1WpE z_`~e95LhX@aVpu`rdvx)YIalAMtTkm3t*h{xtu=SQ)xL#?$LcxP#)bM>oDEj#a!-; zt6*19fhy_H50}OL1NuXCgS6?s{xX&G+sD3OdgHNV%wNi1N!`#8n5WdFSH3J&QHImq zAAsNZ2K{)Qixu)9&3mr)Lr|m6u{ja5_Heaij{gO37je(4K%DRFS9qPXzi2oEX*Lt+J#+&uSw{Q3xUi6V$Qeluj(WaQxU^g3Teb1GY9~Smf1q>6v zK%6Ax8~}3o9FfFryKf;J01bMD&R8~_e>Gc!3A$31t0={M z1~#R8Yh$ozEnaML8LEOd&Vkzo>Mm14+I0A-Ur0nej`mAZ9ZfrIv`Rm$>}|Z=Nf6v~ z+7z-M3Q<`FRjp4ChRavkl(^d_U)RVf9}Rtu^g>3sECkE|{z9oOxuCb>;K=lwTg~UM z6G>XcIhbM`gj~oTW-4afZ#cZ}iy%@|S26f#H(MXiHuCg@(QWfM6&O4}9AwtvE;kPR zMaH}RdcbyQw?u<#Z76dXL`SR4nn6cDXWDB3*6JE`|dcw$dk= zD3_*v+Y$t;^Ew}pbot9Y$v*U1wE*{;vY5h(r-|L` zM`Nm`Ul)|_Fj@5Xs;miCDR>Klc5j#~is)Dm91@T9XtEqXx2_)K#G3!ynyk!}i1*dh z127MZ&!LL~#Kb0PnfWyrD+VK9p7EA~K zdxwvx(}g`;C+CaXr|nPXQ@e66wxI%{a*_?bh^({0U9V@yO;P(}wPAziEQC#ChMtF4 zqyjiwqY@465kWxaZ#|P*pQ(&xmg(8_B*kSyuD>04M`K|FU=UFaB@# zZggeoELk+b15MWa8y$y=@8v1OM-yWM7+kXGUGb0X0qpIj>M&19#JzcX>I&n`Y0PbG zQPBpvpLC8U42-{l(OMi6`e|z*uB67f(5U3)^iO!k$# z05`!ClC|v1qyVRT{g1BZ{nIV{V>4fW9(k2`MGaZExf8(l;;sjkirTD3)3Ceg&QCQ!r7noSil=SB7(B2{{ktE((v65dh#! zm`RjUDU(4}%oIMg{#sc%R;xutzj38?fy?`+d8905yN(5yM&r3NPF2{zO(i!5V^4{(5Jh zz)vtwyO^=t=xTC#8>W(0imsZ1 zQS#h@keU5QAJo-r@Y-LCh9Z}NtgY1dZo(H`!SA#klO>(h4relI?H#vm5_bZZC_^Eu zhfk;rt-=?;tCQqxy+>LKZJr%l)tQGWmN+CYhndrRd*9)8VX?LKxF8k+w(VIGD(ly7 z7N_#yBghwapHQX&&x}Ky!(|h5V~Ovd&j?YCq`rUVP&2M|I+?nOV_4DcIPa9VbLEtO zMB(`(I`DxJpG>Q4a)F%L1LdSma zo;lZ$e)qUYdJBDg3H{-a-vB#P!2WZ$-|SH`h5G+MKnqILw!9wWKj2U(D@VK>DNH2r zgQR}H)D*jZ=X2|as06j?&ZVu0H_AYtWh32E7>?gDmdAur{@1X5>?tVKzj=v&W>6c2 zFu)y#Ustszzp_VG(oymJL6q6n)$c2vK8sN@I;bbp}=|8Iew z|8)rN(Lf0xs)6CMQwfDKxWh$N(=?z{Z;iY016Ha|6*DOTKWLTrS-rASV+w@7NE$S=(*D^JjQiSa37H&85VH<*wKW zI{{)g9c#mkbb)Bolt|BuQ(o)gA5Lw&6B8ZwiFARlI3Rxt8Dl%cHoa~faoAWuFVKF} zG>?ZR)MoNvE>k5hs%0T18AP|i@$txeVn@kwaq9W1oq7TB@d_`|)Rn)svhdS@IT5w3 zn!unS7ch3^F^ExHo211%vJDy$!SvX|%Hm=&gNjtyrLEAhFalC>1JXninED$jLx)Dzv@3JGxZa1U5r}yd-kYfN;nvhLf^ZZ)&F4o5A z=tQlLowIYrrIranO9CK0bPE-DAc6?9`=+Z$-h49J3Qh6X)6{-w`M$2h=Q$e4J)e%(B8k^>57HBOrV1OEgv zRm=K$bSJ(^>t9;H zf}Y77@YqZjyZ0u4E<-MOW@FIsHgGCff_;7l`tIDoz?U~sHO!(TDXxbsPr>hG2K_x( zUmCnSigpk*)Pn(ywW}2teZ6-jJ0P8U+{tv1ftz|^+LNRuLRI6jiL0;5Oa|8dfL*{% zkgUk2XRzE8&D0yLc=kH=Axmp6vPif5?DVb9&~7|vMNoIHu*rsaTrI?gxoXg=VT%Gw znlr>41X9w3Kn$5Lubyyd^lllCF;`lSFegg9dc{|y^N5Jj;WB`Q^bZ9&QZAYEXSj>X zkvU6}5DVkE^{@tTq;WA5#_|JD`qu|MQa|NwgNW;|=NuI6OA~AW4lb7imyD;z1el!B z0Z$q+LhjEd+-7|SyymTUj`qNPl1x#`)~3ZaXz-*k3LvpFj>@;q5#8Sfxk&TweoOgY zoR}ZxSe26jkUSYl$wE@@<_|*}>~~@wEO%Md9o9tOb6oAs6kP#!T}elU%P=rDO2T`( zX>BB;TM6gMFuF`pX(*+)-(`A3ehYv5`KiSk^L)`g{wxnf*LXNq-#LiAjXS3S@)hX+`6wo9b zgn?1A7ljn@_m=>>Tnk;m7qi*oBjejNgf662L`|PvN~9rg_H;n83Nzw9%9i;u zvABdu4TjagxR_KdnG-wbkWJu-Wc>AtS6jzINFDX?qK;X{pR(c8{inoH7sEJ(mEZY zvhw=%i{?`PbHnK%+kJuCV7OU_k8!lm`UDDkjQ-Nsl!b9AML)`T?Mw;KF27~!G;ALn z)KPE=y_1!djDc~V6o9;eZzR@=JZhg7Ex8d?t2T!7p7k|eFODYan01$WU1(#cRMmgZ zml1##?|C8>jSYH%g z=|V7z=i4Tq0-7_%0pf1b`UF_vrNiu>U`=IdH36j+q*uktft;X|)%Yx@Pk(FwP}05> z=M}3k1r_UayqjV9HNL+#9l26C8@jc|RObDLc78f2%?|+Xl+B7-+amx|X8qs&kRx|h zb7)OX+;7}z)ybT|1TCh$)X<4 zpuZ<9?%aG_@!m}3$=nzwS6k#0U>0-E!?X&u8KejsDv*{IV17db(1~Qw9qhG96tK6* ztu*dF*1J&o!MQU~bW?1!kY!WS9Y>3ZJ5F>W3Twc7NS^Tx?p}ttucCOX!F^m547uZT z=VC>Eeq(M_=7IVHMb79XBzt*Xyi4Ge_W1*56Ay?1=r;VGMTNe#l_*IvD>xA$T4b z@_P9#27X=u*R6+whfml!pR(U)h;oOF|Di3t42vt)DmNCTjE%``rFVEK-DP)=@IKDQ zXcDK<_kA4o;tx~#;)BQc>8fnC9Z)};0Bg#6_}a5Qh(G_8#cBr3TfP00Y2&$i_u*u1rF02kO*D!HDkMO_@dmG)>rKjXecI+-$K* zI^b12-_a_y7;exb1B$kQD}o3K0XkIFh6UE&2g2=d`)2M0Pky03ywa|OwT!CP{fu;D zvaBsRG4UT8Agp|+sC{fKQ$OGKCuwkdJ(!V~a0#`Dqa*M?aYUx20xFJ$VhIrhOd(m> zPFL7x-p&;e`rmzkEaF?q6^Ee}-5;Jl1$+AQ(ccDkRI}953c!J73(WmX+&j{$hsc&a zziT~Q=(#zO2v%quvj)r@8sR>`Pd2s~zz9uG|CK4qtvcQ==Khim`}6!@6>&vnrDXq8 z%2$#a=dO<$H~%sW8P1tMm&nqB)-2V?VC-~uv!RT!Y1^4kdrC!%0{RmiCm(5DNKmo|S& zS*3i>vbKzFg-C#z;X`K^7Un;p-%@Mw0C)gmGVQ#vG_X$&B+6Q+)sNmM!<&3~R01G1 zR^g3j)FO1~2Io_=g#L^u7;hzR0{HO#x8x*jdFK=Tn!Gk-BA$5D6_E8ARB${?(%Ht* zB3!y=a3 zQh5=woiv=poewjc9svQ#W5xtL6D!pM)dm^=;jYz|=H{o7z?W!mPwf~dgcvak=D<~@ z!3dYI_m;44x8hI%0A?zhq(JQ@hZ!IL$tiy+Bs|gWw)LyA{1Fv*h7|?uokmXgsqnjfINbx0v5fp;#P8^ zp7fC)CS*f1ldfC9H+d7`p~7b~5fb?_4bA0(*((0N_@jX8J0Vd$nTu~s-YGxn(hK;` zc{MR#1-N)2R&Ac2efhwln0ZwT`V?7IpU&<{2+8%li$N)>7WY>;pa9B%K7zu`ZTWn~ z){9*Z#7xc2pMSHO+|@`(NIt9RHRF?$#^?TJsIlBo{s|v!x8~9mXvhSD zR*RcnCsZ|xOhZ1CajYQySC0SyxdGcEiD_hKVD`e)ouM<8?T;1o6dw`uN{P`>a_K!qHF8?{-FmLZy7Dpc3&D>-x@CuL8l7X zgp!M$o`)DMao#VrQY|?IV7{5uO`!Vd)tEVfw$0{jBO?PgichhF`KAOOxL}8GoCQa1 z=%Vx;8|Y_t&%-C7EO+c`e4}0QAG8FNHF>%s6F5vTY<_^MaOkNa7~usQP3s5#`nyh> z!}Zbk)k_@zXTNFdgqbR0jIK#Xgaz3dR|LkS*!G~uieW`-|)sk(kI59_K-H9v(8btwd z9vY*g7E``ge{e7nf6bD(6=?Zwy~+Sap!oqoD|Vu&m&0O^OKNI5uwRuH4&C^X8;6`S zaY6-2fadd?j(>HZp-?3}umH^!?d`cXS&41%h*)$L&+&b!K)t^f`Q^pp9ZiC-^Z7?S`^C8I?}BCxsvUqL zv|OT^iLD$2@XnIaUVvo=7^JJ+s`@HV{jP*Jc_D80b-|rZbh5HXi5VMV|J~1tZ1jar zPRlY$b0s?H1)zz0O(JBqJ2y`-QEAIzc8RTOy~?6n*zcwX^)@mw_iG4Pb?>f+76NruYdx3CWffPR4D-I-yG(^3J>dm`}wzT zi8*hx9YUqu-K)>_>iWCrp(PWuh+4@~V@tL7t8r>=@)Nwd8d$?FXG7>NS?l@+Vi6g) zu&7(m*ZuE5r5Uai{`!Cil8YVk@D{mfX{Bc>5g9L%TsT7`Mb8Vw@~`8G_I1AYO<2rU z(awCjPa1Hz9>WkqBK07Hg9)fdLe4vU0LX14SrFf2J={Q#xLt$91h#4b0c$k3T zu)_vo_n|$9TchJ!7xFv5O2Nt2rGfqtDA{2{miqzUG(Sdd>=)Oc`-mvsrt1HSPcW@t zV^aPv0%^>zzrE^fH}+ETtH!6}2l14Y`k*EFaR1oncxw2n)s~|H_|0eG1p+cg<}}H$ zOL*cG?3%xPgT@<}NYg8%Myo2_PTB(&;xrMEtbb_%z-&=B|2+pFRdCCI`K$5Cwoy(K zjEXoQWvLhG8m;fUoqihxVX`6V%nPV~xkAC*!hFF+*#G&yiv_5t!q zvx9+SM#2Xb8+?TtOq&@qiQm7Fc5S(zh`SaP?sL*K=2z-~F@&s=lF0Vo!0#1I zI1FkfBq!&HcyAG2Rap)b8ht!J1l|LU=8Urz0aTs`_?Z{N*xx;`F)NT zK*>c`y|4+x$*_-ZAa4nj!D*g);gOF+tg?Cf%FRMwcjb8dpcKUnO5GoCfMUeYC;iY1FkjqH zUQ}ii%NQr=qSRn*kt^(O1OV({UKY@0`a8x=Czb{P?mI5-2~kgS5OX%@gKei6D)A8T z>x}`Hhofxi6{>nZ8J3IZa%Aaj`}Kn_zDk3PmgGdDxMT@^Kk#=(28P^3Hjej7g@e0` zDrBG{A~(xN$22Z;=K47&v1)RW4tEt)D6xZ6Vn(Uxr-BVFV-`6Hwd4-?AirFbmUWC@up~Vu~h9au1({M%~mm(PE*8Y7sT7EyBC`&*BaI*-r% z8SF`lxc%+6#mfWEq(+XfT*0v<{<_p4nKC{F{H+OX?JPagSj`%>8(VTvc_bFOYXP8~ z)H)-~7Qat+52fOaNHvf_4~8WPUP|leWIv&Lti=52&pn1bVUc?L$~lxwel}TFc9s$j z0Hl-ST(slTs1fZh%f1BjCuMAoA5S9gj__NfQ!n!w`z^YHe3xg029ea(%ewEvWSWaD z=5ap7sH4tQY5g9!l@+9Kf8ql;xoBYNQKJ(M?7Nnp0^nMx&-+_xAePYr9u-uG9A?}H zzlB(U143)8P&%LkLD}F2V3I}rW8-rQfF<)h9D7otpKIuQG``@Z`rupMoJYI~DVa?v4>GQpN9+J_x|l}*SjHSbv2T*FL;C-( zrIee%;GZvAGN9J|^A%&`zUTV<{D*TzWp;MsHiN?eO3Gm zUR0(b99A?0=4&^-6)3AWZ2%;Ke%t?__hEG7s1+W}^_Oz{`ugLLSsuuZfe^bSKzQ@+ z8SXqVdy$s;H7G0T^&!Dn=gv7ul4OG`^XfBjmT`yZ}wzAO~Bw%lhVWKY|=OH^|cQ|DP<=&LeN^zXBy+5Pnk{82Eds zQQCqN6Tqb|DCoRZD(I1tBR~MZzJBr=`{T!Fg0+Ge=bo*tL7=QlK4tW9PAI# zVFWN3Y|`9~ex)3S#h`=#U3Oi6Os~p4m_je!RD8xU;BEtw57&HWszwO*=;Ue@1Fi7s zd{bIyEK{IPJC4(e+3ND+p<*?WTV21;MV9$|5zUceoAn+!;eY@T9nFP2f{Iy;8~60^ zO}npjkJFlCrE829tCgS5m-q)CUmpikRBb*o!^JCia&&Y$km)t3l!=vz<&0YFQ2E*0 zn=)9qP6|E)!@Fcrlh$r6*@S0x(Lp#H^qK>&OxwHqN>pHGx6wX?KJ&gLQ13G{@5L#w z(njxkWsz}-flKPx;&NTH@yBVh!jeab>mZ8>GFE>Pu~}SxOs9iXcC0!-+VCB{sAo?Q zv<2oe2Vz%S1wuPMG-6_Y=^{kk$ESmA!n{cT>Vj7>+2dhI98?Wh05jyifP^@kFk+9?cw-iQv^-BDe?&6}83%4ji4-Fwj0c z$W`yz;4(KgGtChQ`s7GJ`XZ-n@rr-`5UXh}f71>Ny436@diwon!a7hqx-}eE#3G%& zdV420N}(q?9=6U4dknt}(m`TE+4>3 z*h{H#f}^fh*)8@)W1?!2LoPjLax!u$!loNKNZ*)@+wI-m`Jf;IF5ZJY&&qtGKsk$y zsQ`@5JnhPS3ZaVY`J9gcDP~!ze$bCEidxtN*NP6ht5a#?aQ;@BUM3)@;;@2CLEyL& zI)fMZ96O3YMMVYNLd4uR`m~F-l4_-kqcrce2=@CS^WYlc`WH{ycwB^QunMk1XRi+r z*!*SWx_i206>q&aT@9GU?(ITDQ#1-xt1LwjsZUm5bhg!sE)Qk+)~hJ+Z&a)FZB`Fp zz?>&Vxt!$h%*>$hop!McTY#V&!xF$ za-#{{9-Vq8VsR-~7gPkECn&RByC%eGbf0G=IgcxnavE!uc*galDN^LfJHh=*i$2wP z>zk#escw!T%5`(7Km$6|T)JzCZbqXy;OOYcbnSwRi-yMd^Y8lv*Gt;MP)p=`pKM;; zY9V*P#4F8yF@tYXjA zfVzHq%*e(D*XFr4zJ;c0@#M+6mX-kK3*yX=rj;6zJCimwXDNk+3tT)jq4KHy2mPp~ zxrt;XV;aBr(pn#UgA79l0ypQpWdl1Rn)SVQx;UClb6a`4NdInQ5Sa{a4~>8mtje~y zW~+{G7RBCef7*CaJZZMwfZiah9yEvOvU_nx*>Ju)JT+AZqtdlkZx5d+iBI8o+nHL@ z7|)m%v|RVQQl}F19!ezE^=N(DM$<>zxUZq837`x~XlNO?v6yU(Dp_tVKj$tjl!~O( z$Z;*&+6wesO_EC{pFH^5jL0H3d)X;SvX(g*Pa9gyE z>Z`Jx=A43R7kE^Mlkm_51c|uKdK}1;oorw00jBwkCvs6KyUlgdXfje1#kusQZmLd^ zfKj6uJPj3mGgleiZkpt5p%hL_;+SY?XjgY%p?7WMeDp^Yof;V_onn(+8~9Aahf>H2 zM~`C&JY6l|`h#@VG&w0A`(=?Ls8_@JYQflKWymyGL^{7Ecv4)a&AK?@{c)*6w$itllpca*?)SC^O$Oz7Wx(&>MLs0e zR3}bx+5dR4&0%}~Wygzxh_W|8{Rvu`Dd%Un?B8BJx`x4_*3aHQSyv;8_KBPDEml4 z^bMijpRA?sX}679aH(RnUm+W2g%2*7{$6vrSLn;CH8RT}kC00i&GsD#q&aXrK=_d0 zQHXM4A5S}tO5jb=m)Tp~KY&^^tmq(pRE23(gPK42vtbY`j61+sjj9%cJJ30b;b%MP z7`TEf?fy-tjWD~~v*U}Vxx?s4Tpy#(7)#n!h4Z<^z9yDiXlTD3>A4lYMO&FbQ@Z;I3s<2c+Km5ggOE9Qe12?5hF!c4Zg3zGJ;fZWO-4# zU&d{++K!br9zmy$fYnEzu;XaPvnHYxRYxdf(hjC}8x8G)UwHpZfyYEdX>J`n1axwu z;PY4y(5VR5pRU?06q$oj<;W-N3txkC%kT1Rv^Uw+d;3f}jGoeM z<7{3J@4)z9TENx71%#z$sWc#*Zz99BC@GVZ+j1(m4I;E%Y8x-dH=aBD0ohl(3x+O6 zET=cVqMz(s_nhE~4KQ`XePJ#L9|&Y7-r&S?Q@u^U~6I2x0 z8Mtz5cKe-zictP%{n;53;e7`2Wz>k!l zUp2`8c=8}LtXJ^-yVEQFH}Yb&YhOd3HA&I*J#he=RGQ=Ew|HHb0~R;YuP+1@-e(8l zc#8P;t=3=EA9m_w@=SSO_@wee*tkn?`&_Qa13$JM)kA6OEMfy+A4GG9QGfm9@#f>Z z)s7@w$KVoNT_&Uj|NQZT4h?PYIzB2fHr|ky+wbZV?AOme1LqWSLO9JCJH1fBI%!u$ z8EQp{s>%SqJ>f@D1D+mnTkXoa#Rh3s;fomH@%{A~2Hq2OaPLyxrmDnIo}ekZq+xI7 z4>K=|h%GpTRFsuv67l-Lag#!Ha{I}1Ert=vy)gryTts-3B_{g;#pB_@`3+rMP z6~Z5`#}np-Oi?L_T>SBIo35A@6?wG$=z06RbSfvL8E;;};WnCPxLhi)lU5ZmLfarV zAFCMWWHj_79Zpmd9IyM#pu4li@@xg{g^(}~{S>U?`_Ls~7GJLU0vb5V-f!T?=!|=pUWnt_ zFSpsHlZNdGull3j^i};-eoM#u4~*uGJl%QgRwWHix16qVkgo0h^WcHkw0ecEC>~BC zurV@;6gvIwljgoueBE%a?&u`q)SH6B75!-zzQHDXv3u)c`w1^x47F82%LXFrLUI3K zeb?pii|zW0dv@?@(^Yw+g=hu9XMfC>;@er4UU%I&QKX&XeSWti@nHWTV#ni6!s$ks zLU$q;bhRvpK1b|yHyYV+y+;r^g=+espdhloR#8zwTbIa=ivQ@}aB;QTMp>6VNJp0H z&`a9_BicBjNBs#o1OpuRwd1Ae)y-{hNwZ@9vTr>r`VtMz*&l&VyPMO!qc)2+U@qWrBt7j?_5)#sd%Hv19nBcb?%kWLe zlsquIJ~d+%MpnoPLYyxLf`dH}8gJ!o6yY<1wj;O((4k4J!6qT>11iV<1}fi6kA$ey z)Pt+w^fZT!Jsszn!x)0d2UrR*U0n70#Y)-d7@JTEUhgH+aI!!)!hE@e8Sfr5dWu;v z;cBw`HM1iy9{b6vfwx?)FcNX1wQHd-mTD3}=w6QwB64)G=_91J{4QVfEy;{4Jb z!J_CvAxPc)=_42hbEE4dL`}H2k1c`SNEyT+-qYqU9Xpkz$yGHKA7UWuPWBR4^(TJ1 z6yWe_A|o-e;y_oTPNh|BwihMm&dJ`i7kNy|*#cH?p&o1`YW7`nmi13=3q`i6zO>Yu zB`DM(_&6^wuj5Q2;l6((oaNMavJ&NF_et>q*c|r7dy;t0k1r0|KU2b7Kqw3$`9jH6 z+%^*&xHJHQt@U***Z%h2URGZ<_|b9Mm|eqx*r@NG+AMX1s7orZHI>()(Xua>tVzA=X3)hmiC}uRt^Li6XW-E= zwA3g0^-4YDeSe`=bUzjmN*-B#kx3^va=M#J+=N%7Fl%@7phxxjp7zQrh#o9B(5D_8 zncLCBg}@4y!A(U|r%NW%n>}olPD=vW(xzPlA9%2~tG1&~uPMN@4zBosbhsE-HxfnQb9Mehm8L6qnTRLENESX5H9$ zC*#)8yuqJh$FEBgLH=mI8er$4IMDEnC5f-Avond;r}Z;_HnQe=Kp87fy=cRdo(hI? z)r$~5uBdS;e+2Hw(9Qi|^f=0n$)d(0<;}frrgp0bkqnv3hUXv4RVuKqypT1U%BJa6U`u#s3 z)Z2HRy(rbt_cMptRuyVjS{XB>xO?|W;gAKm!J1ev0)zFS(;Fkn#dUiluu0pYA8M0w zeWJ+y8Z9h@_!|}+(jmK9he_7SlNSfhk(QQuCo>*{g<9oowTBaT(9oDGW4a6L?l5I$ zIgRzPS)X4@wsnZ_$wuKh{VI=JuB}!S{nt4;zO+jGC7rztoXhk6C(WGgJv}Is%Vtjy z9~A2qEFFqnCG=N2WG^LRg$Cjs(<>y_t|ZBE{TaLt=PT#GdpFQ184iw~6u(}}3*;oV z606ZO0y_D33JQ2QS^QRa=7sXLvZX-20B!?TvY_qCFB`Ui$_7R?;uN%tl0K!Y%gQDC zw=G<3D^5u*KHd_N^xTDo0T}AaO0Ogs(*4F)tD}4MozFQ$@amUSOokJKYA;N#w=}_mj_}Ua5yrS{>_*l zDYE)*qIIcBNqOq)``)Mz%!Q?a!&P@;!urveHPrG|lj4)(|7(&6{(5D;+Jk+pt)c%c z5`dR_gNkROVCq2+Xg=wFz-n!2FEN#SBen`z@qR9RB;(;;}GR+b=n1H;Wz2{Ku((9SvU~R z(}F2V*HmUvyIpPRKI2ABIIim>JmPl=7$x+5eN!iRfC0KH_WWXcsVA?H6GT&OkW`Zh zJiMcJzfXX`_*m>lwgP#s3J9S4C1iWm%cYKblU=a=adn)T1W@NpZ0@Tl1#@$=IPEQn z5YoOAQ>1CG_kuE&-C@6HRNrx&2U=kOw}nzbr=63hBlz9nSiPbiLu(G;)7nktHcp?2 z@f!VEdT&7@==~l7(cg1yy}D{cj}#ru@+jv?=3%J0v{EZ5oVF^u!z8cn+FLiJ8+!}r z82M`D=iOR~TH}@xnSaGIMZ3~wZyd%(tLXslw$)H>99TTB^7V_XG>t(1 z#RoK@qjn!C5fR}Ra*<-W&A53ZSM}re0TVJFoBmQG)b8%7N*|kmQ-b?QUfa=*5Ks-Q zCT;6YPf}}HjXG}9|NA&p-j^Qx^Y^Iy@ORwSCTPoEYv_6IM5WbVRo!7y1rNQpXVL*D zxP_WVYAiRanYZZUepzuJCuV)dLP!r2!N`r=$`y25Q*Cu=hvFv`K7eJ`9kjGncTJM@Q`>w;fbQ2?nHd54Kw z0Fv(m|FDv}y&-}K#slC1rUe7}?FEG|!cHP6UO>Vd_Sz1P-^8BIqFXZH~O9L{S2tv)&E zpXLj^z+!`E>Zb~JF!-GNk?lY?>MZx?lX{Zo(F`{Kr3FkmPXEAE&;0~i@Iiw!*cUZ5 zjj#%A|B((kfjZbI5#)D)>WdUTo00|w{n12NePI(Fnce9?jsfoJaho}1 zy%*SZmt~%)I)_-=aH!LDFbemlQf%sK=ve2>kl)49wc7d>v+Wpg$3J96>{ z`9y@5%ryjH(JK9^hvAU8&6$Zf&`VomPMa z1BBW)qQfr9EVJ$x{%r6azxG>RsMCYr4=F+ohQTH#g!Hg*oZ8&;B07nqlS?}X`oPiV zuq`NyEQBQ6)XVOjeu<7`XwU~r1KI#4PRwkn5w&cZgwb5aGt&qOnHNV0*UhRcAjy}& zj~Xl1xo)(v!$UDYDhTw5X#Og?5B?xC^X;P41+v`B{d6Wcy(cyKG3jf6jH4@E!%C}= zmea$qk|xxMj;+3krtiz$V&SP}eQ2{I$Il@$c zrE`)WFD)$Rz2bd;`i(y2$Q0>obm=l{hd|*@x-j@w0Sl01$ghF6^rPP14ot~8u~7q2 z%l)D2hGY3RkMU+)RAL!pqsv)#$ZR94<^p>Z-b8$=U^ zXZ8^oooqJsIYehJyZt_RBBH86igzYTW~&sg?M?*O5(xULEq1@ocRUF-xY+Qp`?p=xc+>D|&O1O;zsj4H=F|64 z$~!q&DrdXmwpR>W=|Sw{37ik{&7QHtGoJnx2j53xblM@lcYq+j1FU%go95hI`^h3` z^-*mXDWBDD$7;P@?RLYBq&x+RDo0x?k=CPCa{IKF>y`$7m!7#8K=z-9m()A`DU${@ zD^!jF1~HpcSPviO3AJ?Oc5P2wq!A&g_- zF4S$}0`*z<*-c@~&hmZQ*tUb(WEOus}IjlmZTwfM_VSrX4 zouhm-tRzs};-Qf1wsOJBC*p?vR+!hx7%hu!Cir&s`o}=U}nbn1Na)-mKrGSg(ggU$o!V*(i`1DlUqJD4c)Kgj^5y z8&OkwWsT0w28CakT;id`7|w?J;Mf~Smwt8)^PzXpep?xCR3vu&s((0gp0BF6tF>s5 zBYSZG{Gf7Tg~KO|l@CZ2t?_3;@U6itmt`8kFozAzVVJ?P5(^2^jF&M4zXjjYv z$?lv`Z$3%#aqI@OkqwpbIV>!F z%WkEC_iuFzz_H0I0^I(1ZdW1@V%H0sK$2v|M#LoXSb-4Y!>=*$wmv&LuGE79Xilp4 zJMdVhx}0OmC2`BkU#~^8{gh8`0OQY#zEtVy>2R%_@O0nfme&K7Z?ubt>$Cu58QXN5j1NN6YV;6tv^gpVc6`$^X>>(= z98zJLDIkvbVP(5+R4X}tt?Tb7ou>|dtY=PB;cw-q*Z=(;SRR{YCq}~s8d#0bMMPNM zb@cY$oSWAmDa-vlqL<@RO$AImCW=NL05Jz=(hyZ;R4O$9xl5mFuiEtk_Hv!VO>F3U z2PsvoFEE1)}M)Vr==#6<56+s4;6qaSxTZn&QSBxunt(H(cqlOc&8r! zF*H{yd}GqKe0i{5KaJh!&y6%l9;`>O zN&S>d=P+(w^V;#NaQDoS6T}8Ow}CH(Fwj+iBpj=)9M8g+%w@h~ZC5AcGI8i}utH1k zI%eT-f8DBs?CVX5j$ic@0jVD^_>>u}{3jJNKId**b&DRvdX+%G>OkEm<+0hF8pQLR zHvbK@h^mzd3fJj_ud{ws2A5GwkH5<3-&-%0IJWN3@V$tbbRBPy-up-*zS_e(ZW~Vl z+xGljCA2f_u!tk;vrZ>RI_tjugKyf?&3#@6=^c|&Eww0Rp5}UaMr=^OgQ27AJ1htG zr3_|C$keYwLK6qvro%62fABBJ@RUZmT{h@Um?e~V+e_sCftVM9q zJLh2e_XWuK@~b-PB8=%&=C$fE7>oOfK~6%vIq`ar_o0EMRNUS%}#C;s_ z{U!G+@JDD5!~1Xk>^1fOjR6+^3Hq3oo6B`{0OA`e8XAB&2uG?z&om2)z_!*#C|=Z)wh0 zjTG|oF$sZ}3M5n?2I2edPFe6$P{p?Y)82OnHM#Zcx<9v~(i9W~l-Lji1f+Miphyu= zkS@fCfYL&dl0XurWh+voDpf>4I*L-HL=%wSq=hcM6M7Ozxhwm8-?`_`J#)@C_n&j; z8=Y|mhP-dqyUOqP{GR7o>y@y~mj|UFtE#_P_%6&`p<1p~l2t;#w<|#EB3WfuR3^CN z8T@l=D-CvuN1*fo4iv<-rzg(S_9rH!CBA;0keF^!WvUCPhI~V@-}XuR1193^mA2M) zi6-gy)@8{k0V_YiAmBqW-|?3v)I>!^l_Vr`-;~OL@^>R4Fn zd~|?DXC?Cg)}jA?RcWxJT$e&Z4E#LC~fW(ScoVnD6kr3Ze?{+bECN>o!_Dj^xZ|JzsQG%R|Se0I1JgjQ+)T*h_=ToIVCme5}^An9=1BzPqMyPRfUjqZnwd6___=f-?^04maDnD z)e6CYw?=*2lefbrr2lY9#VTOhCDS*S*-Jj%EugNB`b(H}ww;)ALhF=@%gv_Wo7XW=3^^oP3ts2c1BA}g!u{`W(?_{A zYk|ZnT)Fw(&VQ_qB0@q@1{IXXyt>eVeAsYcT^&cxg#eY=MqK_>U`rJc$1&rl2P!=J zypw9VZAWZva(xS;$B>$!NJ~fqsoT;KH0+5>U!kUtg_DI}vV$0_Qa<#>qs(#c?Gew- z(`3sM`;||;QAehv>sI*{_smTBk~hPdBb)2awT8x3z{O#p=?0Krv&^v?N=wQFdVcL? zl0C}MyXEQLj@Xjjh2pxc5uIqw6#$Pk%ID5bOwemfs+30{eqD7pgug&f zE(gx9XC4Q3vF}pk&ACp@1P;DKbZk`fs9UVaJdcl0MQozM=}NtBfY@fYI}e5{F3uKl zC3gh`+~7kj+X%2J1%)0w_Ym}biiK;~w_y^-6qL*>6@#)bFpTXA;HMHp%-6S9jAw8h z5z*M49?vz@6Bar4c+4&Cs9<{F!glG`wYBA*C|4l$G~IU4cAO_>qBfRe0UoBz?qw(r zzth;xgta73=v(ipAfJSpO@{#el_#Wo3+i7UC>H)z$T=mS^?&52S{9i+nO9i zE`Kyzu~A^vx-w(=n>m2(9?EO{=0E1WjRhQ0&f~>O*g89l0RsjtZUM!O$xT2{8&+@c zwt{+Q=!4E4^*Wo|a;aUCI}oNn+=J2)qG^b3z;Thv8l{>C`U`B8Sk)NrkB4W1yZCk6 zPutQwd*?qybjUe(jDUwG0b)^l@Obc1_SH)A%UCl-s}lF6&jYHopQy$cdsDqrfn$E1 z1eSy1{ao|2x0R)41MV!T`Xu~|KGq>hS?nfeId4u#$;A`jjm^%^wnr@>CKx@^bzE{Z zSx?V(W8SM`{LKN!ICfXdK>FLq{2O4=J}z|(M+^7D{hXWt!<#nrnc+hYIe#M(H9cla z=tsj~6P^3nEb}(zx+#FqYCbVQ7v;#egnHT$x-NQDt^U{^E~=iFwUvi@e*6yDmsV+Y zIgVo7M(~!2-ewz%8GYH1a3{>+Un#9!Mf|B0+8U zo+AUjzRi{qR5#qA7BosuVcCl4yYAyiYIA&iyoNtX@#ioEx%{Q4+)kDBrSlj;_XTok zG#aeuP19BmX%6X23hQ&wgCft5G<@45Suf@lsls<>uq75H778zJL<*3HV6mWsp(sLwuuOrTZAVbNYpN`6YJXYwCcj+u_#CvW( zYz;7A1Nqk9!!E}qoTU`b|M@JfeEf?ElZ1Z*3pr3VG3zQ-wUT~dplJk<20Cm4fE)TZ z!;KHl%oxA|lO3cAhu`uS-}QPx?m#|`r;DCK5vBLD`L7OT86>p8CRfPnsp@JDrnO&u zb-5QS=2xDo!8tk=U5IZ2I)a1oY{ID7=`ty9f0NR)&fqR zLU+}HLEqhB6Mbm)2&iHY9G#_C2iJzbJIb>7ZXV$05l@i7ws>|dG+O4ze*gwFcKgIn=W}i*Fs8RZmV}w!bT|L0#Zb(M!gJ`}~I?*%AUhSyn^ZF9MD{Xp0OPNClM zk3PBi7`yCqw;wBFfjR=RtoU~9qaNpxwTwU|EBGh~Chw=W)`Ye99uB=*r$?)!zBf10 zR0HPsASgc&#|H{;(-V}&>~N!B9KJI&okmEuZvA;?jhwuNXZ|ifPBI^NI}UsXuu8v? z%+hl1f>HVp9vr2lk?u;$08W^pA*LV&yAqeT78;&le*F;VEv^}M&fGNL?%5P~?`aqt z3rtD@M_+p;?^lC+yDzK(Y00V%Po#J4y93BT?$OM>I+5I<-9gJ|^;O`9?S+)3Pu2i_ z@4IsWSXYjHwyS9Gg6@G_&(zeshK1m$kC#@+0W)EZw&iXwQ|*DQ1H$30Z-+SlxE`OF zXftwbmz(0zDLgtLrh55dk&S}uK>lm+h2r9tnQY>pfIL2eVrB*I5JkXO6mqa6=$7_& zjfwb8z@-sUb1Q}#zCe5?FEQQ0YJ7hWd?0vH>t}VEpT<2PDb1xL&zwhn^RhP=w-mnO z{rDuy?m`}*7Yw`yD0iRuNmkK|W;k{MC(&|wk6%gMRzNM%H;y-Oxo#8KntnLZJ0|+S z_!evr6{oN+c=dNhn7YY7lAwTMyq%hULlJ4lstj5i4Lpo_GUH0C&jUvjJ*Z)`b7nleCy9_@1tv9$TYs2}bWK$|Q{sa8YLb1|@AliSI4Cw{` zJ3roNOUW3|c4TYzpcoE5OHY3pTW+7N3xe0Z zgrgQDf&tp9&*TpT5CD2WyLdO83phYLcz#%d3NzO#ToZkB7p(3_cNj067CwW?^;KN7 za1ncg@t4Y^VkFt*HXr&4Tp)g=U>6pq_1ny3K5u-_2>z}!=J$P1%uTKO{1=cdM*ppF z|973)ly?pOg;_gLN7-(i#AaPIQl>?@Vxu4xp-IcWQw^IN8!bVL)yH(;$j_F|my~Ga z*D@cb;~_UVnJ!%>BDtYTo@?%wMy;u76lP?)`b1Vn-DMR=V(56ay`QcJT7V=Yt;O8B zu^O}%qSSKtj{VhdycspNG`x)RM#s{TBkTmqa^vc2c^_o+YtfEZiuZ^=ufuUK1gvNA zKHffrA9(s(llD}#+yM3alMqo&&!xAU59T`9O+?^Xq9&4Pg0ni(G7pZUv_mlCm#JX~ zU_tA0+zGo-8Q0ZRIF?yHJzU5`$|q_n*$JY#$hCVe6jUs0{Ec@W{v6*PhByfI7ss9I z%m5srGbA}B4mHukU5A|T1xvnpp#n$!@j?Y-`2-$L_nX;_c6`UArj!|Ew4g#d3|aqZ`rLb4*Hjr(ruV5Z}U56ybdy~^&DvYMKN8?6HV zVaKskW8>?yxr+=FGpBad?aX26G3K8k6=M^vP>cbZf1+S~Z`D;u9xfnU?)FJ{*y%FQ z^@NFcZ16zR9vUXG8v;3aTzaB-s^ekz8?XCU*pGdeZ*X7;cqRX^JrS6kXUnUpz_ zaV4|AH$L9KZb=O``zhHx-wyfpCG06|c3>Cd+n6Hl-u2eJSxL^p*lFg)3-00?_inPa z3{FP!@<>uLV{`u4-ncg+K)>L-5uSk7IcSuw;M1LiQ3`5Gkk&kQP;Tyl;(K#7sz^G0 zk^pUtWFXh1ZN~$keFh0}KK9#qUgs^Mv^oa~fu5jMAyiCIJB(+eWXY;e-DVfZsQsBIi^VDl0vdbZbsR_)s(_okGzU*%={Y!74f-XU>?0)C7tTu_f`%J z+lh>>uQ(A``;I2CjiEF7ipOGncbw=ax8CV&&5SUU4ruLs`1#eEo!8Qu>K|Rn)2T~} zc0Q{$sWJg`DVl_fv~;l{wzJbA^zuc)@8)viV3$%#Axw@DPKf4aKMZM&el2_HW~ypn z|Jq^9oxD%?(hImWoyIShkHQE>V>Q#?b>O%hvn5{wf9+1TXrSAdz7uT1pL@)maX-ir z4^U!!nsFv3Mz(nW4}+>JW0jQf5RYo{nr1SN1jz|fna|x-aE(lneRQ{5hzQFvJ}ZCR zGCb1ubJc3;6u~c`zXma<=|xgC>UX+xEFxT_XI4PzJB}5h6{F5gU!jdrLrdQy45fCcKE)$QdStQc{QI@3 zBh1!x#&U>^SIhEi{~GFKb<+556Ba(Y*&)Hd?DNoj)^ZQ!)yq}Q=a0Q*jz{UKOjPR` zA7=8amfOsgXPMAZL%q|q*Mrs?7316MCS!SKwcqP(B`H*;B1@e9=x%mh*gKhQSdnT< zN!gfDiFzQhT#Yp1RW@020N{w@p$8e4L%{B+;f%9vL0b3cAw4;66<4ukkm|Vtq1ARh z!|IjyW)(PETl}@jKi~Er)&3fz^<8s~u_QO^ROi(Uwk4ysT>0%VL+AZ>=IRV?4yHr zH^~`Le}A!Kw)tJ=vy(~yAimb+@&-ULCcYYu_A!J>GX#K51{ZwLC z_GtK7mr#?9@yhA%YsXNeZ#cKUVw6iaTEM?%`$u$EcrALJ6IMo}}?x)cmyD~Ikb4RnX zwIR2+;Pz6bhXx-?2+l=8mUP%cYPO?bK~3(K&7(^8%k+gT2yV34I>O6L6#HD> z>-(o;yp7le!uM$Qkn~}aw{A4u5Y678NuSwULyJ4#Ia0CaHn1ba4lqTRu0c6l$_Z$U$!m=4=bzrEG=3mQX9-+odq8F99#bzv`n`!Icu0 zi+3IcLE{jljI}01m_FuHs=g%}TNInVe2w+y>P)rNt*;ooo4Ks)E=^IUP2JttiTJR@ zz6w+@26Mjs7>7Sd%pI!wO)bX(6uuQFbY*(5Op4w8+|7lqkL`_?<6a=ywP$4LtuErw zn~Bx_L$Vs~9|Gk=MQZv&$#hiBbCK;p<<2Y%3A_syE&blOw?|VE6Kp) zAeMB+4V#;r4e82Wdc-}Bpqw7)e{Z@SnntFe7N-*_`f&frjX5Qw;A7EDmUHZXG3gaz^BJPH zf}d8VJxK1&BX7+?Ldk<~uVzignl67KL`zFb*35jKBlv${TG;8pUaPx)7ukaxD_6FeT>CjxkzP!Lz*Ur8|o9#Dm-`sLr zBRQlq7pJf$Dq1zvTPFDU+6rQha^b1Z1dq98#Lo~&%@vSg9-dOF+i)8dRM6S3q_-zM z$A#G<)79PZm6!WRe|C7sT1rQV|;AbTm`lg`xwg6L5NOTdSKfjt09eSABO*q}fyy zLCY+N@oBQIvr;LK>u6uomom`xKLgVb4;O?qMsf+&!qQ{*#j|%q5b59cpW{^76bp86oa8=Df)&!kK4tB*c-0$Un9!N_|+eR`=z_hOhfTG-fGqKrd zrgW6in@dWg6E$Eys%~SDvZ>W!IB|WjOq!c>vSG9M%TlG>20J?(wNkYvH(1z?#Ev6Y z+#6H*>aG>Ny~wj%v+mSvrx=_9MMHJ#d~L`r9>5^14x%p^SsHrck&CvJI3Ofu*LSu{EgdHd}{ctl>AvqHYP^GI)V z_2cEBEw1X73iE-sNTJ(oh_A>3w&2NzvVY zLpeSP$JlCosdK|PJw^j=Pnlo>_dP{x*m+GFWMMQ&S0Zx)SJtD?lWG{xOLaGr8V)VT1oZ# zpS(f?#l!AX?TYR_#nWwo7y#j09^NH3Vn0uR^y&x50)VyV4)N-_hz(uuSAu4AJ23lQ zw$B+nklmcVOp2!ifeC6MPHQdVR8 z&U2L4QQ}MUn0pQ-7o!{AL(6s?Yl~?c;eiE~#T&#%l3RHPGMkHA|40T6N~KL@_V!*@ ztC;J8Vdk-=E7K=q8$)QC?gqHCxSi^mZ%75cn#h>I9H$Od=g$4m>iOMiE`C_J zCyvyf6y`7FQL&}2;hbNs59HiAlFvLemvQ$^H*~Iu7?cJqh7-$Kq`LJP_Ts~r9lkLe zx3)Yi(@!tnoL5$0wn%}p#^4%&;2SGzE`P={Z)l-6d*&xZa&M-qOf740OQHiBZKav&PS*x@P6v*m2zg+NlOvOklTwZ< z_L9f*mqr`YtY_5*srf#gqf2k#9;nJ*G{t(;oAFBXGv(A#rdZH%48x3!_1|6|Oe(R) z!3J780@mL7#0f2eOk7I&^T|89N1fzKNrCS5X)b%DCV&B6+q^?dbXOw! zg>W^F_?Y2RAa|ES_dka+8oojZPJY|Cx0O60y3_Y21MIib@eW^G z3G!R9YhK?;$`IJ-+JvHl8lGz0FiJjZ(@Z{(4TA(WlRP{+g5&%>`pQv2IL&4|h3I3C z#zI<$lSWg6b|M+I5aw3ebii8W6?1X(FUwUoc-YmPu%qO~8sC$lPLG-TbLcg~lb8#5 z!qHf+T6*#(S>+m#&z>a21I#@W0>QDCr2Xn9;w1Jmt$nD_f5(A5f*271#hO5QBw>zf zTUd>5ruvgUIeN2U?^o0?g4RW3oZFgdolES-S7fB6$?zKO*{C4DT-1NBz&YV;SH}qA;w6z>wcUyy0XS30YqVyy%FT2*W>{1b@vSqH#6Q8tgN`jBGZ&xON?~RTTZQ_H z<&Yco_Ryd$!b-FVl))g=35l41KvoWX$k&|Vsi%E5YfDH~EHrve=3Pvh;^Oh(20tOU z5&xzVC?|tD_V!QSjcdjptFNO+x7$m{pN}u1Z9V+yttJ&NBM(M%+T+a1ZA$2>M*Pz$ zwonXu)3?9+-TdOa39JvEUU91X^InG#6LB$Wy6tQb$7!wAc6c3n8^5Haxi=4BkXFDC z1gPz(MH(h5Zq*(vR>CnZ`3a`cOKnBC+#v!IetAsScQNX<`uZ**ib+c%5q6yObWYmS zc6-`8v@)kxo|%jp>C*%R@j0Z*8&Jf~a%=QBA{y8T#&eDBH!PWMKgpxT>T06WN-vh; zi7(H(Euocy^t^)t2GTOrrzW%WIAC#sOA#iAAvGCwd+1s==E&48d#+;#*YM7PFwE+W zTR5kpLWIhVaUVikl~2EG-I?G|uH_a)4=wVuKtlE9vAD@J(r)>qACh-Mu5~{>d&2|Q zx{b|P13_y?b#KEZfmv6st+yXSrx?)%6(C4vqcHYdulG=V!0T3zSBOi_Ke_Ll403{Z66N*tUwvCn{#9PY^ok&Q#rGXbx z&fBxs8d(omudZc|4&dqU5Ac@pUwqpT7=Cp-+ zyBrc@apm{Hh7M-pWP#}K03%O1->jZ#iX+UesJZnnXq9N;00MEyx#?n6;P;A0aj4pt z`1orPxAd17uxSDyfWExj4nxb<(qw8)Cw#vagrXC$a)ND#qq7b`-4=la{29c7=l8QN zMsnNsN~d9ryfIkl_ZND?=Vbs@qZK7s*AER;3gzyL(6WREA63v~P^)7upanJ5gC0W? zJ$qh?hOMmVfC|}-1Is-IcE$?Renu|YBNrLGori^*LD|^MpZ;-4Q>ALN929x>&d?9^ zaCR<@fUT?wK737&wg%TJPqIPDNh5f3@t@8qE?S ziYcF{R-RiA-a6C6|P0oym;88#wRPuot@}Of#gsevzj7h(24Sc(=e8 z>EZ&)?{e4v$%^qt`S*6B$3xd@MV}iB3#{gLzD8}jc7|mviVlpZ`1ZHedel5HHr`2K zgQShXF3{FU>a$y24?U18RWa8Ed-Ky?CJMhL2&cIBAtRk&;fLjNeCvMJJNOULKki3b zbTsKn$vw)ARISA{V7PFbL!8MKM>erZSjfPB(e$!*k=&J0L<)<;e|nfxk~;(qLDi=X zBqUSrlT23CcV76WD2`8T$R*J>N>8gvxEd3sG=i$rNE#D`NClTL?_)U_x#fym=kWKY z->tVtM8)b2z4IMzG_DpCIEFt+1tmc~B`?d5_?Z&7nca&n`dp$sKM(MX>f)+U^xz}% zo{72=Vhu0nY*z;|a?O=D{zP#!sk#nE2msteCwe&@u1mAXGUyxXE5NR-fV%RX`BaYd z7DG8SeC;(8kT1(07A%?@R)-!%tmUD}Q7Q0lkC2MG1)ojVT72}mnrW~?bVOMBh*0it zHCs!niXgHZ(>_jsF}ki%XQA{edl7MYL<4n zE)UlPF%&ZWLl3Txov2v#>B(M_x0CzyWNNh<(N)>4QdI@gAmcY%-%+13DWfHYLy@xy zbr~dNJGDqq39<3Q5YK$$FAbDk3wQ1Nhx8r&{46L3g_yqSO=|=iWl>Eh{m@jDYl+Vg z!N22GoOKZG5Ew;R_5-IG5v#)|R&HzJ{!b5@%NN~{YlvEzxm=IFern?&UUU&KM<(_@ zXWy<}4pO?Wcj!nncV})}x{8{s$HUZ1ul~UWZ1`+kcXV<-!FPJ_kmXSKvhgB#N&+h2!-8#wZS)gw8kX5tTCp_`9P<<+eDTDkk^e_N`&4Vy?t* z3aS@cSdx$VbBh(m&}DKbG=lu&a#giQv)AE>*EhphJMkps*iMg>=AGZXJwY>tieSAXvitD?+R8tw2xuMV-pO3bhFuC^gZwUqG zGoO09B-oRqY1V&f0lXI5gd-T1t?o^#;5&18ti7w}D$t+yl^*$zT0nT8{&H=iVnL15 z80~LG;HDM=ux-u4X{+V>r#Dco~1~K6(;* zj3K*0J_cH&9F=NSr2P24W5T3~IQV_>V{_RC*ft1y>1d>epoNQJ2BrQFfFr`n4wu;+ z6|`_?cmkpCM2t~_`RlIIFY9vxKpp9D3oo<}4L3J2hzBpT*q7L6>W(C8TK6dnT=>V0 zxGy=X8|dp_l4Nam+js4RqP*$zVqkPgWv-!(8Y zZ0~3``umsQXLZ*J*U6vCbqM)J53QbM$8VZEFxZe%P$+s++GPw_sXwm4c+a~%3~i6Z z@n|E;ZB3`K3E;_EB=*!fn~5=riRmh$5@2MG>J#e^2?;uW3jXN-@ZO0F@`bUciSl{% zagP=js0YGW$M=9XIr1q=;VXL4I!= z^f~nhxTj=Rsr50|1>b|&FsGGn>pD~BkYXPI;Vd(!n{<_Wm7VXY=zzvgYnXHORbPxj#@;;Qp z0}DClW)T`0aUhz1a7&MhG4A?#Ge)91zD+X2p=4yXGXvURf50CT#YEoq-hCT_55@-v z*KB?2O$J0ke*8=DwG>g%=VfybP8>g;lkG_J)o+cDpX^jMVBKAqKrv0&oD2)Wz3~7kwwGO3=C!9ySXd zd346?F44PBM7~hUzz;jQuHBtkDe>-jZm8B29`86onp~Tk(qe8ZfhQ@XnYP?5gFdGV z8mv~{`rdGHIloj8%;!hhWEZij^MrG3w~iaxK1j6^Q@UWV(z<4OZ_N@&l7%j&2AqZ( z#+M2tCDHuhrfRyxP^$#6#Dg2sax&kG#H*U=2(aCfgr(|9f}kN1m97_$*mwO>2N5*) zgjvfE^$gSgUejC~tK`X;kMdZfdG3NEnXrVHraKm>yk-$$sXPUmZ_Ty0%1ZIX?6bFN z1MPSWyX0&uhz$}?F`6RNZ|7XCD0KFM&^CmLLUV8D$ z7Uvu@d1d0IxEfLqvI3V##7^jSCq3(y^OtxcmUBugYx(Z>dH0=_FWfaaZFO5*Uvk!p zHG>59WaAl|8K#p65PN3s?0n#HY`d*`>YXHAhwVloABY!?lYTB@Xb&%+#C6ZS9Y?Vb zU&GL>0Lqyyb>%}-858>0YO-0t&8Sct1P<+%49lXsrmj|{4%!QqF|wHTJ=r#UQ?M&j$wIR{JH-+KmS;3{5& zYb<0Pg_YLl%vvo3j{j*M*;SfvJ(4ZO8mluH&H?BJ`@0)d4WjZ$#2gd@OVjQ04$GAU+;tJ3Gn4&GKbRqE*yN)&6tqaS|;JFL18>>G{yG|XpFxMvJOFCXP zc!jGMFv#H8Am#Fi81GwHW(RrX%bx4znVAeH+voH%%Wb7Dyn+^9%n`xo3<=-xrt5qg zWX~&sDu<4OPrB$?Z*~8$c$&S$CQcAHd1$qrEjfGiS=g*T?KN+rOGo{EX?4(dh3G`h z(|>Xl9LR)yE}iz#NlY%QKPr z_ZgspEu^&xi(=FpC~I#pCSND@ISLd2Brrdx^pKTAB%>0nM!}d= z^N4!r@tg)xjyV8rv@+y4#Caisvh+q-Rg4Q<7OQeCSoRQSe4^=OQ1n}Hqf!~KrYora!li+49 zz_Snj{Z||K|9!tX_}o9g?f=(RV8Z<~=Og^;s9zlg*5!YCcE+!{@oR4U56um{dij@s zZ~_19Q7^xy>#ynhKQvu`O;Lb0ekGCr{y3^%G3r;0`n6Ji6(+y%_Wxb@Ga92#1L9@h zK8}rw^!g6`A;q_>U_ty7Fg*M2o&W2!y2!sS`}K!kT>_%$ugUOhef)|H|MJUV5WG}i z-@bz_`q!@B`Imp#Uk~^z>HmT|zjFI8i2JV#aRF*$GH>3lrGtfjkd@W;?@KU$U|4?q T4bVjQ?bE+*e63jfUhw|{VYKU5 literal 0 HcmV?d00001 diff --git a/.tmp/run_feishu_case.py b/.tmp/run_feishu_case.py new file mode 100644 index 00000000..81359fda --- /dev/null +++ b/.tmp/run_feishu_case.py @@ -0,0 +1,291 @@ +import argparse +import asyncio +import json +import time +import urllib.parse +import urllib.request +from datetime import datetime +from typing import Any, Optional + +from sqlalchemy import func, select + +from app.core.database import AsyncSessionLocal +from app.models import AICallLog, AIOrchestrationRun, AIProcessingLog, FeishuReplyOutbox + +BASE = "http://127.0.0.1:8000" +CHAT_ID = "oc_c487d87563e48c2f9566ccd5a758212c" +SENDER = "P1验收机器人" +BANNED_PREFIXES = ["让我先", "让我尝试", "我来看看", "我先查", "让我看看", "我需要先"] + + +def login_token() -> str: + data = urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode() + req = urllib.request.Request( + f"{BASE}/auth/login", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + payload = json.loads(resp.read().decode("utf-8")) + return str(payload.get("access_token") or "") + + +def post_capture(token: str, message: str, source_message_id: str) -> dict[str, Any]: + payload = { + "group_chat_id": CHAT_ID, + "message_content": message, + "sender": SENDER, + "feishu_message_id": source_message_id, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + f"{BASE}/ai-steward/ai-steward/signal/capture-feishu", + data=body, + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=120) as resp: + text = resp.read().decode("utf-8") + try: + return json.loads(text) + except Exception: + return {"raw": text} + + +def detect_prompt_scene(prompt: str) -> Optional[str]: + p = str(prompt or "") + if "你是 PMFlow 的团队协作助手。" in p: + return "member_workload" + if "你是 PMFlow 的市场情报助手。" in p: + return "external_trend" + if "用户消息同时包含问题和需求。" in p: + return "mixed" + if "你是 PMFlow 的 AI 项目管理官。" in p: + return "ai_pmo" + if "用户通过飞书直接提问。" in p: + return "feishu_direct" + if "你是 PMFlow 的 RAG 问答助手" in p: + return "default" + return None + + +def infer_scene_from_reply(reply: str) -> str: + t = str(reply or "") + if "[回答]" in t and "[已收到需求]" in t: + return "mixed" + if "[行业动态]" in t and "[建议]" in t: + return "external_trend" + if ("负载" in t or "工作量" in t) and "%" in t and "项目列表" not in t: + return "member_workload" + if "完成率" in t: + return "default" + return "unknown" + + +def has_thinking_leak(text: str) -> bool: + for line in str(text or "").splitlines(): + s = line.strip() + if not s: + continue + if any(s.startswith(prefix) for prefix in BANNED_PREFIXES): + return True + return False + + +async def fetch_max_ai_call_id(db) -> int: + row = (await db.execute(select(func.max(AICallLog.id)))).scalar() + return int(row or 0) + + +async def wait_run(db, source_message_id: str, timeout_sec: int = 720) -> Optional[AIOrchestrationRun]: + deadline = time.time() + timeout_sec + last = None + while time.time() < deadline: + row = ( + await db.execute( + select(AIOrchestrationRun) + .where(AIOrchestrationRun.source_message_id == source_message_id) + .order_by(AIOrchestrationRun.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + if row is not None: + last = row + st = str(row.status or "").lower() + if st in {"succeeded", "failed", "cancelled"}: + return row + await asyncio.sleep(2) + return last + + +async def wait_outbox(db, source_message_id: str, run_status: str, timeout_sec: int = 300) -> list[FeishuReplyOutbox]: + deadline = time.time() + timeout_sec + expected = 2 if run_status == "succeeded" else 1 + latest: list[FeishuReplyOutbox] = [] + while time.time() < deadline: + latest = ( + await db.execute( + select(FeishuReplyOutbox) + .where(FeishuReplyOutbox.source_message_id == source_message_id) + .order_by(FeishuReplyOutbox.id.asc()) + ) + ).scalars().all() + sent = [r for r in latest if str(r.status or "") == "sent"] + if len(sent) >= expected: + return latest + await asyncio.sleep(2) + return latest + + +async def fetch_processing(db, source_message_id: str) -> Optional[AIProcessingLog]: + return ( + await db.execute( + select(AIProcessingLog) + .where(AIProcessingLog.feishu_message_id == source_message_id) + .order_by(AIProcessingLog.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + + +async def fetch_prompt_scene(db, min_call_id: int, keys: list[str]) -> tuple[str, list[dict[str, Any]]]: + rows = ( + await db.execute( + select(AICallLog) + .where( + AICallLog.id > min_call_id, + AICallLog.function_name.in_(["adaptive_rag_answer_normal", "adaptive_rag_answer_deep"]), + ) + .order_by(AICallLog.id.asc()) + .limit(200) + ) + ).scalars().all() + + lowered = [k.lower() for k in keys if k] + picked = "unknown" + candidates: list[dict[str, Any]] = [] + + for row in rows: + prompt = str(row.prompt or "") + pl = prompt.lower() + if lowered and not any(k in pl for k in lowered): + continue + scene = detect_prompt_scene(prompt) + candidates.append( + { + "id": int(row.id), + "scene": scene or "unknown", + "function": str(row.function_name or ""), + "created_at": str(row.created_at or ""), + } + ) + if scene: + picked = scene + + if picked == "unknown" and candidates: + picked = str(candidates[-1].get("scene") or "unknown") + + return picked, candidates + + +def evaluate(idx: int, actual_scene: str, final_reply: str, run_status: str) -> tuple[bool, str]: + leak = has_thinking_leak(final_reply) + if idx == 1: + ok = actual_scene == "member_workload" and "%" in final_reply and "项目列表" not in final_reply and not leak + return ok, "member_workload+百分比+无项目列表" + if idx == 2: + sec = "[行业动态]" in final_reply and ("[对项目影响]" in final_reply or "[对项目的影响]" in final_reply) and "[建议]" in final_reply + src = "未接入实时数据" in final_reply or "训练数据" in final_reply or "数据来源" in final_reply + ok = actual_scene == "external_trend" and sec and src and not leak + return ok, "external_trend+三段输出+来源限制" + if idx == 3: + mixed = "[回答]" in final_reply and "[已收到需求]" in final_reply + prog = "进行中" in final_reply + ok = actual_scene == "mixed" and run_status == "succeeded" and mixed and prog and not leak + return ok, "mixed+创建成功+进行中数量" + if idx == 4: + ok = actual_scene == "default" and "完成率" in final_reply and "%" in final_reply and not leak + return ok, "default+项目状态完成率" + if idx == 5: + ok = actual_scene == "external_trend" and not leak + return ok, "external_trend+无思考外漏" + return False, "unknown" + + +async def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--idx", type=int, required=True) + parser.add_argument("--message", type=str, required=True) + parser.add_argument("--keys", type=str, default="") + parser.add_argument("--prefix", type=str, default="real-scene-p1-manual") + args = parser.parse_args() + + keys = [k.strip() for k in str(args.keys).split("|") if k.strip()] + source_message_id = f"{args.prefix}-{args.idx}-{int(time.time())}" + + token = login_token() + if not token: + raise RuntimeError("token empty") + + async with AsyncSessionLocal() as db: + before_call_id = await fetch_max_ai_call_id(db) + + capture = post_capture(token, args.message, source_message_id) + + async with AsyncSessionLocal() as db: + run_row = await wait_run(db, source_message_id) + run_status = str(getattr(run_row, "status", "missing") or "missing") if run_row is not None else "missing" + outbox_rows = await wait_outbox(db, source_message_id, run_status) + processing = await fetch_processing(db, source_message_id) + prompt_scene, candidates = await fetch_prompt_scene(db, before_call_id, keys) + + sent_rows = [r for r in outbox_rows if str(r.status or "") == "sent"] + ack = str(sent_rows[0].message_text or "").strip() if sent_rows else "" + final_reply = str(sent_rows[-1].message_text or "").strip() if sent_rows else "" + if not final_reply: + final_reply = str(capture.get("direct_reply_text") or "").strip() + + inferred_scene = infer_scene_from_reply(final_reply) + actual_scene = prompt_scene if prompt_scene != "unknown" else inferred_scene + passed, pass_rule = evaluate(args.idx, actual_scene, final_reply, run_status) + + payload = { + "generated_at": datetime.now().isoformat(), + "idx": args.idx, + "source_message_id": source_message_id, + "message": args.message, + "capture_status": str(capture.get("status") or ""), + "run_status": run_status, + "ack": ack, + "final_reply": final_reply, + "prompt_scene": prompt_scene, + "inferred_scene": inferred_scene, + "actual_scene": actual_scene, + "thinking_leak": has_thinking_leak(final_reply), + "has_capacity_warning": "⚠ 产能提示" in final_reply, + "processing": { + "route": str(getattr(processing, "route", "") or ""), + "intent": str(getattr(processing, "intent", "") or ""), + "status": str(getattr(processing, "processing_status", "") or ""), + "duration_ms": int(getattr(processing, "duration_ms", 0) or 0), + "error_message": str(getattr(processing, "error_message", "") or ""), + "reply_source": str(getattr(processing, "reply_source", "") or ""), + }, + "outbox": [ + { + "id": int(r.id), + "status": str(r.status or ""), + "attempts": int(r.attempts or 0), + "created_at": str(r.created_at or ""), + "text": str(r.message_text or ""), + } + for r in outbox_rows + ], + "call_log_candidates": candidates, + "pass": passed, + "pass_rule": pass_rule, + } + print(json.dumps(payload, ensure_ascii=False)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/AGENTS.md b/AGENTS.md index e42c471b..7ad4204c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,6 +85,7 @@ frontend/ # React 前端 - 禁止在 API 层写业务逻辑,业务逻辑归属 `services/` - 禁止在前端组件内直接调用 `fetch`,必须通过 `frontend/src/api/` 封装层 - 前端状态管理:服务端数据用 React Query,本地 UI 状态用 `useState` +- 禁止模型兜底回复:模型失败/超时/空响应/结构化解析失败时,必须显式报错并保留真实失败原因,禁止返回固定兜底文案冒充成功结果 ## 9. 新增代码的参考模式 diff --git a/CLAUDE.md b/CLAUDE.md index b0cfcce6..e225ed50 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ backend/ │ ├── __init__.py # app 包入口(根目录轻量化后保留) │ ├── api/ # 路由层(含 endpoints) │ ├── core/ # 配置、日志、核心能力 -│ │ └── startup/ # 启动阶段拆分模块(db guard/outbox/probe/warmup) +│ │ └── startup/ # 启动阶段拆分模块(db guard/outbox/ingress async/probe/warmup) │ ├── deps.py # 共享依赖导出(get_db/AsyncSessionLocal) │ ├── dependencies/ # 鉴权/租户等依赖注入 │ ├── middleware/ # 请求中间件(审计上下文注入等) @@ -54,7 +54,8 @@ backend/ │ ├── 20260311_add_operation_log_audit_fields.py # operation_logs 审计字段扩展(trace/path/caller) │ ├── 20260311_add_ai_pmo_work_items_view.py # ai_pmo_work_items 兼容视图(映射 work_items) │ ├── 20260311_expand_user_workload_ratio_precision.py # user_workloads.workload_ratio 精度扩容(10,2) -│ └── 20260311_add_conversation_sessions_table.py # 飞书多轮会话上下文持久化表 +│ ├── 20260311_add_conversation_sessions_table.py # 飞书多轮会话上下文持久化表 +│ └── 20260316_add_ai_orchestration_ingress_async_fields.py # ai_orchestration_runs 入站异步重试字段扩展 ├── scripts/ # 后端运维/数据修复脚本 │ ├── backfill_orphan_signal_links.py # 回填孤立 signal 的 project_signal_links │ ├── dev/ # 开发辅助脚本(check_users/seed_all) @@ -64,7 +65,8 @@ backend/ └── (tests 已移除) ``` - 新增:`backend/app/api/llm_providers.py`(AI 管家 LLM provider preset 配置读取接口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_agent_kernel.py`(双 Agent 共享决策内核:复杂度路由/执行门控/修正识别) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py`(双 Agent 共享决策内核:复杂度路由/执行门控/修正识别) +- 新增:`backend/app/core/startup/ai_pmo_ingress_async.py`(AI PMO 入站异步任务轮询启动循环) - 新增:`backend/app/utils/timezone.py`(统一北京时间工具:`now_cn_aware/now_cn_naive`) - 新增:`backend/app/utils/audited_write.py`(写接口审计上下文、caller 归一、北京时间格式化) - 新增:`backend/app/middleware/audit_context.py`(请求级 trace/interface/caller 注入) @@ -73,10 +75,10 @@ backend/ - 新增:`backend/app/services/signal_source_repair_service.py`(脏信号源回填服务) - 新增:`backend/alembic/versions/20260311_shift_all_timestamps_to_beijing.py`(全库历史时间 +8h,数据库默认时区切到 Asia/Shanghai) - 新增:`backend/alembic/versions/20260312_add_signal_planner_fields.py`(signals 增加 planner 状态字段与索引) -- 新增:`backend/app/services/signal_planner/signal_strategic_analysis_service.py`(Signal Planner 写回收口) -- 新增:`backend/app/services/feishu_outbox/service.py`(复用 reply_hash 的 outbox 入队收口) +- 新增:`backend/app/services/signal/signal_planner/signal_strategic_analysis_service.py`(Signal Planner 写回收口) +- 新增:`backend/app/services/integrations/feishu_outbox/service.py`(复用 reply_hash 的 outbox 入队收口) - 新增:`backend/app/services/signal_planner_service.py`(Signal Planner 异步分析服务) -- 新增:`backend/app/services/model_routing/service.py`(函数级模型路由配置服务) +- 新增:`backend/app/services/model/model_routing/service.py`(函数级模型路由配置服务) - 新增:`backend/app/services/task_brief_service.py`(增量任务包中心服务) - 新增:`backend/app/services/model_context_bundle_service.py`(双模型上下文组包/能力汇总/消费验证服务) - 新增:`backend/app/api/model_routing.py`(模型路由配置接口) @@ -91,44 +93,50 @@ backend/ - 移除:`backend/.env.example`(配置模板统一到根目录 `.env.example`) - 新增:`backend/app/utils/project_command_utils.py`(项目创建标题/描述提取与归一化辅助) - 新增:`backend/app/services/ai_pmo_intent_routing.py`(AI PMO 意图/路由常量与解析器收口,统一 `family.action` 路由映射) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_workflow_runtime.py`(AI PMO `ContextAssembler` + `WorkflowRegistry/WorkflowHandler` 运行时骨架) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py`(AI PMO `ContextAssembler` + `WorkflowRegistry/WorkflowHandler` 运行时骨架) - 新增:`backend/app/services/ai_pmo_decision_runtime.py`(AI PMO 决策脑层:`Strategic/Tactical/Simulation/ExecutionPolicy/DecisionEngine`) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_reply_renderer.py`(AI PMO 回执渲染出口 `ReplyRenderer`) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor.py`(AI PMO typed 执行层组合入口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_common.py`(TypedExecutor 共享常量/依赖) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py`(TypedExecutor 解析与批量分析逻辑) -- 新增:`backend/app/services/ai_pmo_typed_executor_mixins_discovery.py`(TypedExecutor discover 候选上下文逻辑) -- 新增:`backend/app/services/ai_pmo_typed_executor_mixins_execute.py`(TypedExecutor execute/create/source-onboard 落库逻辑) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_runtime.py`(AI PMO kernel 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py`(AI PMO 回执渲染出口 `ReplyRenderer`) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor.py`(AI PMO typed 执行层组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py`(TypedExecutor 共享常量/依赖) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py`(TypedExecutor 解析与批量分析逻辑) +- 新增:`backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/`(TypedExecutor discover 候选上下文逻辑) +- 新增:`backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/`(TypedExecutor execute/create/source-onboard 落库逻辑) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_kernel_runtime.py`(AI PMO kernel 组合入口) - 新增:`backend/app/services/ai_pmo_kernel_common.py`(Kernel 共享常量/依赖) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress.py`(Kernel ingress 组合入口) -- 新增:`backend/app/services/ai_pmo_kernel_mixins_ingress_flow.py`(Kernel 入站主流程编排) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_storage.py`(Kernel 入站事件/工作项写入) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_render.py`(Kernel 入站结果组装与输出辅助) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model.py`(Kernel world-model 组合入口) -- 新增:`backend/app/services/ai_pmo_kernel_mixins_world_model_core.py`(Kernel world-model 核心构建与缓存) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model_session.py`(Kernel 会话继承与会话态维护) -- 新增:`backend/app/services/ai_pmo_kernel_mixins_world_model_aggregates.py`(Kernel 组合摘要构建器) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent.py`(Kernel intent 组合入口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_rules.py`(Kernel 规则意图与槽位守卫) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_llm.py`(Kernel LLM 意图识别与仲裁) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_resolve.py`(Kernel 最终意图解析入口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup.py`(Kernel followup 组合入口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_records.py`(followup 决策/计划/回执写入) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_routing.py`(followup 候选定位与恢复分发) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal.py`(project_signal followup 组合入口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_actions.py`(project_signal commit/refresh/cancel 动作执行) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_resume.py`(project_signal 目标工作项恢复闭环) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_verification.py`(followup 执行校验与工具函数) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure.py`(Kernel closure 组合入口) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_helpers.py`(Kernel 闭环通用归一化工具) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_data.py`(Kernel 闭环历史上下文回放) -- 新增:`backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_payload.py`(Kernel 闭环语义与来源上下文组装) -- 移除:`backend/app/services/ai_pmo_kernel_service.py`(顶层兼容壳清理,统一改为 `backend/app/services/ai_pmo_kernel/` 子包) -- 新增:`backend/app/services/feishu_signal_pipeline/`(飞书信号管道按职责拆分的服务包,所有子文件 <= 300 行) -- 新增:`backend/app/services/feishu_signal_pipeline/service.py`(组合入口,按 mixin 聚合能力) -- 新增:`backend/app/services/feishu_signal_pipeline/shared_core.py`、`shared_schema_utils.py`(共享常量/正则/Schema/工具函数拆分) -- 新增:`backend/app/services/feishu_signal_pipeline/mixin_*.py`(按职责拆分的处理流、gate、followup、queue、序列化、提取分类等实现) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/compositions/ingress.py`(Kernel ingress 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/`(Kernel 入站主流程编排) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py`(Kernel 入站事件/工作项写入) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py`(Kernel 入站结果组装与输出辅助) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/compositions/world_model.py`(Kernel world-model 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/`(Kernel world-model 核心构建与缓存) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py`(Kernel 会话继承与会话态维护) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/`(Kernel 组合摘要构建器) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/compositions/intent.py`(Kernel intent 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py`(Kernel 规则意图与槽位守卫) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py`(Kernel LLM 意图识别与仲裁) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py`(Kernel 最终意图解析入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/compositions/followup.py`(Kernel followup 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_records.py`(followup 决策/计划/回执写入) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py`(followup 候选定位与恢复分发) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal.py`(project_signal followup 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_actions.py`(project_signal commit/refresh/cancel 动作执行) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_resume.py`(project_signal 目标工作项恢复闭环) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py`(followup 执行校验与工具函数) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/compositions/closure.py`(Kernel closure 组合入口) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_helpers.py`(Kernel 闭环通用归一化工具) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py`(Kernel 闭环历史上下文回放) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py`(Kernel 闭环语义与来源上下文组装) +- 更新:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/`(新增 `closure/followup/ingress/intent/world_model` 分组目录与 `__init__.py` 包声明,降低同层文件密度) +- 更新:`backend/app/services/ai_pmo/ai_pmo_kernel/compositions/`(新增组合入口分组目录,根目录仅保留 runtime/agent/domain 等真正入口) +- 更新:`backend/app/services/ai_pmo/`(新增 AI PMO 领域分组目录,统一收敛 `ai_pmo_cadence/ai_pmo_decision/ai_pmo_intent/ai_pmo_kernel/ai_pmo_kernel_common_core/ai_pmo_kernel_ingress_flow/ai_pmo_kernel_world_model_aggregates/ai_pmo_kernel_world_model_core/ai_pmo_typed_executor_discovery/ai_pmo_typed_executor_execute`) +- 更新:`backend/app/services/model/`(新增模型能力分组目录,统一收敛 `model_capability/model_context_bundle/model_routing/prompt_profile`) +- 更新:`backend/app/services/integrations/`(新增集成能力分组目录,统一收敛 `integration_core/feishu_integration_service/feishu_long_connection_core/feishu_outbox/feishu_signal_pipeline/wecom`) +- 更新:`backend/app/services/signal/`(新增信号能力分组目录,统一收敛 `signal_capture_service/signal_planner/signal_source_orchestration/signal_source_repair/symbol_index`) +- 移除:`backend/app/services/ai_pmo_kernel_service.py`(顶层兼容壳清理,统一改为 `backend/app/services/ai_pmo/ai_pmo_kernel/` 子包) +- 新增:`backend/app/services/integrations/feishu_signal_pipeline/`(飞书信号管道按职责拆分的服务包,所有子文件 <= 300 行) +- 新增:`backend/app/services/integrations/feishu_signal_pipeline/service.py`(组合入口,按 mixin 聚合能力) +- 新增:`backend/app/services/integrations/feishu_signal_pipeline/shared_core.py`、`shared_schema_utils.py`(共享常量/正则/Schema/工具函数拆分) +- 新增:`backend/app/services/integrations/feishu_signal_pipeline/mixin_*.py`(按职责拆分的处理流、gate、followup、queue、序列化、提取分类等实现) - 更新:`backend/app/services/feishu_signal_pipeline.py`(降级为兼容入口,仅导出包内 `FeishuSignalPipelineService`) - 新增:`backend/app/api/feishu_api/`(飞书接口按配置/长连接/消息发送/连通性测试拆分的子模块目录) - 新增:`backend/app/api/feishu_api/router.py`(飞书 API 聚合路由) @@ -145,13 +153,13 @@ backend/ - 新增:`backend/app/services/sstc/`(SSTC 业务辅助能力拆分:signal/charter/governance/entity_lookup/review) - 新增:`backend/app/services/llm_service/`(LLM 服务按职责拆分的 mixin 包,所有子文件 <= 300 行) - 更新:`backend/app/services/llm.py`(降级为兼容入口,导出 `LLMService` 与 `llm_service`) -- 新增:`backend/app/services/signal_capture_service/`(主动信号捕获服务按入口/编排/分析/新项目拆分) +- 新增:`backend/app/services/signal/signal_capture_service/`(主动信号捕获服务按入口/编排/分析/新项目拆分) - 更新:`backend/app/services/signal_capture.py`(降级为兼容入口) - 新增:`backend/app/services/project_knowledge_graph_service/`(知识图谱服务按 CRUD/增量同步/守卫/快照拆分) - 更新:`backend/app/services/project_knowledge_graph.py`(降级为兼容入口) - 新增:`backend/app/services/context_cache_service/`(上下文缓存服务按缓存核心/画像/快照/失效钩子拆分) - 更新:`backend/app/services/context_cache.py`(降级为兼容入口) -- 新增:`backend/app/config/ai_prompts_catalog/`(Prompt 常量分片目录,按 part_01..part_04 管理) +- 新增:`backend/app/config/ai_prompts_catalog/`(Prompt 常量分片目录,按 `part_01..part_04` + `part_scenes_01..part_scenes_06` 管理) - 更新:`backend/app/config/ai_prompts.py`(兼容聚合入口,保留 `build_default_role_prompt_bundle`) - 新增:`backend/app/api/signals_api/`(signals 路由拆分为 list/create、ingest/fetch、feedback 子模块) - 新增:`backend/app/api/wecom_api/`(wecom 路由拆分为 config/webhook/messaging 子模块) @@ -171,24 +179,25 @@ backend/ - 新增:`backend/app/__init__.py`、`backend/app/deps.py`(根目录仅保留 `__init__.py / main.py / deps.py`) - 更新:`backend/app/services/` 顶层兼容壳批量清理(如 `ai_service.py/llm.py/scheduler_center.py/signal_capture.py/...`),统一改为直接引用对应子包(`ai_service_core/llm_service/scheduler_center_core/signal_capture_service/...`) - 更新:移除 `backend/app/services/alignment_orchestrator.py` 空文件(无引用) -- 新增:`backend/app/services/ai_pmo_kernel_world_model_aggregates/`、`backend/app/services/ai_pmo_kernel_world_model_core/`、`backend/app/services/ai_pmo_kernel_common_core/`(kernel world-model/common 拆分) -- 更新:`backend/app/services/ai_pmo_kernel_runtime.py`、`backend/app/services/ai_agent_kernel.py`、`backend/app/services/ai_pmo_domain_workflow.py` 及 `backend/app/services/ai_pmo_kernel_mixins_*.py` → `backend/app/services/ai_pmo_kernel/`(AI PMO kernel 子包化,清理 services 根目录散落文件) +- 新增:`backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/`、`backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/`、`backend/app/services/ai_pmo/ai_pmo_kernel_common_core/`(kernel world-model/common 拆分) +- 更新:`backend/app/services/ai_pmo_kernel_runtime.py`、`backend/app/services/ai_agent_kernel.py`、`backend/app/services/ai_pmo_domain_workflow.py` 及 `backend/app/services/ai_pmo_kernel_mixins_*.py` → `backend/app/services/ai_pmo/ai_pmo_kernel/`(AI PMO kernel 子包化,清理 services 根目录散落文件) - 更新:`backend/app/services/ai_orchestration_run_service.py` → `backend/app/services/ai_orchestration_run/service.py`(编排运行账本服务子包化) -- 更新:`backend/app/services/ai_pmo_reply_renderer.py`、`backend/app/services/ai_pmo_review_service.py`、`backend/app/services/ai_pmo_typed_executor.py`、`backend/app/services/ai_pmo_typed_executor_common.py`、`backend/app/services/ai_pmo_typed_executor_mixins_parse.py`、`backend/app/services/ai_pmo_workflow_runtime.py` → `backend/app/services/ai_pmo_kernel/`(AI PMO 执行与回执运行时收敛到 kernel 子包) -- 更新:`backend/app/services/cost_optimization_service.py`、`backend/app/services/model_lifecycle_service.py` → `backend/app/services/model_capability/`(模型能力相关服务收敛) -- 更新:`backend/app/services/signal_strategic_analysis_service.py` → `backend/app/services/signal_planner/signal_strategic_analysis_service.py`(Signal Planner 分析写回服务归并) +- 新增:`backend/app/services/ai_orchestration_run/ingress_worker.py`(AI PMO 入站消息异步消费、重试与最终回执发送) +- 更新:`backend/app/services/ai_pmo_reply_renderer.py`、`backend/app/services/ai_pmo_review_service.py`、`backend/app/services/ai_pmo_typed_executor.py`、`backend/app/services/ai_pmo_typed_executor_common.py`、`backend/app/services/ai_pmo_typed_executor_mixins_parse.py`、`backend/app/services/ai_pmo_workflow_runtime.py` → `backend/app/services/ai_pmo/ai_pmo_kernel/`(AI PMO 执行与回执运行时收敛到 kernel 子包) +- 更新:`backend/app/services/cost_optimization_service.py`、`backend/app/services/model_lifecycle_service.py` → `backend/app/services/model/model_capability/`(模型能力相关服务收敛) +- 更新:`backend/app/services/signal_strategic_analysis_service.py` → `backend/app/services/signal/signal_planner/signal_strategic_analysis_service.py`(Signal Planner 分析写回服务归并) - 移除:`backend/app/services/llm_client.py`(全仓库无引用,按废弃文件清理) -- 更新:`backend/app/services/analyzer.py`、`backend/app/services/fetcher.py` → `backend/app/services/signal_capture_service/impact_analyzer.py`、`backend/app/services/signal_capture_service/external_fetcher.py`(信号接入分析/抓取能力归并) -- 更新:`backend/app/services/plane.py` → `backend/app/services/integration_core/plane_service.py`(Plane 兼容服务归并到 integration_core) -- 更新:`backend/app/services/lark.py` → `backend/app/services/integration_core/lark_service.py`(Lark 兼容服务归并到 integration_core) +- 更新:`backend/app/services/analyzer.py`、`backend/app/services/fetcher.py` → `backend/app/services/signal/signal_capture_service/impact_analyzer.py`、`backend/app/services/signal/signal_capture_service/external_fetcher.py`(信号接入分析/抓取能力归并) +- 更新:`backend/app/services/plane.py` → `backend/app/services/integrations/integration_core/plane_service.py`(Plane 兼容服务归并到 integration_core) +- 更新:`backend/app/services/lark.py` → `backend/app/services/integrations/integration_core/lark_service.py`(Lark 兼容服务归并到 integration_core) - 更新:`backend/app/services/workload_alert_service.py` → `backend/app/services/workload/workload_alert_service.py`(负载告警服务归并到 workload 子包) - 更新:`backend/app/services/task_persistence.py`(兼容壳移除,统一直接引用 `backend/app/services/task_persistence_core/`) - 更新:`backend/app/services/project_member_service.py` → `backend/app/services/project_member/service.py`(项目成员分配服务子包化) - 更新:`backend/app/services/recipe_service.py` → `backend/app/services/recipe/service.py`(食谱服务子包化) -- 更新:`backend/app/services/model_routing_service.py` → `backend/app/services/model_routing/service.py`(模型路由服务子包化) -- 更新:`backend/app/services/prompt_profile_service.py` → `backend/app/services/prompt_profile/service.py`(Prompt 画像服务子包化) -- 更新:`backend/app/services/feishu_outbox_service.py` → `backend/app/services/feishu_outbox/service.py`(飞书 outbox 服务子包化) -- 更新:`backend/app/services/wecom_integration.py` → `backend/app/services/wecom/service.py`(企业微信集成服务子包化) +- 更新:`backend/app/services/model_routing_service.py` → `backend/app/services/model/model_routing/service.py`(模型路由服务子包化) +- 更新:`backend/app/services/prompt_profile_service.py` → `backend/app/services/model/prompt_profile/service.py`(Prompt 画像服务子包化) +- 更新:`backend/app/services/feishu_outbox_service.py` → `backend/app/services/integrations/feishu_outbox/service.py`(飞书 outbox 服务子包化) +- 更新:`backend/app/services/wecom_integration.py` → `backend/app/services/integrations/wecom/service.py`(企业微信集成服务子包化) - 更新:`backend/app/services/skill_score_service.py` → `backend/app/services/skill/skill_score_service.py`(技能评分服务归并到 skill 子包) - 更新:`backend/app/services/auth.py`、`backend/app/services/audit_query_service.py`、`backend/app/services/notification_service.py`、`backend/app/services/operation_catalog_service.py`、`backend/app/services/operation_log_service.py` → `backend/app/services/*/service.py`(同名服务子包化,`services` 根目录不再保留 `.py` 文件) - 更新:`backend/app/services/` 根目录 `.py` 已清零(全部收敛到子包),仅保留目录化服务结构 @@ -374,6 +383,11 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - 双模型上下文注入仅允许通过 `ModelContextBundleService` 统一组包:固定三段 `knowledge_graph/task_template/contract_sync`,禁止在业务服务散落拼接 prompt 上下文 - AI PMO `signal.create` 在用户显式要求“先分析再入库 / 自行分析”时,若批量信号结构化分析失败,禁止回退规则标题直接入库,必须返回失败安全态 - 所有 LLM 调用禁止模型级兜底:禁止高阶升级、Safety 引擎、应急直连、stub 假成功;模型异常、超时或结构化输出不合法时必须显式报错 +- 禁止模型兜底回复文案:模型失败、超时、空响应、结构化解析失败时,必须返回真实错误原因;禁止输出“系统处理中/请稍后重试”等固定文案冒充成功结果 +- 用户可见问答链路(`function_name` 含 `answer/reply/qa`)禁止使用 `reasoning_content` 回退,避免思考过程外漏 +- AdaptiveRAG 场景化 Prompt 选择器必须在 `mixin_deep_guard_answer.py` 组装 Prompt 前执行,并遵循固定优先级:成员负载 > 外部趋势 > mixed > AI PMO 入口 > 默认 RAG +- mixed 场景回复必须强制两段格式输出:`[回答]` 与 `[已收到需求] ...,已入库处理`,禁止把需求段落并入问答段落 +- 飞书与 AI PMO 入口(含 `message_intent=mixed`)问答链路必须绕过 deterministic/snapshot/glm_semantic 快路径,统一落到场景化 Prompt 生成,避免旧确定性清单回复覆盖场景格式 - 本地 `ollama` 仅允许知识库问答链路使用,且必须通过 `llm_engine.settings.enable_local_kb_qa` 显式开启;未开启时运行期与连通测试都不可用 - AI Steward 零配置创建任一步 LLM 失败、超时或 JSON 非法时必须直接报错,禁止回退默认 analysis/signal/charter/tasks 草稿 - Signal Source Onboarding 的 planner/worker 任一步失败必须显式报错或标记 failed,禁止 heuristic/fallback 兜底计划与“最小闭环”摘要 @@ -403,6 +417,9 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - AI PMO 消息台列表接口补充:过滤短口水话 `query.read_only` 项(如“怎么样了/在吗”),保留后端记录但不下发前端收件流 - AI PMO `signal_source.onboard` 入库规则:优先使用闭环摘要写入 `signals.summary`,闭环对话写入 `signals.content`,并在 `signals.strategic_analysis.source_context.implementation_modules` 持久化实现模块 - SSTC 信号列表接口(`GET /projects/{project_id}/signals`)标题生成规则:优先 `signals.title`,其次 `signals.summary`,最后回退 `signals.content` 去前缀文本 +- SSTC 宪章详情接口(`GET /projects/{project_id}/charters/{charter_id}`)`alignment_records[]` 补充 `user_name/confirmed_at`,用于前端展示“对齐成员 + 对齐时间” +- SSTC AI 拆解接口(`POST /projects/{project_id}/charters/{charter_id}/tasks/ai-breakdown`)执行任务标题去重(同次 + 历史任务),返回 `skipped_duplicates` 明确跳过计数 +- SSTC 发布创建接口(`POST /projects/{project_id}/releases`)新增已关联任务拦截:若 `task_ids` 命中历史发布计划已绑定任务,返回 `409` + `detail.task_ids` - 信号源编排补充:`SignalSourceOrchestrationService` 已停止运行时调用旧 command-first 执行链,统一改为“GLM 分析后直接调用服务写入”,编排结果字段对外收敛为 `execution` - 新增前端 API 调用:参考 `frontend/src/api/sstc.ts` - 新增数据库表:参考 `alembic/versions/` 最新 migration @@ -416,26 +433,33 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ## 11. 已知 BOUNDARY(不要幻构行为) - 飞书回调字段可能缺失文件名/大小,仅保证 `attachment_id/key/type` 可用 -- 当前 LLM 主链默认直连引擎,不再依赖 OpenClaw 网关;请求超时统一以 `LLM_REQUEST_TIMEOUT` 环境变量为准 -- LLM 非结构化响应在 `content` 为空且存在 `reasoning_content` 时,可按需回退读取 `reasoning_content`(开关:`LLM_REASONING_CONTENT_FALLBACK`,默认关闭) +- LLM 主链按 `OPENCLAW_ROUTING_ENABLED` 走 OpenClaw 路由规则(`backend/.openclaw/pmflow-routing-rules.json`);请求超时统一以 `LLM_REQUEST_TIMEOUT` 环境变量为准 +- LLM 非结构化响应仅在非用户可见链路且显式开启 `LLM_REASONING_CONTENT_FALLBACK` 时,才允许从 `reasoning_content` 回退;`answer/reply/qa` 链路一律禁用该回退 - World Model 静态层已接入 Redis 缓存(key: `wm:static:{tenant_id}:{project_id}`,TTL 600s) - 写操作后主动 invalidate,动态层不缓存 -- AI PMO 意图识别默认 `rule-first`:`AI_PMO_INTENT_LLM_ENABLED=true` 时,LLM 仅在规则低置信或 `default_query_fallback` 场景作为末位兜底;模型超时/不可用/结构化解析失败时回退规则链路,不中断主流程 +- AI PMO 意图识别已切换 `model-only`:`_resolve_intent` 仅接受 LLM 结构化结果,不再走规则/正则兜底链路 - 环境变量以根目录 `.env` / `.env.example` 为唯一配置源;`backend/.env` 已移除且不再读取 - Signal Planner 函数路由支持 `integrations(type='model_routing')` 覆盖配置;配置缺失时默认直连 GLM - Signal Planner 结构化调用采用 `25s` 硬超时:超时或结构化不合法时标记失败并通知人工重试,不再写入降级 `ai_planner(fallback_mode=true)` - AI Steward 用户侧已移除 OpenClaw 指挥规划入口;后续项目分析主链以 GLM 严格失败直出为准 - 飞书业务命令命中旧 command-first 路由时,仅返回“已下线”阻断提示,不再触发实际执行 - 模型命名统一使用裸模型名(如 `GLM-4.7`、`MiniMax-M2.5`),不使用供应商前缀模型名 -- 双 Agent 路由治理:默认直连 `GLM`,复杂意图仍使用 `classify_intent_complex`,不再经过 OpenClaw 控制平面 +- 双 Agent 路由治理:低/高层函数均可由 OpenClaw 规则命中到指定 provider/model(含 `classify_intent_ai_pmo_kernel`) - 双模型 DDL 治理图谱的模型来源优先读取显式模型配置;缺失时回退 `LLM_ROUTING_LOW_MODEL/LLM_ROUTING_HIGH_MODEL` - 写操作链路统一产出 `decision_trace`(intent/tier/confidence/gate_code/correction_action)用于离线回放与分桶评分 - AI PMO 意图仲裁默认开启(`AI_PMO_INTENT_ARBITRATION_ENABLED=true`):规则与 LLM 写操作冲突时按 `AI_PMO_INTENT_CONFLICT_POLICY=pending_manual` 强制进入人工确认,不直接自动执行 - 飞书 `qa_llm` 回复已增加事实熔断:命中“数据库迁移/DDL/Alembic/让我直接处理/模型失败了所以没分析”等漂移语句时,自动回退固定模板,禁止对外宣称未证实执行动作 -- `project.create` 启用槽位完整性闸门:无法从指令提取有效项目名时标记 `slot_incomplete` 并进入人工确认 +- 飞书 `humanized_reply` 与 RAG 问答 Prompt 已显式禁止思考过程外显;回复链路会过滤以“让我先/让我尝试/我来看看/我先查/让我看看/我需要先”开头的前置段落 - 数据库与核心写入链路统一使用北京时间(`Asia/Shanghai`):PostgreSQL 会话时区、数据库默认时区与核心 `_utc_now*` 写入入口均已切换到北京时间 - 后端与脚本层已清零 `datetime.utcnow()/timezone.utc`:统一使用 `backend/app/utils/timezone.py`(`now_cn_aware/now_cn_naive/CN_TZ`)管理时间语义 - `operation_logs.created_at_beijing` 为应用层计算字段(非 DB 列),审计查询接口返回时统一补齐 - 历史脏信号源修复优先复用同项目已存在 `signal_id`,仅无法复用时才触发 `capture-feishu` 补录 - 兼容 SQL 查询入口:`ai_pmo_work_items` 为视图别名(底层映射 `work_items`),避免历史脚本直接查旧表名失败 - `signal_source.onboard` 已加入工作流门禁旁路:进入执行阶段后按闭环结果返回 `succeeded/pending_manual/failed_safe`,不再被前置仿真门禁直接拦截为 `waiting_manual` +- 飞书入站已拆分“短确认同步快路径 + 非短确认异步队列”:短确认命中会话窗口时直接继承上轮意图并同步执行;其余消息先回“已收到,正在处理”后由 `ai_orchestration_runs` 异步 worker 执行与重试 +- `ai_orchestration_runs.project_id` 允许为空:用于承载尚未完成项目归属判定的入站异步任务;既有 `signal_source_onboarding` 运行仍按显式 `project_id` 写入 +- `/ai-pmo/process-text` 问答链路在 AdaptiveRAG 内通过 `asked_via=pmflow_manual` 识别为 AI PMO 入口;mixed 场景通过 `message_intent=mixed` 透传触发专用 Prompt 与两段输出格式化 +- `PROMPT_DECOMPOSE_TASKS` 技能标签来源已改为运行时动态注入:优先读取 `skill_tags`(按 `sort_order/name`),为空时才回退默认技能集合 + +## 12. 技术债务清单 +- [ ] `mixin_auto.py` `_safe_int` NameError:之前被 `simulation_capacity_limit` 门禁遮住未暴露,需单独任务修复 diff --git a/backend/.openclaw/pmflow-routing-rules.json b/backend/.openclaw/pmflow-routing-rules.json index 0818c1d1..cd365b48 100644 --- a/backend/.openclaw/pmflow-routing-rules.json +++ b/backend/.openclaw/pmflow-routing-rules.json @@ -29,6 +29,16 @@ "api_env": "OPENAI_API_KEY", "function_exact": "classify_intent_complex" }, + { + "name": "engine_low_intent_kernel_exact", + "enabled": true, + "priority": 310, + "tier": "low", + "provider": "glm", + "model": "GLM-4.7", + "api_env": "GLM_API_KEY", + "function_exact": "classify_intent_ai_pmo_kernel" + }, { "name": "engine_low_execute_glm_flash", "enabled": true, diff --git a/backend/alembic/versions/20260316_add_ai_orchestration_ingress_async_fields.py b/backend/alembic/versions/20260316_add_ai_orchestration_ingress_async_fields.py new file mode 100644 index 00000000..b11e670a --- /dev/null +++ b/backend/alembic/versions/20260316_add_ai_orchestration_ingress_async_fields.py @@ -0,0 +1,165 @@ +"""add ai orchestration ingress async fields + +Revision ID: add_ai_orchestration_ingress_async_fields +Revises: add_signal_planner_fields +Create Date: 2026-03-16 +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa + + +revision = "add_ai_orchestration_ingress_async_fields" +down_revision = "add_signal_planner_fields" +branch_labels = None +depends_on = None + + +def _table_exists(bind, table_name: str) -> bool: + return sa.inspect(bind).has_table(table_name) + + +def _column_exists(bind, table_name: str, column_name: str) -> bool: + inspector = sa.inspect(bind) + if not inspector.has_table(table_name): + return False + return any(str(item.get("name") or "") == column_name for item in inspector.get_columns(table_name)) + + +def _index_exists(bind, table_name: str, index_name: str) -> bool: + inspector = sa.inspect(bind) + if not inspector.has_table(table_name): + return False + return any(str(item.get("name") or "") == index_name for item in inspector.get_indexes(table_name)) + + +def _create_index_if_missing(bind, table_name: str, index_name: str, columns: list[str]) -> None: + if not _index_exists(bind, table_name, index_name): + op.create_index(index_name, table_name, columns) + + +def _drop_index_if_exists(bind, table_name: str, index_name: str) -> None: + if _index_exists(bind, table_name, index_name): + op.drop_index(index_name, table_name=table_name) + + +def upgrade() -> None: + bind = op.get_bind() + table_name = "ai_orchestration_runs" + if not _table_exists(bind, table_name): + return + + with op.batch_alter_table(table_name) as batch_op: + if _column_exists(bind, table_name, "project_id"): + batch_op.alter_column("project_id", existing_type=sa.BigInteger(), nullable=True) + if not _column_exists(bind, table_name, "source_system"): + batch_op.add_column(sa.Column("source_system", sa.String(length=32), nullable=True)) + if not _column_exists(bind, table_name, "source_message_id"): + batch_op.add_column(sa.Column("source_message_id", sa.String(length=128), nullable=True)) + if not _column_exists(bind, table_name, "sender"): + batch_op.add_column(sa.Column("sender", sa.String(length=128), nullable=True)) + if not _column_exists(bind, table_name, "source_sender_open_id"): + batch_op.add_column(sa.Column("source_sender_open_id", sa.String(length=128), nullable=True)) + if not _column_exists(bind, table_name, "channel_ref"): + batch_op.add_column(sa.Column("channel_ref", sa.String(length=128), nullable=True)) + if not _column_exists(bind, table_name, "chat_type"): + batch_op.add_column(sa.Column("chat_type", sa.String(length=32), nullable=True)) + if not _column_exists(bind, table_name, "captured_at"): + batch_op.add_column(sa.Column("captured_at", sa.TIMESTAMP(), nullable=True)) + if not _column_exists(bind, table_name, "message_text"): + batch_op.add_column(sa.Column("message_text", sa.Text(), nullable=True)) + if not _column_exists(bind, table_name, "project_hint_id"): + batch_op.add_column(sa.Column("project_hint_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=True)) + if not _column_exists(bind, table_name, "retry_count"): + batch_op.add_column(sa.Column("retry_count", sa.Integer(), nullable=False, server_default="0")) + if not _column_exists(bind, table_name, "max_retries"): + batch_op.add_column(sa.Column("max_retries", sa.Integer(), nullable=False, server_default="3")) + if not _column_exists(bind, table_name, "next_retry_at"): + batch_op.add_column(sa.Column("next_retry_at", sa.TIMESTAMP(), nullable=True)) + if not _column_exists(bind, table_name, "last_error"): + batch_op.add_column(sa.Column("last_error", sa.Text(), nullable=True)) + if not _column_exists(bind, table_name, "last_error_at"): + batch_op.add_column(sa.Column("last_error_at", sa.TIMESTAMP(), nullable=True)) + if not _column_exists(bind, table_name, "ack_sent_at"): + batch_op.add_column(sa.Column("ack_sent_at", sa.TIMESTAMP(), nullable=True)) + if not _column_exists(bind, table_name, "result_notified_at"): + batch_op.add_column(sa.Column("result_notified_at", sa.TIMESTAMP(), nullable=True)) + if not _column_exists(bind, table_name, "work_item_id"): + batch_op.add_column( + sa.Column( + "work_item_id", + sa.Integer(), + sa.ForeignKey("work_items.id", ondelete="SET NULL"), + nullable=True, + ) + ) + + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_source_system", ["source_system"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_source_message_id", ["source_message_id"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_source_sender_open_id", ["source_sender_open_id"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_channel_ref", ["channel_ref"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_captured_at", ["captured_at"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_project_hint_id", ["project_hint_id"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_next_retry_at", ["next_retry_at"]) + _create_index_if_missing(bind, table_name, "ix_ai_orchestration_runs_work_item_id", ["work_item_id"]) + _create_index_if_missing( + bind, + table_name, + "ix_ai_orchestration_runs_run_type_status_next_retry", + ["run_type", "status", "next_retry_at"], + ) + + +def downgrade() -> None: + bind = op.get_bind() + table_name = "ai_orchestration_runs" + if not _table_exists(bind, table_name): + return + + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_run_type_status_next_retry") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_work_item_id") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_next_retry_at") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_project_hint_id") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_captured_at") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_channel_ref") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_source_sender_open_id") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_source_message_id") + _drop_index_if_exists(bind, table_name, "ix_ai_orchestration_runs_source_system") + + with op.batch_alter_table(table_name) as batch_op: + if _column_exists(bind, table_name, "work_item_id"): + batch_op.drop_column("work_item_id") + if _column_exists(bind, table_name, "result_notified_at"): + batch_op.drop_column("result_notified_at") + if _column_exists(bind, table_name, "ack_sent_at"): + batch_op.drop_column("ack_sent_at") + if _column_exists(bind, table_name, "last_error_at"): + batch_op.drop_column("last_error_at") + if _column_exists(bind, table_name, "last_error"): + batch_op.drop_column("last_error") + if _column_exists(bind, table_name, "next_retry_at"): + batch_op.drop_column("next_retry_at") + if _column_exists(bind, table_name, "max_retries"): + batch_op.drop_column("max_retries") + if _column_exists(bind, table_name, "retry_count"): + batch_op.drop_column("retry_count") + if _column_exists(bind, table_name, "project_hint_id"): + batch_op.drop_column("project_hint_id") + if _column_exists(bind, table_name, "message_text"): + batch_op.drop_column("message_text") + if _column_exists(bind, table_name, "captured_at"): + batch_op.drop_column("captured_at") + if _column_exists(bind, table_name, "chat_type"): + batch_op.drop_column("chat_type") + if _column_exists(bind, table_name, "channel_ref"): + batch_op.drop_column("channel_ref") + if _column_exists(bind, table_name, "source_sender_open_id"): + batch_op.drop_column("source_sender_open_id") + if _column_exists(bind, table_name, "sender"): + batch_op.drop_column("sender") + if _column_exists(bind, table_name, "source_message_id"): + batch_op.drop_column("source_message_id") + if _column_exists(bind, table_name, "source_system"): + batch_op.drop_column("source_system") diff --git a/backend/app/api/ai_pmo_api/shared.py b/backend/app/api/ai_pmo_api/shared.py index 3885e1bf..5b9dd920 100644 --- a/backend/app/api/ai_pmo_api/shared.py +++ b/backend/app/api/ai_pmo_api/shared.py @@ -16,8 +16,8 @@ from ...core.database import AsyncSessionLocal, get_db from ...dependencies.auth import get_current_user from ...dependencies.tenant import get_tenant_id from ...models import DecisionRecord, IngestEvent, PlanBundle, PortfolioSnapshot, Project, ReplyReceipt, User, WorkItem -from ...services.ai_pmo_cadence import AIPMOCadenceService -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_cadence import AIPMOCadenceService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService router = APIRouter(prefix="/ai-pmo", tags=["AI PMO"]) logger = get_logger(__name__) diff --git a/backend/app/api/ai_steward_api/shared.py b/backend/app/api/ai_steward_api/shared.py index 8824a5ff..65df3d53 100644 --- a/backend/app/api/ai_steward_api/shared.py +++ b/backend/app/api/ai_steward_api/shared.py @@ -23,10 +23,10 @@ from ...services.blocker_detection_core import get_blocker_detection_service from ...services.cognitive_conflict_core import get_cognitive_conflict_service from ...services.daily_briefing_core import get_daily_briefing_service from ...services.event_workflow_core import WorkflowEventType, get_event_workflow_engine -from ...services.feishu_outbox import FeishuOutboxService -from ...services.signal_capture_service import ProactiveSignalCaptureService -from ...services.signal_source_orchestration import SignalSourceOrchestrationService -from ...services.signal_source_repair import ( +from ...services.integrations.feishu_outbox import FeishuOutboxService +from ...services.signal.signal_capture_service import ProactiveSignalCaptureService +from ...services.signal.signal_source_orchestration import SignalSourceOrchestrationService +from ...services.signal.signal_source_repair import ( SignalSourceRepairMissingSignalError, SignalSourceRepairService, SignalSourceRepairTimeoutError, diff --git a/backend/app/api/feishu_api/config_routes.py b/backend/app/api/feishu_api/config_routes.py index 8a30281c..d06fe595 100644 --- a/backend/app/api/feishu_api/config_routes.py +++ b/backend/app/api/feishu_api/config_routes.py @@ -11,8 +11,8 @@ from ...dependencies.auth import get_current_user from ...dependencies.tenant import get_tenant_id from ...models import Integration, User from ...services.auth import has_any_role -from ...services.feishu_integration_service import get_feishu_service -from ...services.feishu_long_connection_core import get_feishu_long_connection_manager +from ...services.integrations.feishu_integration_service import get_feishu_service +from ...services.integrations.feishu_long_connection_core import get_feishu_long_connection_manager from .schemas import FeishuConfigRequest router = APIRouter() diff --git a/backend/app/api/feishu_api/long_connection_routes.py b/backend/app/api/feishu_api/long_connection_routes.py index 1221f730..1449b347 100644 --- a/backend/app/api/feishu_api/long_connection_routes.py +++ b/backend/app/api/feishu_api/long_connection_routes.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException from ...dependencies.auth import get_current_user from ...models import User from ...services.auth import has_any_role -from ...services.feishu_long_connection_core import get_feishu_long_connection_manager +from ...services.integrations.feishu_long_connection_core import get_feishu_long_connection_manager router = APIRouter() diff --git a/backend/app/api/feishu_api/message_routes.py b/backend/app/api/feishu_api/message_routes.py index d5690b83..4ee19873 100644 --- a/backend/app/api/feishu_api/message_routes.py +++ b/backend/app/api/feishu_api/message_routes.py @@ -9,7 +9,7 @@ from ...core.database import get_db from ...dependencies.auth import get_current_user from ...dependencies.tenant import get_tenant_id from ...models import User -from ...services.feishu_integration_service import get_feishu_service +from ...services.integrations.feishu_integration_service import get_feishu_service from .schemas import FeishuSendCardRequest, FeishuSendMessageRequest router = APIRouter() diff --git a/backend/app/api/feishu_api/test_routes.py b/backend/app/api/feishu_api/test_routes.py index 44d9e315..75e38fd8 100644 --- a/backend/app/api/feishu_api/test_routes.py +++ b/backend/app/api/feishu_api/test_routes.py @@ -9,8 +9,8 @@ from ...core.database import get_db from ...dependencies.auth import get_current_user from ...dependencies.tenant import get_tenant_id from ...models import User -from ...services.feishu_integration_service import get_feishu_service -from ...services.feishu_long_connection_core import get_feishu_long_connection_manager +from ...services.integrations.feishu_integration_service import get_feishu_service +from ...services.integrations.feishu_long_connection_core import get_feishu_long_connection_manager from .helpers import build_long_connection_failure router = APIRouter() diff --git a/backend/app/api/knowledge_graph.py b/backend/app/api/knowledge_graph.py index 19ec0317..10a6d164 100644 --- a/backend/app/api/knowledge_graph.py +++ b/backend/app/api/knowledge_graph.py @@ -15,7 +15,7 @@ from ..dependencies.auth import get_current_user from ..dependencies.tenant import get_tenant_id from ..models import User from ..services.auth import has_any_role -from ..services.model_context_bundle import ModelContextBundleService +from ..services.model.model_context_bundle import ModelContextBundleService from ..services.project_knowledge_graph_service import ProjectKnowledgeGraphService router = APIRouter(prefix="/ai-steward/knowledge-graph", tags=["AI Steward"]) diff --git a/backend/app/api/model_capability_api/shared.py b/backend/app/api/model_capability_api/shared.py index 669d6d49..4febbf53 100644 --- a/backend/app/api/model_capability_api/shared.py +++ b/backend/app/api/model_capability_api/shared.py @@ -21,9 +21,9 @@ from ...schemas.model_capability import ( ValidateMatchRequest, ) from ...services.ai_service_core import get_ai_service -from ...services.model_capability.cost_optimization_service import CostOptimizationService +from ...services.model.model_capability.cost_optimization_service import CostOptimizationService from ...services.experiment_sync import ExperimentSyncService -from ...services.model_capability import ModelCapabilityService -from ...services.model_capability.model_lifecycle_service import ModelLifecycleService +from ...services.model.model_capability import ModelCapabilityService +from ...services.model.model_capability.model_lifecycle_service import ModelLifecycleService router = APIRouter(prefix="/model-capability", tags=["model-capability"]) diff --git a/backend/app/api/model_routing.py b/backend/app/api/model_routing.py index b7012125..36cb2579 100644 --- a/backend/app/api/model_routing.py +++ b/backend/app/api/model_routing.py @@ -14,7 +14,7 @@ from ..core.database import get_db from ..dependencies import get_current_user from ..models import User from ..services.auth import has_any_role -from ..services.model_routing import ModelRoutingService +from ..services.model.model_routing import ModelRoutingService from ..services.operation_log_service import log_operation router = APIRouter(prefix="/model-routing", tags=["model_routing"]) diff --git a/backend/app/api/projects_api/__init__.py b/backend/app/api/projects_api/__init__.py index 5357738b..0cb9ddfc 100644 --- a/backend/app/api/projects_api/__init__.py +++ b/backend/app/api/projects_api/__init__.py @@ -4,7 +4,9 @@ from fastapi import APIRouter from . import routes_list, routes_mutation, routes_signals -router = APIRouter(prefix="/projects", tags=["projects"]) +# routes_* already use the shared router with prefix "/projects"; +# keep this package router unprefixed to avoid "/projects/projects/*". +router = APIRouter(tags=["projects"]) for _module in [routes_list, routes_mutation, routes_signals]: router.include_router(_module.router) diff --git a/backend/app/api/projects_api/helpers.py b/backend/app/api/projects_api/helpers.py index dc972960..93e32d5d 100644 --- a/backend/app/api/projects_api/helpers.py +++ b/backend/app/api/projects_api/helpers.py @@ -152,7 +152,7 @@ async def _cleanup_project_dependencies( .values(task_id=None) ) - # 非空外键:必须先删依赖记录。 + # 强关联依赖:删除项目时同步清理关联运行/执行记录。 await db.execute( delete(AIWorkerExecution).where( and_( @@ -263,4 +263,3 @@ async def _cleanup_project_dependencies( ) .values(current_project_id=None) ) - diff --git a/backend/app/api/projects_api/shared.py b/backend/app/api/projects_api/shared.py index e57fa2ce..af68f03a 100644 --- a/backend/app/api/projects_api/shared.py +++ b/backend/app/api/projects_api/shared.py @@ -29,7 +29,7 @@ from ...models import ( UserProject, WorkItem, ) -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...services.context_cache_service import context_cache_service from ...services.operation_log_service import log_operation from ...services.project_member import ( diff --git a/backend/app/api/prompt_profiles.py b/backend/app/api/prompt_profiles.py index 38be13d9..5b13b164 100644 --- a/backend/app/api/prompt_profiles.py +++ b/backend/app/api/prompt_profiles.py @@ -14,7 +14,7 @@ from ..dependencies.auth import get_current_user from ..dependencies.tenant import get_tenant_id from ..models import User from ..services.auth import has_any_role -from ..services.prompt_profile import prompt_profile_service +from ..services.model.prompt_profile import prompt_profile_service router = APIRouter(prefix="/ai-steward/prompt-profiles", tags=["AI Steward"]) diff --git a/backend/app/api/rag_api/shared.py b/backend/app/api/rag_api/shared.py index ad9a96cf..520037f8 100644 --- a/backend/app/api/rag_api/shared.py +++ b/backend/app/api/rag_api/shared.py @@ -28,6 +28,6 @@ from ...schemas.rag import ( SymbolSyncRequest, ) from ...services.adaptive_rag_service import AdaptiveRAGService -from ...services.symbol_index import SymbolIndexService +from ...services.signal.symbol_index import SymbolIndexService router = APIRouter(prefix="/rag", tags=["Adaptive RAG"]) diff --git a/backend/app/api/scheduler.py b/backend/app/api/scheduler.py index 30aadb8f..ec4c698e 100644 --- a/backend/app/api/scheduler.py +++ b/backend/app/api/scheduler.py @@ -10,9 +10,9 @@ from ..core.database import get_db from ..dependencies import require_manager_user from ..models import KnowledgeBaseDoc, Project, User from ..services.adaptive_rag_service import AdaptiveRAGService -from ..services.ai_pmo_cadence import AIPMOCadenceService +from ..services.ai_pmo.ai_pmo_cadence import AIPMOCadenceService from ..services.project_knowledge_graph_service import ProjectKnowledgeGraphService -from ..services.symbol_index import SymbolIndexService +from ..services.signal.symbol_index import SymbolIndexService from ..services import workload as workload_service router = APIRouter(prefix="/scheduler", tags=["scheduler"]) diff --git a/backend/app/api/signals_api/ingest_fetch.py b/backend/app/api/signals_api/ingest_fetch.py index e9c3419b..87800cd1 100644 --- a/backend/app/api/signals_api/ingest_fetch.py +++ b/backend/app/api/signals_api/ingest_fetch.py @@ -12,11 +12,11 @@ from ...core.database import get_db from ...dependencies.tenant import get_tenant_id from ...models import Signal from ...schemas import SignalCreate, SignalPriority, classify_signal_priority -from ...services.signal_capture_service.impact_analyzer import analyze_impact -from ...services.signal_capture_service.external_fetcher import fetch_hn_stories -from ...services.integration_core.lark_service import LarkService +from ...services.signal.signal_capture_service.impact_analyzer import analyze_impact +from ...services.signal.signal_capture_service.external_fetcher import fetch_hn_stories +from ...services.integrations.integration_core.lark_service import LarkService from ...services.llm_service import llm_service -from ...services.integration_core.plane_service import plane_service +from ...services.integrations.integration_core.plane_service import plane_service router = APIRouter() lark_service = LarkService() diff --git a/backend/app/api/skill_tags_api/routes.py b/backend/app/api/skill_tags_api/routes.py index 483d4e92..9d04ac87 100644 --- a/backend/app/api/skill_tags_api/routes.py +++ b/backend/app/api/skill_tags_api/routes.py @@ -10,7 +10,7 @@ from ...core.database import get_db from ...dependencies import get_current_user, get_tenant_id, require_roles from ...models import SkillTag, User, UserSkillBinding from ...services import skill as skill_service -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...services.auth import has_any_role from ...utils.timezone import now_cn_naive from .contracts import ( diff --git a/backend/app/api/sstc_api/charters_ai_alignment.py b/backend/app/api/sstc_api/charters_ai_alignment.py index 8e6dd76c..f2714ec1 100644 --- a/backend/app/api/sstc_api/charters_ai_alignment.py +++ b/backend/app/api/sstc_api/charters_ai_alignment.py @@ -190,6 +190,9 @@ async def confirm_alignment( row.comments = body.comments member_ids = await svc.get_project_member_ids(pid) + if not member_ids: + # 无项目成员时至少以当前操作者作为最小确认集合,避免状态永久卡在“对齐中”。 + member_ids = [int(current_user.id)] confirmed_map: set[int] = set() rows_result = await db.execute( select(AlignmentRecord).where( @@ -253,4 +256,3 @@ async def confirm_alignment( "member_count": len(member_ids), } ) - diff --git a/backend/app/api/sstc_api/charters_detail_update.py b/backend/app/api/sstc_api/charters_detail_update.py index de6cca50..e342aeef 100644 --- a/backend/app/api/sstc_api/charters_detail_update.py +++ b/backend/app/api/sstc_api/charters_detail_update.py @@ -58,6 +58,20 @@ async def get_charter_detail( ) ) alignments = list(align_result.scalars().all()) + alignment_user_name_map: dict[int, str] = {} + alignment_user_ids = sorted({int(item.user_id) for item in alignments if item.user_id is not None}) + if alignment_user_ids: + user_result = await db.execute( + select(User.id, User.name, User.username).where( + and_( + User.tenant_id == tenant_id, + User.id.in_(alignment_user_ids), + ) + ) + ) + for user_id, name, username in user_result.all(): + uid = int(user_id) + alignment_user_name_map[uid] = str(name or username or f"用户 {uid}") source_signal = _select_source_signal(charter, signal_rows) return success_response( @@ -69,7 +83,13 @@ async def get_charter_detail( "signals": [_serialize_charter_signal(signal) for signal in signal_rows], "source_signal": _serialize_source_signal(source_signal) if source_signal else None, "tasks": [_serialize_charter_task(task) for task in tasks], - "alignment_records": [_serialize_alignment_record(record) for record in alignments], + "alignment_records": [ + _serialize_alignment_record( + record, + alignment_user_name_map.get(int(record.user_id)), + ) + for record in alignments + ], "status": charter.status, "version": charter.version, } @@ -173,4 +193,3 @@ async def update_charter( update_payload = _build_charter_summary_payload(charter) update_payload["new_version_created"] = False return success_response(update_payload) - diff --git a/backend/app/api/sstc_api/releases_list_create.py b/backend/app/api/sstc_api/releases_list_create.py index 4760afbb..e8003d43 100644 --- a/backend/app/api/sstc_api/releases_list_create.py +++ b/backend/app/api/sstc_api/releases_list_create.py @@ -54,20 +54,49 @@ async def create_release_plan( charter_id=int(body.charter_id), ) - if not body.task_ids: + normalized_task_ids = sorted({int(task_id) for task_id in body.task_ids if int(task_id) > 0}) + if not normalized_task_ids: raise HTTPException(status_code=400, detail="发布计划必须绑定至少一个任务") + release_result = await db.execute( + select(TruthNode.id, TruthNode.content).where( + and_( + TruthNode.project_id == pid, + TruthNode.tenant_id == tenant_id, + TruthNode.node_type == "release", + ) + ) + ) + linked_task_ids: set[int] = set() + for _release_id, raw_content in release_result.all(): + content = _as_dict(raw_content) + for task_id in content.get("task_ids", []) or []: + if isinstance(task_id, int): + linked_task_ids.add(int(task_id)) + continue + if isinstance(task_id, str) and task_id.strip().isdigit(): + linked_task_ids.add(int(task_id.strip())) + duplicated_task_ids = sorted(set(normalized_task_ids) & linked_task_ids) + if duplicated_task_ids: + raise HTTPException( + status_code=409, + detail={ + "message": "部分任务已关联到其他发布计划", + "task_ids": duplicated_task_ids, + }, + ) + task_result = await db.execute( select(Task).where( and_( - Task.id.in_(body.task_ids), + Task.id.in_(normalized_task_ids), Task.project_id == pid, Task.tenant_id == tenant_id, ) ) ) tasks = list(task_result.scalars().all()) - if len(tasks) != len(body.task_ids): + if len(tasks) != len(normalized_task_ids): raise HTTPException(status_code=400, detail="部分任务不存在") not_done = [t for t in tasks if (t.status or "").lower() not in DONE_STATUSES] if not_done: @@ -108,7 +137,7 @@ async def create_release_plan( entity_type="release", entity_id="new", action="create", - after_data={"title": release_node.title, "task_ids": body.task_ids}, + after_data={"title": release_node.title, "task_ids": normalized_task_ids}, ) await db.commit() await db.refresh(release_node) @@ -120,4 +149,3 @@ async def create_release_plan( "content": _as_dict(release_node.content), } ) - diff --git a/backend/app/api/sstc_api/shared.py b/backend/app/api/sstc_api/shared.py index 8379d548..dcd762ef 100644 --- a/backend/app/api/sstc_api/shared.py +++ b/backend/app/api/sstc_api/shared.py @@ -43,11 +43,11 @@ from ...schemas.sstc import ( ) from ...services.skill import skill_score_service from ...services import workload as workload_service -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...services.ai_service_core import AIService from ...services.auth import has_any_role from ...services.budget_enforcement import BudgetEnforcementService -from ...services.feishu_integration_service import get_feishu_service +from ...services.integrations.feishu_integration_service import get_feishu_service from ...services.llm_service import llm_service from ...services.operation_log_service import log_operation from ...services.skill import recommend_personnel diff --git a/backend/app/api/sstc_api/tasks_ai_breakdown.py b/backend/app/api/sstc_api/tasks_ai_breakdown.py index 46101c4a..592485a1 100644 --- a/backend/app/api/sstc_api/tasks_ai_breakdown.py +++ b/backend/app/api/sstc_api/tasks_ai_breakdown.py @@ -1,5 +1,7 @@ from __future__ import annotations +import re + from .shared import * from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project @@ -8,6 +10,13 @@ from ...services.sstc.signal_core import _as_dict, _parse_node_id, _parse_projec router = APIRouter(tags=["sstc"]) +_TITLE_NORMALIZE_RE = re.compile(r"[\s\-_]+") + + +def _task_title_key(value: str) -> str: + text = str(value or "").strip().lower() + return _TITLE_NORMALIZE_RE.sub("", text) + @router.post("/projects/{project_id}/charters/{charter_id}/tasks/ai-breakdown") async def ai_breakdown_tasks( project_id: str, @@ -45,13 +54,38 @@ async def ai_breakdown_tasks( except Exception as exc: raise HTTPException(status_code=500, detail=f"AI 拆解失败: {exc}") from exc + existing_task_title_result = await db.execute( + select(Task.title).where( + and_( + Task.truth_node_id == cid, + Task.project_id == pid, + Task.tenant_id == tenant_id, + ) + ) + ) + existing_title_keys = { + _task_title_key(str(title)) + for title in existing_task_title_result.scalars().all() + if str(title or "").strip() + } + seen_title_keys = set(existing_title_keys) + created = [] + skipped_duplicates = 0 prompt_tokens = max(len(prompt) // 4, 1) completion_tokens = 0 for item in rows or []: title = compact_title((item.get("task_name") or item.get("title") or ""), max_len=32) if not title: continue + title_key = _task_title_key(title) + if not title_key: + continue + if title_key in seen_title_keys: + skipped_duplicates += 1 + continue + seen_title_keys.add(title_key) + priority = str(item.get("priority") or "P1").lower() mapped_priority = "high" if priority == "p0" else "medium" if priority == "p1" else "low" task = await persist_task( @@ -82,7 +116,7 @@ async def ai_breakdown_tasks( user_id=int(current_user.id), function_name="sstc_ai_breakdown", prompt=prompt, - response=f"created_count={len(created)}", + response=f"created_count={len(created)},skipped_duplicates={skipped_duplicates}", tokens_used=prompt_tokens + completion_tokens, cost=((prompt_tokens * 0.00015 + completion_tokens * 0.0006) / 1000), status="success", @@ -96,7 +130,7 @@ async def ai_breakdown_tasks( entity_type="task", entity_id=str(cid), action="ai_breakdown", - after_data={"created_count": len(created)}, + after_data={"created_count": len(created), "skipped_duplicates": skipped_duplicates}, ) await db.commit() for task in created: @@ -112,10 +146,10 @@ async def ai_breakdown_tasks( return success_response( { "created_count": len(created), + "skipped_duplicates": skipped_duplicates, "items": [ {"id": str(t.id), "title": t.title, "priority": t.priority, "status": t.status} for t in created ], } ) - diff --git a/backend/app/api/tasks.py b/backend/app/api/tasks.py index 6fb6d687..5ea31d76 100644 --- a/backend/app/api/tasks.py +++ b/backend/app/api/tasks.py @@ -9,7 +9,7 @@ from ..dependencies import get_current_user, require_manager_user from ..models import Task, User, UserProject from ..schemas import TaskBreakdownRequest, TaskBreakdownResponse from ..services.llm_service import llm_service -from ..services.integration_core.plane_service import plane_service +from ..services.integrations.integration_core.plane_service import plane_service from ..services.skill import recommend_personnel router = APIRouter(prefix="/tasks", tags=["tasks"]) diff --git a/backend/app/api/users_api/shared.py b/backend/app/api/users_api/shared.py index d5d260e1..99a7ecdd 100644 --- a/backend/app/api/users_api/shared.py +++ b/backend/app/api/users_api/shared.py @@ -37,7 +37,7 @@ from ...schemas import ( UserUpdate, ) from ...services import skill as skill_service -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...services.auth import AuthService from ...services.operation_log_service import log_operation from ...utils.timezone import now_cn_naive diff --git a/backend/app/api/webhooks.py b/backend/app/api/webhooks.py index 15496e86..12ee89e6 100644 --- a/backend/app/api/webhooks.py +++ b/backend/app/api/webhooks.py @@ -11,7 +11,7 @@ from sqlalchemy.future import select from ..core.database import get_db from ..models import Signal from ..services.llm_service import llm_service -from ..services.integration_core.plane_service import plane_service +from ..services.integrations.integration_core.plane_service import plane_service from ..utils.timezone import now_cn_aware router = APIRouter(prefix="/webhooks", tags=["webhooks"]) diff --git a/backend/app/api/wecom_api/config.py b/backend/app/api/wecom_api/config.py index 033f4447..20dc8b66 100644 --- a/backend/app/api/wecom_api/config.py +++ b/backend/app/api/wecom_api/config.py @@ -11,7 +11,7 @@ from ...dependencies.auth import get_current_user from ...dependencies.tenant import get_tenant_id from ...models import Integration, User from ...services.auth import has_any_role -from ...services.wecom import get_wecom_service +from ...services.integrations.wecom import get_wecom_service from .schemas import WeComConfigRequest router = APIRouter() diff --git a/backend/app/api/wecom_api/messaging.py b/backend/app/api/wecom_api/messaging.py index 5f40aea1..038d3498 100644 --- a/backend/app/api/wecom_api/messaging.py +++ b/backend/app/api/wecom_api/messaging.py @@ -9,7 +9,7 @@ from ...core.database import get_db from ...dependencies.auth import get_current_user from ...dependencies.tenant import get_tenant_id from ...models import User -from ...services.wecom import get_wecom_service +from ...services.integrations.wecom import get_wecom_service from .schemas import WeComSendMessageRequest router = APIRouter() diff --git a/backend/app/api/wecom_api/webhook.py b/backend/app/api/wecom_api/webhook.py index 4900645c..7f08415d 100644 --- a/backend/app/api/wecom_api/webhook.py +++ b/backend/app/api/wecom_api/webhook.py @@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy.ext.asyncio import AsyncSession from ...core.database import get_db -from ...services.wecom import get_wecom_service +from ...services.integrations.wecom import get_wecom_service router = APIRouter() diff --git a/backend/app/config/ai_prompts.py b/backend/app/config/ai_prompts.py index 1c4dce84..4c9f0005 100644 --- a/backend/app/config/ai_prompts.py +++ b/backend/app/config/ai_prompts.py @@ -116,5 +116,11 @@ __all__ = [ "PROMPT_WORKLOAD_OPTIMIZATION", "PROMPT_ANALYZE_FOOD_CALORIES", "PROMPT_SIGNAL_PLANNER", + "PROMPT_FEISHU_DIRECT_QA", + "PROMPT_FEISHU_MIXED_QA", + "PROMPT_PROJECT_STATUS_QA", + "PROMPT_MEMBER_WORKLOAD_QA", + "PROMPT_AI_PMO_QA", + "PROMPT_EXTERNAL_TREND_QA", "build_default_role_prompt_bundle", ] diff --git a/backend/app/config/ai_prompts_catalog/__init__.py b/backend/app/config/ai_prompts_catalog/__init__.py index 9f657114..3419ac31 100644 --- a/backend/app/config/ai_prompts_catalog/__init__.py +++ b/backend/app/config/ai_prompts_catalog/__init__.py @@ -4,6 +4,12 @@ from .part_01 import PROMPT_IMPACT_ANALYSIS, PROMPT_TASK_BREAKDOWN, PROMPT_STRAT from .part_02 import PROMPT_FEISHU_SIGNAL_EXTRACTION, PROMPT_FEISHU_PROJECT_PREFILL, PROMPT_FEISHU_TASK_ORCHESTRATION, PROMPT_SIGNAL_SOURCE_ONBOARDING, PROMPT_AI_WORKER_SIGNAL_SOURCE_EXECUTION, PROMPT_AI_PMO_INTENT_CLASSIFICATION, PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS, PROMPT_RAG_INTENT_CLASSIFICATION from .part_03 import PROMPT_RAG_ANSWER, PROMPT_RAG_DB_PROJECT_QA, PROMPT_GENERATE_CHARTER, PROMPT_DECOMPOSE_TASKS, PROMPT_RECOMMEND_ASSIGNEES, PROMPT_ALIGNMENT_SCORE, PROMPT_COGNITIVE_CONFLICTS, PROMPT_EVALUATE_MODEL_REQUIREMENT, PROMPT_ANALYZE_CAPABILITY_GAP, PROMPT_ANALYZE_COST_ROI from .part_04 import PROMPT_ANALYZE_COST_OPTIMIZATION, PROMPT_ASSESS_MODEL_UPDATE_IMPACT, PROMPT_EXPERIMENT_SYNC_DECISION, PROMPT_SKILL_SCORE_UPDATE, PROMPT_WORKLOAD_OPTIMIZATION, PROMPT_ANALYZE_FOOD_CALORIES, PROMPT_SIGNAL_PLANNER +from .part_scenes_01 import PROMPT_FEISHU_DIRECT_QA +from .part_scenes_02 import PROMPT_FEISHU_MIXED_QA +from .part_scenes_03 import PROMPT_PROJECT_STATUS_QA +from .part_scenes_04 import PROMPT_MEMBER_WORKLOAD_QA +from .part_scenes_05 import PROMPT_AI_PMO_QA +from .part_scenes_06 import PROMPT_EXTERNAL_TREND_QA __all__ = [ "PROMPT_IMPACT_ANALYSIS", @@ -42,4 +48,10 @@ __all__ = [ "PROMPT_WORKLOAD_OPTIMIZATION", "PROMPT_ANALYZE_FOOD_CALORIES", "PROMPT_SIGNAL_PLANNER", + "PROMPT_FEISHU_DIRECT_QA", + "PROMPT_FEISHU_MIXED_QA", + "PROMPT_PROJECT_STATUS_QA", + "PROMPT_MEMBER_WORKLOAD_QA", + "PROMPT_AI_PMO_QA", + "PROMPT_EXTERNAL_TREND_QA", ] diff --git a/backend/app/config/ai_prompts_catalog/part_01.py b/backend/app/config/ai_prompts_catalog/part_01.py index fbb21ee6..5dd15b7a 100644 --- a/backend/app/config/ai_prompts_catalog/part_01.py +++ b/backend/app/config/ai_prompts_catalog/part_01.py @@ -188,6 +188,14 @@ PROMPT_FEISHU_HUMANIZED_REPLY = """ 8. 禁止输出数据库内部实施细节(如数据库迁移、DDL、SQL、ALTER TABLE、DROP TABLE、Alembic)。 9. 当 status/classification 无法证明写入动作已成功时,禁止编造处理步骤。 +【输出规范】 +- 严禁输出任何思考过程或推理步骤 +- 严禁以以下句式开头: + "让我先/让我尝试/我来看看/ + 我先查/让我看看/我需要先" +- 直接输出最终回复内容 +- 不解释你的推理过程 + 只输出最终回复正文。 """.strip() diff --git a/backend/app/config/ai_prompts_catalog/part_02.py b/backend/app/config/ai_prompts_catalog/part_02.py index c5e8c3ec..0f747348 100644 --- a/backend/app/config/ai_prompts_catalog/part_02.py +++ b/backend/app/config/ai_prompts_catalog/part_02.py @@ -156,17 +156,15 @@ PROMPT_AI_WORKER_SIGNAL_SOURCE_EXECUTION = """ PROMPT_AI_PMO_INTENT_CLASSIFICATION = """ 首字符必须是 {{,末字符必须是 }}。 -你是 PMFlow 的 AI PMO 意图识别模块。你只做分类,不做执行。 +你是 PMFlow 的 AI PMO 意图识别模块。只做分类,不做执行。 输入: - message_text: {message_text} - session_last_intent: {session_last_intent} - session_project_id: {session_project_id} - world_model_project_id: {world_model_project_id} -- candidate_intents_json: {candidate_intents_json} -- candidate_routes_json: {candidate_routes_json} -输出 JSON: +只输出一个 JSON 对象(不要解释、不要 Markdown): {{ "family": "query|project_create|project_signal|signal_source", "action": "answer|create|discover|onboard", @@ -178,22 +176,16 @@ PROMPT_AI_PMO_INTENT_CLASSIFICATION = """ "reasoning": "一句话简述判定原因" }} -规则: -1. family/action 必须和 candidate_routes_json 可映射,不能自造。 -2. 兼容字段 intent 必须从 candidate_intents_json 内选择,不能自造。 -3. 若 family=project_signal 且 action=discover,requires_confirmation 必须为 true。 -4. requested_count 仅在 project_signal 路由有效,范围 1-5;无法判断时返回 1。 -5. 若可直接回答,优先 family=query action=answer。 -6. “直接新增信号”返回 family=project_signal action=create;“先分析候选让我确认”返回 family=project_signal action=discover。 -7. “新增/接入信号源”返回 family=signal_source action=onboard。 -8. “新增项目”返回 family=project_create action=create。 -2. 若消息是短确认语气,优先结合 session_last_intent 判定。 -9. project_id 无法确定时返回 null,不要猜测无依据项目。 -10. 噪音/寒暄/无关消息返回 family=query action=answer,intent=query.read_only,reasoning 包含 smalltalk_ignore。 -5. 禁止输出 thinking、analysis、解释句、Markdown、代码块。 -11. 若消息表达“新增/增加/添加/新建/创建/建立 + 项目”(如“给我新增个项目”),优先判定 project_create.create;仅当明确是“信号源/来源/webhook/rss/github 接入或创建信号”时才判 signal_source.onboard 或 project_signal.create。 -12. 只输出 JSON,不要额外文本。 -13. 若消息要求直接执行数据库 DDL(建表/改表/删表等),禁止判定为可自动写入意图,必须返回 family=query action=answer,intent=query.read_only,并在 reasoning 包含 ddl_api_required。 +快速规则(按优先级): +1. 含“分析/梳理/方案/设计思路/帮我分析/先分析” => family=query, action=answer, intent=query.read_only。 +2. 含“创建/新建/新增 + 项目”,且不含第1条分析诉求 => family=project_create, action=create, intent=project.create。 +3. 含“新增信号/录入信号/创建信号” => family=project_signal, action=create, intent=signal.create。 +4. 含“先分析候选/拆分候选并确认” => family=project_signal, action=discover, intent=signal.create, requires_confirmation=true。 +5. 含“信号源/来源/webhook/rss/github 接入” => family=signal_source, action=onboard, intent=signal_source.onboard。 +6. 若消息为短确认语气,可结合 session_last_intent;无把握时返回 query.answer。 +7. project_id 不确定返回 null;requested_count 默认 1(仅 project_signal 有效,范围 1-5)。 +8. family/action/intent 必须严格使用上面 JSON 示例给定的枚举值,不能自造。 +9. 若请求 DDL(建表/改表/删表),必须返回 query.answer + query.read_only,reasoning 含 ddl_api_required。 """ PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS = """ diff --git a/backend/app/config/ai_prompts_catalog/part_03.py b/backend/app/config/ai_prompts_catalog/part_03.py index 75ddb687..6ae50fb4 100644 --- a/backend/app/config/ai_prompts_catalog/part_03.py +++ b/backend/app/config/ai_prompts_catalog/part_03.py @@ -14,6 +14,8 @@ PROMPT_RAG_ANSWER = """ 9. 即使检索上下文为空,也要基于 Snapshot 给出当前系统可验证信息,禁止输出默认兜底句。 10. 若字段缺失,显式列出缺失字段名,不要假设。 11. 当问题涉及“成员/人员/负责人/谁在做”时,优先使用 System Snapshot.team_members、User Snapshot 与结构化数据库结果,禁止默认返回项目列表。 +12. 直接输出最终答案,禁止输出思考过程。禁止以"让我/我来/我先/我需要"等开头。 + 若信息不足直接说:"当前信息不足以回答此问题,缺少:[具体缺失字段]" 当前项目ID:{project_id} 问题:{question} @@ -85,7 +87,7 @@ PROMPT_DECOMPOSE_TASKS = """ 宪章内容: {charter_content} -请识别每个任务所需的 AI 开发技能标签(从系统技能中匹配:Python, TypeScript, FastAPI, React, SQLAlchemy, LangChain, LLM调优, RAG构建, 向量数据库, Ollama, Docker, Git 等)。 +请识别每个任务所需的 AI 开发技能标签(仅从以下系统技能中匹配:{available_skills})。 输出 JSON 格式: {{ diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_01.py b/backend/app/config/ai_prompts_catalog/part_scenes_01.py new file mode 100644 index 00000000..e4b3022a --- /dev/null +++ b/backend/app/config/ai_prompts_catalog/part_scenes_01.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +PROMPT_FEISHU_DIRECT_QA = """ +你是 PMFlow 的项目管理助手。 +用户通过飞书直接提问。 + +规则: +1. 只基于给定上下文回答 +2. 证据不足直接说"信息不足,缺少:XXX" +3. 不输出思考过程,直接给答案 +4. 回答简洁,适合飞书消息阅读(不超过200字) +5. 涉及项目数据优先用数字说话 + +当前项目ID:{project_id} +问题:{question} +System Snapshot:{system_snapshot} +User Snapshot:{user_snapshot} +最近会话:{conversation} +检索上下文:{context_blocks} +""".strip() + +__all__ = ["PROMPT_FEISHU_DIRECT_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_02.py b/backend/app/config/ai_prompts_catalog/part_scenes_02.py new file mode 100644 index 00000000..75a83cbd --- /dev/null +++ b/backend/app/config/ai_prompts_catalog/part_scenes_02.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +PROMPT_FEISHU_MIXED_QA = """ +你是 PMFlow 的项目管理助手。 +用户消息同时包含问题和需求。 + +处理规则: +1. 先回答问题部分(简洁,不超过100字) +2. 确认已收到需求部分 +3. 格式: + [回答] xxx + [已收到需求] xxx,已入库处理 + +禁止: +- 输出思考过程 +- 把需求部分当问题回答 +- 合并两部分混着说 + +当前项目ID:{project_id} +消息:{message} +问题部分:{question_part} +需求部分:{signal_part} +System Snapshot:{system_snapshot} +检索上下文:{context_blocks} +""".strip() + +__all__ = ["PROMPT_FEISHU_MIXED_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_03.py b/backend/app/config/ai_prompts_catalog/part_scenes_03.py new file mode 100644 index 00000000..78977a30 --- /dev/null +++ b/backend/app/config/ai_prompts_catalog/part_scenes_03.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +PROMPT_PROJECT_STATUS_QA = """ +你是 PMFlow 的项目状态语义检索助手。 +用户关注项目进度、状态、完成率。 + +规则: +1. 只基于给定上下文回答 +2. 禁止输出思考过程 +3. 用数字量化回答: + 完成率X%,已完成X个任务,进行中X个,待办X个 +4. 涉及多个项目时用列表格式 +5. 证据不足直接说: + 信息不足,缺少:XXX + +当前项目ID:{project_id} +问题:{question} +System Snapshot:{system_snapshot} +User Snapshot:{user_snapshot} +检索上下文:{context_blocks} +""".strip() + +__all__ = ["PROMPT_PROJECT_STATUS_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_04.py b/backend/app/config/ai_prompts_catalog/part_scenes_04.py new file mode 100644 index 00000000..43f22ad5 --- /dev/null +++ b/backend/app/config/ai_prompts_catalog/part_scenes_04.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +PROMPT_MEMBER_WORKLOAD_QA = """ +你是 PMFlow 的团队协作助手。 +用户询问团队成员或负载相关信息。 + +规则: +1. 优先使用 System Snapshot.team_members 和 User Snapshot 数据 +2. 禁止默认返回项目列表 +3. 涉及负载用百分比表示 +4. 推荐负责人时说明推荐理由 +5. 禁止输出思考过程 +6. 不超过150字 + +问题:{question} +System Snapshot:{system_snapshot} +User Snapshot:{user_snapshot} +检索上下文:{context_blocks} +""".strip() + +__all__ = ["PROMPT_MEMBER_WORKLOAD_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_05.py b/backend/app/config/ai_prompts_catalog/part_scenes_05.py new file mode 100644 index 00000000..f8bbfe41 --- /dev/null +++ b/backend/app/config/ai_prompts_catalog/part_scenes_05.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +PROMPT_AI_PMO_QA = """ +你是 PMFlow 的 AI 项目管理官。 +用户通过指令入口提问。 + +规则: +1. 你既是执行者也是回答者 +2. 问题涉及操作时先回答再提示可用指令 +3. 只基于给定上下文回答 +4. 禁止输出思考过程 +5. 回答后附可操作建议: + "你可以继续:[操作A]/[操作B]" + +问题:{question} +当前项目:{project_id} +World Model:{world_model} +检索上下文:{context_blocks} +""".strip() + +__all__ = ["PROMPT_AI_PMO_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_06.py b/backend/app/config/ai_prompts_catalog/part_scenes_06.py new file mode 100644 index 00000000..3f4d565c --- /dev/null +++ b/backend/app/config/ai_prompts_catalog/part_scenes_06.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +PROMPT_EXTERNAL_TREND_QA = """ +你是 PMFlow 的市场情报助手。 +用户询问外部行业或市场信息。 + +规则: +1. 若有外部搜索结果优先使用 +2. 若无外部数据明确说: + "当前未接入实时数据,以下基于训练数据(截止XXXX年)" +3. 结合项目实际给出应用建议: + "对你们[项目名]的影响是..." +4. 禁止输出思考过程 +5. 分段输出: + [行业动态] xxx + [对项目的影响] xxx + [建议] xxx + +问题:{question} +项目上下文:{project_context} +外部搜索结果:{external_results} +""".strip() + +__all__ = ["PROMPT_EXTERNAL_TREND_QA"] diff --git a/backend/app/core/startup/ai_pmo_ingress_async.py b/backend/app/core/startup/ai_pmo_ingress_async.py new file mode 100644 index 00000000..36976475 --- /dev/null +++ b/backend/app/core/startup/ai_pmo_ingress_async.py @@ -0,0 +1,30 @@ +"""AI PMO ingress async queue polling loop.""" + +from __future__ import annotations + +import asyncio +import traceback + +from ..logging import get_logger +from ...services.ai_orchestration_run.ingress_worker import dispatch_pending_ingress_runs + + +logger = get_logger(__name__) + + +async def ai_pmo_ingress_drain_loop() -> None: + while True: + try: + dispatched = await dispatch_pending_ingress_runs(limit=20) + await asyncio.sleep(0.3 if dispatched > 0 else 1.0) + except asyncio.CancelledError: + break + except Exception as exc: + logger.error( + "ai_pmo_ingress_drain_failed", + run_id=getattr(exc, "run_id", None), + error_type=type(exc).__name__, + error=str(exc), + stack_trace=traceback.format_exc(), + ) + await asyncio.sleep(1.0) diff --git a/backend/app/core/startup/feishu_outbox.py b/backend/app/core/startup/feishu_outbox.py index 9d0f4de5..7df150f7 100644 --- a/backend/app/core/startup/feishu_outbox.py +++ b/backend/app/core/startup/feishu_outbox.py @@ -9,7 +9,7 @@ import sqlalchemy as sa from ..logging import get_logger from ..database import engine -from ...services.feishu_integration_service import dispatch_pending_feishu_replies +from ...services.integrations.feishu_integration_service import dispatch_pending_feishu_replies from .db_guard import normalize_dialect logger = get_logger(__name__) diff --git a/backend/app/core/startup/world_model_warmup.py b/backend/app/core/startup/world_model_warmup.py index 2857e078..0a6f17e4 100644 --- a/backend/app/core/startup/world_model_warmup.py +++ b/backend/app/core/startup/world_model_warmup.py @@ -12,7 +12,7 @@ from ..logging import get_logger from ..database import AsyncSessionLocal from .db_guard import is_missing_table_error from ...models import Project, WorkItem -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...utils.timezone import now_cn_aware logger = get_logger(__name__) diff --git a/backend/app/main.py b/backend/app/main.py index 42bab875..5216931e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -15,13 +15,14 @@ from .core.logging import configure_logging, get_logger from .core.response import success_response from .core.database import AsyncSessionLocal, engine from .core.startup.db_guard import check_database_dialect_guard +from .core.startup.ai_pmo_ingress_async import ai_pmo_ingress_drain_loop from .core.startup.feishu_outbox import feishu_outbox_drain_loop from .core.startup.model_probe import probe_model_connectivity_on_startup, startup_log from .core.startup.world_model_warmup import warmup_world_models from .middleware import AuditContextMiddleware from .services.ai_orchestration_run import AIOrchestrationRunService from .services.context_cache_service import register_context_cache_invalidation_hooks -from .services.feishu_long_connection_core import init_feishu_long_connection_manager +from .services.integrations.feishu_long_connection_core import init_feishu_long_connection_manager from .services.scheduler_center_core import ( AIPMOCadenceScheduler, KnowledgeGraphIncrementalScheduler, @@ -85,6 +86,7 @@ async def lifespan(_: FastAPI): await feishu_lc.start() outbox_task = asyncio.create_task(feishu_outbox_drain_loop(), name="feishu-outbox-drain-loop") + ingress_task = asyncio.create_task(ai_pmo_ingress_drain_loop(), name="ai-pmo-ingress-drain-loop") wm_warmup_task = asyncio.create_task(warmup_world_models(), name="wm-world-model-warmup") startup_log("后端运行中", url=f"http://127.0.0.1:{backend_port}") @@ -97,6 +99,12 @@ async def lifespan(_: FastAPI): except asyncio.CancelledError: pass + ingress_task.cancel() + try: + await ingress_task + except asyncio.CancelledError: + pass + if not wm_warmup_task.done(): wm_warmup_task.cancel() with suppress(asyncio.CancelledError): diff --git a/backend/app/models/ai_orchestration_run.py b/backend/app/models/ai_orchestration_run.py index 5afbb23c..15f091fa 100644 --- a/backend/app/models/ai_orchestration_run.py +++ b/backend/app/models/ai_orchestration_run.py @@ -20,7 +20,7 @@ class AIOrchestrationRun(Base): id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) trace_id: Mapped[str] = mapped_column(VARCHAR(64), nullable=False, index=True) run_type: Mapped[str] = mapped_column(VARCHAR(40), nullable=False, index=True) - project_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("projects.id"), nullable=False, index=True) + project_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("projects.id"), nullable=True, index=True) signal_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("signals.id"), nullable=True, index=True) response_mode: Mapped[str] = mapped_column(VARCHAR(16), nullable=False, default="fast", index=True) status: Mapped[str] = mapped_column(VARCHAR(20), nullable=False, default="queued", index=True) @@ -32,6 +32,28 @@ class AIOrchestrationRun(Base): summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True) planner_diagnostics: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) result_payload: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) + source_system: Mapped[Optional[str]] = mapped_column(VARCHAR(32), nullable=True, index=True) + source_message_id: Mapped[Optional[str]] = mapped_column(VARCHAR(128), nullable=True, index=True) + sender: Mapped[Optional[str]] = mapped_column(VARCHAR(128), nullable=True) + source_sender_open_id: Mapped[Optional[str]] = mapped_column(VARCHAR(128), nullable=True, index=True) + channel_ref: Mapped[Optional[str]] = mapped_column(VARCHAR(128), nullable=True, index=True) + chat_type: Mapped[Optional[str]] = mapped_column(VARCHAR(32), nullable=True) + captured_at: Mapped[Optional[TIMESTAMP]] = mapped_column(TIMESTAMP, nullable=True, index=True) + message_text: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + project_hint_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("projects.id"), nullable=True, index=True) + retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + max_retries: Mapped[int] = mapped_column(Integer, nullable=False, default=3) + next_retry_at: Mapped[Optional[TIMESTAMP]] = mapped_column(TIMESTAMP, nullable=True, index=True) + last_error: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + last_error_at: Mapped[Optional[TIMESTAMP]] = mapped_column(TIMESTAMP, nullable=True) + ack_sent_at: Mapped[Optional[TIMESTAMP]] = mapped_column(TIMESTAMP, nullable=True) + result_notified_at: Mapped[Optional[TIMESTAMP]] = mapped_column(TIMESTAMP, nullable=True) + work_item_id: Mapped[Optional[int]] = mapped_column( + Integer, + ForeignKey("work_items.id", ondelete="SET NULL"), + nullable=True, + index=True, + ) created_by_user_id: Mapped[Optional[int]] = mapped_column( BigInteger, ForeignKey("users.id"), diff --git a/backend/app/services/adaptive_rag_service/mixin_answer_entry.py b/backend/app/services/adaptive_rag_service/mixin_answer_entry.py index a8481598..9b4162f5 100644 --- a/backend/app/services/adaptive_rag_service/mixin_answer_entry.py +++ b/backend/app/services/adaptive_rag_service/mixin_answer_entry.py @@ -5,6 +5,28 @@ from .shared import * class AdaptiveRAGAnswerEntryMixin: + @staticmethod + def _should_enforce_scene_prompt_only( + *, + asked_via: str, + message_intent: Optional[str], + ) -> bool: + intent = str(message_intent or "").strip().lower() + if intent == "mixed": + return True + channel = str(asked_via or "").strip().lower() + return channel in { + "feishu", + "lark", + "feishu_group", + "feishu_dm", + "pmflow_manual", + "/ai-pmo/process-text", + "ai-pmo-process-text", + "ai_pmo", + "ai_pmo_ui", + } + async def answer_question( self, *, @@ -19,6 +41,8 @@ class AdaptiveRAGAnswerEntryMixin: thinking_mode: str = THINKING_MODE_NORMAL, action_mode: str = ACTION_MODE_AUTO, sync_requirement: bool = True, + message_intent: Optional[str] = None, + mixed_signal_part: Optional[str] = None, ) -> dict[str, Any]: started = time.perf_counter() clean_q = str(question or "").strip() @@ -30,6 +54,10 @@ class AdaptiveRAGAnswerEntryMixin: active_thinking_mode = self._normalize_thinking_mode(thinking_mode) deep_thinking = active_thinking_mode == THINKING_MODE_DEEP asked_via_lower = str(asked_via or "").strip().lower() + enforce_scene_prompt_only = self._should_enforce_scene_prompt_only( + asked_via=asked_via, + message_intent=message_intent, + ) deep_retrieval_steps = DEEP_RETRIEVAL_STEPS deep_vector_top_k = DEEP_VECTOR_TOP_K if asked_via_lower == "feishu": @@ -166,7 +194,7 @@ class AdaptiveRAGAnswerEntryMixin: user_id=asked_by, ) - if not deep_thinking: + if not deep_thinking and not enforce_scene_prompt_only: deterministic_result = await self._run_deterministic_query( question=effective_q, project_id=project_id, @@ -191,7 +219,7 @@ class AdaptiveRAGAnswerEntryMixin: ) direct_answer = None - if not deep_thinking: + if not deep_thinking and not enforce_scene_prompt_only: direct_answer = self._build_snapshot_direct_answer( effective_q, system_snapshot=system_snapshot, @@ -217,7 +245,7 @@ class AdaptiveRAGAnswerEntryMixin: return direct_result is_snapshot_direct_question = self._is_snapshot_direct_question(effective_q) - if not deep_thinking and not is_snapshot_direct_question: + if not deep_thinking and not enforce_scene_prompt_only and not is_snapshot_direct_question: glm_semantic_result = await self._try_glm_project_semantic_answer(clean_q=effective_q, project_id=project_id) if glm_semantic_result is not None: return await self._persist_fast_answer( @@ -260,5 +288,6 @@ class AdaptiveRAGAnswerEntryMixin: system_snapshot=system_snapshot, user_snapshot=user_snapshot, started=started, + message_intent=message_intent, + mixed_signal_part=mixed_signal_part, ) - diff --git a/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py b/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py index 037b6ae4..c6c361f4 100644 --- a/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py +++ b/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py @@ -30,6 +30,8 @@ class AdaptiveRAGAnswerRetrievalMixin: system_snapshot: dict[str, Any], user_snapshot: Optional[dict[str, Any]], started: float, + message_intent: Optional[str] = None, + mixed_signal_part: Optional[str] = None, ) -> dict[str, Any]: domains = self._route_domains(effective_q) if str(asked_via or "").strip().lower() == "feishu" and source_chat_id and "feishu" not in domains: @@ -76,6 +78,9 @@ class AdaptiveRAGAnswerRetrievalMixin: thinking_mode=active_thinking_mode, context_block_limit=DEEP_CONTEXT_BLOCKS if deep_thinking else None, answer_max_tokens=DEEP_ANSWER_MAX_TOKENS if deep_thinking else None, + asked_via=asked_via, + message_intent=message_intent, + mixed_signal_part=mixed_signal_part, ) used_sources = self._dedupe_sources(retrieved) if not used_sources: @@ -134,6 +139,9 @@ class AdaptiveRAGAnswerRetrievalMixin: thinking_mode=THINKING_MODE_DEEP, context_block_limit=DEEP_CONTEXT_BLOCKS, answer_max_tokens=DEEP_ANSWER_MAX_TOKENS, + asked_via=asked_via, + message_intent=message_intent, + mixed_signal_part=mixed_signal_part, ) deep_sources = self._dedupe_sources(deep_retrieved) if not deep_sources: @@ -284,4 +292,3 @@ class AdaptiveRAGAnswerRetrievalMixin: missing_fields=missing_fields, thinking_mode=active_thinking_mode, ) - diff --git a/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py b/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py index 7ee74008..19d1c41f 100644 --- a/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py +++ b/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py @@ -5,6 +5,161 @@ from .shared import * class AdaptiveRAGDeepGuardAnswerMixin: + @staticmethod + def _is_ai_pmo_process_entry(asked_via: Optional[str]) -> bool: + channel = str(asked_via or "").strip().lower() + return channel in { + "pmflow_manual", + "/ai-pmo/process-text", + "ai-pmo-process-text", + "ai_pmo", + "ai_pmo_ui", + } + + @staticmethod + def _contains_any_keyword(text: str, keywords: tuple[str, ...]) -> bool: + return any(token in text for token in keywords) + + @staticmethod + def _format_mixed_two_block_output( + *, + answer_text: str, + question_part: str, + signal_part: Optional[str], + ) -> str: + raw = str(answer_text or "").strip() + lines = [line.strip() for line in raw.splitlines() if str(line).strip()] + answer_block = "" + signal_block = "" + for line in lines: + if line.startswith("[回答]"): + answer_block = line.replace("[回答]", "", 1).strip() + continue + if line.startswith("[已收到需求]"): + signal_block = line.replace("[已收到需求]", "", 1).strip() + + if not answer_block: + if lines: + answer_block = re.sub(r"^\[[^\]]+\]\s*", "", lines[0]).strip() + if not answer_block: + answer_block = str(question_part or "").strip() or "当前信息不足,请补充问题细节。" + answer_block = answer_block[:140] + + if not signal_block: + signal_block = str(signal_part or "").strip() or "需求信息待补充" + signal_block = re.sub(r"[,,]?\s*已入库处理。?$", "", signal_block).strip() or "需求信息待补充" + + return f"[回答] {answer_block}\n[已收到需求] {signal_block},已入库处理" + + def _select_scene_prompt( + self, + *, + question: str, + project_id: Optional[int], + asked_via: str, + message_intent: Optional[str], + mixed_signal_part: Optional[str], + conversation_text: str, + context_blocks_text: str, + system_snapshot: Optional[dict[str, Any]], + user_snapshot: Optional[dict[str, Any]], + system_snapshot_text: str, + user_snapshot_text: str, + relevance_max: float, + ) -> tuple[str, str]: + normalized_question = re.sub(r"\s+", "", str(question or "").lower()) + member_keywords = ("谁", "成员", "负责人", "负载") + trend_keywords = ("最新", "行业", "市场", "前景", "趋势", "动态") + + if self._contains_any_keyword(normalized_question, member_keywords): + return ( + "member_workload", + PROMPT_MEMBER_WORKLOAD_QA.format( + question=question, + system_snapshot=system_snapshot_text or "{}", + user_snapshot=user_snapshot_text or "{}", + context_blocks=context_blocks_text or "无", + ).strip(), + ) + + if self._contains_any_keyword(normalized_question, trend_keywords): + project_context_text = json.dumps( + { + "project_id": project_id if project_id is not None else "unknown", + "system_snapshot": system_snapshot or {}, + "user_snapshot": user_snapshot or {}, + }, + ensure_ascii=False, + )[:16000] + return ( + "external_trend", + PROMPT_EXTERNAL_TREND_QA.format( + question=question, + project_context=project_context_text or "{}", + external_results=context_blocks_text or "无", + ).strip(), + ) + + if str(message_intent or "").strip().lower() == "mixed": + safe_signal_part = str(mixed_signal_part or "").strip() or "需求信息待补充" + merged_message = f"{question}\n{safe_signal_part}".strip() + return ( + "mixed", + PROMPT_FEISHU_MIXED_QA.format( + project_id=project_id if project_id is not None else "unknown", + message=merged_message, + question_part=question, + signal_part=safe_signal_part, + system_snapshot=system_snapshot_text or "{}", + context_blocks=context_blocks_text or "无", + ).strip(), + ) + + if self._is_ai_pmo_process_entry(asked_via): + world_model_text = json.dumps( + { + "system_snapshot": system_snapshot or {}, + "user_snapshot": user_snapshot or {}, + }, + ensure_ascii=False, + )[:18000] + return ( + "ai_pmo", + PROMPT_AI_PMO_QA.format( + question=question, + project_id=project_id if project_id is not None else "unknown", + world_model=world_model_text or "{}", + context_blocks=context_blocks_text or "无", + ).strip(), + ) + + normalized_asked_via = str(asked_via or "").strip().lower() + if normalized_asked_via in {"feishu", "lark", "feishu_group", "feishu_dm"}: + return ( + "feishu_direct", + PROMPT_FEISHU_DIRECT_QA.format( + project_id=project_id if project_id is not None else "unknown", + question=question, + system_snapshot=system_snapshot_text or "{}", + user_snapshot=user_snapshot_text or "{}", + conversation=conversation_text or "无", + context_blocks=context_blocks_text or "无", + ).strip(), + ) + + return ( + "default", + PROMPT_RAG_ANSWER.format( + project_id=project_id if project_id is not None else "unknown", + question=question, + conversation=conversation_text or "无", + context_blocks=context_blocks_text or "无", + system_snapshot=system_snapshot_text or "{}", + user_snapshot=user_snapshot_text or "{}", + relevance_max=f"{max(0.0, float(relevance_max or 0.0)):.3f}", + ).strip(), + ) + def _build_snapshot_direct_answer( self, question: str, @@ -122,6 +277,9 @@ class AdaptiveRAGDeepGuardAnswerMixin: thinking_mode: str = THINKING_MODE_NORMAL, context_block_limit: Optional[int] = None, answer_max_tokens: Optional[int] = None, + asked_via: str = "pmflow", + message_intent: Optional[str] = None, + mixed_signal_part: Optional[str] = None, ) -> str: active_thinking_mode = self._normalize_thinking_mode(thinking_mode) deep_thinking = active_thinking_mode == THINKING_MODE_DEEP @@ -149,18 +307,24 @@ class AdaptiveRAGDeepGuardAnswerMixin: for row in conversation[-10:]: history_lines.append(f"Q: {row.get('question', '')}\nA: {row.get('answer', '')}") + conversation_text = chr(10).join(history_lines) if history_lines else "无" + context_blocks_text = chr(10).join(context_blocks) if context_blocks else "无" system_snapshot_text = json.dumps(system_snapshot or {}, ensure_ascii=False)[:12000] user_snapshot_text = json.dumps(user_snapshot or {}, ensure_ascii=False)[:6000] - - prompt = PROMPT_RAG_ANSWER.format( - project_id=project_id if project_id is not None else "unknown", + scene_key, prompt = self._select_scene_prompt( question=question, - conversation=chr(10).join(history_lines) if history_lines else "无", - context_blocks=chr(10).join(context_blocks) if context_blocks else "无", - system_snapshot=system_snapshot_text or "{}", - user_snapshot=user_snapshot_text or "{}", - relevance_max=f"{max(0.0, float(relevance_max or 0.0)):.3f}", - ).strip() + project_id=project_id, + asked_via=asked_via, + message_intent=message_intent, + mixed_signal_part=mixed_signal_part, + conversation_text=conversation_text, + context_blocks_text=context_blocks_text, + system_snapshot=system_snapshot, + user_snapshot=user_snapshot, + system_snapshot_text=system_snapshot_text, + user_snapshot_text=user_snapshot_text, + relevance_max=relevance_max, + ) if deep_thinking: prompt = ( f"{prompt}\n\n" @@ -191,6 +355,12 @@ class AdaptiveRAGDeepGuardAnswerMixin: text = str(answer or "").strip() if not text: return "我未能从当前上下文生成有效答案,请补充问题细节后重试。" + if scene_key == "mixed": + return self._format_mixed_two_block_output( + answer_text=text, + question_part=question, + signal_part=mixed_signal_part, + )[:3200] enforced = self._enforce_feature_evidence_in_answer( question=question, answer_text=text, @@ -198,4 +368,3 @@ class AdaptiveRAGDeepGuardAnswerMixin: system_snapshot=system_snapshot, ) return str(enforced or text)[:3200] - diff --git a/backend/app/services/adaptive_rag_service/mixin_deep_guard_rules.py b/backend/app/services/adaptive_rag_service/mixin_deep_guard_rules.py index 81d55c95..c1a3d7ca 100644 --- a/backend/app/services/adaptive_rag_service/mixin_deep_guard_rules.py +++ b/backend/app/services/adaptive_rag_service/mixin_deep_guard_rules.py @@ -168,14 +168,14 @@ class AdaptiveRAGDeepGuardRulesMixin: "请补充目标对象(如 openclaw 路由规则、函数名、入口层级),我会按调用链返回可核验答案。" ) - @staticmethod - def _is_snapshot_direct_question(question: str) -> bool: + @classmethod + def _is_snapshot_direct_question(cls, question: str) -> bool: text = str(question or "").strip().lower() if not text: return False - if AdaptiveRAGService._is_correction_query(text): + if cls._is_correction_query(text): return False - if AdaptiveRAGService._is_engine_process_query(text): + if cls._is_engine_process_query(text): return False has_feature_token = any(k in text for k in FEATURE_QUERY_KEYWORDS) ask_feature_purpose = has_feature_token and any(k in text for k in FEATURE_PURPOSE_HINTS) @@ -226,4 +226,3 @@ class AdaptiveRAGDeepGuardRulesMixin: if is_enumeration and "项目" in anchor_question: return f"{anchor_question}。请列出对应项目的名称和ID。" return f"{anchor_question}。补充问题:{current}" - diff --git a/backend/app/services/adaptive_rag_service/mixin_ingest.py b/backend/app/services/adaptive_rag_service/mixin_ingest.py index 4b948792..4d3e497c 100644 --- a/backend/app/services/adaptive_rag_service/mixin_ingest.py +++ b/backend/app/services/adaptive_rag_service/mixin_ingest.py @@ -71,7 +71,9 @@ class AdaptiveRAGIngestMixin: project_id: Optional[int] = None, ) -> None: async with AsyncSessionLocal() as session: - service = AdaptiveRAGService(session, tenant_id=tenant_id) + from .service import AdaptiveRAGService as AdaptiveRAGServiceRuntime + + service = AdaptiveRAGServiceRuntime(session, tenant_id=tenant_id) try: await service.ingest_feishu_message( content=content, @@ -93,7 +95,9 @@ class AdaptiveRAGIngestMixin: project_id: Optional[int] = None, ) -> None: async with AsyncSessionLocal() as session: - service = AdaptiveRAGService(session, tenant_id=tenant_id) + from .service import AdaptiveRAGService as AdaptiveRAGServiceRuntime + + service = AdaptiveRAGServiceRuntime(session, tenant_id=tenant_id) try: await service.process_knowledge_doc_content( doc_id=doc_id, @@ -138,7 +142,9 @@ class AdaptiveRAGIngestMixin: project_id: Optional[int] = None, ) -> None: async with AsyncSessionLocal() as session: - service = AdaptiveRAGService(session, tenant_id=tenant_id) + from .service import AdaptiveRAGService as AdaptiveRAGServiceRuntime + + service = AdaptiveRAGServiceRuntime(session, tenant_id=tenant_id) try: await service.refresh_url_doc(doc_id=doc_id, project_id=project_id) except Exception: @@ -216,7 +222,9 @@ class AdaptiveRAGIngestMixin: content: str, ) -> None: async with AsyncSessionLocal() as session: - service = AdaptiveRAGService(session, tenant_id=tenant_id) + from .service import AdaptiveRAGService as AdaptiveRAGServiceRuntime + + service = AdaptiveRAGServiceRuntime(session, tenant_id=tenant_id) try: await service.upsert_pmflow_record( record_type=record_type, @@ -227,4 +235,3 @@ class AdaptiveRAGIngestMixin: ) except Exception: await session.rollback() - diff --git a/backend/app/services/adaptive_rag_service/shared_imports.py b/backend/app/services/adaptive_rag_service/shared_imports.py index c74473d2..5f173957 100644 --- a/backend/app/services/adaptive_rag_service/shared_imports.py +++ b/backend/app/services/adaptive_rag_service/shared_imports.py @@ -27,7 +27,16 @@ import httpx from sqlalchemy import and_, bindparam, delete, func, or_, select, text from sqlalchemy.ext.asyncio import AsyncSession -from ...config.ai_prompts import PROMPT_RAG_ANSWER, PROMPT_RAG_DB_PROJECT_QA, PROMPT_RAG_INTENT_CLASSIFICATION +from ...config.ai_prompts import ( + PROMPT_AI_PMO_QA, + PROMPT_EXTERNAL_TREND_QA, + PROMPT_FEISHU_DIRECT_QA, + PROMPT_FEISHU_MIXED_QA, + PROMPT_MEMBER_WORKLOAD_QA, + PROMPT_RAG_ANSWER, + PROMPT_RAG_DB_PROJECT_QA, + PROMPT_RAG_INTENT_CLASSIFICATION, +) from ...core.database import AsyncSessionLocal from ...models import ( KnowledgeBaseChunk, @@ -50,8 +59,8 @@ from ...models import ( ) from ..context_cache_service import context_cache_service from ..llm_service import LLMService -from ..signal_source_orchestration import SignalSourceOrchestrationService -from ..symbol_index import SymbolIndexService +from ..signal.signal_source_orchestration import SignalSourceOrchestrationService +from ..signal.symbol_index import SymbolIndexService from ...utils.timezone import CN_TZ, now_cn_aware, now_cn_naive diff --git a/backend/app/services/adaptive_rag_service/shared_part_02.py b/backend/app/services/adaptive_rag_service/shared_part_02.py index 9a238887..3773ab4f 100644 --- a/backend/app/services/adaptive_rag_service/shared_part_02.py +++ b/backend/app/services/adaptive_rag_service/shared_part_02.py @@ -138,6 +138,9 @@ ENTITY_ALIAS_MAP: dict[str, str] = { TS_SYMBOL_RE = re.compile( r"(?:export\s+)?(?:async\s+)?(?:function|class|interface|type|const)\s+([A-Za-z_][A-Za-z0-9_]*)" ) + + +@dataclass class RetrievedItem: source: str content: str diff --git a/backend/app/services/adaptive_rag_service/shared_utils.py b/backend/app/services/adaptive_rag_service/shared_utils.py index 101c4dc8..33487358 100644 --- a/backend/app/services/adaptive_rag_service/shared_utils.py +++ b/backend/app/services/adaptive_rag_service/shared_utils.py @@ -15,3 +15,5 @@ def _env_int(name: str, default: int, *, minimum: int, maximum: int) -> int: return max(minimum, min(maximum, value)) +__all__ = ["_env_int"] + diff --git a/backend/app/services/ai_orchestration_run/ingress_worker.py b/backend/app/services/ai_orchestration_run/ingress_worker.py new file mode 100644 index 00000000..06134254 --- /dev/null +++ b/backend/app/services/ai_orchestration_run/ingress_worker.py @@ -0,0 +1,265 @@ +"""AI PMO ingress async worker for queued Feishu messages.""" + +from __future__ import annotations + +import asyncio +import contextlib +from typing import Any, Optional + +from ...core.database import AsyncSessionLocal +from ...core.logging import get_logger +from ...utils.timezone import now_cn_naive +from ..integrations.feishu_outbox.service import FeishuOutboxService +from ..signal.signal_capture_service import ProactiveSignalCaptureService +from .service import AIOrchestrationRunService, INGRESS_RUN_TYPE + + +logger = get_logger(__name__) + +FINAL_FAILURE_TEXT = "已自动重试3次仍失败,请重新发送或换个说法。" +INGRESS_HEARTBEAT_INTERVAL_SECONDS = 15 + + +class IngressDispatchError(RuntimeError): + """Raised when dispatching a claimed ingress run fatally fails.""" + + def __init__(self, *, run_id: Optional[int], cause: Exception): + self.run_id = int(run_id) if run_id is not None else None + self.cause = cause + super().__init__(str(cause)) + + +def _safe_int(value: Any) -> Optional[int]: + try: + parsed = int(value) + except Exception: + return None + return parsed + + +def _build_final_reply_text(result: dict[str, Any]) -> str: + if not isinstance(result, dict): + return "" + if str(result.get("reason") or "").strip().lower() == "duplicate_filtered": + return "" + if str(result.get("classification") or "").strip().lower() == "duplicate": + return "" + direct = str(result.get("direct_reply_text") or "").strip() + if direct: + return direct[:500] + status = str(result.get("status") or "").strip().lower() + if status in {"captured", "answered"}: + return "处理完成。" + return "" + + +async def _touch_ingress_run_heartbeat(*, run_id: int) -> None: + """Refresh updated_at so stale-recovery does not treat active runs as dead.""" + async with AsyncSessionLocal() as db: + run_service = AIOrchestrationRunService(db) + row = await run_service.get_run_by_id(run_id=run_id) + if row is None: + await db.commit() + return + if str(row.run_type or "") != INGRESS_RUN_TYPE: + await db.commit() + return + if str(row.status or "").lower() in {"failed", "succeeded", "cancelled", "interrupted", "completed"}: + await db.commit() + return + row.updated_at = now_cn_naive() + await db.flush() + await db.commit() + + +async def _finalize_ingress_failure_with_fresh_session( + *, + run_id: int, + tenant_id: int, + channel_ref: str, + source_message_id: Optional[str], + trace_id: str, + error_message: str, +) -> None: + """Failure finalize path must not depend on the potentially committed caller session.""" + async with AsyncSessionLocal() as failure_db: + run_service = AIOrchestrationRunService(failure_db) + _failed_row, should_notify = await run_service.mark_ingress_failed_or_retrying( + run_id=run_id, + error_message=error_message, + ) + if should_notify and channel_ref: + await FeishuOutboxService.enqueue( + failure_db, + tenant_id=tenant_id, + chat_id=channel_ref, + text=FINAL_FAILURE_TEXT, + source_message_id=source_message_id, + dedupe_seed=f"{trace_id}:final_failed", + trace_id=trace_id or None, + ) + await run_service.mark_ingress_result_notified(run_id=run_id) + await failure_db.commit() + + +async def _claim_next_ingress_run_id() -> Optional[int]: + async with AsyncSessionLocal() as db: + run_service = AIOrchestrationRunService(db) + row = await run_service.claim_next_ingress_run() + if row is None: + await db.commit() + return None + run_id = int(row.id) + await db.commit() + return run_id + + +async def _dispatch_claimed_ingress_run(*, run_id: int) -> None: + async with AsyncSessionLocal() as db: + run_service = AIOrchestrationRunService(db) + row = await run_service.get_run_by_id(run_id=run_id) + if row is None: + await db.commit() + return + if str(row.run_type or "") != INGRESS_RUN_TYPE: + await db.commit() + return + if str(row.status or "") != "running": + await db.commit() + return + + # Copy all values that may be needed after downstream calls. + # Downstream code may commit/rollback and expire ORM attributes. + run_id_value = int(row.id) + tenant_id_value = int(row.tenant_id) + trace_id_value = str(row.trace_id or "").strip() + channel_ref_value = str(row.channel_ref or "").strip() + message_text_value = str(row.message_text or "").strip() + sender_value = str(row.sender or "unknown").strip() or "unknown" + source_sender_open_id_value = str(row.source_sender_open_id or "").strip() or None + captured_at_value = row.captured_at + source_message_id_value = str(row.source_message_id or "").strip() or None + chat_type_value = str(row.chat_type or "").strip() or None + + try: + capture_service = ProactiveSignalCaptureService(db) + stop_heartbeat = asyncio.Event() + + async def _heartbeat_loop() -> None: + while not stop_heartbeat.is_set(): + try: + await _touch_ingress_run_heartbeat(run_id=run_id_value) + except Exception as heartbeat_exc: + logger.warning( + "ingress_async_heartbeat_failed", + run_id=run_id_value, + trace_id=trace_id_value, + error=str(heartbeat_exc), + ) + try: + await asyncio.wait_for(stop_heartbeat.wait(), timeout=INGRESS_HEARTBEAT_INTERVAL_SECONDS) + except asyncio.TimeoutError: + continue + + heartbeat_task = asyncio.create_task(_heartbeat_loop()) + try: + # Explicitly touch once before dispatching downstream LLM-heavy flow. + await _touch_ingress_run_heartbeat(run_id=run_id_value) + result = await capture_service.capture_from_feishu( + group_chat_id=channel_ref_value, + message_content=message_text_value, + sender=sender_value, + tenant_id=tenant_id_value, + proposer_open_id=source_sender_open_id_value, + captured_at=captured_at_value, + mentioned_bot_names=None, + feishu_message_id=source_message_id_value, + chat_type=chat_type_value, + force_sync=True, + ) + finally: + stop_heartbeat.set() + with contextlib.suppress(Exception): + await heartbeat_task + # Explicitly touch once after downstream call returns. + with contextlib.suppress(Exception): + await _touch_ingress_run_heartbeat(run_id=run_id_value) + status = str(result.get("status") or "").strip().lower() if isinstance(result, dict) else "" + if status == "error" or not isinstance(result, dict): + raise RuntimeError(str((result or {}).get("reason") or "ingress_async_unhandled_result")) + + work_item_id = _safe_int((result or {}).get("work_item_id")) + summary = str((result or {}).get("direct_reply_text") or "异步处理完成。").strip()[:1000] + await run_service.mark_ingress_succeeded( + run_id=run_id_value, + result_payload=result if isinstance(result, dict) else {}, + work_item_id=work_item_id, + summary=summary, + ) + + final_reply_text = _build_final_reply_text(result) + if final_reply_text and channel_ref_value: + await FeishuOutboxService.enqueue( + db, + tenant_id=tenant_id_value, + chat_id=channel_ref_value, + text=final_reply_text, + source_message_id=source_message_id_value, + dedupe_seed=f"{trace_id_value}:final", + trace_id=trace_id_value or None, + ) + await run_service.mark_ingress_result_notified(run_id=run_id_value) + await db.commit() + except Exception as exc: + await _finalize_ingress_failure_with_fresh_session( + run_id=run_id_value, + tenant_id=tenant_id_value, + channel_ref=channel_ref_value, + source_message_id=source_message_id_value, + trace_id=trace_id_value, + error_message=str(exc), + ) + logger.warning( + "ingress_async_run_failed", + run_id=run_id_value, + trace_id=trace_id_value, + error=str(exc), + ) + + +async def _mark_run_failed_greenlet_spawn(*, run_id: int) -> None: + """Persist fatal async session/greenlet failure so run is not silently skipped.""" + async with AsyncSessionLocal() as db: + run_service = AIOrchestrationRunService(db) + row = await run_service.get_run_by_id(run_id=int(run_id)) + if row is None: + await db.commit() + return + now_at = now_cn_naive() + row.status = "failed" + row.openclaw_status = "failed" + row.next_retry_at = None + row.last_error = "greenlet_spawn_error" + row.last_error_at = now_at + row.completed_at = now_at + row.summary = "异步处理失败(greenlet_spawn_error)" + row.worker_failed = max(1, int(row.worker_failed or 0) + 1) + await db.flush() + await db.commit() + + +async def dispatch_pending_ingress_runs(limit: int = 20) -> int: + fetch_limit = max(1, min(int(limit), 100)) + dispatched = 0 + for _ in range(fetch_limit): + run_id = await _claim_next_ingress_run_id() + if run_id is None: + break + try: + await _dispatch_claimed_ingress_run(run_id=int(run_id)) + except Exception as exc: + if "greenlet_spawn" in str(exc): + await _mark_run_failed_greenlet_spawn(run_id=int(run_id)) + raise IngressDispatchError(run_id=int(run_id), cause=exc) from exc + dispatched += 1 + return dispatched diff --git a/backend/app/services/ai_orchestration_run/service.py b/backend/app/services/ai_orchestration_run/service.py index c6be4539..05e8f7a4 100644 --- a/backend/app/services/ai_orchestration_run/service.py +++ b/backend/app/services/ai_orchestration_run/service.py @@ -2,20 +2,40 @@ from __future__ import annotations -from datetime import datetime, timedelta, timezone +import hashlib +from datetime import datetime, timedelta from typing import Any, Optional -from sqlalchemy import and_, select +from sqlalchemy import and_, or_, select +from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession +from ...core.database import AsyncSessionLocal from ...models import AIOrchestrationRun -from ...utils.timezone import now_cn_naive +from ...utils.timezone import CN_TZ, now_cn_naive + + def _utc_now_naive() -> datetime: return now_cn_naive() +def _normalize_naive_datetime(value: Optional[datetime]) -> Optional[datetime]: + if value is None: + return None + if not isinstance(value, datetime): + return None + if value.tzinfo is None: + return value + return value.astimezone(CN_TZ).replace(tzinfo=None) + + +INGRESS_RUN_TYPE = "ingress_feishu_message" +INGRESS_RETRY_SCHEDULE_SECONDS: tuple[int, ...] = (10, 30, 90) +INGRESS_RUNNING_STALE_MINUTES = 30 + + class AIOrchestrationRunService: def __init__(self, db: AsyncSession) -> None: self.db = db @@ -26,7 +46,7 @@ class AIOrchestrationRunService: tenant_id: int, trace_id: str, run_type: str, - project_id: int, + project_id: Optional[int], signal_id: Optional[int], response_mode: str, status: str, @@ -66,6 +86,287 @@ class AIOrchestrationRunService: await self.db.flush() return row + @staticmethod + def build_ingress_trace_id( + *, + source_system: str, + source_message_id: str, + ) -> str: + normalized_source = str(source_system or "").strip().lower()[:24] or "unknown" + normalized_message_id = str(source_message_id or "").strip()[:96] + if normalized_message_id: + raw = f"{normalized_source}:{normalized_message_id}" + else: + digest = hashlib.sha1(f"{normalized_source}:{_utc_now_naive().isoformat()}".encode("utf-8"), usedforsecurity=False).hexdigest() + raw = f"{normalized_source}:{digest[:40]}" + return raw[:64] + + async def get_run_by_id(self, *, run_id: int) -> Optional[AIOrchestrationRun]: + return ( + await self.db.execute( + select(AIOrchestrationRun).where(AIOrchestrationRun.id == int(run_id)) + ) + ).scalar_one_or_none() + + async def enqueue_ingress_run( + self, + *, + tenant_id: int, + source_system: str, + source_message_id: str, + sender: str, + message_text: str, + channel_ref: Optional[str], + source_sender_open_id: Optional[str], + chat_type: Optional[str], + captured_at: Optional[datetime], + project_hint_id: Optional[int], + max_retries: int = 3, + ) -> tuple[AIOrchestrationRun, bool]: + trace_id = self.build_ingress_trace_id( + source_system=source_system, + source_message_id=source_message_id, + ) + existing = await self.get_run(tenant_id=tenant_id, trace_id=trace_id) + if existing is not None: + return existing, False + + result_payload = { + "queue_mode": "async_ingress", + "source_system": str(source_system or "").strip()[:32], + "source_message_id": str(source_message_id or "").strip()[:128], + "queued_at": _utc_now_naive().isoformat(), + } + row = AIOrchestrationRun( + tenant_id=int(tenant_id), + trace_id=trace_id, + run_type=INGRESS_RUN_TYPE, + project_id=int(project_hint_id) if project_hint_id is not None else None, + signal_id=None, + response_mode="fast", + status="queued", + planner_status=None, + openclaw_status=None, + worker_total=1, + worker_completed=0, + worker_failed=0, + summary="消息已入异步处理队列。", + planner_diagnostics=None, + result_payload=result_payload, + created_by_user_id=None, + started_at=None, + completed_at=None, + source_system=str(source_system or "").strip()[:32] or None, + source_message_id=str(source_message_id or "").strip()[:128] or None, + sender=str(sender or "").strip()[:128] or "unknown", + source_sender_open_id=str(source_sender_open_id or "").strip()[:128] or None, + channel_ref=str(channel_ref or "").strip()[:128] or None, + chat_type=str(chat_type or "").strip()[:32] or None, + captured_at=_normalize_naive_datetime(captured_at), + message_text=str(message_text or "").strip()[:8000] or "", + project_hint_id=int(project_hint_id) if project_hint_id is not None else None, + retry_count=0, + max_retries=max(0, min(int(max_retries), 5)), + next_retry_at=_utc_now_naive(), + last_error=None, + last_error_at=None, + ack_sent_at=None, + result_notified_at=None, + work_item_id=None, + ) + self.db.add(row) + try: + await self.db.flush() + except IntegrityError: + await self.db.rollback() + existing = await self.get_run(tenant_id=tenant_id, trace_id=trace_id) + if existing is not None: + return existing, False + raise + return row, True + + async def claim_next_ingress_run(self, *, now: Optional[datetime] = None) -> Optional[AIOrchestrationRun]: + now_at = now or _utc_now_naive() + stale_before = now_at - timedelta(minutes=INGRESS_RUNNING_STALE_MINUTES) + stale_rows = ( + await self.db.execute( + select(AIOrchestrationRun) + .where( + and_( + AIOrchestrationRun.run_type == INGRESS_RUN_TYPE, + AIOrchestrationRun.status == "running", + AIOrchestrationRun.updated_at <= stale_before, + ) + ) + .with_for_update(skip_locked=True) + ) + ).scalars().all() + for stale in stale_rows: + if int(stale.retry_count or 0) < int(stale.max_retries or 0): + delay = INGRESS_RETRY_SCHEDULE_SECONDS[min(int(stale.retry_count or 0), len(INGRESS_RETRY_SCHEDULE_SECONDS) - 1)] + stale.retry_count = int(stale.retry_count or 0) + 1 + stale.status = "retrying" + stale.next_retry_at = now_at + timedelta(seconds=int(delay)) + else: + stale.status = "failed" + stale.completed_at = now_at + stale.next_retry_at = None + stale.last_error = "stale_running_recovered" + stale.last_error_at = now_at + + candidate_id = ( + await self.db.execute( + select(AIOrchestrationRun.id) + .where( + and_( + AIOrchestrationRun.run_type == INGRESS_RUN_TYPE, + or_( + and_( + AIOrchestrationRun.status.in_(("queued", "retrying")), + or_( + AIOrchestrationRun.next_retry_at.is_(None), + AIOrchestrationRun.next_retry_at <= now_at, + ), + ), + and_( + AIOrchestrationRun.status == "failed", + AIOrchestrationRun.last_error == "greenlet_spawn_error", + AIOrchestrationRun.retry_count < AIOrchestrationRun.max_retries, + or_( + AIOrchestrationRun.next_retry_at.is_(None), + AIOrchestrationRun.next_retry_at <= now_at, + ), + ), + ), + ) + ) + .order_by(AIOrchestrationRun.id.asc()) + .with_for_update(skip_locked=True) + .limit(1) + ) + ).scalar_one_or_none() + if candidate_id is None: + return None + + row = await self.get_run_by_id(run_id=int(candidate_id)) + if row is None: + return None + if str(row.status or "") == "failed" and str(row.last_error or "") == "greenlet_spawn_error": + row.retry_count = min( + int(row.max_retries or 0), + int(row.retry_count or 0) + 1, + ) + row.status = "running" + row.openclaw_status = "running" + row.started_at = row.started_at or now_at + row.next_retry_at = None + row.last_error = None + row.last_error_at = None + await self.db.flush() + return row + + async def mark_ingress_ack_sent( + self, + *, + tenant_id: int, + trace_id: str, + ) -> Optional[AIOrchestrationRun]: + row = await self.get_run(tenant_id=tenant_id, trace_id=trace_id) + if row is None: + return None + row.ack_sent_at = _utc_now_naive() + await self.db.flush() + return row + + async def mark_ingress_succeeded( + self, + *, + run_id: int, + result_payload: dict[str, Any], + work_item_id: Optional[int], + summary: Optional[str] = None, + ) -> Optional[AIOrchestrationRun]: + row = await self.get_run_by_id(run_id=run_id) + if row is None: + return None + now_at = _utc_now_naive() + row.status = "succeeded" + row.openclaw_status = "succeeded" + row.worker_total = max(1, int(row.worker_total or 1)) + row.worker_completed = max(1, int(row.worker_completed or 0)) + row.worker_failed = max(0, int(row.worker_failed or 0)) + row.result_payload = result_payload if isinstance(result_payload, dict) else {} + if work_item_id is not None: + row.work_item_id = int(work_item_id) + row.summary = (str(summary or "").strip()[:1000] or "异步处理完成。") + row.completed_at = now_at + row.next_retry_at = None + row.last_error = None + row.last_error_at = None + await self.db.flush() + return row + + async def mark_ingress_failed_or_retrying( + self, + *, + run_id: int, + error_message: str, + ) -> tuple[Optional[AIOrchestrationRun], bool]: + # Failure path must not reuse a possibly committed/invalid caller session. + async with AsyncSessionLocal() as new_db: + row = ( + await new_db.execute( + select(AIOrchestrationRun) + .where(AIOrchestrationRun.id == int(run_id)) + .with_for_update(skip_locked=True) + ) + ).scalar_one_or_none() + if row is None: + await new_db.commit() + return None, False + + now_at = _utc_now_naive() + normalized_error = str(error_message or "").strip()[:1000] or "unknown_error" + retry_count = int(row.retry_count or 0) + max_retries = max(0, int(row.max_retries or 0)) + should_retry = retry_count < max_retries + if should_retry: + delay = INGRESS_RETRY_SCHEDULE_SECONDS[min(retry_count, len(INGRESS_RETRY_SCHEDULE_SECONDS) - 1)] + row.retry_count = retry_count + 1 + row.status = "retrying" + row.openclaw_status = "retrying" + row.next_retry_at = now_at + timedelta(seconds=int(delay)) + row.completed_at = None + row.worker_failed = max(0, int(row.worker_failed or 0)) + else: + row.status = "failed" + row.openclaw_status = "failed" + row.next_retry_at = None + row.completed_at = now_at + row.worker_failed = max(1, int(row.worker_failed or 0) + 1) + row.last_error = normalized_error + row.last_error_at = now_at + row.summary = ( + f"异步处理失败(已重试{int(row.retry_count or 0)}次)" + if should_retry + else f"异步处理失败(自动重试{max_retries}次后终止)" + ) + await new_db.flush() + await new_db.commit() + return row, (not should_retry) + + async def mark_ingress_result_notified( + self, + *, + run_id: int, + ) -> Optional[AIOrchestrationRun]: + row = await self.get_run_by_id(run_id=run_id) + if row is None: + return None + row.result_notified_at = _utc_now_naive() + await self.db.flush() + return row + async def get_run(self, *, tenant_id: int, trace_id: str) -> Optional[AIOrchestrationRun]: return ( await self.db.execute( @@ -130,6 +431,7 @@ class AIOrchestrationRunService: select(AIOrchestrationRun).where( and_( AIOrchestrationRun.status.in_(("queued", "running")), + AIOrchestrationRun.run_type != INGRESS_RUN_TYPE, AIOrchestrationRun.started_at.is_not(None), AIOrchestrationRun.started_at < threshold, ) @@ -160,7 +462,7 @@ class AIOrchestrationRunService: return { "trace_id": str(row.trace_id or ""), "run_type": str(row.run_type or ""), - "project_id": int(row.project_id), + "project_id": int(row.project_id) if row.project_id is not None else None, "signal_id": int(row.signal_id) if row.signal_id is not None else None, "response_mode": str(row.response_mode or ""), "run_status": str(row.status or ""), @@ -174,6 +476,18 @@ class AIOrchestrationRunService: "summary": str(row.summary or payload.get("summary") or "").strip(), "planner_diagnostics": row.planner_diagnostics if isinstance(row.planner_diagnostics, dict) else None, "result_snapshot": payload, + "source_system": str(row.source_system or ""), + "source_message_id": str(row.source_message_id or ""), + "sender": str(row.sender or ""), + "channel_ref": str(row.channel_ref or ""), + "project_hint_id": int(row.project_hint_id) if row.project_hint_id is not None else None, + "retry_count": int(row.retry_count or 0), + "max_retries": int(row.max_retries or 0), + "next_retry_at": row.next_retry_at.isoformat() if row.next_retry_at else None, + "last_error": str(row.last_error or ""), + "work_item_id": int(row.work_item_id) if row.work_item_id is not None else None, + "ack_sent_at": row.ack_sent_at.isoformat() if row.ack_sent_at else None, + "result_notified_at": row.result_notified_at.isoformat() if row.result_notified_at else None, "created_by_user_id": int(row.created_by_user_id) if row.created_by_user_id is not None else None, "started_at": row.started_at.isoformat() if row.started_at else None, "completed_at": row.completed_at.isoformat() if row.completed_at else None, diff --git a/backend/app/services/ai_pmo/__init__.py b/backend/app/services/ai_pmo/__init__.py new file mode 100644 index 00000000..a880b216 --- /dev/null +++ b/backend/app/services/ai_pmo/__init__.py @@ -0,0 +1 @@ +"""AI PMO domain service namespace.""" diff --git a/backend/app/services/ai_pmo_cadence/__init__.py b/backend/app/services/ai_pmo/ai_pmo_cadence/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/__init__.py diff --git a/backend/app/services/ai_pmo_cadence/mixin_builders.py b/backend/app/services/ai_pmo/ai_pmo_cadence/mixin_builders.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/mixin_builders.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/mixin_builders.py diff --git a/backend/app/services/ai_pmo_cadence/mixin_program_view.py b/backend/app/services/ai_pmo/ai_pmo_cadence/mixin_program_view.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/mixin_program_view.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/mixin_program_view.py diff --git a/backend/app/services/ai_pmo_cadence/mixin_queries_a.py b/backend/app/services/ai_pmo/ai_pmo_cadence/mixin_queries_a.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/mixin_queries_a.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/mixin_queries_a.py diff --git a/backend/app/services/ai_pmo_cadence/mixin_queries_b.py b/backend/app/services/ai_pmo/ai_pmo_cadence/mixin_queries_b.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/mixin_queries_b.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/mixin_queries_b.py diff --git a/backend/app/services/ai_pmo_cadence/mixin_snapshots.py b/backend/app/services/ai_pmo/ai_pmo_cadence/mixin_snapshots.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/mixin_snapshots.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/mixin_snapshots.py diff --git a/backend/app/services/ai_pmo_cadence/service.py b/backend/app/services/ai_pmo/ai_pmo_cadence/service.py similarity index 100% rename from backend/app/services/ai_pmo_cadence/service.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/service.py diff --git a/backend/app/services/ai_pmo_cadence/shared.py b/backend/app/services/ai_pmo/ai_pmo_cadence/shared.py similarity index 93% rename from backend/app/services/ai_pmo_cadence/shared.py rename to backend/app/services/ai_pmo/ai_pmo_cadence/shared.py index e184e9ed..7ad44cf1 100644 --- a/backend/app/services/ai_pmo_cadence/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_cadence/shared.py @@ -8,7 +8,7 @@ from typing import Any, Optional from sqlalchemy import and_, func, or_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import ( +from ....models import ( Budget, Notification, PortfolioSnapshot, @@ -22,7 +22,7 @@ from ...models import ( UserWorkload, WorkItem, ) -from ...utils.timezone import now_cn_naive +from ....utils.timezone import now_cn_naive def utc_now_naive() -> datetime: diff --git a/backend/app/services/ai_pmo_decision/__init__.py b/backend/app/services/ai_pmo/ai_pmo_decision/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_decision/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_decision/__init__.py diff --git a/backend/app/services/ai_pmo_decision/engine.py b/backend/app/services/ai_pmo/ai_pmo_decision/engine.py similarity index 100% rename from backend/app/services/ai_pmo_decision/engine.py rename to backend/app/services/ai_pmo/ai_pmo_decision/engine.py diff --git a/backend/app/services/ai_pmo_decision/policy.py b/backend/app/services/ai_pmo/ai_pmo_decision/policy.py similarity index 79% rename from backend/app/services/ai_pmo_decision/policy.py rename to backend/app/services/ai_pmo/ai_pmo_decision/policy.py index 6a485be2..c60effb0 100644 --- a/backend/app/services/ai_pmo_decision/policy.py +++ b/backend/app/services/ai_pmo/ai_pmo_decision/policy.py @@ -2,6 +2,8 @@ from __future__ import annotations +import os + from .shared import ( CanonicalIntent, INTENT_QUERY, @@ -25,6 +27,12 @@ class ExecutionPolicyBrain: intent: CanonicalIntent, simulation: dict[str, object], ) -> dict[str, object]: + warn_threshold = self._env_float("CAPACITY_WARN_THRESHOLD", 1.0) + block_threshold = self._env_float("CAPACITY_BLOCK_THRESHOLD", 1.5) + if block_threshold < warn_threshold: + block_threshold = warn_threshold + projected_capacity_ratio = float(simulation.get("projected_capacity_peak_ratio") or 0.0) + capacity_warning: dict[str, object] | None = None normalized = re.sub(r"\s+", "", str(message_text or "")).lower() for gate_code, tokens in self.policy_gate_rules: if any(token.lower().replace(" ", "") in normalized for token in tokens): @@ -84,7 +92,7 @@ class ExecutionPolicyBrain: }, } - if float(simulation.get("projected_capacity_peak_ratio") or 0.0) >= 1.0: + if projected_capacity_ratio >= block_threshold: return { "auto_execute": False, "policy_gate_hit": True, @@ -92,9 +100,17 @@ class ExecutionPolicyBrain: "approval_requirement": { "required": True, "reason": "simulation_capacity_limit", - "message": "执行前仿真显示产能会超载,自动执行被拦截。", + "message": "执行前仿真显示产能严重超载,自动执行被拦截。", }, } + if projected_capacity_ratio >= warn_threshold: + capacity_warning = { + "enabled": True, + "severity": "warning", + "peak_ratio": round(projected_capacity_ratio, 4), + "warn_threshold": round(warn_threshold, 4), + "block_threshold": round(block_threshold, 4), + } if intent.name == INTENT_SIGNAL_SOURCE_ONBOARD and int(simulation.get("missing_skill_count") or 0) > 0: return { @@ -125,4 +141,15 @@ class ExecutionPolicyBrain: "policy_gate_hit": False, "policy_gate_code": None, "approval_requirement": {"required": False}, + "capacity_warning": capacity_warning, } + + @staticmethod + def _env_float(name: str, default: float) -> float: + raw = str(os.getenv(name, "")).strip() + if not raw: + return float(default) + try: + return float(raw) + except (TypeError, ValueError): + return float(default) diff --git a/backend/app/services/ai_pmo_decision/shared.py b/backend/app/services/ai_pmo/ai_pmo_decision/shared.py similarity index 100% rename from backend/app/services/ai_pmo_decision/shared.py rename to backend/app/services/ai_pmo/ai_pmo_decision/shared.py diff --git a/backend/app/services/ai_pmo_decision/simulation.py b/backend/app/services/ai_pmo/ai_pmo_decision/simulation.py similarity index 100% rename from backend/app/services/ai_pmo_decision/simulation.py rename to backend/app/services/ai_pmo/ai_pmo_decision/simulation.py diff --git a/backend/app/services/ai_pmo_decision/strategic.py b/backend/app/services/ai_pmo/ai_pmo_decision/strategic.py similarity index 100% rename from backend/app/services/ai_pmo_decision/strategic.py rename to backend/app/services/ai_pmo/ai_pmo_decision/strategic.py diff --git a/backend/app/services/ai_pmo_decision/tactical.py b/backend/app/services/ai_pmo/ai_pmo_decision/tactical.py similarity index 100% rename from backend/app/services/ai_pmo_decision/tactical.py rename to backend/app/services/ai_pmo/ai_pmo_decision/tactical.py diff --git a/backend/app/services/ai_pmo_intent/__init__.py b/backend/app/services/ai_pmo/ai_pmo_intent/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_intent/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_intent/__init__.py diff --git a/backend/app/services/ai_pmo_intent/intent_resolver.py b/backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py similarity index 99% rename from backend/app/services/ai_pmo_intent/intent_resolver.py rename to backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py index 1d369c3c..26a6a408 100644 --- a/backend/app/services/ai_pmo_intent/intent_resolver.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py @@ -202,7 +202,7 @@ class IntentResolver: return True return bool( re.search( - r"(?:新增|增加|添加|新建|创建|建立|开个|开一个|建个|建一个|做个|做一个|加个|加一个|立项)(?:一个|个)?项目", + r"(?:新增|增加|添加|新建|创建|建立|开个|开一个|建个|建一个|做个|做一个|加个|加一个|立项)(?:一个|个)?(?:[^,。!?,.!?]{0,24})项目", normalized, re.IGNORECASE, ) @@ -212,4 +212,3 @@ class IntentResolver: def _build_signal_title(text: str, *, default: str) -> str: raw = str(text or "").strip() return raw or default - diff --git a/backend/app/services/ai_pmo_intent/models.py b/backend/app/services/ai_pmo/ai_pmo_intent/models.py similarity index 88% rename from backend/app/services/ai_pmo_intent/models.py rename to backend/app/services/ai_pmo/ai_pmo_intent/models.py index 1691afe5..12594d3e 100644 --- a/backend/app/services/ai_pmo_intent/models.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/models.py @@ -10,6 +10,7 @@ def _safe_int(value: Any) -> Optional[int]: except (TypeError, ValueError): return None +@dataclass class CanonicalIntent: name: str confidence: float @@ -18,6 +19,7 @@ class CanonicalIntent: signal_summary: str metadata: dict[str, Any] +@dataclass class WorkflowRoute: family: str action: str @@ -30,3 +32,5 @@ class WorkflowRoute: def key(self) -> str: return f"{self.family}.{self.action}" + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/ai_pmo_intent/route_resolver.py b/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py similarity index 68% rename from backend/app/services/ai_pmo_intent/route_resolver.py rename to backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py index 135f32bc..43e146f1 100644 --- a/backend/app/services/ai_pmo_intent/route_resolver.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py @@ -45,49 +45,11 @@ class RouteResolver: return INTENT_FOLLOWUP_CONTINUE return INTENT_QUERY - @staticmethod - def _parse_chinese_count_token(token: str) -> Optional[int]: - cleaned = str(token or "").strip() - if not cleaned: - return None - map_digit = { - "零": 0, - "一": 1, - "二": 2, - "两": 2, - "三": 3, - "四": 4, - "五": 5, - "六": 6, - "七": 7, - "八": 8, - "九": 9, - "十": 10, - } - if cleaned in map_digit: - return map_digit[cleaned] - if len(cleaned) == 2 and cleaned[0] == "十" and cleaned[1] in map_digit: - return 10 + map_digit[cleaned[1]] - if len(cleaned) == 2 and cleaned[1] == "十" and cleaned[0] in map_digit: - return map_digit[cleaned[0]] * 10 - if len(cleaned) == 3 and cleaned[1] == "十" and cleaned[0] in map_digit and cleaned[2] in map_digit: - return map_digit[cleaned[0]] * 10 + map_digit[cleaned[2]] - return None - @classmethod def extract_requested_count(cls, *, message_text: str, metadata: dict[str, Any]) -> int: metadata_count = _safe_int(metadata.get("requested_count")) if isinstance(metadata, dict) else None if metadata_count is not None and metadata_count > 0: return min(max(metadata_count, 1), 5) - normalized_text = str(message_text or "") - digit_match = re.search(r"(\d{1,2})\s*(?:个|条|项)?\s*(?:信号|候选)", normalized_text, flags=re.IGNORECASE) - if digit_match: - return min(max(int(digit_match.group(1)), 1), 5) - chinese_match = re.search(r"([零一二两三四五六七八九十]{1,3})\s*(?:个|条|项)?\s*(?:信号|候选)", normalized_text) - if chinese_match: - parsed = cls._parse_chinese_count_token(chinese_match.group(1)) - if parsed is not None and parsed > 0: - return min(max(parsed, 1), 5) return 1 @staticmethod @@ -95,22 +57,20 @@ class RouteResolver: explicit_route = str(metadata.get("workflow_route") or "").strip().lower() if isinstance(metadata, dict) else "" if explicit_route == ROUTE_PROJECT_SIGNAL_DISCOVER: return True - normalized = re.sub(r"\s+", "", str(message_text or "")).lower() - if not normalized: - return False - has_discover_hint = any(token in normalized for token in PROJECT_SIGNAL_DISCOVER_HINTS) - has_signal_hint = any(token in normalized for token in ("信号", "候选", "分析", "拆分")) - return has_discover_hint and has_signal_hint + explicit_action = str(metadata.get("workflow_action") or metadata.get("action") or "").strip().lower() if isinstance(metadata, dict) else "" + return explicit_action == ROUTE_ACTION_DISCOVER @staticmethod - def parse_followup_action(*, message_text: str, canonical_intent: str) -> str: - normalized = re.sub(r"\s+", "", str(message_text or "")).lower() - if any(token in normalized for token in FOLLOWUP_REFRESH_HINTS): - return ROUTE_ACTION_REFRESH - if any(token in normalized for token in FOLLOWUP_CANCEL_HINTS): - return ROUTE_ACTION_CANCEL - if any(token in normalized for token in FOLLOWUP_COMMIT_HINTS): - return ROUTE_ACTION_COMMIT + def parse_followup_action(*, metadata: dict[str, Any], canonical_intent: str) -> str: + explicit_action = str(metadata.get("followup_action") or metadata.get("action") or "").strip().lower() + if explicit_action in { + ROUTE_ACTION_CONTINUE, + ROUTE_ACTION_CONFIRM, + ROUTE_ACTION_COMMIT, + ROUTE_ACTION_CANCEL, + ROUTE_ACTION_REFRESH, + }: + return explicit_action if canonical_intent in {INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE}: return ROUTE_ACTION_COMMIT return ROUTE_ACTION_CONTINUE @@ -123,15 +83,17 @@ class RouteResolver: message_text: str, ) -> WorkflowRoute: metadata = dict(canonical_intent.metadata or {}) - route_key = LEGACY_INTENT_ROUTE_MAP.get(canonical_intent.name, ROUTE_QUERY_ANSWER) + explicit_route = cls.normalize_route_key(str(metadata.get("workflow_route") or "")) + route_key = explicit_route if explicit_route != ROUTE_QUERY_ANSWER or str(metadata.get("workflow_route") or "").strip() else "" + if not route_key: + route_key = LEGACY_INTENT_ROUTE_MAP.get(canonical_intent.name, ROUTE_QUERY_ANSWER) if canonical_intent.name == INTENT_SIGNAL_CREATE: - route_key = ( - ROUTE_PROJECT_SIGNAL_DISCOVER - if cls.looks_like_project_signal_discover(message_text=message_text, metadata=metadata) - else ROUTE_PROJECT_SIGNAL_CREATE - ) + if cls.looks_like_project_signal_discover(message_text=message_text, metadata=metadata): + route_key = ROUTE_PROJECT_SIGNAL_DISCOVER + elif route_key not in {ROUTE_PROJECT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_DISCOVER}: + route_key = ROUTE_PROJECT_SIGNAL_CREATE elif canonical_intent.name in {INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE}: - action = cls.parse_followup_action(message_text=message_text, canonical_intent=canonical_intent.name) + action = cls.parse_followup_action(metadata=metadata, canonical_intent=canonical_intent.name) if action == ROUTE_ACTION_CONFIRM: route_key = ROUTE_FOLLOWUP_CONFIRM else: @@ -215,4 +177,3 @@ class RouteResolver: requires_confirmation=requires_confirmation, metadata=metadata, ) - diff --git a/backend/app/services/ai_pmo_intent/shared.py b/backend/app/services/ai_pmo/ai_pmo_intent/shared.py similarity index 97% rename from backend/app/services/ai_pmo_intent/shared.py rename to backend/app/services/ai_pmo/ai_pmo_intent/shared.py index 63732811..6df8a788 100644 --- a/backend/app/services/ai_pmo_intent/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/shared.py @@ -6,8 +6,8 @@ import re from dataclasses import dataclass from typing import Any, Optional -from ...utils.project_command_utils import extract_project_create_title -from ..signal_source_orchestration import SignalSourceOrchestrationService +from ....utils.project_command_utils import extract_project_create_title +from ...signal.signal_source_orchestration import SignalSourceOrchestrationService INTENT_QUERY = "query.read_only" INTENT_PROJECT_CREATE = "project.create" @@ -209,4 +209,3 @@ WRITE_INTENTS = { } INTENT_CONFLICT_POLICIES = {"pending_manual", "prefer_rule", "prefer_llm"} - diff --git a/backend/app/services/ai_pmo_kernel/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/__init__.py similarity index 68% rename from backend/app/services/ai_pmo_kernel/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/__init__.py index ff2667aa..7b90a183 100644 --- a/backend/app/services/ai_pmo_kernel/__init__.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/__init__.py @@ -7,7 +7,14 @@ from .ai_agent_kernel import ( select_intent_route, ) from .ai_pmo_domain_workflow import SignalSourceDomainWorkflow -from .ai_pmo_kernel_runtime import AIPMOKernelService + + +def __getattr__(name: str): + if name == "AIPMOKernelService": + from .ai_pmo_kernel_runtime import AIPMOKernelService + + return AIPMOKernelService + raise AttributeError(name) __all__ = [ "AIPMOKernelService", diff --git a/backend/app/services/ai_pmo_kernel/ai_agent_kernel.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py similarity index 100% rename from backend/app/services/ai_pmo_kernel/ai_agent_kernel.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_domain_workflow.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_domain_workflow.py similarity index 98% rename from backend/app/services/ai_pmo_kernel/ai_pmo_domain_workflow.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_domain_workflow.py index 6dc6bfee..317aeacc 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_domain_workflow.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_domain_workflow.py @@ -6,7 +6,7 @@ from typing import Any, Optional from sqlalchemy.ext.asyncio import AsyncSession -from ..signal_source_orchestration import SignalSourceOrchestrationService +from ...signal.signal_source_orchestration import SignalSourceOrchestrationService class SignalSourceDomainWorkflow: diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_runtime.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_kernel_runtime.py similarity index 86% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_runtime.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_kernel_runtime.py index dd1aecb7..326f98fc 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_runtime.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_kernel_runtime.py @@ -8,11 +8,11 @@ for _name, _value in vars(_common).items(): if not _name.startswith("__"): globals()[_name] = _value -from .ai_pmo_kernel_mixins_closure import KernelClosureMixin -from .ai_pmo_kernel_mixins_followup import KernelFollowupMixin -from .ai_pmo_kernel_mixins_ingress import KernelIngressMixin -from .ai_pmo_kernel_mixins_intent import KernelIntentMixin -from .ai_pmo_kernel_mixins_world_model import KernelWorldModelMixin +from .compositions.closure import KernelClosureMixin +from .compositions.followup import KernelFollowupMixin +from .compositions.ingress import KernelIngressMixin +from .compositions.intent import KernelIntentMixin +from .compositions.world_model import KernelWorldModelMixin class AIPMOKernelService( @@ -54,4 +54,3 @@ class AIPMOKernelService( return async_sessionmaker(bind=bind, expire_on_commit=False, class_=AsyncSession) except Exception: return None - diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_reply_renderer.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py similarity index 100% rename from backend/app/services/ai_pmo_kernel/ai_pmo_reply_renderer.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_review_service.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_review_service.py similarity index 98% rename from backend/app/services/ai_pmo_kernel/ai_pmo_review_service.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_review_service.py index 1e70065e..bf589f61 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_review_service.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_review_service.py @@ -7,7 +7,7 @@ from typing import Any from sqlalchemy import and_, func, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import PortfolioSnapshot, ProjectSignalLink, ReplyReceipt, SignalSourceRegistry, WorkItem +from ....models import PortfolioSnapshot, ProjectSignalLink, ReplyReceipt, SignalSourceRegistry, WorkItem def _safe_int(value: Any) -> int | None: diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor.py similarity index 100% rename from backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor.py diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_common.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py similarity index 82% rename from backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_common.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py index 4193d2bc..4ecd2740 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_common.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py @@ -11,9 +11,9 @@ from typing import Any, Optional from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...config.ai_prompts import PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS -from ...core.logging import get_logger -from ...models import ( +from ....config.ai_prompts import PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS +from ....core.logging import get_logger +from ....models import ( Experiment, Project, ProjectSignalLink, @@ -23,8 +23,8 @@ from ...models import ( UserSkillBinding, UserWorkload, ) -from ...utils.timezone import now_cn_naive -from ..adaptive_rag_service import AdaptiveRAGService +from ....utils.timezone import now_cn_naive +from ...adaptive_rag_service import AdaptiveRAGService from .ai_pmo_domain_workflow import SignalSourceDomainWorkflow from ..ai_pmo_intent import ( INTENT_PROJECT_CREATE, @@ -40,10 +40,10 @@ from ..ai_pmo_intent import ( IntentResolver, RouteResolver, ) -from ..ai_steward_service import AIStewardService -from ..llm_service import llm_service -from ..model_context_bundle import ModelContextBundleService -from ..signal_planner import SignalPlannerService +from ...ai_steward_service import AIStewardService +from ...llm_service import llm_service +from ...model.model_context_bundle import ModelContextBundleService +from ...signal.signal_planner import SignalPlannerService logger = get_logger(__name__) @@ -75,5 +75,3 @@ def _safe_int(value: Any) -> Optional[int]: return int(value) except (TypeError, ValueError): return None - - diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py similarity index 100% rename from backend/app/services/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_workflow_runtime.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py similarity index 98% rename from backend/app/services/ai_pmo_kernel/ai_pmo_workflow_runtime.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py index 12d54927..e423845b 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_workflow_runtime.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py @@ -4,8 +4,8 @@ from __future__ import annotations from typing import Optional, TYPE_CHECKING -from ...models import ConversationSession, IngestEvent, Project, WorkItem -from ...schemas.world_model import WorldModel +from ....models import ConversationSession, IngestEvent, Project, WorkItem +from ....schemas.world_model import WorldModel from ..ai_pmo_intent import ( INTENT_FOLLOWUP_CONTINUE, ROUTE_ACTION_DISCOVER, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/__init__.py new file mode 100644 index 00000000..7a3aec4f --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/__init__.py @@ -0,0 +1,15 @@ +"""Kernel composition mixins grouped by domain.""" + +from .closure import KernelClosureMixin +from .followup import KernelFollowupMixin +from .ingress import KernelIngressMixin +from .intent import KernelIntentMixin +from .world_model import KernelWorldModelMixin + +__all__ = [ + "KernelClosureMixin", + "KernelFollowupMixin", + "KernelIngressMixin", + "KernelIntentMixin", + "KernelWorldModelMixin", +] diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure.py b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/closure.py similarity index 51% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/compositions/closure.py index dfe8e169..48aa38cc 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/closure.py @@ -2,9 +2,9 @@ from __future__ import annotations -from .ai_pmo_kernel_mixins_closure_data import KernelClosureDataMixin -from .ai_pmo_kernel_mixins_closure_helpers import KernelClosureHelpersMixin -from .ai_pmo_kernel_mixins_closure_payload import KernelClosurePayloadMixin +from ..mixins.closure.ai_pmo_kernel_mixins_closure_data import KernelClosureDataMixin +from ..mixins.closure.ai_pmo_kernel_mixins_closure_helpers import KernelClosureHelpersMixin +from ..mixins.closure.ai_pmo_kernel_mixins_closure_payload import KernelClosurePayloadMixin class KernelClosureMixin( diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/followup.py b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/followup.py new file mode 100644 index 00000000..b53d32e4 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/followup.py @@ -0,0 +1,23 @@ +"""AI PMO kernel followup mixin composition.""" + +from __future__ import annotations + +from ..mixins.followup.ai_pmo_kernel_mixins_followup_project_signal import ( + KernelFollowupProjectSignalMixin, +) +from ..mixins.followup.ai_pmo_kernel_mixins_followup_records import KernelFollowupRecordsMixin +from ..mixins.followup.ai_pmo_kernel_mixins_followup_routing import KernelFollowupRoutingMixin +from ..mixins.followup.ai_pmo_kernel_mixins_followup_verification import ( + KernelFollowupVerificationMixin, +) + + +class KernelFollowupMixin( + KernelFollowupRecordsMixin, + KernelFollowupRoutingMixin, + KernelFollowupProjectSignalMixin, + KernelFollowupVerificationMixin, +): + """Compose followup concerns into a single kernel mixin.""" + + pass diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress.py b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/ingress.py similarity index 52% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/compositions/ingress.py index 0fc079fc..08f26867 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/ingress.py @@ -2,9 +2,9 @@ from __future__ import annotations -from ..ai_pmo_kernel_ingress_flow import KernelIngressFlowMixin -from .ai_pmo_kernel_mixins_ingress_render import KernelIngressRenderMixin -from .ai_pmo_kernel_mixins_ingress_storage import KernelIngressStorageMixin +from ...ai_pmo_kernel_ingress_flow import KernelIngressFlowMixin +from ..mixins.ingress.ai_pmo_kernel_mixins_ingress_render import KernelIngressRenderMixin +from ..mixins.ingress.ai_pmo_kernel_mixins_ingress_storage import KernelIngressStorageMixin class KernelIngressMixin( diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent.py b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/intent.py similarity index 52% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/compositions/intent.py index 87ee2a5f..37051ec0 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/intent.py @@ -2,9 +2,9 @@ from __future__ import annotations -from .ai_pmo_kernel_mixins_intent_llm import KernelIntentLLMMixin -from .ai_pmo_kernel_mixins_intent_resolve import KernelIntentResolveMixin -from .ai_pmo_kernel_mixins_intent_rules import KernelIntentRulesMixin +from ..mixins.intent.ai_pmo_kernel_mixins_intent_llm import KernelIntentLLMMixin +from ..mixins.intent.ai_pmo_kernel_mixins_intent_resolve import KernelIntentResolveMixin +from ..mixins.intent.ai_pmo_kernel_mixins_intent_rules import KernelIntentRulesMixin class KernelIntentMixin( diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model.py b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/world_model.py similarity index 53% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/compositions/world_model.py index 306bc70f..4e9f5952 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/compositions/world_model.py @@ -2,9 +2,11 @@ from __future__ import annotations -from ..ai_pmo_kernel_world_model_aggregates import KernelWorldModelAggregatesMixin -from ..ai_pmo_kernel_world_model_core import KernelWorldModelCoreMixin -from .ai_pmo_kernel_mixins_world_model_session import KernelWorldModelSessionMixin +from ...ai_pmo_kernel_world_model_aggregates import KernelWorldModelAggregatesMixin +from ...ai_pmo_kernel_world_model_core import KernelWorldModelCoreMixin +from ..mixins.world_model.ai_pmo_kernel_mixins_world_model_session import ( + KernelWorldModelSessionMixin, +) class KernelWorldModelMixin( diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/__init__.py new file mode 100644 index 00000000..f7019d08 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/__init__.py @@ -0,0 +1 @@ +"""AI PMO kernel mixin groups.""" diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/__init__.py new file mode 100644 index 00000000..2c29b535 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/__init__.py @@ -0,0 +1 @@ +"""Closure-oriented kernel mixins.""" diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_data.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_data.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py index 34cf7b72..c8a2adae 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_data.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_helpers.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_helpers.py similarity index 98% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_helpers.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_helpers.py index 542cdc0d..e7e82003 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_helpers.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_helpers.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_payload.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_payload.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py index 798a0beb..413697a4 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_closure_payload.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/__init__.py new file mode 100644 index 00000000..312f05c8 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/__init__.py @@ -0,0 +1 @@ +"""Followup-oriented kernel mixins.""" diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal.py similarity index 100% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal.py diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_actions.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_actions.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_actions.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_actions.py index b48ad7d5..c5472665 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_actions.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_actions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_resume.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_resume.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_resume.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_resume.py index ab367700..e50196ed 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_project_signal_resume.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_project_signal_resume.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_records.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_records.py similarity index 98% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_records.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_records.py index ded6b3c3..c70c9d0f 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_records.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_records.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_routing.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_routing.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py index 98b9c92e..a9ee459f 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_routing.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_verification.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_verification.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py index 3a97429b..a78fa435 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup_verification.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/__init__.py new file mode 100644 index 00000000..1c1f140a --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/__init__.py @@ -0,0 +1 @@ +"""Ingress-oriented kernel mixins.""" diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_render.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py similarity index 93% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_render.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py index 9950ee77..5dbc9782 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_render.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): @@ -23,6 +23,16 @@ class KernelIngressRenderMixin: ) -> dict[str, Any]: linked_project_ids = [int(work_item.project_id)] if work_item.project_id is not None else [] direct_reply_text = str(render.get("text") or "").strip() + mixed_sidecar_reply = ( + str((intent.metadata or {}).get("mixed_sidecar_reply") or "").strip() + if isinstance(intent.metadata, dict) + else "" + ) + if mixed_sidecar_reply: + if direct_reply_text and direct_reply_text != mixed_sidecar_reply: + direct_reply_text = f"{mixed_sidecar_reply}\n\n{direct_reply_text}" + else: + direct_reply_text = mixed_sidecar_reply if _looks_like_upstream_timeout_message(direct_reply_text): logger.warning( "ai_pmo_upstream_timeout_reply_detected", @@ -169,4 +179,3 @@ class KernelIngressRenderMixin: "status": str(project.status or ""), "owner_id": _safe_int(project.owner_id), } - diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_storage.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py similarity index 99% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_storage.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py index 36055cbe..2e2cc63d 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_ingress_storage.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/__init__.py new file mode 100644 index 00000000..ae5970f8 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/__init__.py @@ -0,0 +1 @@ +"""Intent-oriented kernel mixins.""" diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_llm.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py similarity index 79% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_llm.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py index 6682a219..583fe361 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_llm.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): @@ -10,6 +10,8 @@ for _name, _value in vars(_common).items(): class KernelIntentLLMMixin: + _STAGE2_INTENT_FUNCTION_NAME = "classify_intent_complex" + async def _resolve_intent_with_llm( self, *, @@ -18,17 +20,6 @@ class KernelIntentLLMMixin: world_model: dict[str, Any], rule_intent: Optional[CanonicalIntent] = None, ) -> Optional[CanonicalIntent]: - project_snapshot = world_model.get("project") if isinstance(world_model.get("project"), dict) else {} - project_names = [ - str(project_snapshot.get("title") or "").strip(), - str(project_snapshot.get("name") or "").strip(), - ] - route_decision = select_intent_route(message_text, project_names=project_names) - correction_decision = kernel_resolve_correction(message_text) - forced_low_tier = _should_force_low_tier_intent_route( - message_text=message_text, - correction_action=correction_decision.action, - ) prompt = PROMPT_AI_PMO_INTENT_CLASSIFICATION.format( message_text=str(message_text or "")[:1200], session_last_intent=str(getattr(session, "last_intent", "") or "").strip() if session is not None else "", @@ -48,6 +39,10 @@ class KernelIntentLLMMixin: ).strip() total_budget_seconds = _intent_llm_total_budget_seconds() stage_started_at = time.perf_counter() + stage1_timeout_cap_seconds = max( + 6.0, + float(os.getenv("AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS", "8") or 8.0), + ) async def _invoke_intent_stage( *, @@ -62,6 +57,19 @@ class KernelIntentLLMMixin: llm_timeout_seconds = min(llm_timeout_seconds, max(float(timeout_cap_seconds), 0.0)) if llm_timeout_seconds <= 0.0: return None + max_tokens_default = 260 if route_tier == "low" else 360 + max_tokens_env_key = ( + "AI_PMO_INTENT_LLM_MAX_TOKENS_LOW" + if route_tier == "low" + else "AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH" + ) + try: + llm_max_tokens = int( + str(os.getenv(max_tokens_env_key, str(max_tokens_default)) or max_tokens_default).strip() + ) + except Exception: + llm_max_tokens = max_tokens_default + llm_max_tokens = max(120, min(llm_max_tokens, 2800)) logger.info( "ai_pmo_intent_llm_route", tenant_id=self.tenant_id, @@ -69,9 +77,7 @@ class KernelIntentLLMMixin: function_name=function_name, route_tier=route_tier, timeout_seconds=llm_timeout_seconds, - forced_low_tier=forced_low_tier, - correction_action=correction_decision.action, - correction_keywords=correction_decision.matched_keywords, + max_tokens=llm_max_tokens, route_reasons=route_reasons, total_budget_seconds=total_budget_seconds, ) @@ -84,7 +90,7 @@ class KernelIntentLLMMixin: function_name=function_name, use_prompt_profile=False, temperature=0.0, - max_tokens=260, + max_tokens=llm_max_tokens, response_mode="fast", ), timeout=llm_timeout_seconds, @@ -147,8 +153,8 @@ class KernelIntentLLMMixin: route_function=function_name, gate_action="unresolved", gate_code="intent_only", - correction_action=correction_decision.action, - correction_keywords=correction_decision.matched_keywords, + correction_action="none", + correction_keywords=[], complexity_reasons=route_reasons, ) return self._build_canonical_intent( @@ -164,11 +170,10 @@ class KernelIntentLLMMixin: "route_tier": route_tier, "route_complex": route_tier == "high", "route_reasons": route_reasons, - "forced_low_tier": forced_low_tier, "timeout_seconds": llm_timeout_seconds, "total_budget_seconds": total_budget_seconds, - "correction_action": correction_decision.action, - "correction_keywords": correction_decision.matched_keywords, + "correction_action": "none", + "correction_keywords": [], "decision_trace": decision_trace, "workflow_route": structured_route.key if structured_route is not None else LEGACY_INTENT_ROUTE_MAP.get(intent_name, ROUTE_QUERY_ANSWER), "workflow_family": structured_route.family if structured_route is not None else RouteResolver._split_route_key( @@ -190,25 +195,39 @@ class KernelIntentLLMMixin: }, ) - low_stage_reasons = ["stage1_low_first", *list(route_decision.reasons or [])] - if forced_low_tier: - low_stage_reasons.append("correction_forced_low_tier") + low_stage_reasons = ["llm_only_stage1"] low_intent = await _invoke_intent_stage( function_name=INTENT_LLM_FUNCTION_NAME, route_tier="low", route_reasons=low_stage_reasons, stage_label="stage1_low", + timeout_cap_seconds=stage1_timeout_cap_seconds, ) - if forced_low_tier: - return low_intent - should_escalate, escalate_reasons = _should_escalate_to_high_intent_route( - route_is_complex=bool(route_decision.is_complex), - low_intent=low_intent, - rule_intent=rule_intent, - ) + escalate_reasons: list[str] = [] + if low_intent is None: + escalate_reasons.append("stage1_empty") + elif float(low_intent.confidence or 0.0) < _intent_llm_stage2_confidence_threshold(): + escalate_reasons.append("stage1_low_confidence") + if rule_intent is not None and low_intent is not None and str(rule_intent.name or "") != str(low_intent.name or ""): + escalate_reasons.append("stage1_rule_conflict") + should_escalate = bool(escalate_reasons) if not should_escalate: return low_intent + stage2_enabled = str(os.getenv("AI_PMO_INTENT_STAGE2_ENABLED", "true") or "true").strip().lower() in BOOL_TRUE_VALUES + if low_intent is not None and not stage2_enabled: + logger.info( + "ai_pmo_intent_stage2_skipped", + tenant_id=self.tenant_id, + reason="stage2_disabled", + escalate_reasons=escalate_reasons, + ) + return self._copy_intent_with_metadata( + low_intent, + stage2_skipped=True, + stage2_skip_reason="stage2_disabled", + stage2_reasons=escalate_reasons, + ) elapsed_seconds = time.perf_counter() - stage_started_at remaining_budget_seconds = total_budget_seconds - elapsed_seconds @@ -231,9 +250,9 @@ class KernelIntentLLMMixin: ) return None - high_stage_reasons = [*list(route_decision.reasons or []), *escalate_reasons, "stage2_high_retry"] + high_stage_reasons = [*escalate_reasons, "stage2_high_retry"] high_intent = await _invoke_intent_stage( - function_name="classify_intent_complex", + function_name=self._STAGE2_INTENT_FUNCTION_NAME, route_tier="high", route_reasons=high_stage_reasons, stage_label="stage2_high", @@ -256,4 +275,3 @@ class KernelIntentLLMMixin: stage2_reasons=escalate_reasons, ) return None - diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py new file mode 100644 index 00000000..0fd48b78 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py @@ -0,0 +1,51 @@ +"""AI PMO kernel intent resolve mixin.""" + +from __future__ import annotations + +from .... import ai_pmo_kernel_common_core as _common + +for _name, _value in vars(_common).items(): + if not _name.startswith("__"): + globals()[_name] = _value + + +class KernelIntentResolveMixin: + async def _resolve_intent( + self, + *, + message_text: str, + session: Optional[ConversationSession], + world_model: dict[str, Any], + ) -> tuple[CanonicalIntent, str]: + if self._is_short_confirmation_message( + message_text=message_text, + session=session, + ) and session is not None: + inherited = self._build_short_confirm_intent( + message_text=message_text, + session=session, + world_model=world_model, + ) + logger.info( + "ai_pmo_short_confirm_inherited", + tenant_id=self.tenant_id, + sender=str(getattr(session, "sender", "") or ""), + inherited_from=str(getattr(session, "last_intent", "") or ""), + inherited_intent=str(inherited.name or ""), + project_id=_safe_int(getattr(session, "current_project_id", None)) or _safe_int(world_model.get("resolved_project_id")), + ) + return inherited, "session_inherited" + + # 模型唯一入口:不再使用规则/正则意图分流。 + if not self._intent_llm_enabled(): + raise RuntimeError("intent_model_only_mode_requires_llm_enabled") + + llm_intent = await self._resolve_intent_with_llm( + message_text=message_text, + session=session, + world_model=world_model, + rule_intent=None, + ) + if llm_intent is None: + raise RuntimeError("intent_model_only_no_valid_llm_intent") + return llm_intent, "llm_only" diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_rules.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py similarity index 71% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_rules.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py index 273df9b0..36d430db 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_rules.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): @@ -10,6 +10,42 @@ for _name, _value in vars(_common).items(): class KernelIntentRulesMixin: + CONTEXTUAL_CONFIRM_HINTS = ( + "来吧", + "那就", + "就这么办", + "就按这个", + "可以", + "好的", + "不错", + "开始吧", + "行", + "没问题", + "同意", + "确认", + ) + CONTEXTUAL_DEICTIC_HINTS = ( + "这个", + "这个项目", + "这条", + "它", + "按这个", + "照这个", + ) + CONTEXTUAL_WRITE_ACTION_HINTS = ( + "创建", + "新建", + "新增", + "增加", + "添加", + "执行", + "开始", + "继续处理", + "继续执行", + "推进", + "落地", + ) + @staticmethod def _intent_llm_enabled() -> bool: return _env_bool("AI_PMO_INTENT_LLM_ENABLED", True) @@ -57,6 +93,80 @@ class KernelIntentRulesMixin: metadata = intent.metadata if isinstance(intent.metadata, dict) else {} return bool(metadata.get("fallback")) and str(metadata.get("reason") or "") == "default_query_fallback" + @staticmethod + def _has_question_structure(*, normalized: str) -> bool: + if not normalized: + return False + return ( + "?" in normalized + or "?" in normalized + or any(token in normalized for token in ("怎么", "如何", "为何", "为什么", "是什么", "有哪些", "吗", "呢", "是否", "是不是")) + ) + + @classmethod + def _looks_like_contextual_write_confirmation(cls, *, message_text: str) -> bool: + normalized = re.sub(r"\s+", "", str(message_text or "")).lower() + if not normalized: + return False + if cls._has_question_structure(normalized=normalized): + return False + has_write_action = any(token in normalized for token in cls.CONTEXTUAL_WRITE_ACTION_HINTS) + if not has_write_action: + return False + has_confirm = any(token in normalized for token in cls.CONTEXTUAL_CONFIRM_HINTS) + has_deictic = any(token in normalized for token in cls.CONTEXTUAL_DEICTIC_HINTS) + return has_confirm or has_deictic + + def _recover_session_followup_write_intent( + self, + *, + message_text: str, + session: Optional[ConversationSession], + world_model: dict[str, Any], + rule_intent: CanonicalIntent, + ) -> Optional[CanonicalIntent]: + if session is None: + return None + if rule_intent.name != INTENT_QUERY: + return None + if not self._within_recent_window( + getattr(session, "last_active_at", None), + window_seconds=SESSION_SHORT_CONFIRM_WINDOW_SECONDS, + ): + return None + if not (self._is_default_rule_query(rule_intent) or float(rule_intent.confidence or 0.0) <= 0.7): + return None + if not self._looks_like_contextual_write_confirmation(message_text=message_text): + return None + + inherited_name = str(getattr(session, "last_intent", "") or "").strip() + inherited_text = str(getattr(session, "last_message_text", "") or "").strip() + if inherited_name not in WRITE_INTENTS and inherited_text: + inherited_project_id = _safe_int(getattr(session, "current_project_id", None)) + inferred = self.intent_resolver.resolve(text=inherited_text, project_id=inherited_project_id) + if inferred.name in WRITE_INTENTS: + inherited_name = inferred.name + + if inherited_name not in WRITE_INTENTS: + return None + + project_id = _safe_int(getattr(session, "current_project_id", None)) or _safe_int(world_model.get("resolved_project_id")) + if project_id is not None and int(project_id) <= 0: + project_id = None + + canonical_text = inherited_text or message_text + return self._build_canonical_intent( + intent_name=inherited_name, + message_text=canonical_text, + project_id=project_id, + confidence=0.84, + metadata={ + "context_source": "session_write_recovered", + "inherited_from_intent": str(getattr(session, "last_intent", "") or "").strip(), + "trigger_message": re.sub(r"\s+", " ", str(message_text or "")).strip()[:120], + }, + ) + @staticmethod def _has_required_slots(*, intent: CanonicalIntent, message_text: str) -> tuple[bool, dict[str, Any]]: if intent.name != INTENT_PROJECT_CREATE: diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/__init__.py new file mode 100644 index 00000000..04db0d45 --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/__init__.py @@ -0,0 +1 @@ +"""World-model-oriented kernel mixins.""" diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model_session.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py similarity index 98% rename from backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model_session.py rename to backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py index d4d9de5c..cbe121b4 100644 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_world_model_session.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .. import ai_pmo_kernel_common_core as _common +from .... import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): diff --git a/backend/app/services/ai_pmo_kernel_common_core/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_common_core/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_common_core/__init__.py diff --git a/backend/app/services/ai_pmo_kernel_common_core/constants.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py similarity index 94% rename from backend/app/services/ai_pmo_kernel_common_core/constants.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py index 1ea320da..b891b50e 100644 --- a/backend/app/services/ai_pmo_kernel_common_core/constants.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py @@ -52,9 +52,9 @@ POLICY_GATE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = ( BOOL_TRUE_VALUES = {"1", "true", "yes", "y", "on", "是"} SESSION_PROJECT_INHERIT_WINDOW_SECONDS = 30 * 60 INTENT_LLM_TIMEOUT_DEFAULT_SECONDS = 30.0 -INTENT_LLM_TIMEOUT_COMPLEX_DEFAULT_SECONDS = 30.0 -INTENT_LLM_TIMEOUT_FALLBACK_DEFAULT_SECONDS = 35.0 -INTENT_LLM_TOTAL_BUDGET_DEFAULT_SECONDS = 55.0 +INTENT_LLM_TIMEOUT_COMPLEX_DEFAULT_SECONDS = 55.0 +INTENT_LLM_TIMEOUT_FALLBACK_DEFAULT_SECONDS = 45.0 +INTENT_LLM_TOTAL_BUDGET_DEFAULT_SECONDS = 70.0 INTENT_LLM_STAGE2_CONFIDENCE_DEFAULT = 0.72 INTENT_LLM_STAGE2_MIN_REMAINING_DEFAULT_SECONDS = 12.0 diff --git a/backend/app/services/ai_pmo_kernel_common_core/env.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/env.py similarity index 95% rename from backend/app/services/ai_pmo_kernel_common_core/env.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_common_core/env.py index 9c87a897..17274eb3 100644 --- a/backend/app/services/ai_pmo_kernel_common_core/env.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/env.py @@ -48,7 +48,7 @@ def intent_llm_timeout_seconds(*, function_name: str) -> float: else: default_timeout = INTENT_LLM_TIMEOUT_DEFAULT_SECONDS configured = env_float("AI_PMO_INTENT_LLM_TIMEOUT_SECONDS", default_timeout) - return min(max(float(configured), 12.0), 120.0) + return min(max(float(configured), 12.0), 300.0) def intent_llm_total_budget_seconds() -> float: @@ -56,7 +56,7 @@ def intent_llm_total_budget_seconds() -> float: "AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS", INTENT_LLM_TOTAL_BUDGET_DEFAULT_SECONDS, ) - return min(max(float(configured), 20.0), 120.0) + return min(max(float(configured), 20.0), 300.0) def intent_llm_stage2_confidence_threshold() -> float: diff --git a/backend/app/services/ai_pmo_kernel_common_core/helpers.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py similarity index 97% rename from backend/app/services/ai_pmo_kernel_common_core/helpers.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py index 023869fa..017d0dfd 100644 --- a/backend/app/services/ai_pmo_kernel_common_core/helpers.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py @@ -108,3 +108,6 @@ def _normalize_signal_id_list(value: Any) -> list[int]: seen.add(signal_id) ids.append(signal_id) return ids + + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/ai_pmo_kernel_common_core/imports.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py similarity index 84% rename from backend/app/services/ai_pmo_kernel_common_core/imports.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py index 0e3e7c78..fa2bc2cf 100644 --- a/backend/app/services/ai_pmo_kernel_common_core/imports.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py @@ -15,12 +15,12 @@ from sqlalchemy import and_, func, or_, select from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker -from ...config.ai_prompts import ( +from ....config.ai_prompts import ( PROMPT_AI_PMO_INTENT_CLASSIFICATION, PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS, ) -from ...core.logging import get_logger -from ...models import ( +from ....core.logging import get_logger +from ....models import ( AIProcessingLog, Budget, ConversationSession, @@ -46,7 +46,7 @@ from ...models import ( UserWorkload, WorkItem, ) -from ...schemas.world_model import ( +from ....schemas.world_model import ( BlockedTask, BudgetSummary, CapacityItem, @@ -61,8 +61,8 @@ from ...schemas.world_model import ( TaskSummary, WorldModel, ) -from ...utils.timezone import now_cn_naive -from ..adaptive_rag_service import AdaptiveRAGService +from ....utils.timezone import now_cn_naive +from ...adaptive_rag_service import AdaptiveRAGService from ..ai_pmo_kernel import ( build_decision_trace, gate_execution as kernel_gate_execution, @@ -123,11 +123,11 @@ from ..ai_pmo_kernel.ai_pmo_reply_renderer import ReplyRenderer from ..ai_pmo_kernel.ai_pmo_review_service import AIPMOReviewService from ..ai_pmo_kernel.ai_pmo_typed_executor import TypedExecutor from ..ai_pmo_kernel.ai_pmo_workflow_runtime import ContextAssembler, WorkflowRegistry -from ..ai_steward_service import AIStewardService -from ..context_cache_service import ContextCacheService -from ..llm_service import llm_service -from ..model_context_bundle import ModelContextBundleService -from ..notification_service import NotificationService -from ..signal_planner import SignalPlannerService +from ...ai_steward_service import AIStewardService +from ...context_cache_service import ContextCacheService +from ...llm_service import llm_service +from ...model.model_context_bundle import ModelContextBundleService +from ...notification_service import NotificationService +from ...signal.signal_planner import SignalPlannerService logger = get_logger(__name__) diff --git a/backend/app/services/ai_pmo_kernel_ingress_flow/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_ingress_flow/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/__init__.py diff --git a/backend/app/services/ai_pmo_kernel_ingress_flow/mixin_auto.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py similarity index 79% rename from backend/app/services/ai_pmo_kernel_ingress_flow/mixin_auto.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py index e29fb2b6..293a0ee9 100644 --- a/backend/app/services/ai_pmo_kernel_ingress_flow/mixin_auto.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py @@ -106,6 +106,17 @@ class KernelIngressFlowAutoMixin: }, execution_result=execution_result, ) + capacity_warning_text = self._build_capacity_warning_text( + policy=decision_payload["policy"] if isinstance(decision_payload.get("policy"), dict) else {}, + world_model=world_model, + execution_result=execution_result if isinstance(execution_result, dict) else {}, + ) + if capacity_warning_text: + base_text = str(render.get("text") or "").strip() + render["text"] = f"{base_text}\n\n{capacity_warning_text}" if base_text else capacity_warning_text + payload = render.get("payload") + if isinstance(payload, dict): + payload["capacity_warning_text"] = capacity_warning_text work_item.reply_template_key = str(render["template_key"]) direct_reply_required = await self._claim_reply_receipt( work_item_id=int(work_item.id), @@ -185,3 +196,41 @@ class KernelIngressFlowAutoMixin: log_perf_fn("_build_capture_result", stage_started_at) return capture_result + @staticmethod + def _build_capacity_warning_text( + *, + policy: dict[str, Any], + world_model: dict[str, Any], + execution_result: dict[str, Any], + ) -> Optional[str]: + warning = policy.get("capacity_warning") if isinstance(policy.get("capacity_warning"), dict) else {} + if not bool(warning.get("enabled")): + return None + + project_name = "当前项目" + world_project = world_model.get("project") if isinstance(world_model.get("project"), dict) else {} + execution_project = execution_result.get("project") if isinstance(execution_result.get("project"), dict) else {} + for source in (world_project, execution_project): + candidate = str(source.get("title") or source.get("name") or "").strip() + if candidate: + project_name = candidate + break + + peak_ratio = 0.0 + try: + peak_ratio = float(warning.get("peak_ratio") or 0.0) + except (TypeError, ValueError): + peak_ratio = 0.0 + peak_percent = max(0.0, peak_ratio * 100.0) + if abs(peak_percent - round(peak_percent)) < 0.05: + percent_text = f"{int(round(peak_percent))}%" + else: + percent_text = f"{peak_percent:.1f}%" + + return ( + "⚠ 产能提示:\n" + f"当前{project_name}团队负载已达{percent_text}\n" + "建议:\n" + "· 为项目增加成员\n" + "· 或调整现有任务优先级" + ) diff --git a/backend/app/services/ai_pmo_kernel_ingress_flow/mixin_entry.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py similarity index 73% rename from backend/app/services/ai_pmo_kernel_ingress_flow/mixin_entry.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py index 309a03f8..39f873c1 100644 --- a/backend/app/services/ai_pmo_kernel_ingress_flow/mixin_entry.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py @@ -3,6 +3,89 @@ from __future__ import annotations from .shared import * class KernelIngressFlowEntryMixin: + _MIXED_HINT_CONNECTORS = ("顺便", "另外", "同时", "并且", "还有", "再问") + _MIXED_HINT_QUESTION = ("?", "?", "问下", "多少", "几个", "进展", "状态", "吗", "呢", "如何", "怎么", "有没有") + _MIXED_HINT_REQUIREMENT = ("创建", "新建", "新增", "安排", "执行", "处理", "接入", "录入", "添加") + + @classmethod + def _looks_like_mixed_candidate(cls, *, text: str) -> bool: + normalized = re.sub(r"\s+", "", str(text or "")).lower() + if not normalized: + return False + has_question = any(token in normalized for token in cls._MIXED_HINT_QUESTION) + has_requirement = any(token in normalized for token in cls._MIXED_HINT_REQUIREMENT) + if not (has_question and has_requirement): + return False + if any(token in normalized for token in cls._MIXED_HINT_CONNECTORS): + return True + return "问" in normalized and "创建" in normalized + + @staticmethod + def _supports_mixed_sidecar(*, source_system: str) -> bool: + return str(source_system or "").strip().lower() in {"feishu", "pmflow_manual", "ai_pmo_ui"} + + async def _prepare_mixed_sidecar_context( + self, + *, + source_system: str, + source_message_id: str, + channel_ref: Optional[str], + message_text: str, + project_hint_id: Optional[int], + ) -> tuple[str, dict[str, Any]]: + if not self._supports_mixed_sidecar(source_system=source_system): + return str(message_text or "").strip(), {} + if not self._looks_like_mixed_candidate(text=message_text): + return str(message_text or "").strip(), {} + + rag_service = AdaptiveRAGService(self.db, tenant_id=self.tenant_id) + try: + intent_result = await rag_service.classify_intent(str(message_text or "").strip()) + except Exception as exc: + logger.warning( + "ai_pmo_mixed_precheck_failed", + tenant_id=self.tenant_id, + source_system=source_system, + error=str(exc), + ) + return str(message_text or "").strip(), {} + + intent_label = str(intent_result.get("intent") or "").strip().lower() + if intent_label != "mixed": + return str(message_text or "").strip(), {} + + question_part = str(intent_result.get("question_part") or "").strip() or str(message_text or "").strip() + signal_part = str(intent_result.get("statement_part") or "").strip() or str(message_text or "").strip() + sidecar_answer = "" + try: + rag_result = await rag_service.answer_question( + question=question_part, + asked_by=None, + asked_via=source_system, + project_id=project_hint_id, + source_chat_id=channel_ref, + conversation_key=source_message_id, + message_intent="mixed", + mixed_signal_part=signal_part, + ) + sidecar_answer = str(rag_result.get("answer") or "").strip() + except Exception as exc: + logger.warning( + "ai_pmo_mixed_sidecar_answer_failed", + tenant_id=self.tenant_id, + source_system=source_system, + error=str(exc), + ) + + mixed_meta: dict[str, Any] = { + "mixed_intent": True, + "mixed_question_part": question_part[:500], + "mixed_signal_part": signal_part[:800], + } + if sidecar_answer: + mixed_meta["mixed_sidecar_reply"] = sidecar_answer[:3200] + return signal_part, mixed_meta + async def process_feishu_message( self, *, @@ -70,6 +153,18 @@ class KernelIngressFlowEntryMixin: operator = ingress_prepare["operator"] work_item = ingress_prepare["work_item"] session = ingress_prepare["session"] + mixed_meta: dict[str, Any] = {} + if not self._is_short_confirmation_message( + message_text=clean_text, + session=session, + ): + clean_text, mixed_meta = await self._prepare_mixed_sidecar_context( + source_system=source_system, + source_message_id=synthetic_message_id, + channel_ref=channel_ref, + message_text=clean_text, + project_hint_id=project_hint_id, + ) perf_work_item_id = int(work_item.id) try: stage_started_at = time.perf_counter() @@ -93,18 +188,11 @@ class KernelIngressFlowEntryMixin: message_text=clean_text, closure_payload=closure_payload, ) - if intent.name in WRITE_INTENTS and IntentResolver._looks_like_negated_write_statement(text=clean_text): - metadata = dict(intent.metadata or {}) - metadata["guard_reason"] = "negated_write_statement" - metadata["guarded_from_intent"] = intent.name - intent = CanonicalIntent( - INTENT_QUERY, - max(float(intent.confidence or 0.0), 0.9), - intent.project_id, - "", - clean_text[:300], - metadata, - ) + if mixed_meta: + intent.metadata = { + **(intent.metadata or {}), + **mixed_meta, + } workflow_route = self.route_resolver.from_canonical( canonical_intent=intent, message_text=clean_text, @@ -129,10 +217,7 @@ class KernelIngressFlowEntryMixin: world_model["context_source"] = intent_context_source work_item.context_snapshot = world_model resolved_project_for_session = _safe_int(workflow_route.project_id) or _safe_int(world_model.get("resolved_project_id")) - fallback_intent = self.intent_resolver.resolve( - text=clean_text, - project_id=_safe_int(world_model.get("resolved_project_id")), - ) + fallback_intent = intent if intent.name == INTENT_IGNORE: return await self._finalize_ignored_ingress( work_item=work_item, @@ -273,4 +358,3 @@ class KernelIngressFlowEntryMixin: error=str(exc), ) return {"handled": False, "reason": "kernel_exception", "error": str(exc)} - diff --git a/backend/app/services/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py diff --git a/backend/app/services/ai_pmo_kernel_ingress_flow/service.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/service.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_ingress_flow/service.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/service.py diff --git a/backend/app/services/ai_pmo_kernel_ingress_flow/shared.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/shared.py similarity index 78% rename from backend/app/services/ai_pmo_kernel_ingress_flow/shared.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/shared.py index 2b68a92a..8d1de1e5 100644 --- a/backend/app/services/ai_pmo_kernel_ingress_flow/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/shared.py @@ -9,3 +9,5 @@ for _name, _value in vars(_common).items(): globals()[_name] = _value +__all__ = [name for name in globals() if not name.startswith("__")] + diff --git a/backend/app/services/ai_pmo_kernel_world_model_aggregates/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_aggregates/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/__init__.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_aggregates/mixin_basics.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/mixin_basics.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_aggregates/mixin_basics.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/mixin_basics.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_aggregates/mixin_project.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/mixin_project.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_aggregates/mixin_project.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/mixin_project.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_aggregates/service.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/service.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_aggregates/service.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/service.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_aggregates/shared.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/shared.py similarity index 79% rename from backend/app/services/ai_pmo_kernel_world_model_aggregates/shared.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/shared.py index 90514154..b2ad655c 100644 --- a/backend/app/services/ai_pmo_kernel_world_model_aggregates/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_aggregates/shared.py @@ -7,3 +7,6 @@ from .. import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): globals()[_name] = _value + + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/ai_pmo_kernel_world_model_core/__init__.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_core/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/__init__.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_core/mixin_builder.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_core/mixin_builder.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_core/mixin_cache.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_cache.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_core/mixin_cache.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_cache.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_core/mixin_project.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_project.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_core/mixin_project.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_project.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_core/service.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/service.py similarity index 100% rename from backend/app/services/ai_pmo_kernel_world_model_core/service.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/service.py diff --git a/backend/app/services/ai_pmo_kernel_world_model_core/shared.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/shared.py similarity index 78% rename from backend/app/services/ai_pmo_kernel_world_model_core/shared.py rename to backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/shared.py index e404b302..df3098ab 100644 --- a/backend/app/services/ai_pmo_kernel_world_model_core/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/shared.py @@ -7,3 +7,6 @@ from .. import ai_pmo_kernel_common_core as _common for _name, _value in vars(_common).items(): if not _name.startswith("__"): globals()[_name] = _value + + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/ai_pmo_typed_executor_discovery/__init__.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_discovery/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/__init__.py diff --git a/backend/app/services/ai_pmo_typed_executor_discovery/mixin_analysis.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_analysis.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_discovery/mixin_analysis.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_analysis.py diff --git a/backend/app/services/ai_pmo_typed_executor_discovery/mixin_context.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_context.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_discovery/mixin_context.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_context.py diff --git a/backend/app/services/ai_pmo_typed_executor_discovery/mixin_discover.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_discover.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_discovery/mixin_discover.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_discover.py diff --git a/backend/app/services/ai_pmo_typed_executor_discovery/service.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/service.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_discovery/service.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/service.py diff --git a/backend/app/services/ai_pmo_typed_executor_discovery/shared.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/shared.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_discovery/shared.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/shared.py diff --git a/backend/app/services/ai_pmo_typed_executor_execute/__init__.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/__init__.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_execute/__init__.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/__init__.py diff --git a/backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_query_project.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py similarity index 97% rename from backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_query_project.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py index 3c8e1791..c9db5e67 100644 --- a/backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_query_project.py +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py @@ -3,6 +3,7 @@ from __future__ import annotations from .shared import * # noqa: F401, F403 +from ...adaptive_rag_service import AdaptiveRAGService class ExecuteQueryProjectMixin: diff --git a/backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_signal_create.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_create.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_signal_create.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_create.py diff --git a/backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py diff --git a/backend/app/services/ai_pmo_typed_executor_execute/mixin_helpers.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_helpers.py similarity index 98% rename from backend/app/services/ai_pmo_typed_executor_execute/mixin_helpers.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_helpers.py index f9f63a4c..de5b88c2 100644 --- a/backend/app/services/ai_pmo_typed_executor_execute/mixin_helpers.py +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_helpers.py @@ -24,8 +24,7 @@ class ExecuteHelpersMixin: reason = reason[len(marker) :].strip() if not reason: reason = "unknown_error" - message = f"{marker}{reason}" - return message if "请稍后重试" in message else f"{message}。请稍后重试。" + return f"{marker}{reason}" @staticmethod def _build_signal_title_refine_error_message(error: Exception) -> str: diff --git a/backend/app/services/ai_pmo_typed_executor_execute/service.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/service.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_execute/service.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/service.py diff --git a/backend/app/services/ai_pmo_typed_executor_execute/shared.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/shared.py similarity index 100% rename from backend/app/services/ai_pmo_typed_executor_execute/shared.py rename to backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/shared.py diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup.py b/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup.py deleted file mode 100644 index facc70ef..00000000 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_followup.py +++ /dev/null @@ -1,19 +0,0 @@ -"""AI PMO kernel followup mixin composition.""" - -from __future__ import annotations - -from .ai_pmo_kernel_mixins_followup_project_signal import KernelFollowupProjectSignalMixin -from .ai_pmo_kernel_mixins_followup_records import KernelFollowupRecordsMixin -from .ai_pmo_kernel_mixins_followup_routing import KernelFollowupRoutingMixin -from .ai_pmo_kernel_mixins_followup_verification import KernelFollowupVerificationMixin - - -class KernelFollowupMixin( - KernelFollowupRecordsMixin, - KernelFollowupRoutingMixin, - KernelFollowupProjectSignalMixin, - KernelFollowupVerificationMixin, -): - """Compose followup concerns into a single kernel mixin.""" - - pass diff --git a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_resolve.py b/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_resolve.py deleted file mode 100644 index 5bebf3d4..00000000 --- a/backend/app/services/ai_pmo_kernel/ai_pmo_kernel_mixins_intent_resolve.py +++ /dev/null @@ -1,66 +0,0 @@ -"""AI PMO kernel intent resolve mixin.""" - -from __future__ import annotations - -from .. import ai_pmo_kernel_common_core as _common - -for _name, _value in vars(_common).items(): - if not _name.startswith("__"): - globals()[_name] = _value - - -class KernelIntentResolveMixin: - async def _resolve_intent( - self, - *, - message_text: str, - session: Optional[ConversationSession], - world_model: dict[str, Any], - ) -> tuple[CanonicalIntent, str]: - project_id = _safe_int(world_model.get("resolved_project_id")) - raw_rule_intent = self.intent_resolver.resolve(text=message_text, project_id=project_id) - rule_intent = self._apply_slot_guard(intent=raw_rule_intent, message_text=message_text) - # 查询/噪音场景优先走规则意图,避免额外触发 LLM 分类链路导致不必要失败重试。 - if rule_intent.name in {INTENT_QUERY, INTENT_IGNORE} and float(rule_intent.confidence or 0.0) >= 0.85: - return rule_intent, "rule_preferred" - if self._is_short_confirmation_message(message_text=message_text, session=session): - if session is None: - return rule_intent, "rule_fallback" - return ( - self._build_short_confirm_intent( - message_text=message_text, - session=session, - world_model=world_model, - ), - "session_inherited", - ) - - # 默认规则优先:仅在规则低置信/默认兜底时才触发 LLM。 - if not self._intent_llm_enabled(): - return rule_intent, "rule_fallback" - if not self._should_invoke_llm_last_resort(rule_intent=rule_intent): - return rule_intent, "rule_preferred" - - llm_intent = await self._resolve_intent_with_llm( - message_text=message_text, - session=session, - world_model=world_model, - rule_intent=rule_intent, - ) - if llm_intent is not None: - if self._intent_arbitration_enabled(): - selected_intent, source = self._arbitrate_intent( - rule_intent=rule_intent, - llm_intent=llm_intent, - message_text=message_text, - ) - else: - selected_intent, source = llm_intent, "llm" - return self._apply_slot_guard(intent=selected_intent, message_text=message_text), source - logger.info( - "ai_pmo_intent_rule_fallback", - tenant_id=self.tenant_id, - reason="intent_llm_empty_or_failed_or_timeout", - ) - return rule_intent, "rule_fallback" - diff --git a/backend/app/services/ai_service_core/mixin_business_tasks.py b/backend/app/services/ai_service_core/mixin_business_tasks.py index 91f129eb..767b1c28 100644 --- a/backend/app/services/ai_service_core/mixin_business_tasks.py +++ b/backend/app/services/ai_service_core/mixin_business_tasks.py @@ -3,6 +3,69 @@ from __future__ import annotations from .shared import * class AIServiceBusinessTasksMixin: + @staticmethod + def _default_available_skills() -> list[str]: + return [ + "Python", + "TypeScript", + "FastAPI", + "React", + "SQLAlchemy", + "LangChain", + "LLM调优", + "RAG构建", + "向量数据库", + "Ollama", + "Docker", + "Git", + ] + + async def _resolve_available_skills_text( + self, + *, + db: Optional[AsyncSession], + tenant_id: int, + ) -> str: + from ...models import SkillTag + + async def _query_skill_names(session: AsyncSession) -> list[str]: + rows = ( + await session.execute( + select(SkillTag.name) + .where(SkillTag.tenant_id == int(tenant_id)) + .order_by(SkillTag.sort_order.asc(), SkillTag.name.asc()) + ) + ).scalars().all() + names: list[str] = [] + seen: set[str] = set() + for raw in rows: + name = str(raw or "").strip() + if not name: + continue + lower_name = name.lower() + if lower_name in seen: + continue + seen.add(lower_name) + names.append(name) + return names + + names: list[str] = [] + if db is not None and hasattr(db, "execute"): + try: + names = await _query_skill_names(db) + except Exception: + names = [] + else: + async with AsyncSessionLocal() as session: + try: + names = await _query_skill_names(session) + except Exception: + names = [] + + if not names: + names = self._default_available_skills() + return ", ".join(names) + async def generate_charter_from_signal( self, signal_content: str, @@ -30,9 +93,15 @@ class AIServiceBusinessTasksMixin: from ...config.ai_prompts import PROMPT_DECOMPOSE_TASKS tid = _t2s(tenant_id) + tenant_id_int = _tenant_to_int(tenant_id) + available_skills = await self._resolve_available_skills_text( + db=db, + tenant_id=tenant_id_int, + ) prompt = PROMPT_DECOMPOSE_TASKS.format( charter_content=charter_content, project_id=_t2s(project_id), + available_skills=available_skills, ) text = await self._invoke_with_retry( prompt, db=db, tenant_id=tid, function_name="decompose_tasks_from_charter", @@ -62,4 +131,3 @@ class AIServiceBusinessTasksMixin: prompt, db=db, tenant_id=tid, function_name="recommend_assignees_for_task", ) return self._parse_json(text) - diff --git a/backend/app/services/context_cache_service/shared.py b/backend/app/services/context_cache_service/shared.py index 5fbb98ba..394c734a 100644 --- a/backend/app/services/context_cache_service/shared.py +++ b/backend/app/services/context_cache_service/shared.py @@ -52,3 +52,5 @@ def _json_loads(raw: Optional[str], default: Any) -> Any: except Exception: return default + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/integrations/__init__.py b/backend/app/services/integrations/__init__.py new file mode 100644 index 00000000..4e1dd006 --- /dev/null +++ b/backend/app/services/integrations/__init__.py @@ -0,0 +1 @@ +"""Integrations domain service namespace.""" diff --git a/backend/app/services/feishu_integration_service/__init__.py b/backend/app/services/integrations/feishu_integration_service/__init__.py similarity index 100% rename from backend/app/services/feishu_integration_service/__init__.py rename to backend/app/services/integrations/feishu_integration_service/__init__.py diff --git a/backend/app/services/feishu_integration_service/mixin_config.py b/backend/app/services/integrations/feishu_integration_service/mixin_config.py similarity index 98% rename from backend/app/services/feishu_integration_service/mixin_config.py rename to backend/app/services/integrations/feishu_integration_service/mixin_config.py index 2233f993..0f080c31 100644 --- a/backend/app/services/feishu_integration_service/mixin_config.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_config.py @@ -30,7 +30,7 @@ class FeishuConfigMixin: @staticmethod async def _notify_outbox_ready(db: AsyncSession, *, outbox_id: Optional[int]) -> None: """仅在 PostgreSQL 下发送 outbox ready 事件。""" - if not FeishuIntegrationService._is_postgresql_session(db): + if not FeishuConfigMixin._is_postgresql_session(db): return try: payload = str(int(outbox_id))[:64] if outbox_id is not None else "" @@ -154,4 +154,3 @@ class FeishuConfigMixin: 'code': 1, 'msg': str(e) } - diff --git a/backend/app/services/feishu_integration_service/mixin_message_payload.py b/backend/app/services/integrations/feishu_integration_service/mixin_message_payload.py similarity index 96% rename from backend/app/services/feishu_integration_service/mixin_message_payload.py rename to backend/app/services/integrations/feishu_integration_service/mixin_message_payload.py index f21fc3db..e94c4d17 100644 --- a/backend/app/services/feishu_integration_service/mixin_message_payload.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_message_payload.py @@ -3,6 +3,16 @@ from __future__ import annotations from .shared import * class FeishuMessagePayloadMixin: + @staticmethod + def _strip_bot_mention_tokens(text: Optional[str]) -> str: + raw = str(text or "") + if not raw.strip(): + return "" + normalized = re.sub(r"]*>[^<]*", " ", raw, flags=re.IGNORECASE) + normalized = re.sub(r"@(?:pmflow|_user_1)\b", " ", normalized, flags=re.IGNORECASE) + normalized = re.sub(r"\s+", " ", normalized).strip() + return normalized + @staticmethod def _build_attachment_placeholder_text(attachments: List[Dict[str, Any]]) -> str: if not attachments: @@ -256,4 +266,3 @@ class FeishuMessagePayloadMixin: await self.db.rollback() except Exception: pass - diff --git a/backend/app/services/feishu_integration_service/mixin_receive.py b/backend/app/services/integrations/feishu_integration_service/mixin_receive.py similarity index 96% rename from backend/app/services/feishu_integration_service/mixin_receive.py rename to backend/app/services/integrations/feishu_integration_service/mixin_receive.py index fa286c27..27d1f788 100644 --- a/backend/app/services/feishu_integration_service/mixin_receive.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_receive.py @@ -37,7 +37,12 @@ class FeishuReceiveMixin: # 解析消息内容与附件元数据 message_payload = self._extract_message_payload(event) - message_content = str(message_payload.get("text") or "").strip() or None + raw_message_content = str(message_payload.get("text") or "").strip() or None + message_content = ( + self._strip_bot_mention_tokens(raw_message_content) + if raw_message_content + else "" + ).strip() or None message_attachments = ( message_payload.get("attachments") if isinstance(message_payload.get("attachments"), list) @@ -48,7 +53,7 @@ class FeishuReceiveMixin: trace_id=state_trace_id, message_id=message_id, event=event, - message_content=message_content or "", + message_content=raw_message_content or "", ) if not message_content: print( @@ -210,4 +215,3 @@ class FeishuReceiveMixin: 'scene': scene, }, } - diff --git a/backend/app/services/feishu_integration_service/mixin_receive_routing.py b/backend/app/services/integrations/feishu_integration_service/mixin_receive_routing.py similarity index 100% rename from backend/app/services/feishu_integration_service/mixin_receive_routing.py rename to backend/app/services/integrations/feishu_integration_service/mixin_receive_routing.py diff --git a/backend/app/services/feishu_integration_service/mixin_reply_guard.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_guard.py similarity index 100% rename from backend/app/services/feishu_integration_service/mixin_reply_guard.py rename to backend/app/services/integrations/feishu_integration_service/mixin_reply_guard.py diff --git a/backend/app/services/feishu_integration_service/mixin_reply_llm_core.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py similarity index 88% rename from backend/app/services/feishu_integration_service/mixin_reply_llm_core.py rename to backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py index 9b2a56bc..13583294 100644 --- a/backend/app/services/feishu_integration_service/mixin_reply_llm_core.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py @@ -96,6 +96,7 @@ class FeishuReplyLLMCoreMixin: max_tokens=180, ) reply_text = self._clean_reply_text(raw_reply) + reply_text = self._strip_leading_thinking_paragraphs(reply_text) if not self._is_capability_question(message_content): reply_text = self._strip_identity_lead(reply_text) if (not reply_text) or self._looks_like_provider_error(reply_text): @@ -128,6 +129,40 @@ class FeishuReplyLLMCoreMixin: text = text[4:].strip() return text[:500] + @staticmethod + def _strip_leading_thinking_paragraphs(reply_text: str) -> str: + text = str(reply_text or "").strip() + if not text: + return "" + + # Prefer paragraph blocks; fall back to per-line blocks when needed. + paragraphs = [chunk.strip() for chunk in re.split(r"\n\s*\n", text) if str(chunk).strip()] + if len(paragraphs) <= 1: + lines = [line.strip() for line in text.splitlines() if str(line).strip()] + if len(lines) > 1: + paragraphs = lines + + thinking_prefixes = ( + "让我先", + "让我尝试", + "我来看看", + "我先查", + "让我看看", + "我需要先", + ) + + first_clean_idx: Optional[int] = None + for idx, paragraph in enumerate(paragraphs): + normalized = re.sub(r'^[\s>"\'“”‘’「」【】\(\)\[\]\-—::、\d\.]+', "", paragraph).strip() + if any(normalized.startswith(prefix) for prefix in thinking_prefixes): + continue + first_clean_idx = idx + break + + if first_clean_idx is None: + return "" + return "\n\n".join(paragraphs[first_clean_idx:]).strip()[:500] + @staticmethod def _is_capability_question(message_content: Optional[str]) -> bool: normalized = re.sub(r"\s+", "", str(message_content or "").lower()) @@ -256,4 +291,3 @@ class FeishuReplyLLMCoreMixin: f"我这边当前北京时间是 {today_text}。" "如果你的界面显示不一致,优先检查设备时区和飞书客户端缓存。" ) - diff --git a/backend/app/services/feishu_integration_service/mixin_reply_outbox.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py similarity index 99% rename from backend/app/services/feishu_integration_service/mixin_reply_outbox.py rename to backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py index 84955b9b..33513502 100644 --- a/backend/app/services/feishu_integration_service/mixin_reply_outbox.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py @@ -227,7 +227,7 @@ class FeishuReplyOutboxMixin: send_error: Optional[str] = None try: async with AsyncSessionLocal() as db_send: - service = FeishuIntegrationService(db_send) + service = cls(db_send) await service.load_config(tenant_id=int(claimed.get("tenant_id") or 1)) await service.send_message_to_group( chat_id=str(claimed.get("chat_id") or ""), @@ -294,4 +294,3 @@ class FeishuReplyOutboxMixin: return None raw = f"{source}{body}".encode("utf-8") return hashlib.md5(raw, usedforsecurity=False).hexdigest() - diff --git a/backend/app/services/feishu_integration_service/mixin_reply_templates.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_templates.py similarity index 100% rename from backend/app/services/feishu_integration_service/mixin_reply_templates.py rename to backend/app/services/integrations/feishu_integration_service/mixin_reply_templates.py diff --git a/backend/app/services/feishu_integration_service/mixin_sender_resolution.py b/backend/app/services/integrations/feishu_integration_service/mixin_sender_resolution.py similarity index 100% rename from backend/app/services/feishu_integration_service/mixin_sender_resolution.py rename to backend/app/services/integrations/feishu_integration_service/mixin_sender_resolution.py diff --git a/backend/app/services/feishu_integration_service/mixin_trace.py b/backend/app/services/integrations/feishu_integration_service/mixin_trace.py similarity index 100% rename from backend/app/services/feishu_integration_service/mixin_trace.py rename to backend/app/services/integrations/feishu_integration_service/mixin_trace.py diff --git a/backend/app/services/feishu_integration_service/mixin_transport.py b/backend/app/services/integrations/feishu_integration_service/mixin_transport.py similarity index 100% rename from backend/app/services/feishu_integration_service/mixin_transport.py rename to backend/app/services/integrations/feishu_integration_service/mixin_transport.py diff --git a/backend/app/services/feishu_integration_service/service.py b/backend/app/services/integrations/feishu_integration_service/service.py similarity index 100% rename from backend/app/services/feishu_integration_service/service.py rename to backend/app/services/integrations/feishu_integration_service/service.py diff --git a/backend/app/services/feishu_integration_service/shared.py b/backend/app/services/integrations/feishu_integration_service/shared.py similarity index 72% rename from backend/app/services/feishu_integration_service/shared.py rename to backend/app/services/integrations/feishu_integration_service/shared.py index de2debec..088890a8 100644 --- a/backend/app/services/feishu_integration_service/shared.py +++ b/backend/app/services/integrations/feishu_integration_service/shared.py @@ -28,7 +28,7 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select -from ...models import ( +from ....models import ( User, Project, Signal, @@ -37,13 +37,13 @@ from ...models import ( FeishuReplyOutbox, AIProcessingLog, ) -from ...core.database import AsyncSessionLocal -from ...config.ai_prompts import PROMPT_FEISHU_HUMANIZED_REPLY -from ..notification_service import NotificationService -from ..signal_capture_service import ProactiveSignalCaptureService -from ..llm_service import LLMService +from ....core.database import AsyncSessionLocal +from ....config.ai_prompts import PROMPT_FEISHU_HUMANIZED_REPLY +from ...notification_service import NotificationService +from ...signal.signal_capture_service import ProactiveSignalCaptureService +from ...llm_service import LLMService -from ...utils.timezone import now_cn_naive +from ....utils.timezone import now_cn_naive CN_TZ = timezone(timedelta(hours=8)) WEEKDAY_CN = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] OUTBOX_MAX_ATTEMPTS = 3 @@ -51,3 +51,4 @@ OUTBOX_STALE_SENDING_SECONDS = 120 _OUTBOX_PERF_T0_BY_ID: dict[int, float] = {} +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/feishu_long_connection_core/__init__.py b/backend/app/services/integrations/feishu_long_connection_core/__init__.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/__init__.py rename to backend/app/services/integrations/feishu_long_connection_core/__init__.py diff --git a/backend/app/services/feishu_long_connection_core/manager.py b/backend/app/services/integrations/feishu_long_connection_core/manager.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/manager.py rename to backend/app/services/integrations/feishu_long_connection_core/manager.py diff --git a/backend/app/services/feishu_long_connection_core/mixin_lifecycle.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/mixin_lifecycle.py rename to backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle.py diff --git a/backend/app/services/feishu_long_connection_core/mixin_lifecycle_connection.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_connection.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/mixin_lifecycle_connection.py rename to backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_connection.py diff --git a/backend/app/services/feishu_long_connection_core/mixin_lifecycle_control.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_control.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/mixin_lifecycle_control.py rename to backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_control.py diff --git a/backend/app/services/feishu_long_connection_core/mixin_runtime.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_runtime.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/mixin_runtime.py rename to backend/app/services/integrations/feishu_long_connection_core/mixin_runtime.py diff --git a/backend/app/services/feishu_long_connection_core/registry.py b/backend/app/services/integrations/feishu_long_connection_core/registry.py similarity index 100% rename from backend/app/services/feishu_long_connection_core/registry.py rename to backend/app/services/integrations/feishu_long_connection_core/registry.py diff --git a/backend/app/services/feishu_long_connection_core/shared.py b/backend/app/services/integrations/feishu_long_connection_core/shared.py similarity index 88% rename from backend/app/services/feishu_long_connection_core/shared.py rename to backend/app/services/integrations/feishu_long_connection_core/shared.py index 2b51ff21..47428364 100644 --- a/backend/app/services/feishu_long_connection_core/shared.py +++ b/backend/app/services/integrations/feishu_long_connection_core/shared.py @@ -14,9 +14,9 @@ from typing import Any, Optional from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from sqlalchemy.future import select -from ...core.logging import get_logger -from ...models import Integration -from ...utils.timezone import CN_TZ, now_cn_aware +from ....core.logging import get_logger +from ....models import Integration +from ....utils.timezone import CN_TZ, now_cn_aware from ..feishu_integration_service import FeishuIntegrationService logger = get_logger(__name__) diff --git a/backend/app/services/feishu_outbox/__init__.py b/backend/app/services/integrations/feishu_outbox/__init__.py similarity index 100% rename from backend/app/services/feishu_outbox/__init__.py rename to backend/app/services/integrations/feishu_outbox/__init__.py diff --git a/backend/app/services/feishu_outbox/service.py b/backend/app/services/integrations/feishu_outbox/service.py similarity index 98% rename from backend/app/services/feishu_outbox/service.py rename to backend/app/services/integrations/feishu_outbox/service.py index 37d3a7a4..7a3614c9 100644 --- a/backend/app/services/feishu_outbox/service.py +++ b/backend/app/services/integrations/feishu_outbox/service.py @@ -9,7 +9,7 @@ from sqlalchemy import and_, select from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from ...models import FeishuReplyOutbox +from ....models import FeishuReplyOutbox class FeishuOutboxService: diff --git a/backend/app/services/feishu_signal_pipeline/__init__.py b/backend/app/services/integrations/feishu_signal_pipeline/__init__.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/__init__.py rename to backend/app/services/integrations/feishu_signal_pipeline/__init__.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_command_commit_project.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_project.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_command_commit_project.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_project.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_command_commit_task.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_task.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_command_commit_task.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_task.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_command_exec_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_exec_core.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_command_exec_core.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_command_exec_core.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_context_prefill_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_context_prefill_a.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_context_prefill_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_context_prefill_b.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_core_base.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_core_base.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_core_base.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_core_base.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_extraction.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_extraction.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_followup_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_a.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_followup_a.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_a.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_followup_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_b.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_followup_b.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_b.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_gate_dispatch.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py similarity index 94% rename from backend/app/services/feishu_signal_pipeline/mixin_gate_dispatch.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py index 8bd01f23..b5327f73 100644 --- a/backend/app/services/feishu_signal_pipeline/mixin_gate_dispatch.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py @@ -156,8 +156,19 @@ class FeishuPipelineGateDispatchMixin: source_sender_open_id=source_sender_open_id, sender=sender, ) + intent_result: dict[str, Any] = {} + try: + intent_result = await rag_service.classify_intent(message) + except Exception: + intent_result = {} + mixed_intent = str(intent_result.get("intent") or "").strip().lower() == "mixed" + if mixed_intent: + # mixed 消息不能在 intent_gate.query 提前结束,交给默认链路完成“问答+需求入库”双轨处理。 + return None + + rag_question = str(intent_result.get("question_part") or "").strip() or message rag_result = await rag_service.answer_question( - question=message, + question=rag_question, asked_by=None, asked_via="feishu", project_id=inferred_project_id, @@ -267,4 +278,3 @@ class FeishuPipelineGateDispatchMixin: db_mutations=self._summarize_db_mutations(command_capture), ) return None - diff --git a/backend/app/services/feishu_signal_pipeline/mixin_gate_project.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_project.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_gate_project.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_project.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_gate_task.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_task.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_gate_task.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_task.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_intent_gate_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_intent_gate_core.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_intent_support.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_support.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_intent_support.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_support.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_item_finalize_routing.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_finalize_routing.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_item_finalize_routing.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_item_finalize_routing.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_item_processing.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_processing.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_item_processing.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_item_processing.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_lookup_and_block.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_lookup_and_block.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_lookup_and_block.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_lookup_and_block.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_message_utils_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_a.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_message_utils_a.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_a.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_message_utils_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_b.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_message_utils_b.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_b.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_persistence_lookup.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_persistence_lookup.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_persistence_lookup.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_persistence_lookup.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_process_message_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_core.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_process_message_core.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_core.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_process_message_default.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py similarity index 78% rename from backend/app/services/feishu_signal_pipeline/mixin_process_message_default.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py index 15ae83c4..723f04a8 100644 --- a/backend/app/services/feishu_signal_pipeline/mixin_process_message_default.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py @@ -34,23 +34,45 @@ class FeishuPipelineProcessMessageDefaultMixin: rag_question_part = message if self._is_obvious_question(message): - rag_result = await rag_service.answer_question( - question=rag_question_part, - asked_by=None, - asked_via="feishu", - project_id=inferred_project_id, - source_chat_id=source_chat_id, - conversation_key=conversation_key, - ) - if self._is_requirement_like_question(message): - sidecar_rag_result = rag_result - intent_for_response = "question_requirement" - message = self._convert_requirement_question_to_signal_text( - message=message, - fallback_content=message_content, + intent_result: dict[str, Any] = {} + try: + intent_result = await rag_service.classify_intent(message) + except Exception: + intent_result = {} + intent = str(intent_result.get("intent") or "question").lower() + rag_question_part = str(intent_result.get("question_part") or "").strip() or message + rag_statement_part = str(intent_result.get("statement_part") or "").strip() + if intent == "mixed" or self._is_requirement_like_question(message): + mixed_signal_part = ( + rag_statement_part + or self._convert_requirement_question_to_signal_text( + message=message, + fallback_content=message_content, + ) ) + mixed_rag_result = await rag_service.answer_question( + question=rag_question_part, + asked_by=None, + asked_via="feishu", + project_id=inferred_project_id, + source_chat_id=source_chat_id, + conversation_key=conversation_key, + message_intent="mixed", + mixed_signal_part=mixed_signal_part, + ) + sidecar_rag_result = mixed_rag_result + intent_for_response = "question_requirement" + message = mixed_signal_part force_signal_fastpath = True else: + rag_result = await rag_service.answer_question( + question=rag_question_part, + asked_by=None, + asked_via="feishu", + project_id=inferred_project_id, + source_chat_id=source_chat_id, + conversation_key=conversation_key, + ) return await self._finalize_direct_qa_capture_result( message=message, rag_result=rag_result, @@ -69,23 +91,37 @@ class FeishuPipelineProcessMessageDefaultMixin: rag_statement_part = str(intent_result.get("statement_part") or "").strip() if intent == "question": - rag_result = await rag_service.answer_question( - question=rag_question_part, - asked_by=None, - asked_via="feishu", - project_id=inferred_project_id, - source_chat_id=source_chat_id, - conversation_key=conversation_key, - ) if self._is_requirement_like_question(message): - sidecar_rag_result = rag_result - intent_for_response = "question_requirement" - message = self._convert_requirement_question_to_signal_text( - message=message, - fallback_content=message_content, + mixed_signal_part = ( + rag_statement_part + or self._convert_requirement_question_to_signal_text( + message=message, + fallback_content=message_content, + ) ) + mixed_rag_result = await rag_service.answer_question( + question=rag_question_part, + asked_by=None, + asked_via="feishu", + project_id=inferred_project_id, + source_chat_id=source_chat_id, + conversation_key=conversation_key, + message_intent="mixed", + mixed_signal_part=mixed_signal_part, + ) + sidecar_rag_result = mixed_rag_result + intent_for_response = "question_requirement" + message = mixed_signal_part force_signal_fastpath = True else: + rag_result = await rag_service.answer_question( + question=rag_question_part, + asked_by=None, + asked_via="feishu", + project_id=inferred_project_id, + source_chat_id=source_chat_id, + conversation_key=conversation_key, + ) return await self._finalize_direct_qa_capture_result( message=message, rag_result=rag_result, @@ -98,6 +134,13 @@ class FeishuPipelineProcessMessageDefaultMixin: ) if intent == "mixed": + mixed_signal_part = ( + rag_statement_part + or self._convert_requirement_question_to_signal_text( + message=message, + fallback_content=message_content, + ) + ) mixed_rag_result = await rag_service.answer_question( question=rag_question_part, asked_by=None, @@ -105,9 +148,10 @@ class FeishuPipelineProcessMessageDefaultMixin: project_id=inferred_project_id, source_chat_id=source_chat_id, conversation_key=conversation_key, + message_intent="mixed", + mixed_signal_part=mixed_signal_part, ) - if rag_statement_part: - message = rag_statement_part + message = mixed_signal_part sidecar_rag_result = mixed_rag_result try: @@ -253,4 +297,3 @@ class FeishuPipelineProcessMessageDefaultMixin: process_started_at=process_started_at, ) raise - diff --git a/backend/app/services/feishu_signal_pipeline/mixin_process_message_helpers_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_a.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_process_message_helpers_a.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_a.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_process_message_helpers_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_b.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_process_message_helpers_b.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_b.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_queue_confirm_undo.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_confirm_undo.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_queue_confirm_undo.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_confirm_undo.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_queue_read_write.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_read_write.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_queue_read_write.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_read_write.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_routing_notify.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_routing_notify.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_routing_notify.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_routing_notify.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_serialization.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_serialization.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py diff --git a/backend/app/services/feishu_signal_pipeline/mixin_task_command_queue.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_task_command_queue.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/mixin_task_command_queue.py rename to backend/app/services/integrations/feishu_signal_pipeline/mixin_task_command_queue.py diff --git a/backend/app/services/feishu_signal_pipeline/service.py b/backend/app/services/integrations/feishu_signal_pipeline/service.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/service.py rename to backend/app/services/integrations/feishu_signal_pipeline/service.py diff --git a/backend/app/services/feishu_signal_pipeline/shared.py b/backend/app/services/integrations/feishu_signal_pipeline/shared.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/shared.py rename to backend/app/services/integrations/feishu_signal_pipeline/shared.py diff --git a/backend/app/services/feishu_signal_pipeline/shared_core.py b/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py similarity index 91% rename from backend/app/services/feishu_signal_pipeline/shared_core.py rename to backend/app/services/integrations/feishu_signal_pipeline/shared_core.py index 2e434a65..32ced794 100644 --- a/backend/app/services/feishu_signal_pipeline/shared_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py @@ -14,13 +14,13 @@ from sqlalchemy import and_, delete, func, or_, select, text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from ...config.ai_prompts import ( +from ....config.ai_prompts import ( PROMPT_FEISHU_LEVEL_CLASSIFICATION, PROMPT_FEISHU_PROJECT_PREFILL, PROMPT_FEISHU_SIGNAL_EXTRACTION, ) -from ...core.logging import get_logger -from ...models import ( +from ....core.logging import get_logger +from ....models import ( FeishuItem, AIProcessingLog, FeishuReplyOutbox, @@ -33,27 +33,27 @@ from ...models import ( TruthNode, User, ) -from ..llm_service import LLMService -from ..adaptive_rag_service import AdaptiveRAGService -from ..ai_pmo_kernel import ( +from ...llm_service import LLMService +from ...adaptive_rag_service import AdaptiveRAGService +from ...ai_pmo.ai_pmo_kernel import ( build_decision_trace, gate_execution as kernel_gate_execution, resolve_correction as kernel_resolve_correction, ) -from ..context_cache_service import context_cache_service -from ..notification_service import NotificationService -from ..operation_log_service import log_operation -from ...utils.project_command_utils import ( +from ...context_cache_service import context_cache_service +from ...notification_service import NotificationService +from ...operation_log_service import log_operation +from ....utils.project_command_utils import ( extract_project_create_description, extract_project_create_title, normalize_project_title_candidate, ) -from ..sstc_service_core import ALIGNED_STATUSES -from ..task_persistence_core import build_task_create_draft, persist_task -from ...utils.project_members import ensure_project_default_members -from ...utils.sequence import ensure_project_id_sequence +from ...sstc_service_core import ALIGNED_STATUSES +from ...task_persistence_core import build_task_create_draft, persist_task +from ....utils.project_members import ensure_project_default_members +from ....utils.sequence import ensure_project_id_sequence -from ...utils.timezone import CN_TZ, now_cn_aware +from ....utils.timezone import CN_TZ, now_cn_aware logger = get_logger(__name__) PROJECT_PRIORITY_ORDER = { @@ -214,4 +214,3 @@ DEFAULT_SIGNAL_AUTO_ENTER_THRESHOLD = 0.80 DEFAULT_PROJECT_AUTO_CREATE_THRESHOLD = 0.85 DEFAULT_PROJECT_UNCERTAIN_THRESHOLD = 0.60 - diff --git a/backend/app/services/feishu_signal_pipeline/shared_schema_utils.py b/backend/app/services/integrations/feishu_signal_pipeline/shared_schema_utils.py similarity index 100% rename from backend/app/services/feishu_signal_pipeline/shared_schema_utils.py rename to backend/app/services/integrations/feishu_signal_pipeline/shared_schema_utils.py diff --git a/backend/app/services/integration_core/__init__.py b/backend/app/services/integrations/integration_core/__init__.py similarity index 100% rename from backend/app/services/integration_core/__init__.py rename to backend/app/services/integrations/integration_core/__init__.py diff --git a/backend/app/services/integration_core/lark_service.py b/backend/app/services/integrations/integration_core/lark_service.py similarity index 100% rename from backend/app/services/integration_core/lark_service.py rename to backend/app/services/integrations/integration_core/lark_service.py diff --git a/backend/app/services/integration_core/mixin_clients.py b/backend/app/services/integrations/integration_core/mixin_clients.py similarity index 100% rename from backend/app/services/integration_core/mixin_clients.py rename to backend/app/services/integrations/integration_core/mixin_clients.py diff --git a/backend/app/services/integration_core/mixin_health.py b/backend/app/services/integrations/integration_core/mixin_health.py similarity index 100% rename from backend/app/services/integration_core/mixin_health.py rename to backend/app/services/integrations/integration_core/mixin_health.py diff --git a/backend/app/services/integration_core/mixin_lifecycle.py b/backend/app/services/integrations/integration_core/mixin_lifecycle.py similarity index 100% rename from backend/app/services/integration_core/mixin_lifecycle.py rename to backend/app/services/integrations/integration_core/mixin_lifecycle.py diff --git a/backend/app/services/integration_core/mixin_sync.py b/backend/app/services/integrations/integration_core/mixin_sync.py similarity index 98% rename from backend/app/services/integration_core/mixin_sync.py rename to backend/app/services/integrations/integration_core/mixin_sync.py index ae524d50..53ea9b58 100644 --- a/backend/app/services/integration_core/mixin_sync.py +++ b/backend/app/services/integrations/integration_core/mixin_sync.py @@ -89,7 +89,7 @@ class IntegrationSyncMixin: health.latency_ms = latency if self.event_bus: - from ...core.events import Event, EventType + from ....core.events import Event, EventType await self.event_bus.publish( Event( @@ -112,7 +112,7 @@ class IntegrationSyncMixin: await self._record_failure(integration_type, str(exc)) if self.event_bus: - from ...core.events import Event, EventType + from ....core.events import Event, EventType await self.event_bus.publish( Event( diff --git a/backend/app/services/integration_core/plane_service.py b/backend/app/services/integrations/integration_core/plane_service.py similarity index 100% rename from backend/app/services/integration_core/plane_service.py rename to backend/app/services/integrations/integration_core/plane_service.py diff --git a/backend/app/services/integration_core/registry.py b/backend/app/services/integrations/integration_core/registry.py similarity index 100% rename from backend/app/services/integration_core/registry.py rename to backend/app/services/integrations/integration_core/registry.py diff --git a/backend/app/services/integration_core/schemas.py b/backend/app/services/integrations/integration_core/schemas.py similarity index 100% rename from backend/app/services/integration_core/schemas.py rename to backend/app/services/integrations/integration_core/schemas.py diff --git a/backend/app/services/integration_core/service.py b/backend/app/services/integrations/integration_core/service.py similarity index 100% rename from backend/app/services/integration_core/service.py rename to backend/app/services/integrations/integration_core/service.py diff --git a/backend/app/services/integration_core/shared.py b/backend/app/services/integrations/integration_core/shared.py similarity index 92% rename from backend/app/services/integration_core/shared.py rename to backend/app/services/integrations/integration_core/shared.py index e8df83dc..10bb0eca 100644 --- a/backend/app/services/integration_core/shared.py +++ b/backend/app/services/integrations/integration_core/shared.py @@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional import httpx from pydantic import BaseModel, Field -from ...utils.timezone import now_cn_naive +from ....utils.timezone import now_cn_naive logger = logging.getLogger(__name__) diff --git a/backend/app/services/wecom/__init__.py b/backend/app/services/integrations/wecom/__init__.py similarity index 100% rename from backend/app/services/wecom/__init__.py rename to backend/app/services/integrations/wecom/__init__.py diff --git a/backend/app/services/wecom/service.py b/backend/app/services/integrations/wecom/service.py similarity index 98% rename from backend/app/services/wecom/service.py rename to backend/app/services/integrations/wecom/service.py index 62500eff..27ce94d9 100644 --- a/backend/app/services/wecom/service.py +++ b/backend/app/services/integrations/wecom/service.py @@ -20,10 +20,10 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from sqlalchemy import text -from ...models import ( +from ....models import ( Signal, Project, Integration ) -from ..signal_capture_service import ProactiveSignalCaptureService +from ...signal.signal_capture_service import ProactiveSignalCaptureService class WeChatWorkIntegrationService: diff --git a/backend/app/services/llm_service/mixin_cache_circuit.py b/backend/app/services/llm_service/mixin_cache_circuit.py index fcd65fdc..3eeca44b 100644 --- a/backend/app/services/llm_service/mixin_cache_circuit.py +++ b/backend/app/services/llm_service/mixin_cache_circuit.py @@ -3,6 +3,8 @@ from __future__ import annotations from .shared import * class LLMCacheCircuitMixin: + _provider_circuit_state: dict[str, dict[str, Any]] = {} + def __init__(self) -> None: self.request_timeout = float(os.getenv("LLM_REQUEST_TIMEOUT", "45")) self.redis_url = os.getenv("REDIS_URL") @@ -160,4 +162,3 @@ class LLMCacheCircuitMixin: "last_error": str(exc)[:240], "updated_at_monotonic": now, } - diff --git a/backend/app/services/llm_service/mixin_chat_openai.py b/backend/app/services/llm_service/mixin_chat_openai.py index b7f78b65..86b36495 100644 --- a/backend/app/services/llm_service/mixin_chat_openai.py +++ b/backend/app/services/llm_service/mixin_chat_openai.py @@ -2,7 +2,31 @@ from __future__ import annotations from .shared import * + +class LLMEmptyContentError(ValueError): + """Raised when the model still returns empty content after one forced retry.""" + + class LLMChatOpenAIMixin: + @staticmethod + def _append_direct_answer_retry_instruction(messages: list[dict[str, str]]) -> None: + retry_instruction = "请直接输出答案" + for idx in range(len(messages) - 1, -1, -1): + row = messages[idx] + if not isinstance(row, dict): + continue + role = str(row.get("role") or "").strip().lower() + if role != "user": + continue + original = str(row.get("content") or "").strip() + if retry_instruction in original: + return + patched = dict(row) + patched["content"] = f"{original}\n\n{retry_instruction}" if original else retry_instruction + messages[idx] = patched + return + messages.append({"role": "user", "content": retry_instruction}) + async def _chat_completion_via_openai_compatible( self, *, @@ -28,9 +52,10 @@ class LLMChatOpenAIMixin: "Content-Type": "application/json", } current_max_tokens = max(int(max_tokens or 0), 1) - empty_content_retry_budget = 1 - http_status_retry_budget = 2 + http_status_retry_budget = self._http_status_retry_budget_for_function(function_label) http_status_retry_attempt = 0 + use_json_response_format = bool(structured_output) + direct_answer_retry_used = False while True: payload = { "model": model, @@ -38,6 +63,8 @@ class LLMChatOpenAIMixin: "temperature": temperature, "max_tokens": current_max_tokens, } + if use_json_response_format: + payload["response_format"] = {"type": "json_object"} if is_openclaw_gateway_request: payload["agents"] = { "defaults": { @@ -89,6 +116,19 @@ class LLMChatOpenAIMixin: http_status_retry_attempt += 1 await asyncio.sleep(retry_delay) continue + if use_json_response_format and status_code in {400, 422}: + lower_preview = body_preview.lower() + if "response_format" in lower_preview or "json_object" in lower_preview or "json schema" in lower_preview: + _runtime_log( + "LLM", + ( + "结构化参数降级重试:" + f"function={function_label}, provider={provider_name}, model={model}, " + f"status={status_code}" + ), + ) + use_json_response_format = False + continue resp.raise_for_status() data = resp.json() choices = data.get("choices") or [] @@ -102,27 +142,7 @@ class LLMChatOpenAIMixin: response=data, structured_output=structured_output, ) - if ( - not text - and not structured_output - and self._reasoning_content_fallback_enabled(function_name=function_label) - ): - fallback_text = self._extract_reasoning_fallback_text( - message=message, - choice=first_choice, - response=data, - ) - if fallback_text: - text = fallback_text - _runtime_log( - "LLM", - ( - "空content回退reasoning_content:" - f"function={function_label}, provider={provider_name}, model={model}, " - f"text_len={len(text)}" - ), - ) - if text or structured_output: + if text: break msg_keys = list(message.keys()) if isinstance(message, dict) else [] @@ -132,33 +152,23 @@ class LLMChatOpenAIMixin: choice_preview = json.dumps(first_choice, ensure_ascii=False)[:300] except Exception: choice_preview = str(first_choice)[:300] - empty_error = ValueError( + empty_error = LLMEmptyContentError( "LLM 返回空内容: " f"message_keys={msg_keys}, choice_keys={choice_keys}, choice_preview={choice_preview}" ) - can_retry_empty = ( - empty_content_retry_budget > 0 - and self._should_retry_empty_chat_response( - structured_output=structured_output, - message=message, - choice=first_choice, - current_max_tokens=current_max_tokens, + if not direct_answer_retry_used: + direct_answer_retry_used = True + self._append_direct_answer_retry_instruction(messages) + _runtime_log( + "LLM", + ( + "空content定向重试:" + f"function={function_label}, provider={provider_name}, model={model}, " + "retry_with=请直接输出答案" + ), ) - ) - if not can_retry_empty: - raise empty_error - - empty_content_retry_budget -= 1 - next_max_tokens = self._next_retry_max_tokens_for_empty_chat(current_max_tokens) - _runtime_log( - "LLM", - ( - "空内容提额重试:" - f"function={function_label}, provider={provider_name}, model={model}, " - f"max_tokens={current_max_tokens}->{next_max_tokens}" - ), - ) - current_max_tokens = next_max_tokens + continue + raise empty_error if _looks_like_openclaw_timeout_message(text): elapsed_ms = (time.perf_counter() - request_started_at_perf) * 1000.0 _runtime_log( @@ -190,4 +200,3 @@ class LLMChatOpenAIMixin: ), ) return ChatCompletionResult(text=text, raw_response=data) - diff --git a/backend/app/services/llm_service/mixin_generate_db_structured.py b/backend/app/services/llm_service/mixin_generate_db_structured.py index 79f7b8c9..612aec4f 100644 --- a/backend/app/services/llm_service/mixin_generate_db_structured.py +++ b/backend/app/services/llm_service/mixin_generate_db_structured.py @@ -97,7 +97,27 @@ class LLMGenerateDbStructuredMixin: ), ) if extraction.payload is None: - raise ValueError(str(extraction.error_code or "structured_schema_invalid")) + if self._should_attempt_model_repair(function_name): + repaired = await self._repair_structured_payload_with_model( + function_name=function_name, + provider=provider, + model=model, + api_key=str(engine.get("api_key") or ""), + base_url=str(engine.get("base_url") or ""), + system_prompt=prompt_profile.get("system_prompt") or "", + original_prompt=prompt_with_context, + extraction=extraction, + raw_response=completion.raw_response, + fallback_text=completion.text, + max_tokens=max_tokens, + ) + if isinstance(repaired, StructuredExtractionResult) and repaired.payload is not None: + extraction = repaired + response_text = repaired.raw_text or response_text + else: + raise ValueError(str(extraction.error_code or "structured_schema_invalid")) + else: + raise ValueError(str(extraction.error_code or "structured_schema_invalid")) if extraction.payload is not None and cache_key: await self._structured_cache_set(key=cache_key, value=extraction.payload) status = extraction.error_code or "success" @@ -150,3 +170,110 @@ class LLMGenerateDbStructuredMixin: except Exception as log_exc: _runtime_log("LLM", f"结构化调用日志写入失败:{log_exc}") + @staticmethod + def _build_structured_schema_hint(function_name: str) -> str: + fn = str(function_name or "").strip().lower() + schema = STRUCTURED_OBJECT_SCHEMAS.get(fn) + if not isinstance(schema, dict): + return "{}" + schema_hint = { + "required_keys": list(schema.get("required_keys") or []), + "type_checks": dict(schema.get("type_checks") or {}), + "optional_type_checks": dict(schema.get("optional_type_checks") or {}), + } + return json.dumps(schema_hint, ensure_ascii=False) + + @staticmethod + def _should_attempt_model_repair(function_name: str) -> bool: + fn = str(function_name or "").strip().lower() + if fn in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"}: + return _env_bool("LLM_INTENT_STRUCTURED_REPAIR_ENABLED", True) + return True + + async def _repair_structured_payload_with_model( + self, + *, + function_name: str, + provider: str, + model: str, + api_key: str, + base_url: str, + system_prompt: str, + original_prompt: str, + extraction: StructuredExtractionResult, + raw_response: Any, + fallback_text: str, + max_tokens: int, + ) -> Optional[StructuredExtractionResult]: + source_text = str(extraction.raw_text or "").strip() + if source_text.startswith("[") and source_text.endswith("]"): + source_text = "" + if not source_text: + source_text = str(fallback_text or "").strip() + if not source_text: + return None + schema_hint = self._build_structured_schema_hint(function_name) + repair_prompt = ( + "首字符必须是 {,末字符必须是 }。\n" + "你是 JSON 修复器。把模型输出整理为满足目标 schema 的 JSON 对象。\n" + f"function_name: {function_name}\n" + f"schema_hint_json: {schema_hint}\n" + f"original_task_prompt: {str(original_prompt or '')[:2000]}\n" + f"model_output: {source_text[:2800]}\n" + "要求:\n" + "1. 只输出 JSON 对象。\n" + "2. 必须包含 required_keys。\n" + "3. 若字段缺失,填 null/空字符串/空数组,并保持类型合法。\n" + "4. 禁止输出解释、Markdown、代码块。" + ) + try: + repair_max_tokens = max(180, min(int(max_tokens or 0), 420)) + if str(function_name or "").strip().lower() in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"}: + repair_max_tokens = min(repair_max_tokens, 220) + repair_completion = await self._chat_completion_raw( + provider=provider, + prompt=repair_prompt, + system_prompt=system_prompt, + api_key=api_key, + base_url=base_url, + model=model, + temperature=0.0, + max_tokens=repair_max_tokens, + function_name=function_name, + response_mode="fast", + ) + except Exception as exc: + _runtime_log( + "LLM结构化", + ( + "模型结构化修复失败:" + f"function={function_name}, provider={provider}, model={model}, " + f"error={type(exc).__name__}:{str(exc)[:180]}" + ), + ) + return None + repair_extraction = self._extract_structured_object_from_response( + function_name=function_name, + response=repair_completion.raw_response, + fallback_text=repair_completion.text, + provider=provider, + model=model, + ) + if repair_extraction.payload is None: + _runtime_log( + "LLM结构化", + ( + "模型结构化修复未产出有效 JSON:" + f"function={function_name}, source={repair_extraction.source_field or 'none'}, " + f"error={repair_extraction.error_code or 'unknown'}" + ), + ) + return None + _runtime_log( + "LLM结构化", + ( + f"function={function_name}, structured_source={repair_extraction.source_field or 'none'}, " + f"validation={repair_extraction.validation}, fallback=model_repair" + ), + ) + return repair_extraction diff --git a/backend/app/services/llm_service/mixin_guardrails_prompt.py b/backend/app/services/llm_service/mixin_guardrails_prompt.py index ef1934a7..bf8e08bc 100644 --- a/backend/app/services/llm_service/mixin_guardrails_prompt.py +++ b/backend/app/services/llm_service/mixin_guardrails_prompt.py @@ -137,6 +137,43 @@ class LLMGuardrailsPromptMixin: configured = OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS return min(configured, OPENCLAW_AGENTS_TIMEOUT_SECONDS_MAX) + @staticmethod + def _http_status_retry_budget_for_function(function_name: str) -> int: + fn = str(function_name or "").strip().lower() + is_intent_function = fn in { + "classify_intent_ai_pmo_kernel", + "classify_intent_complex", + "intent_structured_runtime_failover", + } + env_key = "LLM_INTENT_HTTP_RETRY_BUDGET" if is_intent_function else "LLM_HTTP_STATUS_RETRY_BUDGET" + default_budget = "0" if is_intent_function else "2" + raw = str(os.getenv(env_key, default_budget) or default_budget).strip() + try: + parsed = int(raw) + except Exception: + parsed = int(default_budget) + return max(0, min(parsed, 3)) + + @staticmethod + def _empty_chat_retry_budget(*, function_name: str, current_max_tokens: int) -> int: + fn = str(function_name or "").strip().lower() + current = max(int(current_max_tokens or 0), 1) + if current >= 2800: + return 0 + if fn in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"}: + raw_budget = str(os.getenv("LLM_INTENT_EMPTY_RETRY_BUDGET", "2") or "2").strip() + try: + parsed_budget = int(raw_budget) + except Exception: + parsed_budget = 2 + return max(0, min(parsed_budget, 3)) + if fn.startswith("adaptive_rag_answer_"): + # RAG 问答在 GLM 系列上经常先返回 reasoning_content,需至少升档两次才能拿到最终 content。 + return 2 + if any(token in fn for token in ("qa", "query", "reply")): + return 1 + return 1 + @staticmethod def _should_retry_empty_chat_response( *, @@ -145,8 +182,6 @@ class LLMGuardrailsPromptMixin: choice: Any, current_max_tokens: int, ) -> bool: - if structured_output: - return False if int(current_max_tokens or 0) >= 2800: return False finish_reason = str(choice.get("finish_reason") or "").strip().lower() if isinstance(choice, dict) else "" @@ -178,4 +213,3 @@ class LLMGuardrailsPromptMixin: custom_hints = _parse_csv_items(os.getenv("LLM_STRUCTURED_OUTPUT_FUNCTIONS", "")) hint_pool = custom_hints or STRUCTURED_OUTPUT_FUNCTION_HINTS return any(hint and hint in fn for hint in hint_pool) - diff --git a/backend/app/services/llm_service/mixin_openclaw_routing.py b/backend/app/services/llm_service/mixin_openclaw_routing.py index 65ea6a6f..99232de4 100644 --- a/backend/app/services/llm_service/mixin_openclaw_routing.py +++ b/backend/app/services/llm_service/mixin_openclaw_routing.py @@ -7,10 +7,10 @@ class LLMOpenclawRoutingMixin: return _env_bool("LLM_MIXED_ROUTING_ENABLED", False) def _openclaw_routing_enabled(self) -> bool: - return False + return _env_bool("OPENCLAW_ROUTING_ENABLED", False) def _openclaw_control_plane_only(self) -> bool: - return False + return _env_bool("OPENCLAW_CONTROL_PLANE_ONLY", False) def _ensure_openclaw_control_plane_engine(self, engine: dict[str, Any]) -> dict[str, Any]: runtime = dict(engine or {}) @@ -209,4 +209,3 @@ class LLMOpenclawRoutingMixin: return default_tier # mixed routing 开启时,未知函数默认走 high,避免被 active 引擎“全吞”。 return "high" if self._mixed_routing_enabled() else "active" - diff --git a/backend/app/services/llm_service/mixin_structured_candidates.py b/backend/app/services/llm_service/mixin_structured_candidates.py index 3a794b11..fda9aef6 100644 --- a/backend/app/services/llm_service/mixin_structured_candidates.py +++ b/backend/app/services/llm_service/mixin_structured_candidates.py @@ -9,6 +9,7 @@ class LLMStructuredCandidatesMixin: message: Any, choice: Any, response: Any, + include_reasoning: bool = True, ) -> list[tuple[str, Any]]: candidates: list[tuple[str, Any]] = [] if isinstance(message, dict): @@ -22,15 +23,16 @@ class LLMStructuredCandidatesMixin: candidates.append(("choice.text", choice.get("text"))) if isinstance(response, dict): candidates.append(("response.output_text", response.get("output_text"))) - if isinstance(message, dict): - candidates.append(("message.reasoning_content", message.get("reasoning_content"))) - candidates.append(("message.reasoning_content", message.get("reasoning"))) - if isinstance(choice, dict): - candidates.append(("choice.reasoning_content", choice.get("reasoning_content"))) - candidates.append(("choice.reasoning_content", choice.get("reasoning"))) - if isinstance(response, dict): - candidates.append(("response.reasoning_content", response.get("reasoning_content"))) - candidates.append(("response.reasoning_content", response.get("reasoning"))) + if include_reasoning: + if isinstance(message, dict): + candidates.append(("message.reasoning_content", message.get("reasoning_content"))) + candidates.append(("message.reasoning_content", message.get("reasoning"))) + if isinstance(choice, dict): + candidates.append(("choice.reasoning_content", choice.get("reasoning_content"))) + candidates.append(("choice.reasoning_content", choice.get("reasoning"))) + if isinstance(response, dict): + candidates.append(("response.reasoning_content", response.get("reasoning_content"))) + candidates.append(("response.reasoning_content", response.get("reasoning"))) return candidates @classmethod @@ -71,9 +73,108 @@ class LLMStructuredCandidatesMixin: return value is None or isinstance(value, dict) return True + @staticmethod + def _coerce_number(value: Any) -> Optional[float]: + if isinstance(value, bool): + return None + if isinstance(value, (int, float)): + return float(value) + text = str(value or "").strip() + if not text: + return None + try: + return float(text) + except Exception: + return None + + @staticmethod + def _coerce_int(value: Any) -> Optional[int]: + number = LLMStructuredCandidatesMixin._coerce_number(value) + if number is None: + return None + try: + return int(number) + except Exception: + return None + + @staticmethod + def _coerce_bool(value: Any) -> Optional[bool]: + if isinstance(value, bool): + return value + if isinstance(value, (int, float)) and not isinstance(value, bool): + return bool(int(value)) + text = str(value or "").strip().lower() + if not text: + return None + if text in BOOL_TRUE_VALUES: + return True + if text in {"0", "false", "no", "off", "n"}: + return False + return None + + @classmethod + def _coerce_intent_structured_payload(cls, payload: dict[str, Any]) -> dict[str, Any]: + normalized = dict(payload) + confidence = cls._coerce_number(normalized.get("confidence")) + if confidence is None: + for alias in ("score", "probability", "confidence_score"): + confidence = cls._coerce_number(normalized.get(alias)) + if confidence is not None: + break + if confidence is not None: + normalized["confidence"] = confidence + + reasoning_value = normalized.get("reasoning") + if isinstance(reasoning_value, list): + text_items = [str(item).strip() for item in reasoning_value if str(item).strip()] + normalized["reasoning"] = ";".join(text_items) + elif isinstance(reasoning_value, dict): + normalized["reasoning"] = json.dumps(reasoning_value, ensure_ascii=False) + else: + reasoning_text = str(reasoning_value or "").strip() + if reasoning_text: + normalized["reasoning"] = reasoning_text + else: + for alias in ("reason", "rationale", "analysis", "explanation"): + alias_text = str(normalized.get(alias) or "").strip() + if alias_text: + normalized["reasoning"] = alias_text + break + + if "requires_confirmation" in normalized: + bool_value = cls._coerce_bool(normalized.get("requires_confirmation")) + if bool_value is not None: + normalized["requires_confirmation"] = bool_value + + for field in ("project_id", "requested_count"): + if field in normalized: + number = cls._coerce_int(normalized.get(field)) + normalized[field] = number if number is not None else None + + route_hint = str( + normalized.get("workflow_route") or normalized.get("route") or "" + ).strip().lower() + if route_hint and "." in route_hint: + family, action = route_hint.split(".", 1) + if not str(normalized.get("family") or "").strip(): + normalized["family"] = family + if not str(normalized.get("action") or "").strip(): + normalized["action"] = action + return normalized + + @classmethod + def _coerce_structured_payload(cls, *, function_name: str, payload: Any) -> Any: + fn = str(function_name or "").strip().lower() + if not isinstance(payload, dict): + return payload + if fn in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"}: + return cls._coerce_intent_structured_payload(payload) + return payload + def _validate_structured_payload(self, *, function_name: str, payload: Any) -> Optional[str]: fn = str(function_name or "").strip().lower() schema = STRUCTURED_OBJECT_SCHEMAS.get(fn) + payload = self._coerce_structured_payload(function_name=fn, payload=payload) if schema is None: return None if isinstance(payload, (dict, list)) else "structured_json_invalid" if not isinstance(payload, dict): @@ -93,4 +194,3 @@ class LLMStructuredCandidatesMixin: if not any(self._matches_expected_type(payload.get(key), str(expected)) for expected in expected_values): return "structured_schema_invalid" return None - diff --git a/backend/app/services/llm_service/mixin_structured_extract_object.py b/backend/app/services/llm_service/mixin_structured_extract_object.py index 131b0577..246181c2 100644 --- a/backend/app/services/llm_service/mixin_structured_extract_object.py +++ b/backend/app/services/llm_service/mixin_structured_extract_object.py @@ -1,5 +1,7 @@ from __future__ import annotations +import ast + from .shared import * class LLMStructuredExtractObjectMixin: @@ -21,7 +23,13 @@ class LLMStructuredExtractObjectMixin: last_nonempty_field = "" last_nonempty_text = "" - for field, raw_value in self._iter_structured_candidates(message=message, choice=choice, response=response): + last_invalid_result: Optional[StructuredExtractionResult] = None + for field, raw_value in self._iter_structured_candidates( + message=message, + choice=choice, + response=response, + include_reasoning=False, + ): text = self._sanitize_visible_answer(self._extract_text_from_value(raw_value)) normalized = self._strip_code_fence(text) if normalized: @@ -35,19 +43,26 @@ class LLMStructuredExtractObjectMixin: try: parsed = json.loads(candidate) except Exception: - return StructuredExtractionResult( - payload=None, - source_field=field, - provider=provider, - model=model, - error_code="structured_json_invalid", - raw_text=candidate[:4000], - validation="invalid", - ) + parsed = self._parse_loose_structured_candidate(candidate) + if parsed is None: + last_invalid_result = StructuredExtractionResult( + payload=None, + source_field=field, + provider=provider, + model=model, + error_code="structured_json_invalid", + raw_text=candidate[:4000], + validation="invalid", + ) + continue parsed = self._unwrap_structured_payload_once(parsed) + parsed = self._coerce_structured_payload( + function_name=function_name, + payload=parsed, + ) validation_error = self._validate_structured_payload(function_name=function_name, payload=parsed) if validation_error is not None: - return StructuredExtractionResult( + last_invalid_result = StructuredExtractionResult( payload=None, source_field=field, provider=provider, @@ -56,6 +71,7 @@ class LLMStructuredExtractObjectMixin: raw_text=candidate[:4000], validation="invalid", ) + continue return StructuredExtractionResult( payload=parsed if isinstance(parsed, dict) else None, source_field=field, @@ -72,10 +88,38 @@ class LLMStructuredExtractObjectMixin: else "" ) if fallback_candidate: + parsed: Any = None try: parsed = json.loads(fallback_candidate) except Exception: - return StructuredExtractionResult( + parsed = self._parse_loose_structured_candidate(fallback_candidate) + if parsed is not None: + parsed = self._unwrap_structured_payload_once(parsed) + parsed = self._coerce_structured_payload( + function_name=function_name, + payload=parsed, + ) + validation_error = self._validate_structured_payload(function_name=function_name, payload=parsed) + if validation_error is None and isinstance(parsed, dict): + return StructuredExtractionResult( + payload=parsed, + source_field="generated_text", + provider=provider, + model=model, + raw_text=fallback_candidate[:4000], + validation="ok", + ) + last_invalid_result = StructuredExtractionResult( + payload=None, + source_field="generated_text", + provider=provider, + model=model, + error_code=validation_error or "structured_schema_invalid", + raw_text=fallback_candidate[:4000], + validation="invalid", + ) + else: + last_invalid_result = StructuredExtractionResult( payload=None, source_field="generated_text", provider=provider, @@ -84,17 +128,9 @@ class LLMStructuredExtractObjectMixin: raw_text=fallback_candidate[:4000], validation="invalid", ) - parsed = self._unwrap_structured_payload_once(parsed) - validation_error = self._validate_structured_payload(function_name=function_name, payload=parsed) - if validation_error is None and isinstance(parsed, dict): - return StructuredExtractionResult( - payload=parsed, - source_field="generated_text", - provider=provider, - model=model, - raw_text=fallback_candidate[:4000], - validation="ok", - ) + + if last_invalid_result is not None: + return last_invalid_result if not last_nonempty_field and str(fallback_text or "").strip(): last_nonempty_field = "generated_text" @@ -110,6 +146,24 @@ class LLMStructuredExtractObjectMixin: validation="invalid", ) + @staticmethod + def _parse_loose_structured_candidate(candidate: str) -> Any: + text = str(candidate or "").strip() + if not text: + return None + try: + parsed = ast.literal_eval(text) + except Exception: + return None + if isinstance(parsed, str): + try: + return json.loads(parsed) + except Exception: + return None + if isinstance(parsed, (dict, list)): + return parsed + return None + @staticmethod def _classify_structured_exception(exc: Exception) -> str: if isinstance(exc, (asyncio.TimeoutError, httpx.TimeoutException)): @@ -122,4 +176,3 @@ class LLMStructuredExtractObjectMixin: if "status" in lowered or "http" in lowered: return "structured_http_error" return "structured_empty" - diff --git a/backend/app/services/llm_service/mixin_structured_parse.py b/backend/app/services/llm_service/mixin_structured_parse.py index c696b030..9e31dbfc 100644 --- a/backend/app/services/llm_service/mixin_structured_parse.py +++ b/backend/app/services/llm_service/mixin_structured_parse.py @@ -17,8 +17,8 @@ class LLMStructuredParseMixin: if raw.startswith("{") or raw.startswith("["): return raw - object_candidate = LLMService._extract_balanced_json_segment(raw, "{", "}") - array_candidate = LLMService._extract_balanced_json_segment(raw, "[", "]") + object_candidate = LLMStructuredParseMixin._extract_balanced_json_segment(raw, "{", "}") + array_candidate = LLMStructuredParseMixin._extract_balanced_json_segment(raw, "[", "]") if object_candidate and array_candidate: return object_candidate if raw.find("{") <= raw.find("[") else array_candidate if object_candidate: @@ -77,6 +77,10 @@ class LLMStructuredParseMixin: except Exception: return True parsed = self._unwrap_structured_payload_once(parsed) + parsed = self._coerce_structured_payload( + function_name=function_name, + payload=parsed, + ) validation_error = self._validate_structured_payload(function_name=function_name, payload=parsed) if validation_error is not None: return True @@ -103,7 +107,12 @@ class LLMStructuredParseMixin: structured_output: bool, ) -> str: if structured_output: - for _, raw_value in self._iter_structured_candidates(message=message, choice=choice, response=response): + for _, raw_value in self._iter_structured_candidates( + message=message, + choice=choice, + response=response, + include_reasoning=False, + ): text = self._sanitize_visible_answer(self._extract_text_from_value(raw_value)) normalized = self._strip_code_fence(text) if not self._looks_like_json_container(normalized): @@ -160,6 +169,9 @@ class LLMStructuredParseMixin: @staticmethod def _reasoning_content_fallback_enabled(*, function_name: str) -> bool: fn = str(function_name or "").strip().lower() + # P0 guardrail: user-facing answer/reply/qa must never expose reasoning_content. + if fn and any(token in fn for token in ("answer", "reply", "qa")): + return False if fn and ("title_refine" in fn or "project_draft_analysis_glm47_fallback" in fn): return True raw = str(os.getenv("LLM_REASONING_CONTENT_FALLBACK", "0")).strip().lower() @@ -171,7 +183,4 @@ class LLMStructuredParseMixin: return False if "signal_batch_analysis" in fn: return False - if any(token in fn for token in ("answer", "reply", "qa")): - return True return False - diff --git a/backend/app/services/llm_service/mixin_tier_escalation.py b/backend/app/services/llm_service/mixin_tier_escalation.py index 1610fc20..2660aca6 100644 --- a/backend/app/services/llm_service/mixin_tier_escalation.py +++ b/backend/app/services/llm_service/mixin_tier_escalation.py @@ -46,6 +46,9 @@ class LLMTierEscalationMixin: ) -> bool: if not self._low_to_high_escalation_enabled(): return False + fn = str(function_name or "").strip().lower() + if fn in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"}: + return False failed_model = str(failed_engine.get("model") or "").strip() if self._is_glm_47_model(failed_model): return True @@ -143,7 +146,12 @@ class LLMTierEscalationMixin: response_mode: str, error: Exception, ) -> tuple[Optional[StructuredExtractionResult], Optional[dict[str, Any]]]: - if not self._should_retry_glm47_with_high_tier(function_name=function_name, failed_engine=failed_engine): + fn = str(function_name or "").strip().lower() + force_intent_retry = fn in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"} + if not force_intent_retry and not self._should_retry_glm47_with_high_tier( + function_name=function_name, + failed_engine=failed_engine, + ): return None, None try: _, bundle = await self._get_or_init_bundle(db, tenant_id=tenant_id) @@ -167,13 +175,41 @@ class LLMTierEscalationMixin: high_provider = str(high_engine.get("provider") or "").strip().lower() high_model = str(high_engine.get("model") or "").strip() if failed_provider == high_provider and failed_model == high_model: - return None, None + if not force_intent_retry: + return None, None + fallback_engine: Optional[dict[str, Any]] = None + for fallback_tier in ("safety", "low"): + candidate = self._resolve_tier_engine( + tier=fallback_tier, + engines=engines, + active_engine=active_engine, + ) + if not isinstance(candidate, dict): + continue + candidate = self._ensure_engine_allowed_for_function( + engine=candidate, + function_name=function_name, + settings=self._normalize_engine_settings(bundle.get("settings")), + engine_label=f"intent_structured_failover_{fallback_tier}", + ) + candidate_provider = str(candidate.get("provider") or "").strip().lower() + candidate_model = str(candidate.get("model") or "").strip() + if candidate_provider == failed_provider and candidate_model == failed_model: + continue + fallback_engine = candidate + break + if fallback_engine is None: + return None, None + high_engine = fallback_engine + high_provider = str(high_engine.get("provider") or "").strip().lower() + high_model = str(high_engine.get("model") or "").strip() _runtime_log( "LLM路由", ( "低阶结构化失败自动升级:" f"function={function_name}, from={failed_provider}:{failed_model}, " - f"to={high_provider}:{high_model}, reason={str(error)[:120]}" + f"to={high_provider}:{high_model}, reason={str(error)[:120]}, " + f"force_intent_retry={force_intent_retry}" ), ) completion = await self._chat_completion_raw( @@ -185,7 +221,11 @@ class LLMTierEscalationMixin: model=high_model, temperature=temperature, max_tokens=max_tokens, - function_name=function_name, + function_name=( + "intent_structured_runtime_failover" + if force_intent_retry + else function_name + ), response_mode=response_mode, ) extraction = self._extract_structured_object_from_response( @@ -214,4 +254,3 @@ class LLMTierEscalationMixin: ), ) return None, None - diff --git a/backend/app/services/llm_service/shared.py b/backend/app/services/llm_service/shared.py index 39384b43..4ba435d7 100644 --- a/backend/app/services/llm_service/shared.py +++ b/backend/app/services/llm_service/shared.py @@ -2,3 +2,7 @@ from __future__ import annotations from .shared_part_01 import * from .shared_part_02 import * + +# Preserve underscore-prefixed helper symbols for modules that rely on +# `from .shared import *` (for example: `_env_bool`, `_normalize_provider`). +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/llm_service/shared_part_01.py b/backend/app/services/llm_service/shared_part_01.py index 598502b7..837369ab 100644 --- a/backend/app/services/llm_service/shared_part_01.py +++ b/backend/app/services/llm_service/shared_part_01.py @@ -30,7 +30,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from ...config.ai_prompts import build_default_role_prompt_bundle from ...core.database import AsyncSessionLocal from ...models import AICallLog, Integration -from ..model_context_bundle import ModelContextBundleService +from ..model.model_context_bundle import ModelContextBundleService from ...utils.timezone import now_cn_aware try: @@ -126,6 +126,7 @@ STRUCTURED_OUTPUT_FUNCTION_HINTS = ( "feishu_signal_extraction", "signal_source_onboarding_plan", "signal_source_ai_worker_execution", + "project_draft_", "rag_intent_classification", "classify_intent", "feishu_project_prefill", @@ -290,5 +291,3 @@ STRUCTURED_OBJECT_SCHEMAS: dict[str, dict[str, Any]] = { }, }, } - - diff --git a/backend/app/services/llm_service/shared_part_02.py b/backend/app/services/llm_service/shared_part_02.py index 6e562ae8..6979372f 100644 --- a/backend/app/services/llm_service/shared_part_02.py +++ b/backend/app/services/llm_service/shared_part_02.py @@ -65,10 +65,12 @@ def _looks_like_openclaw_timeout_message(text: str) -> bool: ) return any(signal in lowered for signal in timeout_signals) +@dataclass class ChatCompletionResult: text: str raw_response: Any = None +@dataclass class StructuredExtractionResult: payload: Optional[dict[str, Any]] source_field: str @@ -79,3 +81,5 @@ class StructuredExtractionResult: validation: str = "invalid" from_cache: bool = False + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/model/__init__.py b/backend/app/services/model/__init__.py new file mode 100644 index 00000000..c325098e --- /dev/null +++ b/backend/app/services/model/__init__.py @@ -0,0 +1 @@ +"""Model domain service namespace.""" diff --git a/backend/app/services/model_capability/__init__.py b/backend/app/services/model/model_capability/__init__.py similarity index 100% rename from backend/app/services/model_capability/__init__.py rename to backend/app/services/model/model_capability/__init__.py diff --git a/backend/app/services/model_capability/cost_optimization_service.py b/backend/app/services/model/model_capability/cost_optimization_service.py similarity index 98% rename from backend/app/services/model_capability/cost_optimization_service.py rename to backend/app/services/model/model_capability/cost_optimization_service.py index e2df0985..8fbbf817 100644 --- a/backend/app/services/model_capability/cost_optimization_service.py +++ b/backend/app/services/model/model_capability/cost_optimization_service.py @@ -5,7 +5,7 @@ from typing import Any, Dict, Optional from sqlalchemy import and_, func, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import AICallLog, CostTracking, ModelRequirementBinding, ModelVersion, TruthNode +from ....models import AICallLog, CostTracking, ModelRequirementBinding, ModelVersion, TruthNode class CostOptimizationService: diff --git a/backend/app/services/model_capability/mixin_cost_tracking.py b/backend/app/services/model/model_capability/mixin_cost_tracking.py similarity index 99% rename from backend/app/services/model_capability/mixin_cost_tracking.py rename to backend/app/services/model/model_capability/mixin_cost_tracking.py index d9994392..5e70b3be 100644 --- a/backend/app/services/model_capability/mixin_cost_tracking.py +++ b/backend/app/services/model/model_capability/mixin_cost_tracking.py @@ -198,7 +198,7 @@ class ModelCapabilityCostTrackingMixin: tenant_id: int, ) -> Dict[str, Any]: """AI 分析成本效益""" - from ...config.ai_prompts import PROMPT_ANALYZE_COST_ROI + from ....config.ai_prompts import PROMPT_ANALYZE_COST_ROI prompt = PROMPT_ANALYZE_COST_ROI.format( total_cost=f"${total_cost:.2f}", diff --git a/backend/app/services/model_capability/mixin_gap_queries.py b/backend/app/services/model/model_capability/mixin_gap_queries.py similarity index 100% rename from backend/app/services/model_capability/mixin_gap_queries.py rename to backend/app/services/model/model_capability/mixin_gap_queries.py diff --git a/backend/app/services/model_capability/mixin_validation.py b/backend/app/services/model/model_capability/mixin_validation.py similarity index 100% rename from backend/app/services/model_capability/mixin_validation.py rename to backend/app/services/model/model_capability/mixin_validation.py diff --git a/backend/app/services/model_capability/model_lifecycle_service.py b/backend/app/services/model/model_capability/model_lifecycle_service.py similarity index 99% rename from backend/app/services/model_capability/model_lifecycle_service.py rename to backend/app/services/model/model_capability/model_lifecycle_service.py index 1f0637f2..8ddfa229 100644 --- a/backend/app/services/model_capability/model_lifecycle_service.py +++ b/backend/app/services/model/model_capability/model_lifecycle_service.py @@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import ( +from ....models import ( ModelRequirementBinding, ModelUpdateImpact, ModelVersion, diff --git a/backend/app/services/model_capability/service.py b/backend/app/services/model/model_capability/service.py similarity index 100% rename from backend/app/services/model_capability/service.py rename to backend/app/services/model/model_capability/service.py diff --git a/backend/app/services/model_capability/shared.py b/backend/app/services/model/model_capability/shared.py similarity index 83% rename from backend/app/services/model_capability/shared.py rename to backend/app/services/model/model_capability/shared.py index 15941ff1..60ed0523 100644 --- a/backend/app/services/model_capability/shared.py +++ b/backend/app/services/model/model_capability/shared.py @@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional, Union from sqlalchemy import select, and_, func, desc from sqlalchemy.ext.asyncio import AsyncSession -from ...models import ( +from ....models import ( ModelVersion, ModelRequirementBinding, TruthNode, @@ -23,7 +23,7 @@ from ...models import ( CostTracking, User, ) -from ..ai_service_core import AIService +from ...ai_service_core import AIService -from ...utils.timezone import now_cn_naive +from ....utils.timezone import now_cn_naive diff --git a/backend/app/services/model_context_bundle/__init__.py b/backend/app/services/model/model_context_bundle/__init__.py similarity index 100% rename from backend/app/services/model_context_bundle/__init__.py rename to backend/app/services/model/model_context_bundle/__init__.py diff --git a/backend/app/services/model_context_bundle/mixin_build.py b/backend/app/services/model/model_context_bundle/mixin_build.py similarity index 98% rename from backend/app/services/model_context_bundle/mixin_build.py rename to backend/app/services/model/model_context_bundle/mixin_build.py index c45d26bd..e49a18de 100644 --- a/backend/app/services/model_context_bundle/mixin_build.py +++ b/backend/app/services/model/model_context_bundle/mixin_build.py @@ -13,7 +13,7 @@ from .shared import ( datetime, timezone, ) -from ..project_knowledge_graph_service import ProjectKnowledgeGraphService +from ...project_knowledge_graph_service import ProjectKnowledgeGraphService class ModelContextBundleBuildMixin: diff --git a/backend/app/services/model_context_bundle/mixin_core.py b/backend/app/services/model/model_context_bundle/mixin_core.py similarity index 100% rename from backend/app/services/model_context_bundle/mixin_core.py rename to backend/app/services/model/model_context_bundle/mixin_core.py diff --git a/backend/app/services/model_context_bundle/mixin_verify.py b/backend/app/services/model/model_context_bundle/mixin_verify.py similarity index 100% rename from backend/app/services/model_context_bundle/mixin_verify.py rename to backend/app/services/model/model_context_bundle/mixin_verify.py diff --git a/backend/app/services/model_context_bundle/service.py b/backend/app/services/model/model_context_bundle/service.py similarity index 100% rename from backend/app/services/model_context_bundle/service.py rename to backend/app/services/model/model_context_bundle/service.py diff --git a/backend/app/services/model_context_bundle/shared.py b/backend/app/services/model/model_context_bundle/shared.py similarity index 90% rename from backend/app/services/model_context_bundle/shared.py rename to backend/app/services/model/model_context_bundle/shared.py index 61f020d1..4b3935a5 100644 --- a/backend/app/services/model_context_bundle/shared.py +++ b/backend/app/services/model/model_context_bundle/shared.py @@ -10,10 +10,10 @@ from uuid import uuid4 from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import AICallLog +from ....models import AICallLog from ..model_routing import ModelRoutingService -from ..project_knowledge_graph_service import ProjectKnowledgeGraphService -from ..task_brief import ( +from ...project_knowledge_graph_service import ProjectKnowledgeGraphService +from ...task_brief import ( HIGH_CONTRACT_TEMPLATE_ID, HIGH_TEMPLATE_ID, LOW_TEMPLATE_ID, diff --git a/backend/app/services/model_routing/__init__.py b/backend/app/services/model/model_routing/__init__.py similarity index 100% rename from backend/app/services/model_routing/__init__.py rename to backend/app/services/model/model_routing/__init__.py diff --git a/backend/app/services/model_routing/service.py b/backend/app/services/model/model_routing/service.py similarity index 99% rename from backend/app/services/model_routing/service.py rename to backend/app/services/model/model_routing/service.py index 5d58a568..f92d55ee 100644 --- a/backend/app/services/model_routing/service.py +++ b/backend/app/services/model/model_routing/service.py @@ -8,8 +8,8 @@ from typing import Any from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import Integration -from ...utils.timezone import now_cn_naive +from ....models import Integration +from ....utils.timezone import now_cn_naive ALLOWED_PROVIDERS = {"openai", "glm", "deepseek", "ollama"} ALLOWED_TIERS = {"low", "high", "safety"} diff --git a/backend/app/services/prompt_profile/__init__.py b/backend/app/services/model/prompt_profile/__init__.py similarity index 100% rename from backend/app/services/prompt_profile/__init__.py rename to backend/app/services/model/prompt_profile/__init__.py diff --git a/backend/app/services/prompt_profile/service.py b/backend/app/services/model/prompt_profile/service.py similarity index 98% rename from backend/app/services/prompt_profile/service.py rename to backend/app/services/model/prompt_profile/service.py index 7d534e0a..89bfb726 100644 --- a/backend/app/services/prompt_profile/service.py +++ b/backend/app/services/model/prompt_profile/service.py @@ -10,10 +10,10 @@ from typing import Any, Optional from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...config.ai_prompts import build_default_role_prompt_bundle -from ...models import Integration +from ....config.ai_prompts import build_default_role_prompt_bundle +from ....models import Integration -from ...utils.timezone import now_cn_aware +from ....utils.timezone import now_cn_aware INTEGRATION_TYPE = "ai_prompt_profiles" ROLE_KEY_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{1,31}$") MAX_ROLES = 12 diff --git a/backend/app/services/project_knowledge_graph_service/shared.py b/backend/app/services/project_knowledge_graph_service/shared.py index bfbf1e17..2b72b460 100644 --- a/backend/app/services/project_knowledge_graph_service/shared.py +++ b/backend/app/services/project_knowledge_graph_service/shared.py @@ -35,7 +35,7 @@ from ...models import ( User, ) from ..context_cache_service import context_cache_service -from ..symbol_index import SymbolIndexService +from ..signal.symbol_index import SymbolIndexService from ...utils.timezone import now_cn_naive @@ -56,3 +56,5 @@ DDL_BLOCKED_PREFIXES = [ DDL_GUARDRAIL_POLICY_KEY = "policy:ddl_via_interface_only" DDL_GUARDRAIL_POLICY_NAME = "DDL 只能通过受控接口/迁移流程" + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/scheduler_center_core/ai_pmo_cadence.py b/backend/app/services/scheduler_center_core/ai_pmo_cadence.py index 5cdcb1ed..2e9096a9 100644 --- a/backend/app/services/scheduler_center_core/ai_pmo_cadence.py +++ b/backend/app/services/scheduler_center_core/ai_pmo_cadence.py @@ -2,7 +2,20 @@ from __future__ import annotations -from .shared import AIPMOCadenceService, PortfolioSnapshot, Project, and_, async_sessionmaker, asyncio, datetime, logger, os, select, timedelta, workload as workload_service +from .shared import ( + AIPMOCadenceService, + PortfolioSnapshot, + Project, + and_, + async_sessionmaker, + asyncio, + datetime, + logger, + os, + select, + timedelta, + workload_service, +) class AIPMOCadenceScheduler: diff --git a/backend/app/services/scheduler_center_core/knowledge_graph.py b/backend/app/services/scheduler_center_core/knowledge_graph.py index 5b4f0b48..6dcc7de7 100644 --- a/backend/app/services/scheduler_center_core/knowledge_graph.py +++ b/backend/app/services/scheduler_center_core/knowledge_graph.py @@ -2,7 +2,14 @@ from __future__ import annotations -from .shared import ProjectKnowledgeGraphService, async_sessionmaker, asyncio, logger, os, workload as workload_service +from .shared import ( + ProjectKnowledgeGraphService, + async_sessionmaker, + asyncio, + logger, + os, + workload_service, +) class KnowledgeGraphIncrementalScheduler: diff --git a/backend/app/services/scheduler_center_core/shared.py b/backend/app/services/scheduler_center_core/shared.py index 626db119..75cfb172 100644 --- a/backend/app/services/scheduler_center_core/shared.py +++ b/backend/app/services/scheduler_center_core/shared.py @@ -15,7 +15,7 @@ from ...models import KnowledgeBaseDoc, PortfolioSnapshot, Project from ..workload import workload_alert_service from .. import workload as workload_service from ..adaptive_rag_service import AdaptiveRAGService -from ..ai_pmo_cadence import AIPMOCadenceService +from ..ai_pmo.ai_pmo_cadence import AIPMOCadenceService from ..project_knowledge_graph_service import ProjectKnowledgeGraphService logger = get_logger(__name__) diff --git a/backend/app/services/signal/__init__.py b/backend/app/services/signal/__init__.py new file mode 100644 index 00000000..862b694d --- /dev/null +++ b/backend/app/services/signal/__init__.py @@ -0,0 +1 @@ +"""Signal domain service namespace.""" diff --git a/backend/app/services/signal_capture_service/__init__.py b/backend/app/services/signal/signal_capture_service/__init__.py similarity index 100% rename from backend/app/services/signal_capture_service/__init__.py rename to backend/app/services/signal/signal_capture_service/__init__.py diff --git a/backend/app/services/signal_capture_service/external_fetcher.py b/backend/app/services/signal/signal_capture_service/external_fetcher.py similarity index 100% rename from backend/app/services/signal_capture_service/external_fetcher.py rename to backend/app/services/signal/signal_capture_service/external_fetcher.py diff --git a/backend/app/services/signal_capture_service/impact_analyzer.py b/backend/app/services/signal/signal_capture_service/impact_analyzer.py similarity index 100% rename from backend/app/services/signal_capture_service/impact_analyzer.py rename to backend/app/services/signal/signal_capture_service/impact_analyzer.py diff --git a/backend/app/services/signal_capture_service/mixin_analysis_normalize.py b/backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py similarity index 98% rename from backend/app/services/signal_capture_service/mixin_analysis_normalize.py rename to backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py index 490ca424..48fb862c 100644 --- a/backend/app/services/signal_capture_service/mixin_analysis_normalize.py +++ b/backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py @@ -22,7 +22,7 @@ class SignalCaptureAnalysisNormalizeMixin: pass # 兼容模型返回“解释文本 + JSON”场景:抽取首个 JSON 对象再解析 - obj_text = ProactiveSignalCaptureService._extract_first_json_object(content) + obj_text = SignalCaptureAnalysisNormalizeMixin._extract_first_json_object(content) if not obj_text: raise ValueError("empty llm response") parsed = json.loads(obj_text) @@ -150,7 +150,7 @@ class SignalCaptureAnalysisNormalizeMixin: @staticmethod def _sanitize_feishu_message(message: str) -> str: - text = ProactiveSignalCaptureService._normalize_single_line_text(message or "", max_len=2000) + text = SignalCaptureAnalysisNormalizeMixin._normalize_single_line_text(message or "", max_len=2000) if not text: return "" @@ -265,4 +265,3 @@ class SignalCaptureAnalysisNormalizeMixin: def _rule_based_chat_signal(self, message: str) -> Optional[Dict[str, Any]]: _ = message return None - diff --git a/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py b/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py new file mode 100644 index 00000000..742c190a --- /dev/null +++ b/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py @@ -0,0 +1,305 @@ +from __future__ import annotations + +from .shared import * + +class SignalCaptureEntryMixin: + def __init__(self, db: AsyncSession): + self.db = db + self.llm = LLMService() + + async def capture_from_feishu( + self, + group_chat_id: str, + message_content: str, + sender: str, + tenant_id: int = 1, + proposer_open_id: Optional[str] = None, + captured_at: Optional[datetime] = None, + mentioned_bot_names: Optional[List[str]] = None, + feishu_message_id: Optional[str] = None, + chat_type: Optional[str] = None, + force_sync: bool = False, + ) -> Dict[str, Any]: + """ + 从飞书群聊捕获信号 + + 触发条件: + - 提到产品、竞品、技术关键词 + - 讨论架构、技术选型 + - 分享技术文章 + """ + trace_id = f"feishu-{uuid4().hex[:12]}" + captured_dt = captured_at or now_cn_aware() + if captured_dt.tzinfo is None: + captured_dt = captured_dt.replace(tzinfo=CN_TZ) + + proposer_name = self._normalize_single_line_text(sender or "未知用户", max_len=80) or "未知用户" + proposer_open_id = self._normalize_single_line_text(proposer_open_id or "", max_len=128) or None + self._emit_ai_think( + "capture_start", + trace_id=trace_id, + tenant_id=tenant_id, + group_chat_id=group_chat_id, + sender=proposer_name, + ) + + cleaned_message = self._sanitize_feishu_message(message_content) + if not cleaned_message: + self._emit_ai_think("capture_skip_empty", trace_id=trace_id) + return {"status": "ignored", "reason": "empty_or_emoji_only"} + + kernel = AIPMOKernelService(self.db, tenant_id=tenant_id) + try: + should_sync_process = bool(force_sync) + if not should_sync_process: + session = await kernel._get_or_create_session( + tenant_id=int(tenant_id), + sender=proposer_name, + ) + should_sync_process = kernel._is_short_confirmation_message( + message_text=cleaned_message, + session=session, + ) + + if should_sync_process: + return await self._process_feishu_with_kernel_sync( + trace_id=trace_id, + tenant_id=tenant_id, + kernel=kernel, + group_chat_id=group_chat_id, + cleaned_message=cleaned_message, + proposer_name=proposer_name, + proposer_open_id=proposer_open_id, + captured_dt=captured_dt, + feishu_message_id=feishu_message_id, + chat_type=chat_type, + ) + + if self._is_non_requirement_message(cleaned_message) and not self._is_question_like(cleaned_message): + self._emit_ai_think( + "capture_skip_noise", + trace_id=trace_id, + message_preview=cleaned_message[:120], + ) + return {"status": "ignored", "reason": "non_requirement_noise"} + + source_message_id = str(feishu_message_id or "").strip() + if not source_message_id: + source_message_id = AIPMOKernelService._build_source_message_id( + source_system="feishu", + source_message_id=None, + channel_ref=group_chat_id, + sender=proposer_name, + message_text=cleaned_message, + ) + + run_row, created = await AIOrchestrationRunService(self.db).enqueue_ingress_run( + tenant_id=int(tenant_id), + source_system="feishu", + source_message_id=source_message_id, + sender=proposer_name, + message_text=cleaned_message, + channel_ref=group_chat_id, + source_sender_open_id=proposer_open_id, + chat_type=chat_type, + captured_at=captured_dt, + project_hint_id=None, + max_retries=3, + ) + if created: + await AIOrchestrationRunService(self.db).mark_ingress_ack_sent( + tenant_id=int(tenant_id), + trace_id=str(run_row.trace_id or ""), + ) + await self.db.commit() + pipeline_result = self._build_async_processing_ack_result( + trace_id=str(run_row.trace_id or ""), + ) + else: + await self.db.commit() + pipeline_result = { + "status": "ignored", + "reason": "duplicate_filtered", + "classification": "duplicate", + "linked_project_ids": [], + "signal_id": None, + "created_project_id": None, + "auto_tasks": [], + "ai_trace_id": str(run_row.trace_id or trace_id), + "direct_reply_required": False, + "direct_reply_text": "", + } + try: + await self._sync_feishu_recent_cache( + tenant_id=tenant_id, + group_chat_id=group_chat_id, + sender=proposer_name, + sender_open_id=proposer_open_id, + message=cleaned_message, + pipeline_result=pipeline_result, + ) + except Exception as cache_exc: + self._emit_ai_think( + "capture_recent_cache_sync_failed", + trace_id=trace_id, + error=str(cache_exc), + ) + return pipeline_result + except Exception as exc: + self._emit_ai_think("capture_kernel_failed", trace_id=trace_id, error=str(exc)) + try: + await self.db.rollback() + except Exception as rollback_exc: + self._emit_ai_think( + "capture_kernel_failed_rollback_failed", + trace_id=trace_id, + error=str(rollback_exc), + ) + return { + "status": "error", + "reason": "kernel_failed", + "classification": "error", + "project_scope": "error", + "linked_project_ids": [], + "signal_id": None, + "created_project_id": None, + "auto_tasks": [], + "ai_trace_id": trace_id, + "direct_reply_required": True, + "direct_reply_text": "系统处理中出现异常,本次未执行入库。请稍后重试,或补充更明确的目标/对象/动作。", + } + + return await self._capture_from_feishu_legacy_flow( + trace_id=trace_id, + group_chat_id=group_chat_id, + message_content=message_content, + cleaned_message=cleaned_message, + proposer_name=proposer_name, + proposer_open_id=proposer_open_id, + mentioned_bot_names=mentioned_bot_names, + tenant_id=tenant_id, + captured_dt=captured_dt, + ) + + async def _process_feishu_with_kernel_sync( + self, + *, + trace_id: str, + tenant_id: int, + kernel: AIPMOKernelService, + group_chat_id: str, + cleaned_message: str, + proposer_name: str, + proposer_open_id: Optional[str], + captured_dt: datetime, + feishu_message_id: Optional[str], + chat_type: Optional[str], + ) -> Dict[str, Any]: + pipeline_result = await kernel.process_feishu_message( + group_chat_id=group_chat_id, + message_content=cleaned_message, + sender=proposer_name, + source_message_id=feishu_message_id, + source_sender_open_id=proposer_open_id, + captured_at=captured_dt, + chat_type=chat_type, + ) + if not bool(pipeline_result.get("handled")): + self._emit_ai_think( + "capture_kernel_failed", + trace_id=trace_id, + reason=pipeline_result.get("reason"), + error=pipeline_result.get("error"), + ) + return self._build_kernel_error_result(trace_id=trace_id, reason=str(pipeline_result.get("reason") or "kernel_not_handled")) + + self._emit_ai_think( + "capture_done_ai_pmo", + trace_id=trace_id, + classification=pipeline_result.get("classification"), + linked_project_ids=pipeline_result.get("linked_project_ids", []), + work_item_id=pipeline_result.get("work_item_id"), + ) + pipeline_result = await self._apply_pipeline_post_actions( + trace_id=trace_id, + tenant_id=tenant_id, + cleaned_message=cleaned_message, + proposer_name=proposer_name, + captured_at=captured_dt, + pipeline_result=pipeline_result, + ) + try: + await self._sync_feishu_recent_cache( + tenant_id=tenant_id, + group_chat_id=group_chat_id, + sender=proposer_name, + sender_open_id=proposer_open_id, + message=cleaned_message, + pipeline_result=pipeline_result, + ) + except Exception as cache_exc: + self._emit_ai_think( + "capture_recent_cache_sync_failed", + trace_id=trace_id, + error=str(cache_exc), + ) + try: + asyncio.create_task( + AdaptiveRAGService.ingest_feishu_message_async( + tenant_id=tenant_id, + content=cleaned_message, + sender=proposer_name, + chat_id=group_chat_id, + message_id=feishu_message_id, + captured_at=captured_dt, + project_id=( + int(pipeline_result["linked_project_ids"][0]) + if isinstance(pipeline_result.get("linked_project_ids"), list) + and pipeline_result.get("linked_project_ids") + else None + ), + ) + ) + except Exception: + pass + return pipeline_result + + @staticmethod + def _build_kernel_error_result(*, trace_id: str, reason: str) -> Dict[str, Any]: + return { + "status": "error", + "reason": str(reason or "kernel_failed"), + "classification": "error", + "project_scope": "error", + "linked_project_ids": [], + "signal_id": None, + "created_project_id": None, + "auto_tasks": [], + "ai_trace_id": trace_id, + "direct_reply_required": True, + "direct_reply_text": "系统处理中出现异常,本次未执行入库。请稍后重试,或补充更明确的目标/对象/动作。", + } + + @staticmethod + def _build_async_processing_ack_result(*, trace_id: str) -> Dict[str, Any]: + return { + "status": "captured", + "classification": "command", + "linked_project_ids": [], + "signal_id": None, + "created_project_id": None, + "auto_tasks": [], + "ai_trace_id": trace_id, + "results": [ + { + "route_status": "queued_for_decision", + "summary": "message_queued_async", + } + ], + "direct_reply_required": True, + "direct_reply_text": "已收到,正在处理。", + "reply_template_key": "async_processing_ack", + "work_item_id": None, + "decision_level": None, + "review_summary": None, + } diff --git a/backend/app/services/signal_capture_service/mixin_capture_legacy.py b/backend/app/services/signal/signal_capture_service/mixin_capture_legacy.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_capture_legacy.py rename to backend/app/services/signal/signal_capture_service/mixin_capture_legacy.py diff --git a/backend/app/services/signal_capture_service/mixin_external_sources.py b/backend/app/services/signal/signal_capture_service/mixin_external_sources.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_external_sources.py rename to backend/app/services/signal/signal_capture_service/mixin_external_sources.py diff --git a/backend/app/services/signal_capture_service/mixin_new_project.py b/backend/app/services/signal/signal_capture_service/mixin_new_project.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_new_project.py rename to backend/app/services/signal/signal_capture_service/mixin_new_project.py diff --git a/backend/app/services/signal_capture_service/mixin_pipeline_cache.py b/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_pipeline_cache.py rename to backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py diff --git a/backend/app/services/signal_capture_service/mixin_source_analyzers.py b/backend/app/services/signal/signal_capture_service/mixin_source_analyzers.py similarity index 99% rename from backend/app/services/signal_capture_service/mixin_source_analyzers.py rename to backend/app/services/signal/signal_capture_service/mixin_source_analyzers.py index 5f3f37b0..ec6f4cf5 100644 --- a/backend/app/services/signal_capture_service/mixin_source_analyzers.py +++ b/backend/app/services/signal/signal_capture_service/mixin_source_analyzers.py @@ -191,7 +191,7 @@ URL: {url} {"project_id": project_id, "signal_id": signal_id}, ) if existing.first() is None: - from ...models import ProjectSignalLink + from ....models import ProjectSignalLink link = ProjectSignalLink( project_id=project_id, signal_id=signal_id, diff --git a/backend/app/services/signal_capture_service/mixin_task_orchestration_main.py b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_main.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_task_orchestration_main.py rename to backend/app/services/signal/signal_capture_service/mixin_task_orchestration_main.py diff --git a/backend/app/services/signal_capture_service/mixin_task_orchestration_skill.py b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_skill.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_task_orchestration_skill.py rename to backend/app/services/signal/signal_capture_service/mixin_task_orchestration_skill.py diff --git a/backend/app/services/signal_capture_service/mixin_task_orchestration_team.py b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_team.py similarity index 100% rename from backend/app/services/signal_capture_service/mixin_task_orchestration_team.py rename to backend/app/services/signal/signal_capture_service/mixin_task_orchestration_team.py diff --git a/backend/app/services/signal_capture_service/service.py b/backend/app/services/signal/signal_capture_service/service.py similarity index 100% rename from backend/app/services/signal_capture_service/service.py rename to backend/app/services/signal/signal_capture_service/service.py diff --git a/backend/app/services/signal_capture_service/shared.py b/backend/app/services/signal/signal_capture_service/shared.py similarity index 85% rename from backend/app/services/signal_capture_service/shared.py rename to backend/app/services/signal/signal_capture_service/shared.py index 11a7359b..e0247dc0 100644 --- a/backend/app/services/signal_capture_service/shared.py +++ b/backend/app/services/signal/signal_capture_service/shared.py @@ -26,29 +26,30 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from sqlalchemy import and_, text -from ...core.logging import get_logger -from ...core.database import AsyncSessionLocal -from ...models import ( +from ....core.logging import get_logger +from ....core.database import AsyncSessionLocal +from ....models import ( Signal, Project, ProjectDomain, User, ProjectSignalLink, Task, TruthNode, SkillTag, UserProject, UserWorkload, UserSkillBinding, ) -from ...config.ai_prompts import ( +from ....config.ai_prompts import ( PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION, PROMPT_FEISHU_TASK_ORCHESTRATION, ) -from ..llm_service import LLMService -from ..notification_service import NotificationService -from .. import workload as workload_service -from ..adaptive_rag_service import AdaptiveRAGService -from ..context_cache_service import context_cache_service +from ...llm_service import LLMService +from ...notification_service import NotificationService +from ... import workload as workload_service +from ...adaptive_rag_service import AdaptiveRAGService +from ...ai_orchestration_run import AIOrchestrationRunService +from ...context_cache_service import context_cache_service from ..signal_source_orchestration import SignalSourceOrchestrationService -from ..ai_pmo_kernel import AIPMOKernelService -from ...utils.project_members import ensure_project_default_members -from ...utils.sequence import ensure_project_id_sequence, ensure_signal_id_sequence +from ...ai_pmo.ai_pmo_kernel import AIPMOKernelService +from ....utils.project_members import ensure_project_default_members +from ....utils.sequence import ensure_project_id_sequence, ensure_signal_id_sequence -from ...utils.timezone import CN_TZ, now_cn_aware +from ....utils.timezone import CN_TZ, now_cn_aware EMOJI_UNICODE_RE = re.compile( r"[\U0001F300-\U0001F6FF\U0001F700-\U0001F77F\U0001F780-\U0001F7FF" r"\U0001F800-\U0001F8FF\U0001F900-\U0001F9FF\U0001FA00-\U0001FAFF" @@ -171,3 +172,4 @@ def _build_feishu_rag_conversation_key( return f"feishu:{chat}:{actor}" +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/signal_planner/__init__.py b/backend/app/services/signal/signal_planner/__init__.py similarity index 100% rename from backend/app/services/signal_planner/__init__.py rename to backend/app/services/signal/signal_planner/__init__.py diff --git a/backend/app/services/signal_planner/mixin_analyze.py b/backend/app/services/signal/signal_planner/mixin_analyze.py similarity index 100% rename from backend/app/services/signal_planner/mixin_analyze.py rename to backend/app/services/signal/signal_planner/mixin_analyze.py diff --git a/backend/app/services/signal_planner/mixin_loaders.py b/backend/app/services/signal/signal_planner/mixin_loaders.py similarity index 100% rename from backend/app/services/signal_planner/mixin_loaders.py rename to backend/app/services/signal/signal_planner/mixin_loaders.py diff --git a/backend/app/services/signal_planner/mixin_prompt.py b/backend/app/services/signal/signal_planner/mixin_prompt.py similarity index 100% rename from backend/app/services/signal_planner/mixin_prompt.py rename to backend/app/services/signal/signal_planner/mixin_prompt.py diff --git a/backend/app/services/signal_planner/mixin_utils.py b/backend/app/services/signal/signal_planner/mixin_utils.py similarity index 100% rename from backend/app/services/signal_planner/mixin_utils.py rename to backend/app/services/signal/signal_planner/mixin_utils.py diff --git a/backend/app/services/signal_planner/service.py b/backend/app/services/signal/signal_planner/service.py similarity index 100% rename from backend/app/services/signal_planner/service.py rename to backend/app/services/signal/signal_planner/service.py diff --git a/backend/app/services/signal_planner/shared.py b/backend/app/services/signal/signal_planner/shared.py similarity index 74% rename from backend/app/services/signal_planner/shared.py rename to backend/app/services/signal/signal_planner/shared.py index 5dc2b4c9..627f139b 100644 --- a/backend/app/services/signal_planner/shared.py +++ b/backend/app/services/signal/signal_planner/shared.py @@ -9,10 +9,10 @@ from typing import Any, Optional from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker -from ...config.ai_prompts import PROMPT_SIGNAL_PLANNER -from ...core.logging import get_logger -from ...core.database import AsyncSessionLocal -from ...models import ( +from ....config.ai_prompts import PROMPT_SIGNAL_PLANNER +from ....core.logging import get_logger +from ....core.database import AsyncSessionLocal +from ....models import ( Project, ProjectSignalLink, Signal, @@ -22,10 +22,10 @@ from ...models import ( UserSkillBinding, UserWorkload, ) -from ...utils.timezone import now_cn_naive -from ..feishu_outbox import FeishuOutboxService -from ..llm_service import llm_service -from ..model_routing import ModelRoutingService +from ....utils.timezone import now_cn_naive +from ...integrations.feishu_outbox import FeishuOutboxService +from ...llm_service import llm_service +from ...model.model_routing import ModelRoutingService from .signal_strategic_analysis_service import SignalStrategicAnalysisService logger = get_logger(__name__) diff --git a/backend/app/services/signal_planner/signal_strategic_analysis_service.py b/backend/app/services/signal/signal_planner/signal_strategic_analysis_service.py similarity index 97% rename from backend/app/services/signal_planner/signal_strategic_analysis_service.py rename to backend/app/services/signal/signal_planner/signal_strategic_analysis_service.py index 65c6a04a..16ef25d7 100644 --- a/backend/app/services/signal_planner/signal_strategic_analysis_service.py +++ b/backend/app/services/signal/signal_planner/signal_strategic_analysis_service.py @@ -8,8 +8,8 @@ from typing import Any, Optional from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import Signal -from ...utils.timezone import now_cn_naive +from ....models import Signal +from ....utils.timezone import now_cn_naive class SignalStrategicAnalysisService: diff --git a/backend/app/services/signal_source_orchestration/__init__.py b/backend/app/services/signal/signal_source_orchestration/__init__.py similarity index 100% rename from backend/app/services/signal_source_orchestration/__init__.py rename to backend/app/services/signal/signal_source_orchestration/__init__.py diff --git a/backend/app/services/signal_source_orchestration/mixin_entry_orchestrate.py b/backend/app/services/signal/signal_source_orchestration/mixin_entry_orchestrate.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_entry_orchestrate.py rename to backend/app/services/signal/signal_source_orchestration/mixin_entry_orchestrate.py diff --git a/backend/app/services/signal_source_orchestration/mixin_entry_terminal_a.py b/backend/app/services/signal/signal_source_orchestration/mixin_entry_terminal_a.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_entry_terminal_a.py rename to backend/app/services/signal/signal_source_orchestration/mixin_entry_terminal_a.py diff --git a/backend/app/services/signal_source_orchestration/mixin_entry_terminal_b.py b/backend/app/services/signal/signal_source_orchestration/mixin_entry_terminal_b.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_entry_terminal_b.py rename to backend/app/services/signal/signal_source_orchestration/mixin_entry_terminal_b.py diff --git a/backend/app/services/signal_source_orchestration/mixin_execute_isolation.py b/backend/app/services/signal/signal_source_orchestration/mixin_execute_isolation.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_execute_isolation.py rename to backend/app/services/signal/signal_source_orchestration/mixin_execute_isolation.py diff --git a/backend/app/services/signal_source_orchestration/mixin_lookup_parse.py b/backend/app/services/signal/signal_source_orchestration/mixin_lookup_parse.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_lookup_parse.py rename to backend/app/services/signal/signal_source_orchestration/mixin_lookup_parse.py diff --git a/backend/app/services/signal_source_orchestration/mixin_planning_locator_roles.py b/backend/app/services/signal/signal_source_orchestration/mixin_planning_locator_roles.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_planning_locator_roles.py rename to backend/app/services/signal/signal_source_orchestration/mixin_planning_locator_roles.py diff --git a/backend/app/services/signal_source_orchestration/mixin_planning_onboard.py b/backend/app/services/signal/signal_source_orchestration/mixin_planning_onboard.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_planning_onboard.py rename to backend/app/services/signal/signal_source_orchestration/mixin_planning_onboard.py diff --git a/backend/app/services/signal_source_orchestration/mixin_registry_execute.py b/backend/app/services/signal/signal_source_orchestration/mixin_registry_execute.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_registry_execute.py rename to backend/app/services/signal/signal_source_orchestration/mixin_registry_execute.py diff --git a/backend/app/services/signal_source_orchestration/mixin_result_resolution.py b/backend/app/services/signal/signal_source_orchestration/mixin_result_resolution.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_result_resolution.py rename to backend/app/services/signal/signal_source_orchestration/mixin_result_resolution.py diff --git a/backend/app/services/signal_source_orchestration/mixin_result_run_updates.py b/backend/app/services/signal/signal_source_orchestration/mixin_result_run_updates.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_result_run_updates.py rename to backend/app/services/signal/signal_source_orchestration/mixin_result_run_updates.py diff --git a/backend/app/services/signal_source_orchestration/mixin_worker_prompt_persist.py b/backend/app/services/signal/signal_source_orchestration/mixin_worker_prompt_persist.py similarity index 100% rename from backend/app/services/signal_source_orchestration/mixin_worker_prompt_persist.py rename to backend/app/services/signal/signal_source_orchestration/mixin_worker_prompt_persist.py diff --git a/backend/app/services/signal_source_orchestration/service.py b/backend/app/services/signal/signal_source_orchestration/service.py similarity index 100% rename from backend/app/services/signal_source_orchestration/service.py rename to backend/app/services/signal/signal_source_orchestration/service.py diff --git a/backend/app/services/signal_source_orchestration/shared.py b/backend/app/services/signal/signal_source_orchestration/shared.py similarity index 82% rename from backend/app/services/signal_source_orchestration/shared.py rename to backend/app/services/signal/signal_source_orchestration/shared.py index 19c22393..066c2538 100644 --- a/backend/app/services/signal_source_orchestration/shared.py +++ b/backend/app/services/signal/signal_source_orchestration/shared.py @@ -16,23 +16,23 @@ from sqlalchemy import and_, select from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from ...config.ai_prompts import ( +from ....config.ai_prompts import ( PROMPT_AI_WORKER_SIGNAL_SOURCE_EXECUTION, PROMPT_SIGNAL_SOURCE_ONBOARDING, ) -from ...models import ( +from ....models import ( AIWorkerExecution, Project, Signal, SignalSourceRegistry, User, ) -from ...core.database import AsyncSessionLocal -from ..ai_orchestration_run import AIOrchestrationRunService -from ..prompt_profile import prompt_profile_service -from ..llm_service import llm_service +from ....core.database import AsyncSessionLocal +from ...ai_orchestration_run import AIOrchestrationRunService +from ...model.prompt_profile import prompt_profile_service +from ...llm_service import llm_service -from ...utils.timezone import now_cn_naive +from ....utils.timezone import now_cn_naive SOURCE_ONBOARDING_ACTION_RE = re.compile( r"(新增|增加|添加|接入|配置|注册|建立|加个|加一个|加).{0,8}(信号源|来源|监控源|channel|source|webhook|飞书|github|url|rss)", re.IGNORECASE, @@ -54,4 +54,3 @@ COMPLETE_WORKER_WAIT_TIMEOUT_SECONDS = 6.0 def _utc_now_naive() -> datetime: return now_cn_naive() - diff --git a/backend/app/services/signal_source_repair/__init__.py b/backend/app/services/signal/signal_source_repair/__init__.py similarity index 100% rename from backend/app/services/signal_source_repair/__init__.py rename to backend/app/services/signal/signal_source_repair/__init__.py diff --git a/backend/app/services/signal_source_repair/errors.py b/backend/app/services/signal/signal_source_repair/errors.py similarity index 100% rename from backend/app/services/signal_source_repair/errors.py rename to backend/app/services/signal/signal_source_repair/errors.py diff --git a/backend/app/services/signal_source_repair/mixin_capture.py b/backend/app/services/signal/signal_source_repair/mixin_capture.py similarity index 100% rename from backend/app/services/signal_source_repair/mixin_capture.py rename to backend/app/services/signal/signal_source_repair/mixin_capture.py diff --git a/backend/app/services/signal_source_repair/mixin_data_access.py b/backend/app/services/signal/signal_source_repair/mixin_data_access.py similarity index 100% rename from backend/app/services/signal_source_repair/mixin_data_access.py rename to backend/app/services/signal/signal_source_repair/mixin_data_access.py diff --git a/backend/app/services/signal_source_repair/mixin_persist.py b/backend/app/services/signal/signal_source_repair/mixin_persist.py similarity index 100% rename from backend/app/services/signal_source_repair/mixin_persist.py rename to backend/app/services/signal/signal_source_repair/mixin_persist.py diff --git a/backend/app/services/signal_source_repair/mixin_repair.py b/backend/app/services/signal/signal_source_repair/mixin_repair.py similarity index 100% rename from backend/app/services/signal_source_repair/mixin_repair.py rename to backend/app/services/signal/signal_source_repair/mixin_repair.py diff --git a/backend/app/services/signal_source_repair/mixin_utils.py b/backend/app/services/signal/signal_source_repair/mixin_utils.py similarity index 100% rename from backend/app/services/signal_source_repair/mixin_utils.py rename to backend/app/services/signal/signal_source_repair/mixin_utils.py diff --git a/backend/app/services/signal_source_repair/service.py b/backend/app/services/signal/signal_source_repair/service.py similarity index 100% rename from backend/app/services/signal_source_repair/service.py rename to backend/app/services/signal/signal_source_repair/service.py diff --git a/backend/app/services/signal_source_repair/shared.py b/backend/app/services/signal/signal_source_repair/shared.py similarity index 70% rename from backend/app/services/signal_source_repair/shared.py rename to backend/app/services/signal/signal_source_repair/shared.py index 2b349c5f..ad5eadc2 100644 --- a/backend/app/services/signal_source_repair/shared.py +++ b/backend/app/services/signal/signal_source_repair/shared.py @@ -10,11 +10,11 @@ from typing import Any, Optional from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import SignalSourceRegistry, User -from ...utils.audited_write import get_audit_context -from ...utils.timezone import now_cn_naive -from ..notification_service import NotificationService -from ..operation_log_service import log_operation +from ....models import SignalSourceRegistry, User +from ....utils.audited_write import get_audit_context +from ....utils.timezone import now_cn_naive +from ...notification_service import NotificationService +from ...operation_log_service import log_operation from ..signal_capture_service import ProactiveSignalCaptureService __all__ = [ diff --git a/backend/app/services/symbol_index/__init__.py b/backend/app/services/signal/symbol_index/__init__.py similarity index 100% rename from backend/app/services/symbol_index/__init__.py rename to backend/app/services/signal/symbol_index/__init__.py diff --git a/backend/app/services/symbol_index/mixin_parse_python.py b/backend/app/services/signal/symbol_index/mixin_parse_python.py similarity index 100% rename from backend/app/services/symbol_index/mixin_parse_python.py rename to backend/app/services/signal/symbol_index/mixin_parse_python.py diff --git a/backend/app/services/symbol_index/mixin_parse_typescript.py b/backend/app/services/signal/symbol_index/mixin_parse_typescript.py similarity index 100% rename from backend/app/services/symbol_index/mixin_parse_typescript.py rename to backend/app/services/signal/symbol_index/mixin_parse_typescript.py diff --git a/backend/app/services/symbol_index/mixin_query_overview.py b/backend/app/services/signal/symbol_index/mixin_query_overview.py similarity index 100% rename from backend/app/services/symbol_index/mixin_query_overview.py rename to backend/app/services/signal/symbol_index/mixin_query_overview.py diff --git a/backend/app/services/symbol_index/mixin_serialize.py b/backend/app/services/signal/symbol_index/mixin_serialize.py similarity index 100% rename from backend/app/services/symbol_index/mixin_serialize.py rename to backend/app/services/signal/symbol_index/mixin_serialize.py diff --git a/backend/app/services/symbol_index/mixin_sync_core.py b/backend/app/services/signal/symbol_index/mixin_sync_core.py similarity index 100% rename from backend/app/services/symbol_index/mixin_sync_core.py rename to backend/app/services/signal/symbol_index/mixin_sync_core.py diff --git a/backend/app/services/symbol_index/service.py b/backend/app/services/signal/symbol_index/service.py similarity index 100% rename from backend/app/services/symbol_index/service.py rename to backend/app/services/signal/symbol_index/service.py diff --git a/backend/app/services/symbol_index/shared.py b/backend/app/services/signal/symbol_index/shared.py similarity index 91% rename from backend/app/services/symbol_index/shared.py rename to backend/app/services/signal/symbol_index/shared.py index fbbc6b46..e6ba6b54 100644 --- a/backend/app/services/symbol_index/shared.py +++ b/backend/app/services/signal/symbol_index/shared.py @@ -19,10 +19,10 @@ from typing import Any, Optional from sqlalchemy import and_, delete, func, or_, select from sqlalchemy.ext.asyncio import AsyncSession -from ...models import SymbolDefinition, SymbolReference +from ....models import SymbolDefinition, SymbolReference -from ...utils.timezone import CN_TZ, now_cn_aware, now_cn_naive +from ....utils.timezone import CN_TZ, now_cn_aware, now_cn_naive TS_SYMBOL_RE = re.compile( r"^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|const)\s+([A-Za-z_][A-Za-z0-9_]*)", re.MULTILINE, diff --git a/backend/app/services/signal_capture_service/mixin_capture_entry.py b/backend/app/services/signal_capture_service/mixin_capture_entry.py deleted file mode 100644 index 075c816d..00000000 --- a/backend/app/services/signal_capture_service/mixin_capture_entry.py +++ /dev/null @@ -1,173 +0,0 @@ -from __future__ import annotations - -from .shared import * - -class SignalCaptureEntryMixin: - def __init__(self, db: AsyncSession): - self.db = db - self.llm = LLMService() - - async def capture_from_feishu( - self, - group_chat_id: str, - message_content: str, - sender: str, - tenant_id: int = 1, - proposer_open_id: Optional[str] = None, - captured_at: Optional[datetime] = None, - mentioned_bot_names: Optional[List[str]] = None, - feishu_message_id: Optional[str] = None, - chat_type: Optional[str] = None, - ) -> Dict[str, Any]: - """ - 从飞书群聊捕获信号 - - 触发条件: - - 提到产品、竞品、技术关键词 - - 讨论架构、技术选型 - - 分享技术文章 - """ - trace_id = f"feishu-{uuid4().hex[:12]}" - captured_dt = captured_at or now_cn_aware() - if captured_dt.tzinfo is None: - captured_dt = captured_dt.replace(tzinfo=CN_TZ) - - proposer_name = self._normalize_single_line_text(sender or "未知用户", max_len=80) or "未知用户" - proposer_open_id = self._normalize_single_line_text(proposer_open_id or "", max_len=128) or None - self._emit_ai_think( - "capture_start", - trace_id=trace_id, - tenant_id=tenant_id, - group_chat_id=group_chat_id, - sender=proposer_name, - ) - - cleaned_message = self._sanitize_feishu_message(message_content) - if not cleaned_message: - self._emit_ai_think("capture_skip_empty", trace_id=trace_id) - return {"status": "ignored", "reason": "empty_or_emoji_only"} - if self._is_non_requirement_message(cleaned_message) and not self._is_question_like(cleaned_message): - self._emit_ai_think( - "capture_skip_noise", - trace_id=trace_id, - message_preview=cleaned_message[:120], - ) - return {"status": "ignored", "reason": "non_requirement_noise"} - - kernel = AIPMOKernelService(self.db, tenant_id=tenant_id) - try: - pipeline_result = await kernel.process_feishu_message( - group_chat_id=group_chat_id, - message_content=cleaned_message, - sender=proposer_name, - source_message_id=feishu_message_id, - source_sender_open_id=proposer_open_id, - captured_at=captured_dt, - chat_type=chat_type, - ) - if not bool(pipeline_result.get("handled")): - self._emit_ai_think( - "capture_kernel_failed", - trace_id=trace_id, - reason=pipeline_result.get("reason"), - error=pipeline_result.get("error"), - ) - return { - "status": "error", - "reason": str(pipeline_result.get("reason") or "kernel_not_handled"), - "classification": "error", - "project_scope": "error", - "linked_project_ids": [], - "signal_id": None, - "created_project_id": None, - "auto_tasks": [], - "ai_trace_id": trace_id, - "direct_reply_required": True, - "direct_reply_text": "系统处理中出现异常,本次未执行入库。请稍后重试,或补充更明确的目标/对象/动作。", - } - self._emit_ai_think( - "capture_done_ai_pmo", - trace_id=trace_id, - classification=pipeline_result.get("classification"), - linked_project_ids=pipeline_result.get("linked_project_ids", []), - work_item_id=pipeline_result.get("work_item_id"), - ) - pipeline_result = await self._apply_pipeline_post_actions( - trace_id=trace_id, - tenant_id=tenant_id, - cleaned_message=cleaned_message, - proposer_name=proposer_name, - captured_at=captured_dt, - pipeline_result=pipeline_result, - ) - try: - await self._sync_feishu_recent_cache( - tenant_id=tenant_id, - group_chat_id=group_chat_id, - sender=proposer_name, - sender_open_id=proposer_open_id, - message=cleaned_message, - pipeline_result=pipeline_result, - ) - except Exception as cache_exc: - self._emit_ai_think( - "capture_recent_cache_sync_failed", - trace_id=trace_id, - error=str(cache_exc), - ) - try: - asyncio.create_task( - AdaptiveRAGService.ingest_feishu_message_async( - tenant_id=tenant_id, - content=cleaned_message, - sender=proposer_name, - chat_id=group_chat_id, - message_id=feishu_message_id, - captured_at=captured_dt, - project_id=( - int(pipeline_result["linked_project_ids"][0]) - if isinstance(pipeline_result.get("linked_project_ids"), list) - and pipeline_result.get("linked_project_ids") - else None - ), - ) - ) - except Exception: - pass - return pipeline_result - except Exception as exc: - self._emit_ai_think("capture_kernel_failed", trace_id=trace_id, error=str(exc)) - try: - await self.db.rollback() - except Exception as rollback_exc: - self._emit_ai_think( - "capture_kernel_failed_rollback_failed", - trace_id=trace_id, - error=str(rollback_exc), - ) - return { - "status": "error", - "reason": "kernel_failed", - "classification": "error", - "project_scope": "error", - "linked_project_ids": [], - "signal_id": None, - "created_project_id": None, - "auto_tasks": [], - "ai_trace_id": trace_id, - "direct_reply_required": True, - "direct_reply_text": "系统处理中出现异常,本次未执行入库。请稍后重试,或补充更明确的目标/对象/动作。", - } - - return await self._capture_from_feishu_legacy_flow( - trace_id=trace_id, - group_chat_id=group_chat_id, - message_content=message_content, - cleaned_message=cleaned_message, - proposer_name=proposer_name, - proposer_open_id=proposer_open_id, - mentioned_bot_names=mentioned_bot_names, - tenant_id=tenant_id, - captured_dt=captured_dt, - ) - diff --git a/backend/app/services/skill/shared.py b/backend/app/services/skill/shared.py index e7e6ef1a..aa343d74 100644 --- a/backend/app/services/skill/shared.py +++ b/backend/app/services/skill/shared.py @@ -15,7 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from ...models import SkillTag, UserSkillBinding, UserWorkload, User, UserStatus, Task -from ..ai_pmo_kernel import AIPMOKernelService +from ..ai_pmo.ai_pmo_kernel import AIPMOKernelService # 系统预设 AI 开发专属技能标签 @@ -35,4 +35,3 @@ SYSTEM_SKILL_TAGS = [ {"name": "Git", "slug": "git", "category": "tool", "sort_order": 12}, ] - diff --git a/backend/app/services/sstc/charter_data.py b/backend/app/services/sstc/charter_data.py index 061d598a..12579e3c 100644 --- a/backend/app/services/sstc/charter_data.py +++ b/backend/app/services/sstc/charter_data.py @@ -118,11 +118,20 @@ def _serialize_charter_task(task: Task) -> dict[str, Any]: "due_date": task.due_date.isoformat() if task.due_date else None, } -def _serialize_alignment_record(record: AlignmentRecord) -> dict[str, Any]: +def _serialize_alignment_record( + record: AlignmentRecord, + user_name: Optional[str] = None, +) -> dict[str, Any]: + confirmed_at = None + if bool(record.is_confirmed): + ts = record.updated_at or record.created_at + confirmed_at = ts.isoformat() if ts else None + return { "user_id": str(record.user_id), + "user_name": user_name or f"用户 {record.user_id}", "score": record.alignment_score, "confirmed": bool(record.is_confirmed), "comments": record.comments, + "confirmed_at": confirmed_at, } - diff --git a/backend/app/services/sstc/shared.py b/backend/app/services/sstc/shared.py index a950ccb1..2ad74c29 100644 --- a/backend/app/services/sstc/shared.py +++ b/backend/app/services/sstc/shared.py @@ -48,7 +48,7 @@ from ...schemas.sstc import ( ) from ...services.llm_service import llm_service from ...services.ai_service_core import AIService -from ...services.ai_pmo_kernel import AIPMOKernelService +from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...services.operation_log_service import log_operation from ...services.auth import has_any_role from ...services.skill import recommend_personnel @@ -61,7 +61,7 @@ from ...services.sstc_service_core import ( ) from ...services import workload as workload_service from ...services.budget_enforcement import BudgetEnforcementService -from ...services.feishu_integration_service import get_feishu_service +from ...services.integrations.feishu_integration_service import get_feishu_service from ...services.adaptive_rag_service import AdaptiveRAGService from ...services.context_cache_service import context_cache_service from ...services.task_persistence_core import build_task_create_draft, persist_task @@ -142,4 +142,6 @@ RELEASE_STATUS_TRANSITIONS: dict[str, set[str]] = { "verified": set(), } +# Keep underscore-prefixed constants/functions available to `from .shared import *`. +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/sstc_service_core/mixin_build_views.py b/backend/app/services/sstc_service_core/mixin_build_views.py index e6d9bd5b..7f94d872 100644 --- a/backend/app/services/sstc_service_core/mixin_build_views.py +++ b/backend/app/services/sstc_service_core/mixin_build_views.py @@ -1,6 +1,7 @@ from __future__ import annotations from .shared_helpers import * +from .shared_helpers import _to_json class SSTCBuildViewsMixin: async def build_overview(self, project_id: int) -> dict[str, Any]: @@ -295,4 +296,3 @@ class SSTCBuildViewsMixin: ], "tasks_by_charter": tasks_by_charter, } - diff --git a/backend/app/services/sstc_service_core/mixin_fetch_core.py b/backend/app/services/sstc_service_core/mixin_fetch_core.py index 310fd5a4..a0a02c1e 100644 --- a/backend/app/services/sstc_service_core/mixin_fetch_core.py +++ b/backend/app/services/sstc_service_core/mixin_fetch_core.py @@ -1,6 +1,7 @@ from __future__ import annotations from .shared_helpers import * +from .shared_helpers import _avg class SSTCFetchCoreMixin: def __init__(self, db: AsyncSession, tenant_id: int): @@ -246,4 +247,3 @@ class SSTCFetchCoreMixin: "overdue": overdue, "progress": round((done / total) * 100, 2) if total else 0, } - diff --git a/backend/app/services/sstc_service_core/service.py b/backend/app/services/sstc_service_core/service.py index d3f57f64..25a46d0f 100644 --- a/backend/app/services/sstc_service_core/service.py +++ b/backend/app/services/sstc_service_core/service.py @@ -3,6 +3,15 @@ from __future__ import annotations from .mixin_build_views import SSTCBuildViewsMixin from .mixin_fetch_core import SSTCFetchCoreMixin from .mixin_members import SSTCMembersMixin +from .shared import ( + ALIGNED_STATUSES, + ARCHIVED_STATUSES, + BLOCKED_STATUSES, + DONE_STATUSES, + RELEASE_LIVE_STATUSES, + RELEASE_RELEASING_STATUSES, + RELEASE_VERIFIED_STATUSES, +) class SSTCService( diff --git a/backend/app/services/sstc_service_core/shared.py b/backend/app/services/sstc_service_core/shared.py index dd180a68..084914f7 100644 --- a/backend/app/services/sstc_service_core/shared.py +++ b/backend/app/services/sstc_service_core/shared.py @@ -56,3 +56,6 @@ def _avg(values: Iterable[float]) -> float: return float(sum(values) / len(values)) +# Keep underscore-prefixed helpers available to `from .shared import *`. +__all__ = [name for name in globals() if not name.startswith("__")] + diff --git a/backend/app/services/sstc_service_core/shared_helpers.py b/backend/app/services/sstc_service_core/shared_helpers.py index 4803b670..bce5de56 100644 --- a/backend/app/services/sstc_service_core/shared_helpers.py +++ b/backend/app/services/sstc_service_core/shared_helpers.py @@ -22,3 +22,6 @@ def _avg(values: Iterable[float]) -> float: return 0.0 return float(sum(values) / len(values)) + +# Keep underscore-prefixed helpers available to `from .shared_helpers import *`. +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/task_brief/__init__.py b/backend/app/services/task_brief/__init__.py index 01ccb308..279cbc92 100644 --- a/backend/app/services/task_brief/__init__.py +++ b/backend/app/services/task_brief/__init__.py @@ -1,5 +1,11 @@ from __future__ import annotations from .service import TaskBriefService +from .shared import HIGH_CONTRACT_TEMPLATE_ID, HIGH_TEMPLATE_ID, LOW_TEMPLATE_ID -__all__ = ["TaskBriefService"] +__all__ = [ + "TaskBriefService", + "LOW_TEMPLATE_ID", + "HIGH_TEMPLATE_ID", + "HIGH_CONTRACT_TEMPLATE_ID", +] diff --git a/backend/app/services/task_persistence_core/persist.py b/backend/app/services/task_persistence_core/persist.py index 518f858b..5e707f0a 100644 --- a/backend/app/services/task_persistence_core/persist.py +++ b/backend/app/services/task_persistence_core/persist.py @@ -3,7 +3,7 @@ from __future__ import annotations from .models import TaskCreateDraft -from .shared import AsyncSession, Optional, Task, log_operation, workload as workload_service +from .shared import AsyncSession, Optional, Task, log_operation, workload_service async def persist_task( diff --git a/backend/app/services/workload/workload_alert_service.py b/backend/app/services/workload/workload_alert_service.py index e8e586c0..cd113e38 100644 --- a/backend/app/services/workload/workload_alert_service.py +++ b/backend/app/services/workload/workload_alert_service.py @@ -8,10 +8,10 @@ from typing import Optional from sqlalchemy import and_, func, select from sqlalchemy.ext.asyncio import AsyncSession -from ..models import OperationLog, Task, User, UserProject, UserWorkload -from ..integration_core.lark_service import lark_service +from ...models import OperationLog, Task, User, UserProject, UserWorkload +from ..integrations.integration_core.lark_service import lark_service -from ..utils.timezone import now_cn_naive +from ...utils.timezone import now_cn_naive DONE_STATUSES = {"done", "completed", "closed", "resolved", "archived"} diff --git a/backend/scripts/p1_real_validate.py b/backend/scripts/p1_real_validate.py new file mode 100644 index 00000000..2c27de67 --- /dev/null +++ b/backend/scripts/p1_real_validate.py @@ -0,0 +1,313 @@ +import asyncio +import json +import time +import urllib.request +import urllib.parse +from datetime import datetime +from typing import Any, Optional + +from sqlalchemy import select, func + +from app.core.database import AsyncSessionLocal +from app.models import AIOrchestrationRun, FeishuReplyOutbox, AIProcessingLog, AICallLog + +BASE = "http://127.0.0.1:8000" +CHAT_ID = "oc_c487d87563e48c2f9566ccd5a758212c" +SENDER = "P1验收机器人" + +CASES = [ + {"idx": 1, "message": "杨宇现在负载多少", "expected_scene": "member_workload", "keys": ["杨宇", "负载"]}, + {"idx": 2, "message": "最近AI行业有什么动态", "expected_scene": "external_trend", "keys": ["AI行业", "行业", "动态", "趋势"]}, + {"idx": 3, "message": "帮我创建个项目叫路由验收测试,顺便问下现在有几个进行中的项目", "expected_scene": "mixed", "keys": ["顺便", "问题部分", "需求部分", "进行中的项目"]}, + {"idx": 4, "message": "AI劝退助手项目进展怎么样", "expected_scene": "default", "keys": ["AI劝退助手", "进展怎么样", "项目进展"]}, + {"idx": 5, "message": "帮我看看宠物行业最新动态", "expected_scene": "external_trend", "keys": ["宠物行业", "最新动态", "行业", "动态"]}, +] + +BANNED_PREFIXES = ["让我先", "让我尝试", "我来看看", "我先查", "让我看看", "我需要先"] + + +def login_token() -> str: + data = urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode() + req = urllib.request.Request( + f"{BASE}/auth/login", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + payload = json.loads(resp.read().decode("utf-8")) + return str(payload.get("access_token") or "") + + +def post_capture(token: str, *, message: str, message_id: str) -> dict[str, Any]: + payload = { + "group_chat_id": CHAT_ID, + "message_content": message, + "sender": SENDER, + "feishu_message_id": message_id, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + f"{BASE}/ai-steward/ai-steward/signal/capture-feishu", + data=body, + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=240) as resp: + text = resp.read().decode("utf-8") + try: + return json.loads(text) + except Exception: + return {"raw": text} + + +def detect_prompt_scene(prompt: str) -> Optional[str]: + p = str(prompt or "") + if "你是 PMFlow 的团队协作助手。" in p: + return "member_workload" + if "你是 PMFlow 的市场情报助手。" in p: + return "external_trend" + if "用户消息同时包含问题和需求。" in p: + return "mixed" + if "你是 PMFlow 的 AI 项目管理官。" in p: + return "ai_pmo" + if "用户通过飞书直接提问。" in p: + return "feishu_direct" + if "你是 PMFlow 的 RAG 问答助手" in p: + return "default" + return None + + +def infer_scene_from_reply(reply: str) -> str: + t = str(reply or "") + if "[回答]" in t and "[已收到需求]" in t: + return "mixed" + if "[行业动态]" in t and "[建议]" in t: + return "external_trend" + if ("负载" in t or "工作量" in t) and "%" in t and "项目列表" not in t: + return "member_workload" + if "完成率" in t: + return "default" + return "unknown" + + +def has_thinking_leak(reply: str) -> bool: + for line in str(reply or "").splitlines(): + s = line.strip() + if not s: + continue + if any(s.startswith(prefix) for prefix in BANNED_PREFIXES): + return True + return False + + +async def fetch_max_ai_call_id(db) -> int: + m = (await db.execute(select(func.max(AICallLog.id)))).scalar() + return int(m or 0) + + +async def wait_run(db, source_message_id: str, timeout: int = 540) -> Optional[AIOrchestrationRun]: + deadline = time.time() + timeout + last = None + while time.time() < deadline: + row = ( + await db.execute( + select(AIOrchestrationRun) + .where(AIOrchestrationRun.source_message_id == source_message_id) + .order_by(AIOrchestrationRun.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + if row is not None: + last = row + s = str(row.status or "").lower() + if s in {"succeeded", "failed", "cancelled"}: + return row + await asyncio.sleep(2) + return last + + +async def fetch_outbox(db, source_message_id: str): + return ( + await db.execute( + select(FeishuReplyOutbox) + .where(FeishuReplyOutbox.source_message_id == source_message_id) + .order_by(FeishuReplyOutbox.id.asc()) + ) + ).scalars().all() + + +async def wait_outbox(db, source_message_id: str, run_status: str, timeout: int = 240): + deadline = time.time() + timeout + expected = 2 if run_status == "succeeded" else 1 + latest = [] + while time.time() < deadline: + latest = await fetch_outbox(db, source_message_id) + sent = [r for r in latest if str(r.status or "") == "sent"] + if len(sent) >= expected: + return latest + await asyncio.sleep(2) + return latest + + +async def fetch_processing(db, source_message_id: str): + return ( + await db.execute( + select(AIProcessingLog) + .where(AIProcessingLog.feishu_message_id == source_message_id) + .order_by(AIProcessingLog.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + + +async def fetch_scene_from_call_logs(db, min_id: int, keys: list[str]): + rows = ( + await db.execute( + select(AICallLog) + .where( + AICallLog.id > min_id, + AICallLog.function_name.in_(["adaptive_rag_answer_normal", "adaptive_rag_answer_deep"]), + ) + .order_by(AICallLog.id.asc()) + .limit(160) + ) + ).scalars().all() + + picked = "unknown" + inspected = [] + lk = [k.lower() for k in keys if k] + for r in rows: + prompt = str(r.prompt or "") + pl = prompt.lower() + if lk and not any(k in pl for k in lk): + continue + sc = detect_prompt_scene(prompt) + inspected.append( + { + "id": int(r.id), + "scene": sc or "unknown", + "function_name": str(r.function_name or ""), + "created_at": str(r.created_at or ""), + } + ) + if sc: + picked = sc + + if picked == "unknown" and inspected: + picked = str(inspected[-1].get("scene") or "unknown") + return picked, inspected + + +def evaluate(case_idx: int, scene: str, reply: str, run_status: str): + leak = has_thinking_leak(reply) + if case_idx == 1: + ok = scene == "member_workload" and "%" in reply and "项目列表" not in reply and not leak + return ok, "member_workload+百分比+无项目列表" + if case_idx == 2: + sec = "[行业动态]" in reply and ("[对项目影响]" in reply or "[对项目的影响]" in reply) and "[建议]" in reply + src = "未接入实时数据" in reply or "训练数据" in reply or "数据来源" in reply + ok = scene == "external_trend" and sec and src and not leak + return ok, "external_trend+三段+来源限制" + if case_idx == 3: + mixed = "[回答]" in reply and "[已收到需求]" in reply + prog = "进行中" in reply + ok = scene == "mixed" and run_status == "succeeded" and mixed and prog and not leak + return ok, "mixed+创建+进行中数量" + if case_idx == 4: + ok = scene == "default" and "完成率" in reply and "%" in reply and not leak + return ok, "default+完成率" + if case_idx == 5: + ok = scene == "external_trend" and not leak + return ok, "external_trend+无思考外漏" + return False, "unknown" + + +async def run_case(token: str, case: dict[str, Any]): + idx = int(case["idx"]) + message = str(case["message"]) + source_message_id = f"real-scene-p1-v2-{idx}-{int(time.time())}" + + async with AsyncSessionLocal() as db: + before_call_id = await fetch_max_ai_call_id(db) + + capture = post_capture(token, message=message, message_id=source_message_id) + + async with AsyncSessionLocal() as db: + run_row = await wait_run(db, source_message_id) + run_status = str(getattr(run_row, "status", "missing") or "missing") if run_row is not None else "missing" + outbox_rows = await wait_outbox(db, source_message_id, run_status) + processing = await fetch_processing(db, source_message_id) + prompt_scene, candidates = await fetch_scene_from_call_logs(db, before_call_id, list(case.get("keys") or [])) + + sent = [r for r in outbox_rows if str(r.status or "") == "sent"] + ack = str(sent[0].message_text or "").strip() if sent else "" + final_reply = str(sent[-1].message_text or "").strip() if sent else "" + if not final_reply: + final_reply = str(capture.get("direct_reply_text") or "").strip() + + inferred_scene = infer_scene_from_reply(final_reply) + actual_scene = prompt_scene if prompt_scene != "unknown" else inferred_scene + + passed, rule = evaluate(idx, actual_scene, final_reply, run_status) + + return { + "idx": idx, + "source_message_id": source_message_id, + "message": message, + "expected_scene": str(case["expected_scene"]), + "prompt_scene": prompt_scene, + "inferred_scene": inferred_scene, + "actual_scene": actual_scene, + "capture_status": str(capture.get("status") or ""), + "run_status": run_status, + "ack": ack, + "final_reply": final_reply, + "has_capacity_warning": "⚠ 产能提示" in final_reply, + "thinking_leak": has_thinking_leak(final_reply), + "processing": { + "route": str(getattr(processing, "route", "") or ""), + "intent": str(getattr(processing, "intent", "") or ""), + "status": str(getattr(processing, "processing_status", "") or ""), + "duration_ms": int(getattr(processing, "duration_ms", 0) or 0), + "error_message": str(getattr(processing, "error_message", "") or ""), + "reply_source": str(getattr(processing, "reply_source", "") or ""), + }, + "outbox": [ + { + "id": int(r.id), + "status": str(r.status or ""), + "attempts": int(r.attempts or 0), + "created_at": str(r.created_at or ""), + "text": str(r.message_text or ""), + } + for r in outbox_rows + ], + "call_log_candidates": candidates, + "pass": passed, + "pass_rule": rule, + } + + +async def main(): + token = login_token() + if not token: + raise RuntimeError("token empty") + + results = [] + for case in CASES: + print(f"[RUN] case {case['idx']} -> {case['message']}", flush=True) + one = await run_case(token, case) + print(f"[DONE] case {one['idx']} scene={one['actual_scene']} pass={one['pass']} run={one['run_status']}", flush=True) + results.append(one) + await asyncio.sleep(2) + + payload = { + "generated_at": datetime.now().isoformat(), + "chat_id": CHAT_ID, + "results": results, + } + print("===RESULT_JSON_START===") + print(json.dumps(payload, ensure_ascii=False)) + print("===RESULT_JSON_END===") + + +asyncio.run(main()) diff --git a/backend/tmp_p1_real_validate_v4.py b/backend/tmp_p1_real_validate_v4.py new file mode 100644 index 00000000..1b5360d8 --- /dev/null +++ b/backend/tmp_p1_real_validate_v4.py @@ -0,0 +1,330 @@ +import asyncio +import json +import time +import urllib.parse +import urllib.request +from datetime import datetime +from typing import Any + +from sqlalchemy import text + +from app.core.database import AsyncSessionLocal + +BASE = "http://127.0.0.1:8000" +CHAT_ID = "oc_c487d87563e48c2f9566ccd5a758212c" +SENDER = "P1验收机器人" + +CASES = [ + {"idx": 1, "message": "杨宇现在负载多少", "expected_scene": "member_workload", "keys": ["杨宇", "负载"]}, + {"idx": 2, "message": "最近AI行业有什么动态", "expected_scene": "external_trend", "keys": ["AI行业", "行业", "动态", "趋势"]}, + {"idx": 3, "message": "帮我创建个项目叫路由验收测试,顺便问下现在有几个进行中的项目", "expected_scene": "mixed", "keys": ["顺便", "问题部分", "需求部分", "进行中的项目"]}, + {"idx": 4, "message": "AI劝退助手项目进展怎么样", "expected_scene": "default", "keys": ["AI劝退助手", "进展怎么样", "项目进展"]}, + {"idx": 5, "message": "帮我看看宠物行业最新动态", "expected_scene": "external_trend", "keys": ["宠物行业", "最新动态", "行业", "动态"]}, +] + +BANNED_PREFIXES = ["让我先", "让我尝试", "我来看看", "我先查", "让我看看", "我需要先"] + + +def login_token() -> str: + data = urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode() + req = urllib.request.Request( + f"{BASE}/auth/login", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + payload = json.loads(resp.read().decode("utf-8")) + return str(payload.get("access_token") or "") + + +def post_capture(token: str, message: str, source_message_id: str) -> dict[str, Any]: + payload = { + "group_chat_id": CHAT_ID, + "message_content": message, + "sender": SENDER, + "feishu_message_id": source_message_id, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + f"{BASE}/ai-steward/ai-steward/signal/capture-feishu", + data=body, + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=240) as resp: + text_body = resp.read().decode("utf-8") + try: + return json.loads(text_body) + except Exception: + return {"raw": text_body} + + +def detect_prompt_scene(prompt: str) -> str: + p = str(prompt or "") + if "你是 PMFlow 的团队协作助手。" in p: + return "member_workload" + if "你是 PMFlow 的市场情报助手。" in p: + return "external_trend" + if "用户消息同时包含问题和需求。" in p: + return "mixed" + if "你是 PMFlow 的 AI 项目管理官。" in p: + return "ai_pmo" + if "用户通过飞书直接提问。" in p: + return "feishu_direct" + if "你是 PMFlow 的 RAG 问答助手" in p: + return "default" + return "unknown" + + +def infer_scene_from_reply(reply: str) -> str: + t = str(reply or "") + if "[回答]" in t and "[已收到需求]" in t: + return "mixed" + if "[行业动态]" in t and "[建议]" in t: + return "external_trend" + if ("负载" in t or "工作量" in t) and "%" in t and "项目列表" not in t: + return "member_workload" + if "完成率" in t: + return "default" + return "unknown" + + +def has_thinking_leak(reply: str) -> bool: + for line in str(reply or "").splitlines(): + s = line.strip() + if not s: + continue + if any(s.startswith(prefix) for prefix in BANNED_PREFIXES): + return True + return False + + +async def get_max_ai_call_id() -> int: + async with AsyncSessionLocal() as db: + row = ( + await db.execute(text("SELECT COALESCE(MAX(id), 0) AS v FROM ai_call_logs")) + ).mappings().first() + return int((row or {}).get("v") or 0) + + +async def wait_run(source_message_id: str, timeout_sec: int = 600) -> dict[str, Any] | None: + deadline = time.time() + timeout_sec + last = None + while time.time() < deadline: + async with AsyncSessionLocal() as db: + row = ( + await db.execute( + text( + """ + SELECT id, status, trace_id, source_message_id, created_at, updated_at + FROM ai_orchestration_runs + WHERE source_message_id = :sid + ORDER BY id DESC + LIMIT 1 + """ + ), + {"sid": source_message_id}, + ) + ).mappings().first() + if row: + last = dict(row) + status = str(last.get("status") or "").lower() + if status in {"succeeded", "failed", "cancelled", "waiting_manual"}: + return last + await asyncio.sleep(2) + return last + + +async def wait_outbox(source_message_id: str, expected_sent: int, timeout_sec: int = 300) -> list[dict[str, Any]]: + deadline = time.time() + timeout_sec + latest: list[dict[str, Any]] = [] + while time.time() < deadline: + async with AsyncSessionLocal() as db: + rows = ( + await db.execute( + text( + """ + SELECT id, status, attempts, message_text, created_at + FROM feishu_reply_outbox + WHERE source_message_id = :sid + ORDER BY id ASC + """ + ), + {"sid": source_message_id}, + ) + ).mappings().all() + latest = [dict(r) for r in rows] + sent_count = sum(1 for r in latest if str(r.get("status") or "") == "sent") + if sent_count >= expected_sent: + return latest + await asyncio.sleep(2) + return latest + + +async def fetch_processing(source_message_id: str) -> dict[str, Any]: + async with AsyncSessionLocal() as db: + row = ( + await db.execute( + text( + """ + SELECT id, route, intent, processing_status, duration_ms, error_message, reply_source, created_at + FROM ai_processing_logs + WHERE feishu_message_id = :sid + ORDER BY id DESC + LIMIT 1 + """ + ), + {"sid": source_message_id}, + ) + ).mappings().first() + return dict(row or {}) + + +async def fetch_prompt_scene(min_id: int, keys: list[str]) -> tuple[str, list[dict[str, Any]]]: + async with AsyncSessionLocal() as db: + rows = ( + await db.execute( + text( + """ + SELECT id, function_name, prompt, created_at + FROM ai_call_logs + WHERE id > :min_id + AND function_name IN ('adaptive_rag_answer_normal', 'adaptive_rag_answer_deep') + ORDER BY id ASC + LIMIT 400 + """ + ), + {"min_id": min_id}, + ) + ).mappings().all() + + lowered = [str(k).lower() for k in (keys or []) if str(k).strip()] + picked = "unknown" + candidates: list[dict[str, Any]] = [] + for r in rows: + prompt = str(r.get("prompt") or "") + pl = prompt.lower() + if lowered and not any(k in pl for k in lowered): + continue + scene = detect_prompt_scene(prompt) + candidates.append( + { + "id": int(r.get("id") or 0), + "scene": scene, + "function_name": str(r.get("function_name") or ""), + "created_at": str(r.get("created_at") or ""), + } + ) + if scene != "unknown": + picked = scene + + if picked == "unknown" and candidates: + picked = str(candidates[-1].get("scene") or "unknown") + return picked, candidates + + +def evaluate(case_idx: int, scene: str, reply: str, run_status: str) -> tuple[bool, str]: + leak = has_thinking_leak(reply) + if case_idx == 1: + ok = scene == "member_workload" and "%" in reply and "项目列表" not in reply and not leak + return ok, "member_workload+百分比+无项目列表" + if case_idx == 2: + sec = "[行业动态]" in reply and ("[对项目影响]" in reply or "[对项目的影响]" in reply) and "[建议]" in reply + src = "未接入实时数据" in reply or "训练数据" in reply or "数据来源" in reply + ok = scene == "external_trend" and sec and src and not leak + return ok, "external_trend+三段+来源限制" + if case_idx == 3: + mixed = "[回答]" in reply and "[已收到需求]" in reply + prog = "进行中" in reply + ok = scene == "mixed" and run_status == "succeeded" and mixed and prog and not leak + return ok, "mixed+创建+进行中数量" + if case_idx == 4: + ok = scene == "default" and "完成率" in reply and "%" in reply and not leak + return ok, "default+完成率" + if case_idx == 5: + ok = scene == "external_trend" and not leak + return ok, "external_trend+无思考外漏" + return False, "unknown" + + +async def run_case(token: str, case: dict[str, Any]) -> dict[str, Any]: + idx = int(case["idx"]) + source_message_id = f"real-scene-p1-v4-{idx}-{int(time.time())}" + message = str(case["message"]) + + before_call_id = await get_max_ai_call_id() + capture = post_capture(token, message, source_message_id) + + run_row = await wait_run(source_message_id) + run_status = str((run_row or {}).get("status") or "missing") + expected_sent = 2 if run_status == "succeeded" else 1 + outbox_rows = await wait_outbox(source_message_id, expected_sent) + + sent = [r for r in outbox_rows if str(r.get("status") or "") == "sent"] + ack = str(sent[0].get("message_text") or "").strip() if sent else "" + final_reply = str(sent[-1].get("message_text") or "").strip() if sent else "" + if not final_reply: + final_reply = str(capture.get("direct_reply_text") or "").strip() + + processing = await fetch_processing(source_message_id) + prompt_scene, candidates = await fetch_prompt_scene(before_call_id, list(case.get("keys") or [])) + inferred_scene = infer_scene_from_reply(final_reply) + actual_scene = prompt_scene if prompt_scene != "unknown" else inferred_scene + passed, pass_rule = evaluate(idx, actual_scene, final_reply, run_status) + + return { + "idx": idx, + "source_message_id": source_message_id, + "message": message, + "expected_scene": str(case["expected_scene"]), + "capture_status": str(capture.get("status") or ""), + "run_status": run_status, + "prompt_scene": prompt_scene, + "inferred_scene": inferred_scene, + "actual_scene": actual_scene, + "ack": ack, + "final_reply": final_reply, + "thinking_leak": has_thinking_leak(final_reply), + "has_capacity_warning": "⚠ 产能提示" in final_reply, + "processing": { + "route": str(processing.get("route") or ""), + "intent": str(processing.get("intent") or ""), + "status": str(processing.get("processing_status") or ""), + "duration_ms": int(processing.get("duration_ms") or 0), + "error_message": str(processing.get("error_message") or ""), + "reply_source": str(processing.get("reply_source") or ""), + }, + "outbox": outbox_rows, + "call_log_candidates": candidates, + "pass": passed, + "pass_rule": pass_rule, + } + + +async def main() -> None: + token = login_token() + if not token: + raise RuntimeError("token empty") + + results = [] + for case in CASES: + print(f"[RUN] case {case['idx']} -> {case['message']}", flush=True) + one = await run_case(token, case) + print( + f"[DONE] case {one['idx']} scene={one['actual_scene']} pass={one['pass']} run={one['run_status']} leak={one['thinking_leak']}", + flush=True, + ) + results.append(one) + await asyncio.sleep(2) + + payload = { + "generated_at": datetime.now().isoformat(), + "chat_id": CHAT_ID, + "results": results, + } + print("===RESULT_JSON_START===") + print(json.dumps(payload, ensure_ascii=False)) + print("===RESULT_JSON_END===") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/tmp_run_feishu_case.py b/backend/tmp_run_feishu_case.py new file mode 100644 index 00000000..81359fda --- /dev/null +++ b/backend/tmp_run_feishu_case.py @@ -0,0 +1,291 @@ +import argparse +import asyncio +import json +import time +import urllib.parse +import urllib.request +from datetime import datetime +from typing import Any, Optional + +from sqlalchemy import func, select + +from app.core.database import AsyncSessionLocal +from app.models import AICallLog, AIOrchestrationRun, AIProcessingLog, FeishuReplyOutbox + +BASE = "http://127.0.0.1:8000" +CHAT_ID = "oc_c487d87563e48c2f9566ccd5a758212c" +SENDER = "P1验收机器人" +BANNED_PREFIXES = ["让我先", "让我尝试", "我来看看", "我先查", "让我看看", "我需要先"] + + +def login_token() -> str: + data = urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode() + req = urllib.request.Request( + f"{BASE}/auth/login", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + payload = json.loads(resp.read().decode("utf-8")) + return str(payload.get("access_token") or "") + + +def post_capture(token: str, message: str, source_message_id: str) -> dict[str, Any]: + payload = { + "group_chat_id": CHAT_ID, + "message_content": message, + "sender": SENDER, + "feishu_message_id": source_message_id, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urllib.request.Request( + f"{BASE}/ai-steward/ai-steward/signal/capture-feishu", + data=body, + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=120) as resp: + text = resp.read().decode("utf-8") + try: + return json.loads(text) + except Exception: + return {"raw": text} + + +def detect_prompt_scene(prompt: str) -> Optional[str]: + p = str(prompt or "") + if "你是 PMFlow 的团队协作助手。" in p: + return "member_workload" + if "你是 PMFlow 的市场情报助手。" in p: + return "external_trend" + if "用户消息同时包含问题和需求。" in p: + return "mixed" + if "你是 PMFlow 的 AI 项目管理官。" in p: + return "ai_pmo" + if "用户通过飞书直接提问。" in p: + return "feishu_direct" + if "你是 PMFlow 的 RAG 问答助手" in p: + return "default" + return None + + +def infer_scene_from_reply(reply: str) -> str: + t = str(reply or "") + if "[回答]" in t and "[已收到需求]" in t: + return "mixed" + if "[行业动态]" in t and "[建议]" in t: + return "external_trend" + if ("负载" in t or "工作量" in t) and "%" in t and "项目列表" not in t: + return "member_workload" + if "完成率" in t: + return "default" + return "unknown" + + +def has_thinking_leak(text: str) -> bool: + for line in str(text or "").splitlines(): + s = line.strip() + if not s: + continue + if any(s.startswith(prefix) for prefix in BANNED_PREFIXES): + return True + return False + + +async def fetch_max_ai_call_id(db) -> int: + row = (await db.execute(select(func.max(AICallLog.id)))).scalar() + return int(row or 0) + + +async def wait_run(db, source_message_id: str, timeout_sec: int = 720) -> Optional[AIOrchestrationRun]: + deadline = time.time() + timeout_sec + last = None + while time.time() < deadline: + row = ( + await db.execute( + select(AIOrchestrationRun) + .where(AIOrchestrationRun.source_message_id == source_message_id) + .order_by(AIOrchestrationRun.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + if row is not None: + last = row + st = str(row.status or "").lower() + if st in {"succeeded", "failed", "cancelled"}: + return row + await asyncio.sleep(2) + return last + + +async def wait_outbox(db, source_message_id: str, run_status: str, timeout_sec: int = 300) -> list[FeishuReplyOutbox]: + deadline = time.time() + timeout_sec + expected = 2 if run_status == "succeeded" else 1 + latest: list[FeishuReplyOutbox] = [] + while time.time() < deadline: + latest = ( + await db.execute( + select(FeishuReplyOutbox) + .where(FeishuReplyOutbox.source_message_id == source_message_id) + .order_by(FeishuReplyOutbox.id.asc()) + ) + ).scalars().all() + sent = [r for r in latest if str(r.status or "") == "sent"] + if len(sent) >= expected: + return latest + await asyncio.sleep(2) + return latest + + +async def fetch_processing(db, source_message_id: str) -> Optional[AIProcessingLog]: + return ( + await db.execute( + select(AIProcessingLog) + .where(AIProcessingLog.feishu_message_id == source_message_id) + .order_by(AIProcessingLog.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + + +async def fetch_prompt_scene(db, min_call_id: int, keys: list[str]) -> tuple[str, list[dict[str, Any]]]: + rows = ( + await db.execute( + select(AICallLog) + .where( + AICallLog.id > min_call_id, + AICallLog.function_name.in_(["adaptive_rag_answer_normal", "adaptive_rag_answer_deep"]), + ) + .order_by(AICallLog.id.asc()) + .limit(200) + ) + ).scalars().all() + + lowered = [k.lower() for k in keys if k] + picked = "unknown" + candidates: list[dict[str, Any]] = [] + + for row in rows: + prompt = str(row.prompt or "") + pl = prompt.lower() + if lowered and not any(k in pl for k in lowered): + continue + scene = detect_prompt_scene(prompt) + candidates.append( + { + "id": int(row.id), + "scene": scene or "unknown", + "function": str(row.function_name or ""), + "created_at": str(row.created_at or ""), + } + ) + if scene: + picked = scene + + if picked == "unknown" and candidates: + picked = str(candidates[-1].get("scene") or "unknown") + + return picked, candidates + + +def evaluate(idx: int, actual_scene: str, final_reply: str, run_status: str) -> tuple[bool, str]: + leak = has_thinking_leak(final_reply) + if idx == 1: + ok = actual_scene == "member_workload" and "%" in final_reply and "项目列表" not in final_reply and not leak + return ok, "member_workload+百分比+无项目列表" + if idx == 2: + sec = "[行业动态]" in final_reply and ("[对项目影响]" in final_reply or "[对项目的影响]" in final_reply) and "[建议]" in final_reply + src = "未接入实时数据" in final_reply or "训练数据" in final_reply or "数据来源" in final_reply + ok = actual_scene == "external_trend" and sec and src and not leak + return ok, "external_trend+三段输出+来源限制" + if idx == 3: + mixed = "[回答]" in final_reply and "[已收到需求]" in final_reply + prog = "进行中" in final_reply + ok = actual_scene == "mixed" and run_status == "succeeded" and mixed and prog and not leak + return ok, "mixed+创建成功+进行中数量" + if idx == 4: + ok = actual_scene == "default" and "完成率" in final_reply and "%" in final_reply and not leak + return ok, "default+项目状态完成率" + if idx == 5: + ok = actual_scene == "external_trend" and not leak + return ok, "external_trend+无思考外漏" + return False, "unknown" + + +async def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--idx", type=int, required=True) + parser.add_argument("--message", type=str, required=True) + parser.add_argument("--keys", type=str, default="") + parser.add_argument("--prefix", type=str, default="real-scene-p1-manual") + args = parser.parse_args() + + keys = [k.strip() for k in str(args.keys).split("|") if k.strip()] + source_message_id = f"{args.prefix}-{args.idx}-{int(time.time())}" + + token = login_token() + if not token: + raise RuntimeError("token empty") + + async with AsyncSessionLocal() as db: + before_call_id = await fetch_max_ai_call_id(db) + + capture = post_capture(token, args.message, source_message_id) + + async with AsyncSessionLocal() as db: + run_row = await wait_run(db, source_message_id) + run_status = str(getattr(run_row, "status", "missing") or "missing") if run_row is not None else "missing" + outbox_rows = await wait_outbox(db, source_message_id, run_status) + processing = await fetch_processing(db, source_message_id) + prompt_scene, candidates = await fetch_prompt_scene(db, before_call_id, keys) + + sent_rows = [r for r in outbox_rows if str(r.status or "") == "sent"] + ack = str(sent_rows[0].message_text or "").strip() if sent_rows else "" + final_reply = str(sent_rows[-1].message_text or "").strip() if sent_rows else "" + if not final_reply: + final_reply = str(capture.get("direct_reply_text") or "").strip() + + inferred_scene = infer_scene_from_reply(final_reply) + actual_scene = prompt_scene if prompt_scene != "unknown" else inferred_scene + passed, pass_rule = evaluate(args.idx, actual_scene, final_reply, run_status) + + payload = { + "generated_at": datetime.now().isoformat(), + "idx": args.idx, + "source_message_id": source_message_id, + "message": args.message, + "capture_status": str(capture.get("status") or ""), + "run_status": run_status, + "ack": ack, + "final_reply": final_reply, + "prompt_scene": prompt_scene, + "inferred_scene": inferred_scene, + "actual_scene": actual_scene, + "thinking_leak": has_thinking_leak(final_reply), + "has_capacity_warning": "⚠ 产能提示" in final_reply, + "processing": { + "route": str(getattr(processing, "route", "") or ""), + "intent": str(getattr(processing, "intent", "") or ""), + "status": str(getattr(processing, "processing_status", "") or ""), + "duration_ms": int(getattr(processing, "duration_ms", 0) or 0), + "error_message": str(getattr(processing, "error_message", "") or ""), + "reply_source": str(getattr(processing, "reply_source", "") or ""), + }, + "outbox": [ + { + "id": int(r.id), + "status": str(r.status or ""), + "attempts": int(r.attempts or 0), + "created_at": str(r.created_at or ""), + "text": str(r.message_text or ""), + } + for r in outbox_rows + ], + "call_log_candidates": candidates, + "pass": passed, + "pass_rule": pass_rule, + } + print(json.dumps(payload, ensure_ascii=False)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 8f700188..0eab75b2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -80,6 +80,22 @@ services: LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:?missing_LLM_ROUTING_SAFETY_MODEL} LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:?missing_LLM_DIRECT_EMERGENCY_ENABLED} LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:?missing_LLM_REQUEST_TIMEOUT} + AI_PMO_INTENT_LLM_ENABLED: ${AI_PMO_INTENT_LLM_ENABLED:-true} + AI_PMO_INTENT_ARBITRATION_ENABLED: ${AI_PMO_INTENT_ARBITRATION_ENABLED:-true} + AI_PMO_INTENT_CONFLICT_POLICY: ${AI_PMO_INTENT_CONFLICT_POLICY:-pending_manual} + AI_PMO_INTENT_LLM_TIMEOUT_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_SECONDS:-30} + AI_PMO_INTENT_LLM_TIMEOUT_COMPLEX_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_COMPLEX_SECONDS:-55} + AI_PMO_INTENT_LLM_TIMEOUT_FALLBACK_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_FALLBACK_SECONDS:-45} + AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS: ${AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS:-70} + AI_PMO_INTENT_LLM_STAGE2_CONFIDENCE_THRESHOLD: ${AI_PMO_INTENT_LLM_STAGE2_CONFIDENCE_THRESHOLD:-0.72} + AI_PMO_INTENT_LLM_STAGE2_MIN_REMAINING_SECONDS: ${AI_PMO_INTENT_LLM_STAGE2_MIN_REMAINING_SECONDS:-12} + AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS: ${AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS:-8} + AI_PMO_INTENT_LLM_MAX_TOKENS_LOW: ${AI_PMO_INTENT_LLM_MAX_TOKENS_LOW:-260} + AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH: ${AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH:-360} + LLM_HTTP_STATUS_RETRY_BUDGET: ${LLM_HTTP_STATUS_RETRY_BUDGET:-2} + LLM_INTENT_HTTP_RETRY_BUDGET: ${LLM_INTENT_HTTP_RETRY_BUDGET:-0} + LLM_INTENT_EMPTY_RETRY_BUDGET: ${LLM_INTENT_EMPTY_RETRY_BUDGET:-2} + LLM_INTENT_STRUCTURED_REPAIR_ENABLED: ${LLM_INTENT_STRUCTURED_REPAIR_ENABLED:-true} AUTO_CREATE_TABLES: "false" GLM_API_KEY: ${GLM_API_KEY:?missing_GLM_API_KEY} GLM_BASE_URL: ${GLM_BASE_URL:?missing_GLM_BASE_URL} @@ -91,6 +107,14 @@ services: OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:?missing_OLLAMA_BASE_URL} OLLAMA_MODEL: ${OLLAMA_MODEL:?missing_OLLAMA_MODEL} PYTHONUNBUFFERED: "1" + HTTP_PROXY: "" + HTTPS_PROXY: "" + ALL_PROXY: "" + http_proxy: "" + https_proxy: "" + all_proxy: "" + NO_PROXY: "localhost,127.0.0.1,db,redis,backend,frontend,api.edgefn.net,open.bigmodel.cn" + no_proxy: "localhost,127.0.0.1,db,redis,backend,frontend,api.edgefn.net,open.bigmodel.cn" command: [ "uvicorn", diff --git a/docker-compose.yml b/docker-compose.yml index fbe47ac5..6b80aba2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,6 +80,22 @@ services: LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:?missing_LLM_ROUTING_SAFETY_MODEL} LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:?missing_LLM_DIRECT_EMERGENCY_ENABLED} LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:?missing_LLM_REQUEST_TIMEOUT} + AI_PMO_INTENT_LLM_ENABLED: ${AI_PMO_INTENT_LLM_ENABLED:-true} + AI_PMO_INTENT_ARBITRATION_ENABLED: ${AI_PMO_INTENT_ARBITRATION_ENABLED:-true} + AI_PMO_INTENT_CONFLICT_POLICY: ${AI_PMO_INTENT_CONFLICT_POLICY:-pending_manual} + AI_PMO_INTENT_LLM_TIMEOUT_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_SECONDS:-30} + AI_PMO_INTENT_LLM_TIMEOUT_COMPLEX_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_COMPLEX_SECONDS:-55} + AI_PMO_INTENT_LLM_TIMEOUT_FALLBACK_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_FALLBACK_SECONDS:-45} + AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS: ${AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS:-70} + AI_PMO_INTENT_LLM_STAGE2_CONFIDENCE_THRESHOLD: ${AI_PMO_INTENT_LLM_STAGE2_CONFIDENCE_THRESHOLD:-0.72} + AI_PMO_INTENT_LLM_STAGE2_MIN_REMAINING_SECONDS: ${AI_PMO_INTENT_LLM_STAGE2_MIN_REMAINING_SECONDS:-12} + AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS: ${AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS:-8} + AI_PMO_INTENT_LLM_MAX_TOKENS_LOW: ${AI_PMO_INTENT_LLM_MAX_TOKENS_LOW:-260} + AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH: ${AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH:-360} + LLM_HTTP_STATUS_RETRY_BUDGET: ${LLM_HTTP_STATUS_RETRY_BUDGET:-2} + LLM_INTENT_HTTP_RETRY_BUDGET: ${LLM_INTENT_HTTP_RETRY_BUDGET:-0} + LLM_INTENT_EMPTY_RETRY_BUDGET: ${LLM_INTENT_EMPTY_RETRY_BUDGET:-2} + LLM_INTENT_STRUCTURED_REPAIR_ENABLED: ${LLM_INTENT_STRUCTURED_REPAIR_ENABLED:-true} AUTO_CREATE_TABLES: "false" GLM_API_KEY: ${GLM_API_KEY:?missing_GLM_API_KEY} GLM_BASE_URL: ${GLM_BASE_URL:?missing_GLM_BASE_URL} @@ -90,6 +106,14 @@ services: OLLAMA_HOST: ${OLLAMA_HOST:?missing_OLLAMA_HOST} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:?missing_OLLAMA_BASE_URL} OLLAMA_MODEL: ${OLLAMA_MODEL:?missing_OLLAMA_MODEL} + HTTP_PROXY: "" + HTTPS_PROXY: "" + ALL_PROXY: "" + http_proxy: "" + https_proxy: "" + all_proxy: "" + NO_PROXY: "localhost,127.0.0.1,db,redis,backend,frontend,api.edgefn.net,open.bigmodel.cn" + no_proxy: "localhost,127.0.0.1,db,redis,backend,frontend,api.edgefn.net,open.bigmodel.cn" ports: - "${BACKEND_PORT:?missing_BACKEND_PORT}:8000" volumes: diff --git a/frontend/src/api/sstc.ts b/frontend/src/api/sstc.ts index 3c9f9a75..79b8f0f5 100644 --- a/frontend/src/api/sstc.ts +++ b/frontend/src/api/sstc.ts @@ -482,9 +482,11 @@ export const sstcApi = { tasks: SSTCTask[]; alignment_records: Array<{ user_id: string; + user_name?: string | null; score: number; confirmed: boolean; comments?: string | null; + confirmed_at?: string | null; }>; } > { @@ -496,9 +498,11 @@ export const sstcApi = { tasks: SSTCTask[]; alignment_records: Array<{ user_id: string; + user_name?: string | null; score: number; confirmed: boolean; comments?: string | null; + confirmed_at?: string | null; }>; } > diff --git a/frontend/src/components/sstc/SSTCCharterStagePanel.tsx b/frontend/src/components/sstc/SSTCCharterStagePanel.tsx index 0b3d06ed..73f2f0ee 100644 --- a/frontend/src/components/sstc/SSTCCharterStagePanel.tsx +++ b/frontend/src/components/sstc/SSTCCharterStagePanel.tsx @@ -5,6 +5,7 @@ import type { SSTCSignal, SSTCTask, } from "@/api/sstc"; +import type { User } from "@/api/users"; import { CHARTER_MODULE_SCHEMAS, type CharterModuleKey } from "@/components/sstc/charterModules"; import { FIXED_ALIGNMENT_QA_QUESTIONS, @@ -22,6 +23,7 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; +import { formatBeijingDateTime } from "@/lib/datetime"; import { toZhPriority, toZhSignalType, toZhStatus } from "@/lib/display"; import { compactTitle } from "@/lib/title"; @@ -46,9 +48,11 @@ interface CharterDetailShape extends SSTCCharter { tasks: SSTCTask[]; alignment_records: Array<{ user_id: string; + user_name?: string | null; score: number; confirmed: boolean; comments?: string | null; + confirmed_at?: string | null; }>; } @@ -92,6 +96,8 @@ interface SSTCCharterStagePanelProps { } >; }; + allModulesConfirmed: boolean; + shouldShowCharterGenerating: boolean; serializedCharterContent: Record; sourceSignalOpen: boolean; showAutoFields: boolean; @@ -105,9 +111,13 @@ interface SSTCCharterStagePanelProps { setShowAllAlignmentQa: (show: boolean | ((prev: boolean) => boolean)) => void; setEditingAlignmentQaIndex: (index: number | null) => void; setActiveModuleKey: (key: CharterModuleKey) => void; + projectUsers: User[]; onSelectCharter: (charterId: string) => void; updateModuleField: (moduleKey: CharterModuleKey, fieldKey: string, value: string) => void; updateAlignmentQaAnswer: (answerIndex: number, value: string) => void; + confirmModule: (moduleKey: CharterModuleKey) => void; + onStartAlignment: () => void; + onConfirmAlignment: () => void; getFieldEditState: (moduleKey: CharterModuleKey, fieldKey: string) => ModuleFieldEditState; getModuleStepStatus: (moduleKey: CharterModuleKey) => string; updateCharterMutation: UpdateCharterMutationLike; @@ -128,6 +138,8 @@ export function SSTCCharterStagePanel({ activeModule, activeModuleIndex, charterProgress, + allModulesConfirmed, + shouldShowCharterGenerating, serializedCharterContent, sourceSignalOpen, showAutoFields, @@ -141,9 +153,13 @@ export function SSTCCharterStagePanel({ setShowAllAlignmentQa, setEditingAlignmentQaIndex, setActiveModuleKey, + projectUsers, onSelectCharter, updateModuleField, updateAlignmentQaAnswer, + confirmModule, + onStartAlignment, + onConfirmAlignment, getFieldEditState, getModuleStepStatus, updateCharterMutation, @@ -169,9 +185,15 @@ export function SSTCCharterStagePanel({ ? CHARTER_MODULE_SCHEMAS[activeModuleIndex + 1] : null; const hasMultipleCharters = charters.length > 1; + const confirmedModules = CHARTER_MODULE_SCHEMAS.filter( + (schema) => getModuleStepStatus(schema.key) === "已完成" + ).length; const filledModulePercent = Math.round( - (charterProgress.completedModules / Math.max(CHARTER_MODULE_SCHEMAS.length, 1)) * 100 + (confirmedModules / Math.max(CHARTER_MODULE_SCHEMAS.length, 1)) * 100 ); + const charterDisplayStatus = allModulesConfirmed ? "已对齐" : toZhStatus(selectedCharter?.status); + const activeModuleStatus = getModuleStepStatus(activeModule.key); + const activeModuleProgress = charterProgress.moduleProgress[activeModule.key]; const aiReferenceText = sourceSignals.length > 0 ? `参考了 ${sourceSignals @@ -179,6 +201,16 @@ export function SSTCCharterStagePanel({ .map((signal, signalIndex) => `信号 #${signal.id.match(/\d+/)?.[0] || signalIndex + 1}`) .join(" 和 ")} 与项目上下文资料` : "参考了项目上下文资料"; + const alignmentUserNameMap = new Map( + projectUsers.map((member) => [String(member.id), member.name || member.username]) + ); + const alignmentRecords = (charterDetail?.alignment_records || []).filter((record) => { + if (projectUsers.length === 0) return false; + return ( + alignmentUserNameMap.has(String(record.user_id)) || + String(record.user_name || "").trim().length > 0 + ); + }); return (

)} @@ -290,15 +322,21 @@ export function SSTCCharterStagePanel({ size="sm" variant="ghost" className="h-7 px-2 text-xs text-slate-600 hover:bg-blue-50 hover:text-blue-700" - disabled={!selectedCharter} - onClick={() => selectedCharter && confirmAlignmentMutation.mutate(selectedCharter.id)} + disabled={!selectedCharter || confirmAlignmentMutation.isPending} + onClick={onConfirmAlignment} > - 确认对齐 + {confirmAlignmentMutation.isPending ? "提交中..." : "确认对齐"} )} + {allModulesConfirmed && ( +
+ 6 个模块已逐项确认,可提交“确认对齐”完成整体对齐。 +
+ )} + {aiPrefillCharterMutation.isPending && (
AI 正在根据信号源自动生成 6 模块草稿,请稍候… @@ -307,8 +345,21 @@ export function SSTCCharterStagePanel({
-
-
+ {shouldShowCharterGenerating ? ( +
+
+
生成中...
+
+
+
+
+
+
+
+
+ ) : ( +
+
diff --git a/frontend/src/components/sstc/SSTCReleaseStagePanel.tsx b/frontend/src/components/sstc/SSTCReleaseStagePanel.tsx index c88615ab..cd07eb88 100644 --- a/frontend/src/components/sstc/SSTCReleaseStagePanel.tsx +++ b/frontend/src/components/sstc/SSTCReleaseStagePanel.tsx @@ -89,6 +89,7 @@ export function SSTCReleaseStagePanel({ stageLocked={stageLocked} charters={charters} tasks={tasks} + releases={releases} onRefresh={onRefresh} /> diff --git a/frontend/src/components/sstc/SSTCWorkspace.tsx b/frontend/src/components/sstc/SSTCWorkspace.tsx index 462241b5..b49a917d 100644 --- a/frontend/src/components/sstc/SSTCWorkspace.tsx +++ b/frontend/src/components/sstc/SSTCWorkspace.tsx @@ -52,6 +52,8 @@ export function SSTCWorkspace(props: SSTCWorkspaceViewProps) { activeModule: orchestration.activeModule, activeModuleIndex: orchestration.activeModuleIndex, charterProgress: orchestration.charterProgress, + allModulesConfirmed: orchestration.allModulesConfirmed, + shouldShowCharterGenerating: orchestration.shouldShowCharterGenerating, serializedCharterContent: orchestration.serializedCharterContent, sourceSignalOpen: orchestration.sourceSignalOpen, showAutoFields: orchestration.showAutoFields, @@ -67,9 +69,13 @@ export function SSTCWorkspace(props: SSTCWorkspaceViewProps) { setActiveModuleKey: orchestration.setActiveModuleKey, isMobileViewport: props.isMobileViewport, readOnly: props.charterReadOnly, + projectUsers: orchestration.projectUsers, onSelectCharter: props.onSelectCharter, updateModuleField: orchestration.updateModuleField, updateAlignmentQaAnswer: orchestration.updateAlignmentQaAnswer, + confirmModule: orchestration.confirmModule, + onStartAlignment: orchestration.handleStartAlignment, + onConfirmAlignment: orchestration.handleConfirmAlignment, getFieldEditState: orchestration.getFieldEditState, getModuleStepStatus: orchestration.getModuleStepStatus, updateCharterMutation: orchestration.updateCharterMutation, @@ -108,6 +114,7 @@ export function SSTCWorkspace(props: SSTCWorkspaceViewProps) { {props.activeStage === "execution" && ( { + const state = {} as Record; + for (const schema of CHARTER_MODULE_SCHEMAS) { + state[schema.key] = false; + } + return state; +} + +function parseModuleConfirmationState(raw: unknown): Record { + const initial = createEmptyModuleConfirmationState(); + if (!raw || typeof raw !== "object" || Array.isArray(raw)) { + return initial; + } + const source = raw as Record; + for (const schema of CHARTER_MODULE_SCHEMAS) { + initial[schema.key] = Boolean(source[schema.key]); + } + return initial; +} + export interface SSTCWorkspaceProps { projectId: string; activeStage: StageKey; @@ -84,6 +104,9 @@ export function useSSTCWorkspaceOrchestration({ const [charterDraftTitle, setCharterDraftTitle] = useState(""); const [charterModuleDraft, setCharterModuleDraft] = useState(createEmptyCharterModuleDraft()); const [charterBaselineDraft, setCharterBaselineDraft] = useState(createEmptyCharterModuleDraft()); + const [moduleConfirmations, setModuleConfirmations] = useState>( + createEmptyModuleConfirmationState() + ); const [activeModuleKey, setActiveModuleKey] = useState("module_1"); const [sourceSignalOpen, setSourceSignalOpen] = useState(false); const [showAutoFields, setShowAutoFields] = useState(false); @@ -170,21 +193,49 @@ export function useSSTCWorkspaceOrchestration({ const charterProgress = useMemo(() => calculateCharterProgress(charterModuleDraft), [charterModuleDraft]); - const serializedCharterContent = useMemo( + const serializedCharterContent = useMemo(() => { + const payload = buildCharterContentPayload( + charterModuleDraft, + (selectedCharter?.content as Record) || {} + ); + payload.module_confirmations = moduleConfirmations; + return payload; + }, [charterModuleDraft, selectedCharter?.content, moduleConfirmations]); + + const hasAnyModuleContent = useMemo( () => - buildCharterContentPayload(charterModuleDraft, (selectedCharter?.content as Record) || {}), - [charterModuleDraft, selectedCharter?.content] + CHARTER_MODULE_SCHEMAS.some((schema) => + Object.entries(charterModuleDraft.modules[schema.key] || {}).some(([fieldKey, value]) => { + if (schema.key === "module_2" && fieldKey === "alignment_qa") { + return parseAlignmentQaAnswers(String(value || "")).some((answer) => answer.trim().length > 0); + } + return String(value || "").trim().length > 0; + }) + ), + [charterModuleDraft] + ); + + const requiredModuleKeys = useMemo( + () => CHARTER_MODULE_SCHEMAS.filter((schema) => schema.fields.some((field) => !!field.required)).map((schema) => schema.key), + [] + ); + + const allModulesConfirmed = useMemo( + () => requiredModuleKeys.every((moduleKey) => !!moduleConfirmations[moduleKey]), + [moduleConfirmations, requiredModuleKeys] ); useEffect(() => { setCharterDraftTitle(selectedCharter?.title || ""); - const parsed = parseCharterModuleDraft((selectedCharter?.content as Record) || {}); + const currentContent = (selectedCharter?.content as Record) || {}; + const parsed = parseCharterModuleDraft(currentContent); if (!parsed.modules.module_2.alignment_qa.trim()) { const fallback = buildFallbackAlignmentAnswers(parsed); parsed.modules.module_2.alignment_qa = buildAlignmentQaText(fallback); } setCharterModuleDraft(parsed); setCharterBaselineDraft(parsed); + setModuleConfirmations(parseModuleConfirmationState(currentContent.module_confirmations)); setActiveModuleKey("module_1"); setSourceSignalOpen(false); setShowAutoFields(false); @@ -248,13 +299,37 @@ export function useSSTCWorkspaceOrchestration({ const startAlignmentMutation = useMutation({ mutationFn: (charterId: string) => sstcApi.startAlignment(projectId, charterId), - onSuccess: onRefresh, + onSuccess: () => { + toast({ title: "已发起对齐流程", variant: "success" }); + onRefresh(); + }, + onError: (error) => { + toast({ + title: getApiErrorMessage(error, "发起对齐失败"), + variant: "error", + }); + }, }); const confirmAlignmentMutation = useMutation({ mutationFn: (charterId: string) => sstcApi.confirmAlignment(projectId, charterId, 90, true, "已确认对齐"), - onSuccess: onRefresh, + onSuccess: (data) => { + const confirmedCount = Number(data?.confirmed_count || 0); + const memberCount = Number(data?.member_count || 0); + const statusText = String(data?.charter_status || ""); + toast({ + title: `对齐已提交(${confirmedCount}/${memberCount || 0})${statusText ? ` · ${statusText}` : ""}`, + variant: "success", + }); + onRefresh(); + }, + onError: (error) => { + toast({ + title: getApiErrorMessage(error, "确认对齐失败"), + variant: "error", + }); + }, }); const updateCharterMutation = useMutation({ @@ -302,6 +377,26 @@ export function useSSTCWorkspaceOrchestration({ aiPrefillCharterMutation, ]); + const shouldShowCharterGenerating = useMemo(() => { + if (!selectedCharter?.id) return false; + if (hasAnyModuleContent) return false; + const hasSignal = + (selectedCharter.signal_ids?.length || 0) > 0 || + (charterDetail?.signals?.length || 0) > 0 || + !!charterDetail?.source_signal; + if (!hasSignal) return false; + const prefillAttempted = !!aiPrefillAttempted[selectedCharter.id]; + return aiPrefillCharterMutation.isPending || !prefillAttempted; + }, [ + selectedCharter?.id, + selectedCharter?.signal_ids, + charterDetail?.signals?.length, + charterDetail?.source_signal, + hasAnyModuleContent, + aiPrefillAttempted, + aiPrefillCharterMutation.isPending, + ]); + const aiBreakdownMutation = useMutation({ mutationFn: () => sstcApi.aiBreakdownTasks(projectId, selectedCharter!.id), onSuccess: onRefresh, @@ -500,6 +595,7 @@ export function useSSTCWorkspaceOrchestration({ const activeModule = CHARTER_MODULE_SCHEMAS.find((item) => item.key === activeModuleKey) || CHARTER_MODULE_SCHEMAS[0]; const updateModuleField = (moduleKey: CharterModuleKey, fieldKey: string, value: string) => { + const prevValue = charterModuleDraft.modules[moduleKey][fieldKey] || ""; setCharterModuleDraft((prev) => ({ ...prev, modules: { @@ -510,6 +606,9 @@ export function useSSTCWorkspaceOrchestration({ }, }, })); + if (prevValue !== value && moduleConfirmations[moduleKey]) { + setModuleConfirmations((prev) => ({ ...prev, [moduleKey]: false })); + } }; const moduleOrder = CHARTER_MODULE_SCHEMAS.map((schema) => schema.key); @@ -526,6 +625,43 @@ export function useSSTCWorkspaceOrchestration({ updateModuleField("module_2", "alignment_qa", buildAlignmentQaText(nextAnswers)); }; + const confirmModule = (moduleKey: CharterModuleKey) => { + if (!selectedCharter) return; + const progress = charterProgress.moduleProgress[moduleKey]; + if (!progress?.done) { + toast({ title: "请先补全当前模块的必填项,再执行确认", variant: "error" }); + return; + } + if (moduleConfirmations[moduleKey]) return; + + const previous = moduleConfirmations; + const next = { ...previous, [moduleKey]: true }; + setModuleConfirmations(next); + updateCharterMutation.mutate( + { + charterId: selectedCharter.id, + title: charterDraftTitle || selectedCharter.title, + content: { + ...serializedCharterContent, + module_confirmations: next, + }, + }, + { + onSuccess: () => { + const schema = CHARTER_MODULE_SCHEMAS.find((item) => item.key === moduleKey); + toast({ title: `${schema?.shortTitle || "模块"}已确认`, variant: "success" }); + }, + onError: (error) => { + setModuleConfirmations(previous); + toast({ + title: getApiErrorMessage(error, "模块确认失败"), + variant: "error", + }); + }, + } + ); + }; + const getFieldEditState = (moduleKey: CharterModuleKey, fieldKey: string) => { const baseline = (charterBaselineDraft.modules[moduleKey][fieldKey] || "").trim(); const current = (charterModuleDraft.modules[moduleKey][fieldKey] || "").trim(); @@ -535,18 +671,28 @@ export function useSSTCWorkspaceOrchestration({ }; const getModuleStepStatus = (moduleKey: CharterModuleKey): string => { + if (moduleConfirmations[moduleKey]) return "已完成"; if (moduleKey === activeModule.key) return "进行中"; const progress = charterProgress.moduleProgress[moduleKey]; - if (progress.done) return "已完成"; const moduleContent = charterModuleDraft.modules[moduleKey]; const hasContent = Object.values(moduleContent).some((value) => String(value || "").trim().length > 0); if (!hasContent) return moduleKey === "module_6" ? "选填" : "未开始"; - const changed = Object.keys(moduleContent).some( - (fieldKey) => - String(charterModuleDraft.modules[moduleKey][fieldKey] || "").trim() !== - String(charterBaselineDraft.modules[moduleKey][fieldKey] || "").trim() - ); - return changed ? "已编辑" : "AI已预填"; + if (progress.done) return "进行中"; + return "进行中"; + }; + + const handleStartAlignment = () => { + if (!selectedCharter) return; + startAlignmentMutation.mutate(selectedCharter.id); + }; + + const handleConfirmAlignment = () => { + if (!selectedCharter) return; + if (!allModulesConfirmed) { + toast({ title: "请先逐个确认 6 个模块,再提交整体对齐", variant: "error" }); + return; + } + confirmAlignmentMutation.mutate(selectedCharter.id); }; return { @@ -563,6 +709,8 @@ export function useSSTCWorkspaceOrchestration({ setCharterModuleDraft, charterBaselineDraft, setCharterBaselineDraft, + moduleConfirmations, + setModuleConfirmations, activeModuleKey, setActiveModuleKey, sourceSignalOpen, @@ -587,6 +735,8 @@ export function useSSTCWorkspaceOrchestration({ selectedCharter, charterProgress, serializedCharterContent, + allModulesConfirmed, + shouldShowCharterGenerating, rankedSignals, highValueSignalCount, displaySignals, @@ -601,6 +751,8 @@ export function useSSTCWorkspaceOrchestration({ confirmAlignmentMutation, updateCharterMutation, aiPrefillCharterMutation, + handleStartAlignment, + handleConfirmAlignment, aiBreakdownMutation, patchTaskStatusMutation, bulkTaskStatusMutation, @@ -633,6 +785,7 @@ export function useSSTCWorkspaceOrchestration({ activeModuleIndex, alignmentQaAnswers, updateAlignmentQaAnswer, + confirmModule, getFieldEditState, getModuleStepStatus, }; diff --git a/frontend/src/pages/project-board/ProjectBoardPage.tsx b/frontend/src/pages/project-board/ProjectBoardPage.tsx index 2a85224c..f66df28e 100644 --- a/frontend/src/pages/project-board/ProjectBoardPage.tsx +++ b/frontend/src/pages/project-board/ProjectBoardPage.tsx @@ -214,6 +214,7 @@ export default function ProjectBoardPage() { queryClient.invalidateQueries({ queryKey: ["sstc-signals", id] }), queryClient.invalidateQueries({ queryKey: ["sstc-signals-picker", id] }), queryClient.invalidateQueries({ queryKey: ["sstc-charters", id] }), + queryClient.invalidateQueries({ queryKey: ["sstc-charter-detail", id] }), queryClient.invalidateQueries({ queryKey: ["sstc-task-board", id] }), queryClient.invalidateQueries({ queryKey: ["sstc-releases", id] }), queryClient.invalidateQueries({ queryKey: ["sstc-reviews", id] }), diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index 08efe66d..2cdf87f6 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/skillradarchart.tsx","./src/components/themeinit.tsx","./src/components/ai-config/configsubpageheader.tsx","./src/components/ai-config/featureintroguide.tsx","./src/components/ai-steward/floatingqachat.tsx","./src/components/ai-steward/promptrolesettings.tsx","./src/components/ai-steward/qahistorypanel.tsx","./src/components/budget/budgetcontrolpanel.tsx","./src/components/command/commanditems.ts","./src/components/inbox/inboxactionpanel.tsx","./src/components/inbox/inboxlistitem.tsx","./src/components/layout/layout.tsx","./src/components/layout/sidebar.tsx","./src/components/layout/topbar.tsx","./src/components/model-capability/costoptimizationpanel.tsx","./src/components/model-capability/experimentsyncpanel.tsx","./src/components/model-capability/modelcapabilitymatrix.tsx","./src/components/model-capability/modelupdateimpactpanel.tsx","./src/components/sstc/aivisibilitypanel.tsx","./src/components/sstc/sstcchainview.tsx","./src/components/sstc/sstccharterstagepanel.tsx","./src/components/sstc/sstcexecutionboardpanel.tsx","./src/components/sstc/sstcglobalheader.tsx","./src/components/sstc/sstcreleasecreatedialog.tsx","./src/components/sstc/sstcreleasestagepanel.tsx","./src/components/sstc/sstcreviewcreatedialog.tsx","./src/components/sstc/sstcreviewstagepanel.tsx","./src/components/sstc/sstcriskfloatpanel.tsx","./src/components/sstc/sstcsignalstagepanel.tsx","./src/components/sstc/sstcstagenav.tsx","./src/components/sstc/sstctaskcreatedialog.tsx","./src/components/sstc/sstctaskstagepanel.tsx","./src/components/sstc/sstcworkspace.tsx","./src/components/sstc/signalcontenthovercard.tsx","./src/components/sstc/chartermodules.ts","./src/components/sstc/sstcutils.ts","./src/components/sstc/usesstcworkspaceorchestration.ts","./src/components/ui/rolebadge.tsx","./src/components/ui/skillbadge.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/users/userformdialog.tsx","./src/hooks/usetheme.ts","./src/lib/api.ts","./src/lib/apierror.ts","./src/lib/datetime.ts","./src/lib/display.ts","./src/lib/feishueventmode.ts","./src/lib/formatinboxtime.ts","./src/lib/inbox.ts","./src/lib/inboxtextmap.ts","./src/lib/rbac.ts","./src/lib/sessionguard.ts","./src/lib/title.ts","./src/lib/utils.ts","./src/lib/zhcn.ts","./src/pages/aiconfigpage.tsx","./src/pages/aistewardpage.tsx","./src/pages/charterdetailpage.tsx","./src/pages/contractsyncboardpage.tsx","./src/pages/dashboardpage.tsx","./src/pages/feishuconfigpage.tsx","./src/pages/inboxpage.tsx","./src/pages/knowledgegraphcontextboardpage.tsx","./src/pages/loginpage.tsx","./src/pages/modelroutingconfigpage.tsx","./src/pages/projectboardpage.tsx","./src/pages/projectspage.tsx","./src/pages/recipespage.tsx","./src/pages/schedulercenterpage.tsx","./src/pages/skilltaskspage.tsx","./src/pages/skillspage.tsx","./src/pages/taskbriefcenterpage.tsx","./src/pages/taskbriefrunspage.tsx","./src/pages/taskbrieftemplatepage.tsx","./src/pages/userprofilepage.tsx","./src/pages/userskillspage.tsx","./src/pages/userspage.tsx","./src/pages/workloaddetailpage.tsx","./src/pages/workloadspage.tsx","./src/router/index.tsx","./src/stores/auth.ts","./src/types/inbox.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/skillradarchart.tsx","./src/components/themeinit.tsx","./src/components/ai-config/configsubpageheader.tsx","./src/components/ai-config/featureintroguide.tsx","./src/components/ai-steward/floatingqachat.tsx","./src/components/ai-steward/promptrolesettings.tsx","./src/components/ai-steward/qahistorypanel.tsx","./src/components/budget/budgetcontrolpanel.tsx","./src/components/command/commanditems.ts","./src/components/inbox/inboxactionpanel.tsx","./src/components/inbox/inboxlistitem.tsx","./src/components/layout/layout.tsx","./src/components/layout/sidebar.tsx","./src/components/layout/topbar.tsx","./src/components/model-capability/costoptimizationpanel.tsx","./src/components/model-capability/experimentsyncpanel.tsx","./src/components/model-capability/modelcapabilitymatrix.tsx","./src/components/model-capability/modelupdateimpactpanel.tsx","./src/components/sstc/aivisibilitypanel.tsx","./src/components/sstc/sstcchainview.tsx","./src/components/sstc/sstccharterstagepanel.tsx","./src/components/sstc/sstcexecutionboardpanel.tsx","./src/components/sstc/sstcglobalheader.tsx","./src/components/sstc/sstcreleasecreatedialog.tsx","./src/components/sstc/sstcreleasestagepanel.tsx","./src/components/sstc/sstcreviewcreatedialog.tsx","./src/components/sstc/sstcreviewstagepanel.tsx","./src/components/sstc/sstcriskfloatpanel.tsx","./src/components/sstc/sstcsignalstagepanel.tsx","./src/components/sstc/sstcstagenav.tsx","./src/components/sstc/sstctaskcreatedialog.tsx","./src/components/sstc/sstctaskstagepanel.tsx","./src/components/sstc/sstcworkspace.tsx","./src/components/sstc/signalcontenthovercard.tsx","./src/components/sstc/chartermodules.ts","./src/components/sstc/sstcutils.ts","./src/components/sstc/usesstcworkspaceorchestration.ts","./src/components/ui/rolebadge.tsx","./src/components/ui/skillbadge.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/users/userformdialog.tsx","./src/hooks/usetheme.ts","./src/lib/api.ts","./src/lib/apierror.ts","./src/lib/datetime.ts","./src/lib/display.ts","./src/lib/feishueventmode.ts","./src/lib/formatinboxtime.ts","./src/lib/inbox.ts","./src/lib/inboxtextmap.ts","./src/lib/rbac.ts","./src/lib/sessionguard.ts","./src/lib/title.ts","./src/lib/utils.ts","./src/lib/zhcn.ts","./src/pages/aiconfigpage.tsx","./src/pages/aistewardpage.tsx","./src/pages/charterdetailpage.tsx","./src/pages/contractsyncboardpage.tsx","./src/pages/dashboardpage.tsx","./src/pages/feishuconfigpage.tsx","./src/pages/inboxpage.tsx","./src/pages/knowledgegraphcontextboardpage.tsx","./src/pages/loginpage.tsx","./src/pages/modelroutingconfigpage.tsx","./src/pages/projectboardpage.tsx","./src/pages/projectspage.tsx","./src/pages/recipespage.tsx","./src/pages/schedulercenterpage.tsx","./src/pages/skilltaskspage.tsx","./src/pages/skillspage.tsx","./src/pages/taskbriefcenterpage.tsx","./src/pages/taskbriefrunspage.tsx","./src/pages/taskbrieftemplatepage.tsx","./src/pages/userprofilepage.tsx","./src/pages/userskillspage.tsx","./src/pages/userspage.tsx","./src/pages/workloaddetailpage.tsx","./src/pages/workloadspage.tsx","./src/pages/ai-config/aiconfigpage.tsx","./src/pages/ai-config/constants.ts","./src/pages/ai-config/types.ts","./src/pages/ai-config/components/governancetoolscard.tsx","./src/pages/ai-config/components/prioritybadge.tsx","./src/pages/ai-config/components/statusbadge.tsx","./src/pages/ai-config/sections/advancedtab.tsx","./src/pages/ai-config/sections/configuretab.tsx","./src/pages/ai-config/sections/knowledgetab.tsx","./src/pages/ai-config/sections/overviewtab.tsx","./src/pages/ai-steward/aistewardpage.tsx","./src/pages/ai-steward/constants.ts","./src/pages/ai-steward/types.ts","./src/pages/ai-steward/components/featureoverviewcards.tsx","./src/pages/ai-steward/sections/blockerstab.tsx","./src/pages/ai-steward/sections/briefingtab.tsx","./src/pages/ai-steward/sections/conflictstab.tsx","./src/pages/ai-steward/sections/createprojecttab.tsx","./src/pages/ai-steward/sections/enginetab.tsx","./src/pages/ai-steward/sections/overviewtab.tsx","./src/pages/ai-steward/sections/signalstab.tsx","./src/pages/charter-detail/charterdetailpage.tsx","./src/pages/contract-sync-board/contractsyncboardpage.tsx","./src/pages/dashboard/dashboardpage.tsx","./src/pages/feishu-config/feishuconfigpage.tsx","./src/pages/feishu-config/helpers.ts","./src/pages/feishu-config/components/setupguidecard.tsx","./src/pages/inbox/inboxpage.tsx","./src/pages/inbox/helpers.ts","./src/pages/inbox/components/inboxfilterspanel.tsx","./src/pages/knowledge-graph-context-board/knowledgegraphcontextboardpage.tsx","./src/pages/login/loginpage.tsx","./src/pages/model-routing-config/modelroutingconfigpage.tsx","./src/pages/project-board/projectboardpage.tsx","./src/pages/project-board/helpers.ts","./src/pages/project-board/components/createcharterdialog.tsx","./src/pages/project-board/components/projectsettingsdialog.tsx","./src/pages/projects/projectspage.tsx","./src/pages/projects/helpers.ts","./src/pages/projects/components/deleteprojectdialog.tsx","./src/pages/projects/components/projectquerydialog.tsx","./src/pages/recipes/recipespage.tsx","./src/pages/recipes/helpers.ts","./src/pages/recipes/components/analyzerecipedialog.tsx","./src/pages/recipes/components/createrecipedialog.tsx","./src/pages/scheduler-center/schedulercenterpage.tsx","./src/pages/skill-tasks/skilltaskspage.tsx","./src/pages/skills/skillspage.tsx","./src/pages/task-brief-center/taskbriefcenterpage.tsx","./src/pages/task-brief-runs/taskbriefrunspage.tsx","./src/pages/task-brief-template/taskbrieftemplatepage.tsx","./src/pages/user-profile/userprofilepage.tsx","./src/pages/user-skills/userskillspage.tsx","./src/pages/users/userspage.tsx","./src/pages/users/helpers.ts","./src/pages/users/components/bulkassigndialog.tsx","./src/pages/users/components/deleteuserdialog.tsx","./src/pages/users/components/importusersdialog.tsx","./src/pages/users/components/resetpassworddialog.tsx","./src/pages/workload-detail/workloaddetailpage.tsx","./src/pages/workloads/workloadspage.tsx","./src/router/index.tsx","./src/stores/auth.ts","./src/types/inbox.ts"],"version":"5.6.3"} \ No newline at end of file