briefloop 0.11.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- briefloop-0.11.12.dist-info/METADATA +473 -0
- briefloop-0.11.12.dist-info/RECORD +424 -0
- briefloop-0.11.12.dist-info/WHEEL +5 -0
- briefloop-0.11.12.dist-info/entry_points.txt +3 -0
- briefloop-0.11.12.dist-info/licenses/LICENSE +22 -0
- briefloop-0.11.12.dist-info/top_level.txt +1 -0
- multi_agent_brief/__init__.py +36 -0
- multi_agent_brief/analysis_blocks/__init__.py +11 -0
- multi_agent_brief/analysis_blocks/builder.py +206 -0
- multi_agent_brief/analysis_blocks/renderer.py +237 -0
- multi_agent_brief/analysis_blocks/schemas.py +52 -0
- multi_agent_brief/analysis_modules/__init__.py +8 -0
- multi_agent_brief/analysis_modules/base.py +87 -0
- multi_agent_brief/analysis_modules/market_competitor/__init__.py +68 -0
- multi_agent_brief/analysis_modules/market_competitor/auditor.py +290 -0
- multi_agent_brief/analysis_modules/market_competitor/config.py +187 -0
- multi_agent_brief/analysis_modules/market_competitor/event_builder.py +226 -0
- multi_agent_brief/analysis_modules/market_competitor/renderer.py +199 -0
- multi_agent_brief/analysis_modules/market_competitor/schemas.py +518 -0
- multi_agent_brief/analysis_modules/policy_regulatory/__init__.py +7 -0
- multi_agent_brief/analysis_modules/policy_regulatory/audit.py +195 -0
- multi_agent_brief/analysis_modules/policy_regulatory/module.py +387 -0
- multi_agent_brief/analysis_modules/policy_regulatory/schemas.py +129 -0
- multi_agent_brief/analysis_modules/registry.py +73 -0
- multi_agent_brief/audience/__init__.py +19 -0
- multi_agent_brief/audience/profiles.py +246 -0
- multi_agent_brief/audience_memory/__init__.py +23 -0
- multi_agent_brief/audience_memory/profile.py +358 -0
- multi_agent_brief/audit/__init__.py +36 -0
- multi_agent_brief/audit/case_applicability.py +181 -0
- multi_agent_brief/audit/deterministic.py +471 -0
- multi_agent_brief/audit/editorial_governance.py +404 -0
- multi_agent_brief/audit/final_quality.py +764 -0
- multi_agent_brief/audit/harness.py +242 -0
- multi_agent_brief/audit/interfaces.py +96 -0
- multi_agent_brief/audit/limitation_hygiene.py +238 -0
- multi_agent_brief/audit/proposal_boundary.py +34 -0
- multi_agent_brief/audit/redaction.py +27 -0
- multi_agent_brief/audit/rule_packs.py +96 -0
- multi_agent_brief/audit/semantic.py +435 -0
- multi_agent_brief/capabilities/__init__.py +22 -0
- multi_agent_brief/capabilities/catalog.py +215 -0
- multi_agent_brief/capabilities/detect.py +199 -0
- multi_agent_brief/capabilities/models.py +60 -0
- multi_agent_brief/capabilities/recommend.py +206 -0
- multi_agent_brief/cli/__init__.py +2 -0
- multi_agent_brief/cli/analysis_commands.py +140 -0
- multi_agent_brief/cli/approval_commands.py +107 -0
- multi_agent_brief/cli/audit_commands.py +59 -0
- multi_agent_brief/cli/capability_commands.py +548 -0
- multi_agent_brief/cli/claude_commands.py +159 -0
- multi_agent_brief/cli/competitors_commands.py +182 -0
- multi_agent_brief/cli/controls_commands.py +137 -0
- multi_agent_brief/cli/deliver_commands.py +942 -0
- multi_agent_brief/cli/eval_cases_commands.py +99 -0
- multi_agent_brief/cli/experiments_commands.py +395 -0
- multi_agent_brief/cli/feedback_commands.py +192 -0
- multi_agent_brief/cli/finalize_commands.py +153 -0
- multi_agent_brief/cli/gates_commands.py +174 -0
- multi_agent_brief/cli/hermes_commands.py +356 -0
- multi_agent_brief/cli/improve_commands.py +224 -0
- multi_agent_brief/cli/init_commands.py +564 -0
- multi_agent_brief/cli/init_wizard.py +1473 -0
- multi_agent_brief/cli/input_commands.py +237 -0
- multi_agent_brief/cli/main.py +288 -0
- multi_agent_brief/cli/onboard_commands.py +193 -0
- multi_agent_brief/cli/product_commands.py +1449 -0
- multi_agent_brief/cli/provenance_commands.py +100 -0
- multi_agent_brief/cli/release_commands.py +68 -0
- multi_agent_brief/cli/repair_commands.py +169 -0
- multi_agent_brief/cli/run_commands.py +292 -0
- multi_agent_brief/cli/runtime_commands.py +81 -0
- multi_agent_brief/cli/secrets_commands.py +232 -0
- multi_agent_brief/cli/semantic_support_commands.py +80 -0
- multi_agent_brief/cli/sources_commands.py +647 -0
- multi_agent_brief/cli/start_commands.py +8 -0
- multi_agent_brief/cli/state_commands.py +446 -0
- multi_agent_brief/cli/status_commands.py +26 -0
- multi_agent_brief/cli/workbuddy_commands.py +103 -0
- multi_agent_brief/configs/artifact_contracts.yaml +514 -0
- multi_agent_brief/configs/orchestrator_contract.yaml +181 -0
- multi_agent_brief/configs/policy_packs/default.yaml +65 -0
- multi_agent_brief/configs/policy_profiles/evidence_extract_default.yaml +58 -0
- multi_agent_brief/configs/policy_profiles/finance_default.yaml +44 -0
- multi_agent_brief/configs/policy_profiles/internet_default.yaml +44 -0
- multi_agent_brief/configs/policy_profiles/manufacturing_default.yaml +33 -0
- multi_agent_brief/configs/policy_profiles/solar_manufacturing_default.yaml +64 -0
- multi_agent_brief/configs/report_packs/evidence_extract.yaml +48 -0
- multi_agent_brief/configs/report_packs/management_monthly.yaml +36 -0
- multi_agent_brief/configs/report_packs/market_weekly.yaml +36 -0
- multi_agent_brief/configs/report_packs/solar_industry_periodic.yaml +50 -0
- multi_agent_brief/configs/report_templates/evidence_extract.yaml +55 -0
- multi_agent_brief/configs/report_templates/management_monthly.yaml +48 -0
- multi_agent_brief/configs/report_templates/market_weekly.yaml +49 -0
- multi_agent_brief/configs/report_templates/solar_industry_periodic.yaml +75 -0
- multi_agent_brief/configs/stage_specs.yaml +180 -0
- multi_agent_brief/contracts/__init__.py +41 -0
- multi_agent_brief/contracts/base.py +99 -0
- multi_agent_brief/contracts/errors.py +41 -0
- multi_agent_brief/contracts/migrations/__init__.py +5 -0
- multi_agent_brief/contracts/migrations/claim_v1_to_v2.py +33 -0
- multi_agent_brief/contracts/registry.py +159 -0
- multi_agent_brief/contracts/role_topology.py +25 -0
- multi_agent_brief/contracts/schemas/__init__.py +35 -0
- multi_agent_brief/contracts/schemas/analysis_pack.py +135 -0
- multi_agent_brief/contracts/schemas/atomic_claim_graph.py +304 -0
- multi_agent_brief/contracts/schemas/audit_report.py +80 -0
- multi_agent_brief/contracts/schemas/candidate_item.py +51 -0
- multi_agent_brief/contracts/schemas/claim.py +197 -0
- multi_agent_brief/contracts/schemas/claim_draft.py +386 -0
- multi_agent_brief/contracts/schemas/claim_support_matrix.py +334 -0
- multi_agent_brief/contracts/schemas/evidence_span_registry.py +270 -0
- multi_agent_brief/contracts/schemas/policy_profile.py +247 -0
- multi_agent_brief/contracts/schemas/report_spec.py +257 -0
- multi_agent_brief/contracts/schemas/semantic_assessment_report.py +480 -0
- multi_agent_brief/contracts/schemas/source_evidence_pack_manifest.py +233 -0
- multi_agent_brief/contracts/schemas/source_item.py +96 -0
- multi_agent_brief/contracts/semantic_assessment_status.py +12 -0
- multi_agent_brief/contracts/source_metadata.py +252 -0
- multi_agent_brief/contracts/target_contract.py +460 -0
- multi_agent_brief/contracts/validator.py +225 -0
- multi_agent_brief/controls/__init__.py +23 -0
- multi_agent_brief/controls/contract.py +106 -0
- multi_agent_brief/controls/switchboard.py +1076 -0
- multi_agent_brief/core/__init__.py +2 -0
- multi_agent_brief/core/citations.py +14 -0
- multi_agent_brief/core/claim_ledger.py +94 -0
- multi_agent_brief/core/config.py +191 -0
- multi_agent_brief/core/env.py +69 -0
- multi_agent_brief/core/previous.py +83 -0
- multi_agent_brief/core/schemas.py +181 -0
- multi_agent_brief/core/selection.py +189 -0
- multi_agent_brief/delivery/__init__.py +6 -0
- multi_agent_brief/delivery/base.py +36 -0
- multi_agent_brief/delivery/feishu.py +204 -0
- multi_agent_brief/delivery/gws.py +237 -0
- multi_agent_brief/evaluation_cases/__init__.py +15 -0
- multi_agent_brief/evaluation_cases/contract.py +429 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/improvement/ledger.jsonl +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/config.yaml +12 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/sources.yaml +6 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/config.yaml +6 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/input/human_feedback.md +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/brief.md +14 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audited_brief.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/claim_ledger.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/config.yaml +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/user.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/config.yaml +6 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/claim_ledger.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/feedback_issues.json +25 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/repair_plan.json +27 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/claim_ledger.json +11 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/quality_gate_report.json +22 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audited_brief.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/claim_ledger.json +40 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/sources.yaml +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/claim_ledger.json +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/output/intermediate/release_readiness_report.json +31 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/ledger.jsonl +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/memory.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/output/intermediate/improvement_memory_snapshot.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/config.yaml +12 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/input/sources/source-001.txt +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/brief.md +37 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/delivery/brief.md +37 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/atomic_claim_graph.json +17 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audit_report.json +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audited_brief.md +37 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/claim_ledger.json +20 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/evidence_span_registry.json +20 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/finalize_report.json +40 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/auditor_quality_gate_report.json +42 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/finalize_quality_gate_report.json +42 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/screened_candidates.json +33 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix.md +12 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix_trace.md +10 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/report_spec.yaml +24 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/sources.yaml +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/input/sources/README.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/output/intermediate/source_evidence_pack_manifest.json +26 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/config.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/claim_ledger.json +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/config.yaml +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/improvement/ledger.jsonl +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/user.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audited_brief.md +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/claim_ledger.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/manifest.yaml +861 -0
- multi_agent_brief/evaluation_cases/fixtures.py +20 -0
- multi_agent_brief/evaluation_cases/runner.py +1603 -0
- multi_agent_brief/experiments/__init__.py +45 -0
- multi_agent_brief/experiments/experiment_080.py +5835 -0
- multi_agent_brief/experiments/schemas.py +63 -0
- multi_agent_brief/experiments/target_contract.py +10 -0
- multi_agent_brief/feedback/__init__.py +17 -0
- multi_agent_brief/feedback/feedback_contract.py +482 -0
- multi_agent_brief/feedback/feedback_state.py +898 -0
- multi_agent_brief/hermes/__init__.py +25 -0
- multi_agent_brief/hermes/adapter.py +832 -0
- multi_agent_brief/improvement/__init__.py +83 -0
- multi_agent_brief/improvement/contract.py +1114 -0
- multi_agent_brief/improvement/memory.py +451 -0
- multi_agent_brief/improvement/product_definition.py +228 -0
- multi_agent_brief/improvement/state.py +961 -0
- multi_agent_brief/inputs/__init__.py +2 -0
- multi_agent_brief/inputs/classifier.py +322 -0
- multi_agent_brief/inputs/contracts.py +59 -0
- multi_agent_brief/inputs/extractor.py +310 -0
- multi_agent_brief/install/__init__.py +2 -0
- multi_agent_brief/install/writer.py +97 -0
- multi_agent_brief/onboarding/__init__.py +11 -0
- multi_agent_brief/onboarding/io.py +103 -0
- multi_agent_brief/onboarding/mapper.py +484 -0
- multi_agent_brief/onboarding/schema.py +46 -0
- multi_agent_brief/orchestrator/__init__.py +2 -0
- multi_agent_brief/orchestrator/fact_layer_import.py +352 -0
- multi_agent_brief/orchestrator/handoff.py +2209 -0
- multi_agent_brief/orchestrator/role_topology.py +42 -0
- multi_agent_brief/orchestrator/run_archive.py +888 -0
- multi_agent_brief/orchestrator/run_integrity.py +346 -0
- multi_agent_brief/orchestrator/runtime_state/__init__.py +70 -0
- multi_agent_brief/orchestrator/runtime_state/_io.py +167 -0
- multi_agent_brief/orchestrator/runtime_state/_transactions.py +298 -0
- multi_agent_brief/orchestrator/runtime_state/artifact_registry.py +1825 -0
- multi_agent_brief/orchestrator/runtime_state/atomic_claim_graph.py +96 -0
- multi_agent_brief/orchestrator/runtime_state/claim_ledger_freeze.py +510 -0
- multi_agent_brief/orchestrator/runtime_state/claim_metadata_enrichment.py +836 -0
- multi_agent_brief/orchestrator/runtime_state/claim_support_matrix.py +582 -0
- multi_agent_brief/orchestrator/runtime_state/completion_gates.py +672 -0
- multi_agent_brief/orchestrator/runtime_state/contracts_loader.py +66 -0
- multi_agent_brief/orchestrator/runtime_state/decisions.py +239 -0
- multi_agent_brief/orchestrator/runtime_state/errors.py +61 -0
- multi_agent_brief/orchestrator/runtime_state/event_log.py +256 -0
- multi_agent_brief/orchestrator/runtime_state/evidence_span_registry.py +233 -0
- multi_agent_brief/orchestrator/runtime_state/fact_layer.py +755 -0
- multi_agent_brief/orchestrator/runtime_state/identity.py +90 -0
- multi_agent_brief/orchestrator/runtime_state/lifecycle.py +675 -0
- multi_agent_brief/orchestrator/runtime_state/manifest.py +82 -0
- multi_agent_brief/orchestrator/runtime_state/operations.py +224 -0
- multi_agent_brief/orchestrator/runtime_state/paths.py +38 -0
- multi_agent_brief/orchestrator/runtime_state/repair.py +984 -0
- multi_agent_brief/orchestrator/runtime_state/safety_surfaces.py +147 -0
- multi_agent_brief/orchestrator/runtime_state/semantic_assessment_report.py +692 -0
- multi_agent_brief/orchestrator/runtime_state/semantic_support_acceptance.py +683 -0
- multi_agent_brief/orchestrator/runtime_state/source_evidence_pack.py +131 -0
- multi_agent_brief/orchestrator/runtime_state/stage_completion.py +1320 -0
- multi_agent_brief/orchestrator/runtime_state/trajectory.py +200 -0
- multi_agent_brief/orchestrator/runtime_state/workflow.py +350 -0
- multi_agent_brief/orchestrator/source_evidence.py +43 -0
- multi_agent_brief/orchestrator/timing.py +410 -0
- multi_agent_brief/orchestrator_contract.py +96 -0
- multi_agent_brief/outputs/__init__.py +2 -0
- multi_agent_brief/outputs/atomic_reader_projection.py +288 -0
- multi_agent_brief/outputs/docx.py +32 -0
- multi_agent_brief/outputs/finalize.py +1036 -0
- multi_agent_brief/outputs/ib_docx.py +983 -0
- multi_agent_brief/outputs/naming.py +38 -0
- multi_agent_brief/outputs/pdf.py +20 -0
- multi_agent_brief/outputs/reader_final_gate.py +509 -0
- multi_agent_brief/outputs/reader_projection.py +397 -0
- multi_agent_brief/outputs/source_appendix.py +753 -0
- multi_agent_brief/outputs/templates/__init__.py +112 -0
- multi_agent_brief/product/__init__.py +49 -0
- multi_agent_brief/product/bundle_projection.py +686 -0
- multi_agent_brief/product/citation_profile.py +145 -0
- multi_agent_brief/product/guidance_manifestation.py +421 -0
- multi_agent_brief/product/materiality_selection.py +472 -0
- multi_agent_brief/product/policy_gate_adapter.py +116 -0
- multi_agent_brief/product/policy_profile.py +39 -0
- multi_agent_brief/product/policy_projection.py +161 -0
- multi_agent_brief/product/policy_registry.py +83 -0
- multi_agent_brief/product/policy_resolver.py +225 -0
- multi_agent_brief/product/quality_closeout.py +188 -0
- multi_agent_brief/product/quality_panel.py +2217 -0
- multi_agent_brief/product/release_approval.py +1034 -0
- multi_agent_brief/product/report_pack.py +130 -0
- multi_agent_brief/product/report_pack_aliases.py +68 -0
- multi_agent_brief/product/report_registry.py +101 -0
- multi_agent_brief/product/report_spec.py +181 -0
- multi_agent_brief/product/support_wording.py +525 -0
- multi_agent_brief/product/template_conformance.py +516 -0
- multi_agent_brief/product/template_projection.py +123 -0
- multi_agent_brief/product/template_registry.py +244 -0
- multi_agent_brief/product/template_render_plan.py +315 -0
- multi_agent_brief/product/template_renderer.py +224 -0
- multi_agent_brief/product/trajectory_regulation.py +441 -0
- multi_agent_brief/provenance/__init__.py +5 -0
- multi_agent_brief/provenance/builder.py +841 -0
- multi_agent_brief/provenance/contract.py +21 -0
- multi_agent_brief/provenance/io.py +144 -0
- multi_agent_brief/provenance/model.py +110 -0
- multi_agent_brief/provenance/references.py +43 -0
- multi_agent_brief/provenance/validator.py +135 -0
- multi_agent_brief/quality_gates/__init__.py +19 -0
- multi_agent_brief/quality_gates/contract.py +581 -0
- multi_agent_brief/quality_gates/state.py +2498 -0
- multi_agent_brief/repair/__init__.py +5 -0
- multi_agent_brief/repair/router.py +842 -0
- multi_agent_brief/runtime_assets.py +431 -0
- multi_agent_brief/sources/__init__.py +16 -0
- multi_agent_brief/sources/api_filings.py +236 -0
- multi_agent_brief/sources/api_news.py +174 -0
- multi_agent_brief/sources/base.py +142 -0
- multi_agent_brief/sources/cached_package.py +119 -0
- multi_agent_brief/sources/cli_provider.py +263 -0
- multi_agent_brief/sources/coverage.py +336 -0
- multi_agent_brief/sources/decider.py +756 -0
- multi_agent_brief/sources/doctor.py +373 -0
- multi_agent_brief/sources/evidence_pack.py +364 -0
- multi_agent_brief/sources/feishu_provider.py +326 -0
- multi_agent_brief/sources/filing_resolver.py +400 -0
- multi_agent_brief/sources/industry_packs.py +130 -0
- multi_agent_brief/sources/join.py +206 -0
- multi_agent_brief/sources/local_signal.py +81 -0
- multi_agent_brief/sources/local_signal_planner.py +636 -0
- multi_agent_brief/sources/manual.py +259 -0
- multi_agent_brief/sources/mcp_provider.py +309 -0
- multi_agent_brief/sources/mineru_provider.py +601 -0
- multi_agent_brief/sources/normalizer.py +95 -0
- multi_agent_brief/sources/opencli_provider.py +282 -0
- multi_agent_brief/sources/planner.py +178 -0
- multi_agent_brief/sources/registry.py +367 -0
- multi_agent_brief/sources/rss.py +190 -0
- multi_agent_brief/sources/search_backends/__init__.py +31 -0
- multi_agent_brief/sources/search_backends/base.py +51 -0
- multi_agent_brief/sources/search_backends/brave.py +192 -0
- multi_agent_brief/sources/search_backends/capabilities.py +94 -0
- multi_agent_brief/sources/search_backends/exa.py +180 -0
- multi_agent_brief/sources/search_backends/firecrawl.py +164 -0
- multi_agent_brief/sources/search_backends/serper.py +241 -0
- multi_agent_brief/sources/search_backends/tavily.py +124 -0
- multi_agent_brief/sources/sourcehub.py +505 -0
- multi_agent_brief/sources/web_search.py +267 -0
- multi_agent_brief/status.py +966 -0
- multi_agent_brief/tools/__init__.py +3 -0
- multi_agent_brief/tools/draft_cleanup.py +198 -0
- multi_agent_brief/workbuddy/__init__.py +1 -0
- multi_agent_brief/workbuddy/diagnose.py +569 -0
- multi_agent_brief/workbuddy/skill_pack.py +309 -0
- multi_agent_brief/workspace/__init__.py +1 -0
- multi_agent_brief/workspace/init_profile.py +45 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
"""Source Decider: resolve llm_decide profile into concrete source candidates.
|
|
2
|
+
|
|
3
|
+
Reads source_discovery from sources.yaml, searches for relevant sources,
|
|
4
|
+
and generates source_candidates.yaml for user review before merging.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from datetime import date, datetime, timedelta
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import yaml
|
|
16
|
+
except ModuleNotFoundError:
|
|
17
|
+
yaml = None # type: ignore[assignment]
|
|
18
|
+
|
|
19
|
+
from multi_agent_brief.sources.local_signal_planner import (
|
|
20
|
+
build_local_signal_tasks,
|
|
21
|
+
generate_collector_tasks,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
E_SOURCE_CANDIDATES_PLAN_ONLY = "E_SOURCE_CANDIDATES_PLAN_ONLY"
|
|
25
|
+
E_SOURCE_CANDIDATES_UNSUPPORTED_SCHEMA = "E_SOURCE_CANDIDATES_UNSUPPORTED_SCHEMA"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SourceCandidatesError(RuntimeError):
|
|
29
|
+
"""Raised when source_candidates.yaml cannot be safely consumed."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
message: str,
|
|
34
|
+
*,
|
|
35
|
+
error_code: str,
|
|
36
|
+
details: dict[str, Any] | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(message)
|
|
39
|
+
self.error_code = error_code
|
|
40
|
+
self.details = details or {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _load_yaml(path: Path) -> dict[str, Any]:
|
|
45
|
+
"""Load YAML file, return empty dict if unavailable."""
|
|
46
|
+
if yaml is None:
|
|
47
|
+
raise RuntimeError("PyYAML is required: pip install pyyaml")
|
|
48
|
+
if not path.exists():
|
|
49
|
+
return {}
|
|
50
|
+
with open(path, encoding="utf-8") as f:
|
|
51
|
+
return yaml.safe_load(f) or {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _save_yaml(path: Path, data: dict[str, Any]) -> None:
|
|
55
|
+
"""Save dict as YAML."""
|
|
56
|
+
if yaml is None:
|
|
57
|
+
raise RuntimeError("PyYAML is required: pip install pyyaml")
|
|
58
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
59
|
+
yaml.dump(data, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _validate_mergeable_candidates(candidates: Any) -> None:
|
|
63
|
+
"""Reject non-evidence candidate schemas before mutating sources.yaml."""
|
|
64
|
+
if not isinstance(candidates, dict):
|
|
65
|
+
raise SourceCandidatesError(
|
|
66
|
+
"source_candidates.yaml uses an unsupported schema: expected a mapping.",
|
|
67
|
+
error_code=E_SOURCE_CANDIDATES_UNSUPPORTED_SCHEMA,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
artifact_type = str(candidates.get("artifact_type") or "")
|
|
71
|
+
evidence_status = str(candidates.get("evidence_status") or "")
|
|
72
|
+
if artifact_type == "source_plan_only" or evidence_status == "not_evidence":
|
|
73
|
+
raise SourceCandidatesError(
|
|
74
|
+
"source_candidates.yaml is a source plan only; it cannot be "
|
|
75
|
+
"merged into sources.yaml as evidence. Collect approved sources "
|
|
76
|
+
"into input/sources/ or use a supported candidate schema.",
|
|
77
|
+
error_code=E_SOURCE_CANDIDATES_PLAN_ONLY,
|
|
78
|
+
details={
|
|
79
|
+
"artifact_type": artifact_type,
|
|
80
|
+
"evidence_status": evidence_status,
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
metadata = candidates.get("metadata")
|
|
85
|
+
if not isinstance(metadata, dict):
|
|
86
|
+
raise SourceCandidatesError(
|
|
87
|
+
"source_candidates.yaml uses an unsupported schema for merge: "
|
|
88
|
+
"missing metadata object.",
|
|
89
|
+
error_code=E_SOURCE_CANDIDATES_UNSUPPORTED_SCHEMA,
|
|
90
|
+
)
|
|
91
|
+
if metadata.get("generated_by") != "source_decider":
|
|
92
|
+
raise SourceCandidatesError(
|
|
93
|
+
"source_candidates.yaml uses an unsupported schema for merge: "
|
|
94
|
+
"metadata.generated_by must be source_decider.",
|
|
95
|
+
error_code=E_SOURCE_CANDIDATES_UNSUPPORTED_SCHEMA,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _ensure_mapping(
|
|
100
|
+
container: dict[str, Any],
|
|
101
|
+
key: str,
|
|
102
|
+
default: dict[str, Any],
|
|
103
|
+
*,
|
|
104
|
+
path: str | None = None,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
"""Return a mapping section, normalizing YAML null to defaults."""
|
|
107
|
+
value = container.get(key)
|
|
108
|
+
if value is None:
|
|
109
|
+
value = dict(default)
|
|
110
|
+
container[key] = value
|
|
111
|
+
if not isinstance(value, dict):
|
|
112
|
+
label = path or key
|
|
113
|
+
raise ValueError(f"{label} must be a mapping.")
|
|
114
|
+
for default_key, default_value in default.items():
|
|
115
|
+
value.setdefault(default_key, default_value)
|
|
116
|
+
return value
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _ensure_list(
|
|
120
|
+
container: dict[str, Any],
|
|
121
|
+
key: str,
|
|
122
|
+
*,
|
|
123
|
+
path: str,
|
|
124
|
+
default: list[Any] | None = None,
|
|
125
|
+
) -> list[Any]:
|
|
126
|
+
"""Return a list field, normalizing YAML null to a concrete list."""
|
|
127
|
+
value = container.get(key)
|
|
128
|
+
if value is None:
|
|
129
|
+
value = list(default or [])
|
|
130
|
+
container[key] = value
|
|
131
|
+
if not isinstance(value, list):
|
|
132
|
+
raise ValueError(f"{path} must be a list.")
|
|
133
|
+
return value
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _canonical_filing_ticker_entry(entry: Any) -> dict[str, Any] | None:
|
|
137
|
+
"""Return canonical filing_resolver ticker config for writer paths."""
|
|
138
|
+
if isinstance(entry, dict):
|
|
139
|
+
return dict(entry)
|
|
140
|
+
if isinstance(entry, str):
|
|
141
|
+
value = entry.strip()
|
|
142
|
+
if not value:
|
|
143
|
+
return None
|
|
144
|
+
if re.fullmatch(r"[A-Z0-9][A-Z0-9.\-]{0,9}", value):
|
|
145
|
+
return {"ticker": value}
|
|
146
|
+
return {"company_name": value}
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _filing_ticker_key(entry: Any) -> str | None:
|
|
151
|
+
normalized = _canonical_filing_ticker_entry(entry)
|
|
152
|
+
if normalized is None:
|
|
153
|
+
return None
|
|
154
|
+
for key in ("ticker", "company_name", "cik"):
|
|
155
|
+
value = normalized.get(key)
|
|
156
|
+
if value:
|
|
157
|
+
return f"{key}:{str(value).strip()}"
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def load_source_discovery(sources_path: Path) -> dict[str, Any]:
|
|
162
|
+
"""Extract source_discovery section from sources.yaml."""
|
|
163
|
+
data = _load_yaml(sources_path)
|
|
164
|
+
return data.get("source_discovery", {})
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def build_search_queries(discovery: dict[str, Any]) -> list[str]:
|
|
168
|
+
"""Build standard web search queries from source_discovery fields.
|
|
169
|
+
|
|
170
|
+
Does NOT include local signal queries — those are handled by
|
|
171
|
+
build_search_tasks_with_metadata() which adds platform/market metadata.
|
|
172
|
+
"""
|
|
173
|
+
company = discovery.get("company", "")
|
|
174
|
+
industry = discovery.get("industry", "")
|
|
175
|
+
focus_areas = discovery.get("focus_areas", [])
|
|
176
|
+
|
|
177
|
+
queries = []
|
|
178
|
+
|
|
179
|
+
# Industry-level query
|
|
180
|
+
if industry:
|
|
181
|
+
queries.append(f"{industry} industry news recent")
|
|
182
|
+
|
|
183
|
+
# Company-level query
|
|
184
|
+
if company:
|
|
185
|
+
queries.append(f"{company} official announcements news")
|
|
186
|
+
|
|
187
|
+
# Focus area queries
|
|
188
|
+
if isinstance(focus_areas, str):
|
|
189
|
+
focus_areas = [a.strip() for a in focus_areas.split(",") if a.strip()]
|
|
190
|
+
for area in focus_areas[:5]: # cap at 5 focus areas
|
|
191
|
+
if company:
|
|
192
|
+
queries.append(f"{company} {area}")
|
|
193
|
+
elif industry:
|
|
194
|
+
queries.append(f"{industry} {area}")
|
|
195
|
+
|
|
196
|
+
return queries
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def build_daily_news_search_tasks(
|
|
200
|
+
discovery: dict[str, Any],
|
|
201
|
+
*,
|
|
202
|
+
days: int = 7,
|
|
203
|
+
daily_max_results: int = 20,
|
|
204
|
+
report_date: str | date | None = None,
|
|
205
|
+
) -> list[dict[str, Any]]:
|
|
206
|
+
"""Build one user-need-customized news search task per day.
|
|
207
|
+
|
|
208
|
+
This is a source discovery helper. It does not execute searches and does
|
|
209
|
+
not write report content.
|
|
210
|
+
"""
|
|
211
|
+
if days <= 0:
|
|
212
|
+
return []
|
|
213
|
+
if daily_max_results <= 0:
|
|
214
|
+
daily_max_results = 20
|
|
215
|
+
|
|
216
|
+
end_date = _parse_report_date(report_date)
|
|
217
|
+
terms = _build_user_need_terms(discovery)
|
|
218
|
+
if not terms:
|
|
219
|
+
terms = ["industry news"]
|
|
220
|
+
base_query = " ".join(terms)
|
|
221
|
+
language = str(discovery.get("language", "en")).lower()
|
|
222
|
+
news_word = "新闻 动态" if language.startswith("zh") else "news updates"
|
|
223
|
+
preferred_domains, excluded_domains = build_news_domain_preferences(discovery)
|
|
224
|
+
|
|
225
|
+
tasks: list[dict[str, Any]] = []
|
|
226
|
+
for offset in range(days, 0, -1):
|
|
227
|
+
window_start = end_date - timedelta(days=offset)
|
|
228
|
+
window_end = window_start + timedelta(days=1)
|
|
229
|
+
query = (
|
|
230
|
+
f"{base_query} {news_word}"
|
|
231
|
+
f" after:{window_start.isoformat()}"
|
|
232
|
+
f" before:{window_end.isoformat()}"
|
|
233
|
+
)
|
|
234
|
+
tasks.append(
|
|
235
|
+
{
|
|
236
|
+
"query": query,
|
|
237
|
+
"domains": preferred_domains or None,
|
|
238
|
+
"vertical": "news",
|
|
239
|
+
"topic": "news",
|
|
240
|
+
"source_intent": "initial_daily_news_backfill",
|
|
241
|
+
"date_window_start": window_start.isoformat(),
|
|
242
|
+
"date_window_end": window_end.isoformat(),
|
|
243
|
+
"max_results": daily_max_results,
|
|
244
|
+
"preferred_domains": preferred_domains,
|
|
245
|
+
"excluded_domains": excluded_domains,
|
|
246
|
+
"customized_from": [
|
|
247
|
+
field
|
|
248
|
+
for field in (
|
|
249
|
+
"company",
|
|
250
|
+
"industry",
|
|
251
|
+
"task_objective",
|
|
252
|
+
"focus_areas",
|
|
253
|
+
"audience",
|
|
254
|
+
)
|
|
255
|
+
if discovery.get(field)
|
|
256
|
+
],
|
|
257
|
+
"tbs": _google_custom_date_range(window_start, window_end),
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
return tasks
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def build_news_domain_preferences(discovery: dict[str, Any]) -> tuple[list[str], list[str]]:
|
|
264
|
+
"""Return user-configured preferred and excluded news domains."""
|
|
265
|
+
selection = discovery.get("news_source_selection") or {}
|
|
266
|
+
preferred = _extract_domain_list(selection, "preferred_domains")
|
|
267
|
+
excluded = _extract_domain_list(selection, "excluded_domains")
|
|
268
|
+
|
|
269
|
+
if not preferred:
|
|
270
|
+
preferred = _extract_domain_list(discovery, "preferred_news_domains")
|
|
271
|
+
if not excluded:
|
|
272
|
+
excluded = _extract_domain_list(discovery, "excluded_news_domains")
|
|
273
|
+
|
|
274
|
+
return preferred, excluded
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _extract_domain_list(container: dict[str, Any], key: str) -> list[str]:
|
|
278
|
+
raw = container.get(key)
|
|
279
|
+
values: list[str] = []
|
|
280
|
+
if isinstance(raw, str):
|
|
281
|
+
values = [item.strip() for item in raw.split(",") if item.strip()]
|
|
282
|
+
elif isinstance(raw, list):
|
|
283
|
+
values = [str(item).strip() for item in raw if str(item).strip()]
|
|
284
|
+
|
|
285
|
+
normalized: list[str] = []
|
|
286
|
+
seen: set[str] = set()
|
|
287
|
+
for value in values:
|
|
288
|
+
domain = _normalize_domain(value)
|
|
289
|
+
if domain and domain not in seen:
|
|
290
|
+
normalized.append(domain)
|
|
291
|
+
seen.add(domain)
|
|
292
|
+
return normalized
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _normalize_domain(value: str) -> str:
|
|
296
|
+
candidate = value.strip().lower()
|
|
297
|
+
if not candidate:
|
|
298
|
+
return ""
|
|
299
|
+
if "://" not in candidate:
|
|
300
|
+
candidate = f"//{candidate}"
|
|
301
|
+
parsed = urlparse(candidate)
|
|
302
|
+
host = parsed.netloc or parsed.path.split("/", 1)[0]
|
|
303
|
+
host = host.split("@")[-1].split(":")[0].strip().strip(".")
|
|
304
|
+
if host.startswith("www."):
|
|
305
|
+
host = host[4:]
|
|
306
|
+
return host
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _url_matches_domain(url: str, domains: list[str]) -> bool:
|
|
310
|
+
host = _normalize_domain(url)
|
|
311
|
+
if not host:
|
|
312
|
+
return False
|
|
313
|
+
for domain in domains:
|
|
314
|
+
if host == domain or host.endswith(f".{domain}"):
|
|
315
|
+
return True
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _parse_report_date(value: str | date | None) -> date:
|
|
320
|
+
if isinstance(value, date):
|
|
321
|
+
return value
|
|
322
|
+
if isinstance(value, str) and value:
|
|
323
|
+
try:
|
|
324
|
+
return datetime.fromisoformat(value).date()
|
|
325
|
+
except ValueError:
|
|
326
|
+
return datetime.strptime(value[:10], "%Y-%m-%d").date()
|
|
327
|
+
return date.today()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _google_custom_date_range(start: date, end: date) -> str:
|
|
331
|
+
return (
|
|
332
|
+
"cdr:1,"
|
|
333
|
+
f"cd_min:{start.month}/{start.day}/{start.year},"
|
|
334
|
+
f"cd_max:{end.month}/{end.day}/{end.year}"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _build_user_need_terms(discovery: dict[str, Any]) -> list[str]:
|
|
339
|
+
terms: list[str] = []
|
|
340
|
+
for key in ("company", "industry"):
|
|
341
|
+
value = str(discovery.get(key) or "").strip()
|
|
342
|
+
if value:
|
|
343
|
+
terms.append(value)
|
|
344
|
+
|
|
345
|
+
focus_areas = discovery.get("focus_areas", [])
|
|
346
|
+
if isinstance(focus_areas, str):
|
|
347
|
+
focus_areas = [a.strip() for a in focus_areas.split(",") if a.strip()]
|
|
348
|
+
for area in list(focus_areas)[:5]:
|
|
349
|
+
value = str(area).strip()
|
|
350
|
+
if value:
|
|
351
|
+
terms.append(value)
|
|
352
|
+
|
|
353
|
+
task_objective = str(discovery.get("task_objective") or "").strip()
|
|
354
|
+
if task_objective:
|
|
355
|
+
terms.append(task_objective[:120])
|
|
356
|
+
|
|
357
|
+
audience = str(discovery.get("audience") or "").strip()
|
|
358
|
+
if audience:
|
|
359
|
+
terms.append(audience[:80])
|
|
360
|
+
|
|
361
|
+
compact: list[str] = []
|
|
362
|
+
seen: set[str] = set()
|
|
363
|
+
for term in terms:
|
|
364
|
+
normalized = " ".join(term.split())
|
|
365
|
+
key = normalized.lower()
|
|
366
|
+
if normalized and key not in seen:
|
|
367
|
+
compact.append(normalized)
|
|
368
|
+
seen.add(key)
|
|
369
|
+
return compact
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def build_search_tasks_with_metadata(discovery: dict[str, Any]) -> list[dict[str, Any]]:
|
|
373
|
+
"""Build search tasks as dicts with metadata for pipeline injection.
|
|
374
|
+
|
|
375
|
+
Returns list of dicts with 'query' plus optional metadata keys:
|
|
376
|
+
topic, market, language, platform_group, signal_type.
|
|
377
|
+
"""
|
|
378
|
+
tasks: list[dict[str, Any]] = []
|
|
379
|
+
|
|
380
|
+
# Standard queries — delegate to build_search_queries
|
|
381
|
+
preferred_domains, excluded_domains = build_news_domain_preferences(discovery)
|
|
382
|
+
for q in build_search_queries(discovery):
|
|
383
|
+
task: dict[str, Any] = {"query": q, "domains": preferred_domains or None}
|
|
384
|
+
if preferred_domains:
|
|
385
|
+
task["preferred_domains"] = preferred_domains
|
|
386
|
+
if excluded_domains:
|
|
387
|
+
task["excluded_domains"] = excluded_domains
|
|
388
|
+
tasks.append(task)
|
|
389
|
+
|
|
390
|
+
# Local signal tasks with metadata
|
|
391
|
+
local_tasks = build_local_signal_tasks(discovery)
|
|
392
|
+
existing_q = {t.get("query") for t in tasks}
|
|
393
|
+
for task in local_tasks:
|
|
394
|
+
if task.query and task.query not in existing_q:
|
|
395
|
+
tasks.append({
|
|
396
|
+
"query": task.query,
|
|
397
|
+
"domains": None,
|
|
398
|
+
"topic": "consumer_signal",
|
|
399
|
+
"market": task.market,
|
|
400
|
+
"language": task.language,
|
|
401
|
+
"platform_group": task.platform_group,
|
|
402
|
+
"signal_type": task.signal_type,
|
|
403
|
+
})
|
|
404
|
+
existing_q.add(task.query)
|
|
405
|
+
|
|
406
|
+
return tasks
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def generate_source_candidates(
|
|
410
|
+
discovery: dict[str, Any],
|
|
411
|
+
search_results: list[dict[str, Any]] | None = None,
|
|
412
|
+
) -> dict[str, Any]:
|
|
413
|
+
"""Generate source_candidates.yaml content from discovery + search results.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
discovery: source_discovery section from sources.yaml
|
|
417
|
+
search_results: list of {"query": str, "results": [{"title", "url", "snippet"}]}
|
|
418
|
+
"""
|
|
419
|
+
company = discovery.get("company", "")
|
|
420
|
+
industry = discovery.get("industry", "")
|
|
421
|
+
language = discovery.get("language", "zh")
|
|
422
|
+
max_age = discovery.get("max_source_age_days", 14)
|
|
423
|
+
|
|
424
|
+
candidates: dict[str, Any] = {
|
|
425
|
+
"metadata": {
|
|
426
|
+
"company": company,
|
|
427
|
+
"industry": industry,
|
|
428
|
+
"language": language,
|
|
429
|
+
"max_source_age_days": max_age,
|
|
430
|
+
"generated_by": "source_decider",
|
|
431
|
+
"status": "pending_review",
|
|
432
|
+
},
|
|
433
|
+
"recommended_sources": [],
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if search_results:
|
|
437
|
+
for sr in search_results:
|
|
438
|
+
query = sr.get("query", "")
|
|
439
|
+
search_metadata = sr.get("metadata") or {}
|
|
440
|
+
excluded_domains = _extract_domain_list(
|
|
441
|
+
search_metadata,
|
|
442
|
+
"excluded_domains",
|
|
443
|
+
)
|
|
444
|
+
for result in sr.get("results", []):
|
|
445
|
+
url = result.get("url", "")
|
|
446
|
+
if excluded_domains and _url_matches_domain(url, excluded_domains):
|
|
447
|
+
continue
|
|
448
|
+
title = result.get("title", "")
|
|
449
|
+
snippet = result.get("snippet", "")
|
|
450
|
+
|
|
451
|
+
# Simple tier classification based on URL patterns
|
|
452
|
+
tier = "industry_media"
|
|
453
|
+
if any(kw in url for kw in [".gov", "gov.cn", "regulator"]):
|
|
454
|
+
tier = "government_regulator"
|
|
455
|
+
elif any(kw in url for kw in ["research", "report", "analysis", "journal"]):
|
|
456
|
+
tier = "research_institution"
|
|
457
|
+
elif company and company.lower() in url.lower():
|
|
458
|
+
tier = "company_official"
|
|
459
|
+
|
|
460
|
+
candidates["recommended_sources"].append({
|
|
461
|
+
"name": title[:80],
|
|
462
|
+
"url": url,
|
|
463
|
+
"category": tier,
|
|
464
|
+
"query": query,
|
|
465
|
+
"snippet": snippet[:200],
|
|
466
|
+
"published_at": result.get("published_at", ""),
|
|
467
|
+
"source_name": result.get("source_name", ""),
|
|
468
|
+
"search_intent": search_metadata.get("source_intent", ""),
|
|
469
|
+
"date_window_start": search_metadata.get("date_window_start", ""),
|
|
470
|
+
"date_window_end": search_metadata.get("date_window_end", ""),
|
|
471
|
+
"enabled": True,
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
# Add template entries for common source types
|
|
475
|
+
template_sources = _get_template_sources(industry, language)
|
|
476
|
+
candidates["template_sources"] = template_sources
|
|
477
|
+
|
|
478
|
+
# Add filing sources for companies that likely have SEC/public filings
|
|
479
|
+
filing_sources = _get_filing_sources(discovery)
|
|
480
|
+
if filing_sources:
|
|
481
|
+
candidates["filing_sources"] = filing_sources
|
|
482
|
+
|
|
483
|
+
# Add local social listening tasks from local signal planner
|
|
484
|
+
local_tasks = build_local_signal_tasks(discovery)
|
|
485
|
+
if local_tasks:
|
|
486
|
+
candidates["local_social_listening_tasks"] = [
|
|
487
|
+
task.to_dict() for task in local_tasks
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
return candidates
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _get_template_sources(industry: str, language: str) -> list[dict[str, Any]]:
|
|
494
|
+
"""Get template source entries based on industry."""
|
|
495
|
+
templates = {
|
|
496
|
+
"finance": [
|
|
497
|
+
{"name": "Industry regulator website", "category": "government_regulator", "enabled": True},
|
|
498
|
+
{"name": "Stock exchange filings", "category": "company_official", "enabled": True},
|
|
499
|
+
{"name": "Financial news outlet", "category": "industry_media", "enabled": True},
|
|
500
|
+
],
|
|
501
|
+
"technology": [
|
|
502
|
+
{"name": "Tech company blogs", "category": "company_official", "enabled": True},
|
|
503
|
+
{"name": "Industry research reports", "category": "research_institution", "enabled": True},
|
|
504
|
+
{"name": "Tech news media", "category": "industry_media", "enabled": True},
|
|
505
|
+
],
|
|
506
|
+
"manufacturing": [
|
|
507
|
+
{"name": "Industry association", "category": "industry_media", "enabled": True},
|
|
508
|
+
{"name": "Trade publications", "category": "industry_media", "enabled": True},
|
|
509
|
+
{"name": "Government policy portal", "category": "government_regulator", "enabled": True},
|
|
510
|
+
],
|
|
511
|
+
}
|
|
512
|
+
return templates.get(industry, templates.get("finance", []))
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _get_filing_sources(discovery: dict[str, Any]) -> list[dict[str, Any]]:
|
|
516
|
+
"""Suggest filing-resolver sources for companies with public disclosure filings.
|
|
517
|
+
|
|
518
|
+
Returns a list of filing source candidates. Each entry represents a ticker/entity
|
|
519
|
+
that disclosure-filing-resolver can fetch SEC EDGAR filings for.
|
|
520
|
+
Only generates suggestions when company info is available.
|
|
521
|
+
"""
|
|
522
|
+
company = discovery.get("company", "").strip()
|
|
523
|
+
if not company:
|
|
524
|
+
return []
|
|
525
|
+
|
|
526
|
+
# Simple heuristic: suggest SEC EDGAR as a filing source for the company.
|
|
527
|
+
# The actual ticker/CIK resolution happens at pipeline time via filing-resolver.
|
|
528
|
+
# Users can edit tickers in source_candidates.yaml before merging.
|
|
529
|
+
sources = []
|
|
530
|
+
sources.append({
|
|
531
|
+
"name": f"{company} — SEC EDGAR filings",
|
|
532
|
+
"provider": "filing_resolver",
|
|
533
|
+
"tickers": [company], # placeholder; user should refine to actual ticker
|
|
534
|
+
"filing_types": ["10-K", "10-Q", "8-K"],
|
|
535
|
+
"category": "company_official",
|
|
536
|
+
"enabled": True,
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
return sources
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def merge_candidates_to_sources(
|
|
543
|
+
sources_path: Path,
|
|
544
|
+
candidates_path: Path,
|
|
545
|
+
*,
|
|
546
|
+
overwrite: bool = False,
|
|
547
|
+
) -> dict[str, Any]:
|
|
548
|
+
"""Merge approved candidates into sources.yaml.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
sources_path: path to sources.yaml
|
|
552
|
+
candidates_path: path to source_candidates.yaml
|
|
553
|
+
overwrite: if True, replace rss/web_search sections; if False, append
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
Summary of changes
|
|
557
|
+
"""
|
|
558
|
+
sources = _load_yaml(sources_path)
|
|
559
|
+
candidates = _load_yaml(candidates_path)
|
|
560
|
+
_validate_mergeable_candidates(candidates)
|
|
561
|
+
|
|
562
|
+
recommended = candidates.get("recommended_sources") or []
|
|
563
|
+
enabled = [s for s in recommended if s.get("enabled", True)]
|
|
564
|
+
|
|
565
|
+
# Group by category.
|
|
566
|
+
# Only explicitly verified rss_feed sources go to rss.feeds.
|
|
567
|
+
# All other URL categories (industry_media, research_institution,
|
|
568
|
+
# government_regulator, company_official) go to manual sources as
|
|
569
|
+
# URL entries — they are web pages, not RSS/Atom feeds.
|
|
570
|
+
rss_feeds = []
|
|
571
|
+
manual_sources = []
|
|
572
|
+
|
|
573
|
+
for src in enabled:
|
|
574
|
+
category = src.get("category", "")
|
|
575
|
+
url = src.get("url", "")
|
|
576
|
+
name = src.get("name", "")
|
|
577
|
+
|
|
578
|
+
if not url:
|
|
579
|
+
continue
|
|
580
|
+
|
|
581
|
+
if category == "rss_feed":
|
|
582
|
+
rss_feeds.append({"name": name, "url": url, "category": category, "enabled": True})
|
|
583
|
+
else:
|
|
584
|
+
# All other URL types go to manual sources (not RSS)
|
|
585
|
+
manual_source = {
|
|
586
|
+
"name": name,
|
|
587
|
+
"url": url,
|
|
588
|
+
"category": category,
|
|
589
|
+
"enabled": True,
|
|
590
|
+
}
|
|
591
|
+
for key in ("published_at", "source_name", "search_intent", "date_window_start", "date_window_end"):
|
|
592
|
+
if src.get(key):
|
|
593
|
+
manual_source[key] = src[key]
|
|
594
|
+
manual_sources.append(manual_source)
|
|
595
|
+
|
|
596
|
+
# Merge into sources. YAML empty fields parse as None, so normalize known
|
|
597
|
+
# list sections before appending or iterating.
|
|
598
|
+
manual = _ensure_mapping(
|
|
599
|
+
sources,
|
|
600
|
+
"manual",
|
|
601
|
+
{"enabled": True, "sources": []},
|
|
602
|
+
path="manual",
|
|
603
|
+
)
|
|
604
|
+
manual_entries = _ensure_list(manual, "sources", path="manual.sources")
|
|
605
|
+
rss = _ensure_mapping(
|
|
606
|
+
sources,
|
|
607
|
+
"rss",
|
|
608
|
+
{"enabled": True, "feeds": []},
|
|
609
|
+
path="rss",
|
|
610
|
+
)
|
|
611
|
+
rss_entries = _ensure_list(rss, "feeds", path="rss.feeds")
|
|
612
|
+
|
|
613
|
+
if overwrite:
|
|
614
|
+
manual_entries[:] = [
|
|
615
|
+
src for src in manual_entries
|
|
616
|
+
if src.get("category") == "local_files" or (src.get("path") and not src.get("url"))
|
|
617
|
+
]
|
|
618
|
+
rss_entries.clear()
|
|
619
|
+
|
|
620
|
+
existing_manual_urls = {s.get("url") for s in manual_entries}
|
|
621
|
+
existing_rss_urls = {f.get("url") for f in rss_entries}
|
|
622
|
+
|
|
623
|
+
added_manual = 0
|
|
624
|
+
added_rss = 0
|
|
625
|
+
|
|
626
|
+
for src in manual_sources:
|
|
627
|
+
if src["url"] not in existing_manual_urls:
|
|
628
|
+
manual_entries.append(src)
|
|
629
|
+
added_manual += 1
|
|
630
|
+
|
|
631
|
+
for feed in rss_feeds:
|
|
632
|
+
if feed["url"] not in existing_rss_urls:
|
|
633
|
+
rss_entries.append(feed)
|
|
634
|
+
added_rss += 1
|
|
635
|
+
|
|
636
|
+
# Ensure web_search section exists, but do NOT auto-enable it.
|
|
637
|
+
# Only enable web_search if it was already enabled OR the user explicitly
|
|
638
|
+
# set a real backend (not mock). Mock data must never leak into real reports
|
|
639
|
+
# unless the user explicitly opted in with allow_mock_search: true.
|
|
640
|
+
web_search = _ensure_mapping(
|
|
641
|
+
sources,
|
|
642
|
+
"web_search",
|
|
643
|
+
{"enabled": False, "max_results": 20, "recency_days": 7},
|
|
644
|
+
path="web_search",
|
|
645
|
+
)
|
|
646
|
+
# Do not auto-enable web_search on merge.
|
|
647
|
+
|
|
648
|
+
# Update source_strategy
|
|
649
|
+
source_strategy = _ensure_mapping(
|
|
650
|
+
sources,
|
|
651
|
+
"source_strategy",
|
|
652
|
+
{"profile": "research", "enabled_providers": ["manual"]},
|
|
653
|
+
path="source_strategy",
|
|
654
|
+
)
|
|
655
|
+
providers = _ensure_list(
|
|
656
|
+
source_strategy,
|
|
657
|
+
"enabled_providers",
|
|
658
|
+
path="source_strategy.enabled_providers",
|
|
659
|
+
default=["manual"],
|
|
660
|
+
)
|
|
661
|
+
if "rss" not in providers and added_rss > 0:
|
|
662
|
+
providers.append("rss")
|
|
663
|
+
# Only add web_search to enabled_providers if it is actually enabled
|
|
664
|
+
if "web_search" not in providers and web_search.get("enabled"):
|
|
665
|
+
providers.append("web_search")
|
|
666
|
+
|
|
667
|
+
# Merge filing_sources into filing_resolver config
|
|
668
|
+
filing_sources = [
|
|
669
|
+
s for s in (candidates.get("filing_sources") or []) if s.get("enabled", True)
|
|
670
|
+
]
|
|
671
|
+
added_filing = 0
|
|
672
|
+
if filing_sources:
|
|
673
|
+
fr = _ensure_mapping(
|
|
674
|
+
sources,
|
|
675
|
+
"filing_resolver",
|
|
676
|
+
{"enabled": True, "tickers": [], "filing_types": ["10-K", "10-Q", "8-K"]},
|
|
677
|
+
path="filing_resolver",
|
|
678
|
+
)
|
|
679
|
+
tickers = _ensure_list(fr, "tickers", path="filing_resolver.tickers")
|
|
680
|
+
filing_types = _ensure_list(
|
|
681
|
+
fr,
|
|
682
|
+
"filing_types",
|
|
683
|
+
path="filing_resolver.filing_types",
|
|
684
|
+
default=["10-K", "10-Q", "8-K"],
|
|
685
|
+
)
|
|
686
|
+
canonical_tickers: list[dict[str, Any]] = []
|
|
687
|
+
existing_tickers: set[str] = set()
|
|
688
|
+
for existing in tickers:
|
|
689
|
+
entry = _canonical_filing_ticker_entry(existing)
|
|
690
|
+
key = _filing_ticker_key(entry)
|
|
691
|
+
if entry is not None and key and key not in existing_tickers:
|
|
692
|
+
canonical_tickers.append(entry)
|
|
693
|
+
existing_tickers.add(key)
|
|
694
|
+
tickers[:] = canonical_tickers
|
|
695
|
+
for fs in filing_sources:
|
|
696
|
+
for ticker in (fs.get("tickers") or []):
|
|
697
|
+
entry = _canonical_filing_ticker_entry(ticker)
|
|
698
|
+
key = _filing_ticker_key(entry)
|
|
699
|
+
if entry is not None and key and key not in existing_tickers:
|
|
700
|
+
tickers.append(entry)
|
|
701
|
+
existing_tickers.add(key)
|
|
702
|
+
added_filing += 1
|
|
703
|
+
# Merge filing_types if provided
|
|
704
|
+
for ft in (fs.get("filing_types") or []):
|
|
705
|
+
if ft not in filing_types:
|
|
706
|
+
filing_types.append(ft)
|
|
707
|
+
# Add filing_resolver to enabled_providers
|
|
708
|
+
if "filing_resolver" not in providers:
|
|
709
|
+
providers.append("filing_resolver")
|
|
710
|
+
|
|
711
|
+
# Mark candidates as merged
|
|
712
|
+
candidates["metadata"]["status"] = "merged"
|
|
713
|
+
candidates["metadata"]["merged_manual"] = added_manual
|
|
714
|
+
candidates["metadata"]["merged_rss"] = added_rss
|
|
715
|
+
candidates["metadata"]["merged_filing"] = added_filing
|
|
716
|
+
|
|
717
|
+
# Inject local social listening tasks into web_search search_tasks
|
|
718
|
+
local_tasks = [
|
|
719
|
+
t for t in (candidates.get("local_social_listening_tasks") or [])
|
|
720
|
+
if t.get("enabled", True)
|
|
721
|
+
]
|
|
722
|
+
added_local = 0
|
|
723
|
+
if local_tasks and web_search.get("enabled"):
|
|
724
|
+
search_tasks = _ensure_list(
|
|
725
|
+
web_search,
|
|
726
|
+
"search_tasks",
|
|
727
|
+
path="web_search.search_tasks",
|
|
728
|
+
)
|
|
729
|
+
existing_search_q = {t.get("query") for t in search_tasks}
|
|
730
|
+
for task in local_tasks:
|
|
731
|
+
query = task.get("query", "")
|
|
732
|
+
if query and query not in existing_search_q:
|
|
733
|
+
search_tasks.append({
|
|
734
|
+
"query": query,
|
|
735
|
+
"domains": None,
|
|
736
|
+
"topic": "consumer_signal",
|
|
737
|
+
"market": task.get("market", ""),
|
|
738
|
+
"language": task.get("language", ""),
|
|
739
|
+
"platform_group": task.get("platform_group", ""),
|
|
740
|
+
"signal_type": task.get("signal_type", "consumer_discussion"),
|
|
741
|
+
})
|
|
742
|
+
existing_search_q.add(query)
|
|
743
|
+
added_local += 1
|
|
744
|
+
candidates["metadata"]["merged_local_tasks"] = added_local
|
|
745
|
+
|
|
746
|
+
_save_yaml(sources_path, sources)
|
|
747
|
+
_save_yaml(candidates_path, candidates)
|
|
748
|
+
|
|
749
|
+
return {
|
|
750
|
+
"added_manual": added_manual,
|
|
751
|
+
"added_rss": added_rss,
|
|
752
|
+
"added_filing": added_filing,
|
|
753
|
+
"added_local": added_local,
|
|
754
|
+
"total_enabled": len(enabled),
|
|
755
|
+
"total_disabled": len(recommended) - len(enabled),
|
|
756
|
+
}
|