aiecs 1.0.1__py3-none-any.whl → 1.7.17__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +435 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3949 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1731 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +894 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +377 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +230 -37
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +328 -0
- aiecs/llm/clients/google_function_calling_mixin.py +415 -0
- aiecs/llm/clients/googleai_client.py +314 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +1186 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1464 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1016 -0
- aiecs/tools/docs/document_writer_tool.py +2008 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +220 -141
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/METADATA +52 -15
- aiecs-1.7.17.dist-info/RECORD +337 -0
- aiecs-1.7.17.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Per-Entity-Type Matching Configuration for Knowledge Fusion.
|
|
3
|
+
|
|
4
|
+
Provides configurable matching pipeline settings per entity type,
|
|
5
|
+
enabling precision/recall tradeoffs for different entity categories.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional, Set
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Valid matching stages
|
|
20
|
+
VALID_STAGES = {"exact", "alias", "abbreviation", "normalized", "semantic", "string"}
|
|
21
|
+
|
|
22
|
+
# Default stages enabled (string is the fallback)
|
|
23
|
+
DEFAULT_ENABLED_STAGES = ["exact", "alias", "abbreviation", "normalized", "semantic", "string"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class EntityTypeConfig:
|
|
28
|
+
"""
|
|
29
|
+
Configuration for a specific entity type.
|
|
30
|
+
|
|
31
|
+
Defines which matching stages are enabled and threshold overrides
|
|
32
|
+
for a particular entity type (e.g., Person, Organization, Concept).
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```python
|
|
36
|
+
person_config = EntityTypeConfig(
|
|
37
|
+
enabled_stages=["exact", "alias", "normalized"],
|
|
38
|
+
thresholds={"alias_match_score": 0.99},
|
|
39
|
+
semantic_enabled=False
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
enabled_stages: List[str] = field(
|
|
45
|
+
default_factory=lambda: DEFAULT_ENABLED_STAGES.copy()
|
|
46
|
+
)
|
|
47
|
+
semantic_enabled: bool = True
|
|
48
|
+
thresholds: Dict[str, float] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
def __post_init__(self):
|
|
51
|
+
"""Validate enabled stages."""
|
|
52
|
+
invalid = set(self.enabled_stages) - VALID_STAGES
|
|
53
|
+
if invalid:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Invalid matching stages: {invalid}. "
|
|
56
|
+
f"Valid stages are: {VALID_STAGES}"
|
|
57
|
+
)
|
|
58
|
+
# Validate threshold values are in range
|
|
59
|
+
for key, value in self.thresholds.items():
|
|
60
|
+
if not 0.0 <= value <= 1.0:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Threshold '{key}' must be between 0.0 and 1.0, got {value}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def merge_overrides(self, overrides: "EntityTypeConfig") -> None:
|
|
66
|
+
"""
|
|
67
|
+
Merge threshold overrides from another config.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
overrides: Config with override values
|
|
71
|
+
"""
|
|
72
|
+
# Always apply enabled_stages from override, consistent with other properties
|
|
73
|
+
self.enabled_stages = overrides.enabled_stages.copy()
|
|
74
|
+
self.semantic_enabled = overrides.semantic_enabled
|
|
75
|
+
self.thresholds.update(overrides.thresholds)
|
|
76
|
+
|
|
77
|
+
def is_stage_enabled(self, stage: str) -> bool:
|
|
78
|
+
"""Check if a matching stage is enabled."""
|
|
79
|
+
if stage == "semantic":
|
|
80
|
+
return self.semantic_enabled and stage in self.enabled_stages
|
|
81
|
+
return stage in self.enabled_stages
|
|
82
|
+
|
|
83
|
+
def get_threshold(
|
|
84
|
+
self, threshold_name: str, default: Optional[float] = None
|
|
85
|
+
) -> Optional[float]:
|
|
86
|
+
"""Get a threshold value, or default if not set."""
|
|
87
|
+
return self.thresholds.get(threshold_name, default)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class FusionMatchingConfig:
|
|
92
|
+
"""
|
|
93
|
+
Global fusion matching configuration with per-entity-type inheritance.
|
|
94
|
+
|
|
95
|
+
Configuration follows strict inheritance order:
|
|
96
|
+
1. System defaults (hardcoded fallbacks)
|
|
97
|
+
2. Global configuration (this class)
|
|
98
|
+
3. Per-entity-type configuration (entity_type_configs)
|
|
99
|
+
4. Runtime overrides (method parameters)
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
```python
|
|
103
|
+
config = FusionMatchingConfig(
|
|
104
|
+
alias_match_score=0.98,
|
|
105
|
+
entity_type_configs={
|
|
106
|
+
"Person": EntityTypeConfig(
|
|
107
|
+
enabled_stages=["exact", "alias", "normalized"],
|
|
108
|
+
semantic_enabled=False,
|
|
109
|
+
thresholds={"alias_match_score": 0.99}
|
|
110
|
+
),
|
|
111
|
+
"Organization": EntityTypeConfig(
|
|
112
|
+
semantic_enabled=True,
|
|
113
|
+
),
|
|
114
|
+
"_default": EntityTypeConfig(), # Fallback for unknown types
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Get effective config for an entity type
|
|
119
|
+
person_config = config.get_config_for_type("Person")
|
|
120
|
+
```
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# Global threshold defaults
|
|
124
|
+
alias_match_score: float = 0.98
|
|
125
|
+
abbreviation_match_score: float = 0.95
|
|
126
|
+
normalization_match_score: float = 0.90
|
|
127
|
+
semantic_threshold: float = 0.85
|
|
128
|
+
string_similarity_threshold: float = 0.80
|
|
129
|
+
|
|
130
|
+
# Default enabled stages
|
|
131
|
+
enabled_stages: List[str] = field(
|
|
132
|
+
default_factory=lambda: DEFAULT_ENABLED_STAGES.copy()
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Whether semantic matching is enabled globally
|
|
136
|
+
semantic_enabled: bool = True
|
|
137
|
+
|
|
138
|
+
# Per-entity-type configurations
|
|
139
|
+
entity_type_configs: Dict[str, EntityTypeConfig] = field(default_factory=dict)
|
|
140
|
+
|
|
141
|
+
# Configuration source tracking for debugging
|
|
142
|
+
_config_sources: Dict[str, str] = field(
|
|
143
|
+
default_factory=dict, repr=False, compare=False
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def __post_init__(self):
|
|
147
|
+
"""Validate configuration."""
|
|
148
|
+
invalid = set(self.enabled_stages) - VALID_STAGES
|
|
149
|
+
if invalid:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"Invalid matching stages: {invalid}. "
|
|
152
|
+
f"Valid stages are: {VALID_STAGES}"
|
|
153
|
+
)
|
|
154
|
+
self._validate_thresholds()
|
|
155
|
+
|
|
156
|
+
def _validate_thresholds(self) -> None:
|
|
157
|
+
"""Validate all threshold values are in range [0.0, 1.0]."""
|
|
158
|
+
thresholds = {
|
|
159
|
+
"alias_match_score": self.alias_match_score,
|
|
160
|
+
"abbreviation_match_score": self.abbreviation_match_score,
|
|
161
|
+
"normalization_match_score": self.normalization_match_score,
|
|
162
|
+
"semantic_threshold": self.semantic_threshold,
|
|
163
|
+
"string_similarity_threshold": self.string_similarity_threshold,
|
|
164
|
+
}
|
|
165
|
+
for name, value in thresholds.items():
|
|
166
|
+
if not 0.0 <= value <= 1.0:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"Threshold '{name}' must be between 0.0 and 1.0, got {value}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def get_config_for_type(self, entity_type: str) -> EntityTypeConfig:
|
|
172
|
+
"""
|
|
173
|
+
Get effective config for entity type with inheritance.
|
|
174
|
+
|
|
175
|
+
Resolution order:
|
|
176
|
+
1. Start with global defaults
|
|
177
|
+
2. Apply per-type overrides if exists
|
|
178
|
+
3. Fall back to _default config if type not found
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
entity_type: Entity type name (e.g., "Person", "Organization")
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Effective EntityTypeConfig for the type
|
|
185
|
+
"""
|
|
186
|
+
# Start with global defaults
|
|
187
|
+
effective = EntityTypeConfig(
|
|
188
|
+
enabled_stages=self.enabled_stages.copy(),
|
|
189
|
+
semantic_enabled=self.semantic_enabled,
|
|
190
|
+
thresholds={
|
|
191
|
+
"alias_match_score": self.alias_match_score,
|
|
192
|
+
"abbreviation_match_score": self.abbreviation_match_score,
|
|
193
|
+
"normalization_match_score": self.normalization_match_score,
|
|
194
|
+
"semantic_threshold": self.semantic_threshold,
|
|
195
|
+
"string_similarity_threshold": self.string_similarity_threshold,
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Apply per-type overrides (if exists)
|
|
200
|
+
if entity_type in self.entity_type_configs:
|
|
201
|
+
type_config = self.entity_type_configs[entity_type]
|
|
202
|
+
effective.merge_overrides(type_config)
|
|
203
|
+
self._log_config_source(entity_type, "per-type")
|
|
204
|
+
elif "_default" in self.entity_type_configs:
|
|
205
|
+
effective.merge_overrides(self.entity_type_configs["_default"])
|
|
206
|
+
self._log_config_source(entity_type, "_default")
|
|
207
|
+
else:
|
|
208
|
+
self._log_config_source(entity_type, "global")
|
|
209
|
+
|
|
210
|
+
return effective
|
|
211
|
+
|
|
212
|
+
def _log_config_source(self, entity_type: str, source: str) -> None:
|
|
213
|
+
"""Log configuration source for debugging."""
|
|
214
|
+
self._config_sources[entity_type] = source
|
|
215
|
+
logger.debug(f"Config for '{entity_type}' resolved from: {source}")
|
|
216
|
+
|
|
217
|
+
def get_global_thresholds(self) -> Dict[str, float]:
|
|
218
|
+
"""Get all global threshold values."""
|
|
219
|
+
return {
|
|
220
|
+
"alias_match_score": self.alias_match_score,
|
|
221
|
+
"abbreviation_match_score": self.abbreviation_match_score,
|
|
222
|
+
"normalization_match_score": self.normalization_match_score,
|
|
223
|
+
"semantic_threshold": self.semantic_threshold,
|
|
224
|
+
"string_similarity_threshold": self.string_similarity_threshold,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
def add_entity_type_config(
|
|
228
|
+
self, entity_type: str, config: EntityTypeConfig
|
|
229
|
+
) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Add or update configuration for an entity type.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
entity_type: Entity type name
|
|
235
|
+
config: Configuration for the type
|
|
236
|
+
"""
|
|
237
|
+
self.entity_type_configs[entity_type] = config
|
|
238
|
+
logger.info(f"Added config for entity type: {entity_type}")
|
|
239
|
+
|
|
240
|
+
def remove_entity_type_config(self, entity_type: str) -> bool:
|
|
241
|
+
"""
|
|
242
|
+
Remove configuration for an entity type.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
entity_type: Entity type to remove
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
True if config was removed, False if not found
|
|
249
|
+
"""
|
|
250
|
+
if entity_type in self.entity_type_configs:
|
|
251
|
+
del self.entity_type_configs[entity_type]
|
|
252
|
+
return True
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
def get_configured_entity_types(self) -> List[str]:
|
|
256
|
+
"""Get list of entity types with explicit configurations."""
|
|
257
|
+
return list(self.entity_type_configs.keys())
|
|
258
|
+
|
|
259
|
+
def get_config_sources(self) -> Dict[str, str]:
|
|
260
|
+
"""Get mapping of entity types to their config sources (for debugging)."""
|
|
261
|
+
return dict(self._config_sources)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def load_matching_config_from_dict(data: Dict[str, Any]) -> FusionMatchingConfig:
|
|
265
|
+
"""
|
|
266
|
+
Load FusionMatchingConfig from a dictionary.
|
|
267
|
+
|
|
268
|
+
Expected format:
|
|
269
|
+
```python
|
|
270
|
+
{
|
|
271
|
+
"alias_match_score": 0.98,
|
|
272
|
+
"abbreviation_match_score": 0.95,
|
|
273
|
+
"normalization_match_score": 0.90,
|
|
274
|
+
"semantic_threshold": 0.85,
|
|
275
|
+
"string_similarity_threshold": 0.80,
|
|
276
|
+
"enabled_stages": ["exact", "alias", "abbreviation", "normalized", "semantic"],
|
|
277
|
+
"semantic_enabled": true,
|
|
278
|
+
"entity_types": {
|
|
279
|
+
"Person": {
|
|
280
|
+
"enabled_stages": ["exact", "alias", "normalized"],
|
|
281
|
+
"semantic_enabled": false,
|
|
282
|
+
"thresholds": {"alias_match_score": 0.99}
|
|
283
|
+
},
|
|
284
|
+
"_default": {
|
|
285
|
+
"enabled_stages": ["exact", "alias", "abbreviation", "normalized", "semantic"],
|
|
286
|
+
"semantic_enabled": true,
|
|
287
|
+
"thresholds": {}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
data: Configuration dictionary
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
FusionMatchingConfig instance
|
|
298
|
+
"""
|
|
299
|
+
if data is None:
|
|
300
|
+
raise ValueError("Configuration data cannot be None")
|
|
301
|
+
|
|
302
|
+
# Extract global settings
|
|
303
|
+
global_settings = {
|
|
304
|
+
k: v
|
|
305
|
+
for k, v in data.items()
|
|
306
|
+
if k not in ("entity_types", "entity_type_configs")
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# Parse entity type configs
|
|
310
|
+
entity_type_configs: Dict[str, EntityTypeConfig] = {}
|
|
311
|
+
# Handle None values: use 'or' operator to fallback to empty dict if value is None
|
|
312
|
+
entity_types_data = data.get("entity_types") or data.get("entity_type_configs") or {}
|
|
313
|
+
|
|
314
|
+
for entity_type, type_data in entity_types_data.items():
|
|
315
|
+
# Skip None values - they represent missing or invalid configs
|
|
316
|
+
if type_data is None:
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
entity_type_configs[entity_type] = EntityTypeConfig(
|
|
320
|
+
enabled_stages=type_data.get("enabled_stages", DEFAULT_ENABLED_STAGES.copy()),
|
|
321
|
+
semantic_enabled=type_data.get("semantic_enabled", True),
|
|
322
|
+
thresholds=type_data.get("thresholds", {}),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return FusionMatchingConfig(
|
|
326
|
+
alias_match_score=global_settings.get("alias_match_score", 0.98),
|
|
327
|
+
abbreviation_match_score=global_settings.get("abbreviation_match_score", 0.95),
|
|
328
|
+
normalization_match_score=global_settings.get("normalization_match_score", 0.90),
|
|
329
|
+
semantic_threshold=global_settings.get("semantic_threshold", 0.85),
|
|
330
|
+
string_similarity_threshold=global_settings.get("string_similarity_threshold", 0.80),
|
|
331
|
+
enabled_stages=global_settings.get("enabled_stages", DEFAULT_ENABLED_STAGES.copy()),
|
|
332
|
+
semantic_enabled=global_settings.get("semantic_enabled", True),
|
|
333
|
+
entity_type_configs=entity_type_configs,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def load_matching_config_from_json(filepath: str) -> FusionMatchingConfig:
|
|
338
|
+
"""
|
|
339
|
+
Load FusionMatchingConfig from a JSON file.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
filepath: Path to JSON configuration file
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
FusionMatchingConfig instance
|
|
346
|
+
|
|
347
|
+
Raises:
|
|
348
|
+
FileNotFoundError: If file doesn't exist
|
|
349
|
+
json.JSONDecodeError: If file is not valid JSON
|
|
350
|
+
"""
|
|
351
|
+
path = Path(filepath)
|
|
352
|
+
if not path.exists():
|
|
353
|
+
raise FileNotFoundError(f"Configuration file not found: {filepath}")
|
|
354
|
+
|
|
355
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
356
|
+
data = json.load(f)
|
|
357
|
+
|
|
358
|
+
config = load_matching_config_from_dict(data)
|
|
359
|
+
logger.info(f"Loaded matching config from JSON: {filepath}")
|
|
360
|
+
return config
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def load_matching_config_from_yaml(filepath: str) -> FusionMatchingConfig:
|
|
364
|
+
"""
|
|
365
|
+
Load FusionMatchingConfig from a YAML file.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
filepath: Path to YAML configuration file
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
FusionMatchingConfig instance
|
|
372
|
+
|
|
373
|
+
Raises:
|
|
374
|
+
FileNotFoundError: If file doesn't exist
|
|
375
|
+
yaml.YAMLError: If file is not valid YAML
|
|
376
|
+
"""
|
|
377
|
+
path = Path(filepath)
|
|
378
|
+
if not path.exists():
|
|
379
|
+
raise FileNotFoundError(f"Configuration file not found: {filepath}")
|
|
380
|
+
|
|
381
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
382
|
+
data = yaml.safe_load(f)
|
|
383
|
+
|
|
384
|
+
if data is None:
|
|
385
|
+
raise ValueError(f"YAML file is empty or contains only null: {filepath}")
|
|
386
|
+
|
|
387
|
+
config = load_matching_config_from_dict(data)
|
|
388
|
+
logger.info(f"Loaded matching config from YAML: {filepath}")
|
|
389
|
+
return config
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def load_matching_config(filepath: str) -> FusionMatchingConfig:
|
|
393
|
+
"""
|
|
394
|
+
Load FusionMatchingConfig from file (auto-detects format).
|
|
395
|
+
|
|
396
|
+
Supports JSON (.json) and YAML (.yaml, .yml) formats.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
filepath: Path to configuration file
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
FusionMatchingConfig instance
|
|
403
|
+
|
|
404
|
+
Raises:
|
|
405
|
+
FileNotFoundError: If file doesn't exist
|
|
406
|
+
ValueError: If file format is not supported
|
|
407
|
+
"""
|
|
408
|
+
path = Path(filepath)
|
|
409
|
+
suffix = path.suffix.lower()
|
|
410
|
+
|
|
411
|
+
if suffix == ".json":
|
|
412
|
+
return load_matching_config_from_json(filepath)
|
|
413
|
+
elif suffix in (".yaml", ".yml"):
|
|
414
|
+
return load_matching_config_from_yaml(filepath)
|
|
415
|
+
else:
|
|
416
|
+
raise ValueError(
|
|
417
|
+
f"Unsupported config file format: {suffix}. "
|
|
418
|
+
f"Supported formats: .json, .yaml, .yml"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def save_matching_config_to_dict(config: FusionMatchingConfig) -> Dict[str, Any]:
|
|
423
|
+
"""
|
|
424
|
+
Convert FusionMatchingConfig to dictionary.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
config: Configuration to convert
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Dictionary representation
|
|
431
|
+
"""
|
|
432
|
+
entity_types = {}
|
|
433
|
+
for entity_type, type_config in config.entity_type_configs.items():
|
|
434
|
+
entity_types[entity_type] = {
|
|
435
|
+
"enabled_stages": type_config.enabled_stages,
|
|
436
|
+
"semantic_enabled": type_config.semantic_enabled,
|
|
437
|
+
"thresholds": type_config.thresholds,
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
"alias_match_score": config.alias_match_score,
|
|
442
|
+
"abbreviation_match_score": config.abbreviation_match_score,
|
|
443
|
+
"normalization_match_score": config.normalization_match_score,
|
|
444
|
+
"semantic_threshold": config.semantic_threshold,
|
|
445
|
+
"string_similarity_threshold": config.string_similarity_threshold,
|
|
446
|
+
"enabled_stages": config.enabled_stages,
|
|
447
|
+
"semantic_enabled": config.semantic_enabled,
|
|
448
|
+
"entity_types": entity_types,
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def save_matching_config_to_json(
|
|
453
|
+
config: FusionMatchingConfig, filepath: str
|
|
454
|
+
) -> None:
|
|
455
|
+
"""
|
|
456
|
+
Save FusionMatchingConfig to JSON file.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
config: Configuration to save
|
|
460
|
+
filepath: Path to output file
|
|
461
|
+
"""
|
|
462
|
+
path = Path(filepath)
|
|
463
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
464
|
+
|
|
465
|
+
data = save_matching_config_to_dict(config)
|
|
466
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
467
|
+
json.dump(data, f, indent=2)
|
|
468
|
+
|
|
469
|
+
logger.info(f"Saved matching config to JSON: {filepath}")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def save_matching_config_to_yaml(
|
|
473
|
+
config: FusionMatchingConfig, filepath: str
|
|
474
|
+
) -> None:
|
|
475
|
+
"""
|
|
476
|
+
Save FusionMatchingConfig to YAML file.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
config: Configuration to save
|
|
480
|
+
filepath: Path to output file
|
|
481
|
+
"""
|
|
482
|
+
path = Path(filepath)
|
|
483
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
484
|
+
|
|
485
|
+
data = save_matching_config_to_dict(config)
|
|
486
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
487
|
+
yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False)
|
|
488
|
+
|
|
489
|
+
logger.info(f"Saved matching config to YAML: {filepath}")
|