aiecs 1.0.1__py3-none-any.whl → 1.7.6__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 +399 -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 +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -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 +884 -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 +364 -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 +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -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 +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -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 +175 -131
- 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.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.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.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pattern Matching Engine
|
|
3
|
+
|
|
4
|
+
Implements graph pattern matching for custom query execution.
|
|
5
|
+
|
|
6
|
+
Phase: 3.3 - Full Custom Query Execution
|
|
7
|
+
Version: 1.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import List, Dict, Any, Optional
|
|
11
|
+
from aiecs.domain.knowledge_graph.models.entity import Entity
|
|
12
|
+
from aiecs.domain.knowledge_graph.models.relation import Relation
|
|
13
|
+
from aiecs.domain.knowledge_graph.models.path import Path
|
|
14
|
+
from aiecs.domain.knowledge_graph.models.path_pattern import PathPattern
|
|
15
|
+
from aiecs.infrastructure.graph_storage.base import GraphStore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PatternMatch:
|
|
19
|
+
"""
|
|
20
|
+
Represents a single pattern match result
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
entities: Matched entities
|
|
24
|
+
relations: Matched relations
|
|
25
|
+
bindings: Variable bindings (if pattern uses variables)
|
|
26
|
+
score: Match score (0.0-1.0)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
entities: List[Entity],
|
|
32
|
+
relations: List[Relation],
|
|
33
|
+
bindings: Optional[Dict[str, Any]] = None,
|
|
34
|
+
score: float = 1.0,
|
|
35
|
+
):
|
|
36
|
+
self.entities = entities
|
|
37
|
+
self.relations = relations
|
|
38
|
+
self.bindings = bindings or {}
|
|
39
|
+
self.score = score
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return f"PatternMatch(entities={len(self.entities)}, relations={len(self.relations)}, score={self.score})"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PatternMatcher:
|
|
46
|
+
"""
|
|
47
|
+
Graph Pattern Matching Engine
|
|
48
|
+
|
|
49
|
+
Executes pattern matching queries against a graph store.
|
|
50
|
+
Supports:
|
|
51
|
+
- Single pattern matching
|
|
52
|
+
- Multiple pattern matching (AND semantics)
|
|
53
|
+
- Optional pattern matching
|
|
54
|
+
- Cycle detection and handling
|
|
55
|
+
- Result projection and aggregation
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, graph_store: GraphStore):
|
|
59
|
+
"""
|
|
60
|
+
Initialize pattern matcher
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
graph_store: Graph storage backend
|
|
64
|
+
"""
|
|
65
|
+
self.graph_store = graph_store
|
|
66
|
+
|
|
67
|
+
async def match_pattern(
|
|
68
|
+
self,
|
|
69
|
+
pattern: PathPattern,
|
|
70
|
+
start_entity_id: Optional[str] = None,
|
|
71
|
+
max_matches: int = 100,
|
|
72
|
+
) -> List[PatternMatch]:
|
|
73
|
+
"""
|
|
74
|
+
Match a single pattern in the graph
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
pattern: Pattern to match
|
|
78
|
+
start_entity_id: Optional starting entity ID
|
|
79
|
+
max_matches: Maximum number of matches to return
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of pattern matches
|
|
83
|
+
"""
|
|
84
|
+
matches = []
|
|
85
|
+
|
|
86
|
+
if start_entity_id:
|
|
87
|
+
# Start from specific entity
|
|
88
|
+
start_entity = await self.graph_store.get_entity(start_entity_id)
|
|
89
|
+
if not start_entity:
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
# Find paths matching the pattern
|
|
93
|
+
paths = await self._find_matching_paths(start_entity, pattern, max_matches)
|
|
94
|
+
|
|
95
|
+
for path in paths:
|
|
96
|
+
match = PatternMatch(entities=path.nodes, relations=path.edges, score=1.0)
|
|
97
|
+
matches.append(match)
|
|
98
|
+
else:
|
|
99
|
+
# Find all entities matching the pattern
|
|
100
|
+
# This is more expensive - iterate through all entities
|
|
101
|
+
all_entities = await self._get_all_entities(pattern.entity_types)
|
|
102
|
+
|
|
103
|
+
for entity in all_entities[:max_matches]:
|
|
104
|
+
paths = await self._find_matching_paths(entity, pattern, max_matches=1)
|
|
105
|
+
|
|
106
|
+
if paths:
|
|
107
|
+
match = PatternMatch(
|
|
108
|
+
entities=paths[0].nodes,
|
|
109
|
+
relations=paths[0].edges,
|
|
110
|
+
score=1.0,
|
|
111
|
+
)
|
|
112
|
+
matches.append(match)
|
|
113
|
+
|
|
114
|
+
if len(matches) >= max_matches:
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
return matches
|
|
118
|
+
|
|
119
|
+
async def match_multiple_patterns(
|
|
120
|
+
self,
|
|
121
|
+
patterns: List[PathPattern],
|
|
122
|
+
start_entity_id: Optional[str] = None,
|
|
123
|
+
max_matches: int = 100,
|
|
124
|
+
) -> List[PatternMatch]:
|
|
125
|
+
"""
|
|
126
|
+
Match multiple patterns (AND semantics)
|
|
127
|
+
|
|
128
|
+
All patterns must match for a result to be included.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
patterns: List of patterns to match
|
|
132
|
+
start_entity_id: Optional starting entity ID
|
|
133
|
+
max_matches: Maximum number of matches to return
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of pattern matches where all patterns matched
|
|
137
|
+
"""
|
|
138
|
+
if not patterns:
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
# Match first pattern
|
|
142
|
+
first_matches = await self.match_pattern(patterns[0], start_entity_id, max_matches)
|
|
143
|
+
|
|
144
|
+
if len(patterns) == 1:
|
|
145
|
+
return first_matches
|
|
146
|
+
|
|
147
|
+
# Filter matches that also match remaining patterns
|
|
148
|
+
combined_matches = []
|
|
149
|
+
|
|
150
|
+
for match in first_matches:
|
|
151
|
+
# Check if remaining patterns match
|
|
152
|
+
all_match = True
|
|
153
|
+
combined_entities = list(match.entities)
|
|
154
|
+
combined_relations = list(match.relations)
|
|
155
|
+
|
|
156
|
+
for pattern in patterns[1:]:
|
|
157
|
+
# Try to match pattern starting from any entity in current
|
|
158
|
+
# match
|
|
159
|
+
pattern_matched = False
|
|
160
|
+
|
|
161
|
+
for entity in match.entities:
|
|
162
|
+
sub_matches = await self.match_pattern(pattern, entity.id, max_matches=1)
|
|
163
|
+
|
|
164
|
+
if sub_matches:
|
|
165
|
+
# Add new entities and relations
|
|
166
|
+
for sub_match in sub_matches:
|
|
167
|
+
combined_entities.extend(sub_match.entities)
|
|
168
|
+
combined_relations.extend(sub_match.relations)
|
|
169
|
+
pattern_matched = True
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
if not pattern_matched:
|
|
173
|
+
all_match = False
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
if all_match:
|
|
177
|
+
combined_match = PatternMatch(
|
|
178
|
+
entities=combined_entities,
|
|
179
|
+
relations=combined_relations,
|
|
180
|
+
score=match.score,
|
|
181
|
+
)
|
|
182
|
+
combined_matches.append(combined_match)
|
|
183
|
+
|
|
184
|
+
return combined_matches[:max_matches]
|
|
185
|
+
|
|
186
|
+
async def match_optional_patterns(
|
|
187
|
+
self,
|
|
188
|
+
required_patterns: List[PathPattern],
|
|
189
|
+
optional_patterns: List[PathPattern],
|
|
190
|
+
start_entity_id: Optional[str] = None,
|
|
191
|
+
max_matches: int = 100,
|
|
192
|
+
) -> List[PatternMatch]:
|
|
193
|
+
"""
|
|
194
|
+
Match required patterns with optional patterns
|
|
195
|
+
|
|
196
|
+
Required patterns must match. Optional patterns are included if they match.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
required_patterns: Patterns that must match
|
|
200
|
+
optional_patterns: Patterns that may or may not match
|
|
201
|
+
start_entity_id: Optional starting entity ID
|
|
202
|
+
max_matches: Maximum number of matches to return
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
List of pattern matches
|
|
206
|
+
"""
|
|
207
|
+
# Match required patterns first
|
|
208
|
+
required_matches = await self.match_multiple_patterns(required_patterns, start_entity_id, max_matches)
|
|
209
|
+
|
|
210
|
+
if not optional_patterns:
|
|
211
|
+
return required_matches
|
|
212
|
+
|
|
213
|
+
# Try to extend with optional patterns
|
|
214
|
+
extended_matches = []
|
|
215
|
+
|
|
216
|
+
for match in required_matches:
|
|
217
|
+
combined_entities = list(match.entities)
|
|
218
|
+
combined_relations = list(match.relations)
|
|
219
|
+
|
|
220
|
+
# Try to match each optional pattern
|
|
221
|
+
for pattern in optional_patterns:
|
|
222
|
+
for entity in match.entities:
|
|
223
|
+
sub_matches = await self.match_pattern(pattern, entity.id, max_matches=1)
|
|
224
|
+
|
|
225
|
+
if sub_matches:
|
|
226
|
+
# Add optional entities and relations
|
|
227
|
+
for sub_match in sub_matches:
|
|
228
|
+
combined_entities.extend(sub_match.entities)
|
|
229
|
+
combined_relations.extend(sub_match.relations)
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
extended_match = PatternMatch(
|
|
233
|
+
entities=combined_entities,
|
|
234
|
+
relations=combined_relations,
|
|
235
|
+
score=match.score,
|
|
236
|
+
)
|
|
237
|
+
extended_matches.append(extended_match)
|
|
238
|
+
|
|
239
|
+
return extended_matches
|
|
240
|
+
|
|
241
|
+
async def _find_matching_paths(
|
|
242
|
+
self,
|
|
243
|
+
start_entity: Entity,
|
|
244
|
+
pattern: PathPattern,
|
|
245
|
+
max_matches: int = 100,
|
|
246
|
+
) -> List[Path]:
|
|
247
|
+
"""
|
|
248
|
+
Find paths matching a pattern starting from an entity
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
start_entity: Starting entity
|
|
252
|
+
pattern: Pattern to match
|
|
253
|
+
max_matches: Maximum number of paths to return
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
List of matching paths
|
|
257
|
+
"""
|
|
258
|
+
# Use graph store's traverse method with pattern constraints
|
|
259
|
+
paths = await self.graph_store.traverse(
|
|
260
|
+
start_entity.id,
|
|
261
|
+
relation_type=(pattern.relation_types[0] if pattern.relation_types else None),
|
|
262
|
+
max_depth=pattern.max_depth,
|
|
263
|
+
max_results=max_matches,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Filter paths based on pattern constraints
|
|
267
|
+
matching_paths = []
|
|
268
|
+
|
|
269
|
+
for path in paths:
|
|
270
|
+
if self._path_matches_pattern(path, pattern):
|
|
271
|
+
matching_paths.append(path)
|
|
272
|
+
|
|
273
|
+
return matching_paths
|
|
274
|
+
|
|
275
|
+
def _path_matches_pattern(self, path: Path, pattern: PathPattern) -> bool:
|
|
276
|
+
"""
|
|
277
|
+
Check if a path matches a pattern
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
path: Path to check
|
|
281
|
+
pattern: Pattern to match against
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True if path matches pattern
|
|
285
|
+
"""
|
|
286
|
+
# Check path length
|
|
287
|
+
if len(path.edges) < pattern.min_path_length:
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
if len(path.edges) > pattern.max_depth:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
# Check entity types
|
|
294
|
+
if pattern.entity_types:
|
|
295
|
+
for entity in path.nodes:
|
|
296
|
+
if entity.entity_type not in pattern.entity_types:
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
# Check relation types
|
|
300
|
+
if pattern.relation_types:
|
|
301
|
+
for relation in path.edges:
|
|
302
|
+
if relation.relation_type not in pattern.relation_types:
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
# Check required relation sequence
|
|
306
|
+
if pattern.required_relation_sequence:
|
|
307
|
+
if len(path.edges) != len(pattern.required_relation_sequence):
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
for i, relation in enumerate(path.edges):
|
|
311
|
+
if relation.relation_type != pattern.required_relation_sequence[i]:
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
# Check cycles
|
|
315
|
+
if not pattern.allow_cycles:
|
|
316
|
+
entity_ids = [entity.id for entity in path.nodes]
|
|
317
|
+
if len(entity_ids) != len(set(entity_ids)):
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
# Check excluded entities
|
|
321
|
+
if pattern.excluded_entity_ids:
|
|
322
|
+
for entity in path.nodes:
|
|
323
|
+
if entity.id in pattern.excluded_entity_ids:
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
async def _get_all_entities(self, entity_types: Optional[List[str]] = None) -> List[Entity]:
|
|
329
|
+
"""
|
|
330
|
+
Get all entities, optionally filtered by type
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
entity_types: Optional list of entity types to filter by
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
List of entities
|
|
337
|
+
"""
|
|
338
|
+
# This is a placeholder - actual implementation depends on graph store
|
|
339
|
+
# For now, we'll return empty list and rely on start_entity_id
|
|
340
|
+
# In a real implementation, this would query the graph store for all
|
|
341
|
+
# entities
|
|
342
|
+
return []
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Query Executor
|
|
3
|
+
|
|
4
|
+
Executes custom queries with pattern matching, projection, and aggregation.
|
|
5
|
+
|
|
6
|
+
Phase: 3.3 - Full Custom Query Execution
|
|
7
|
+
Version: 1.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import List, Dict, Any, Optional
|
|
11
|
+
from aiecs.domain.knowledge_graph.models.query import GraphQuery
|
|
12
|
+
from aiecs.infrastructure.graph_storage.base import GraphStore
|
|
13
|
+
from aiecs.application.knowledge_graph.pattern_matching.pattern_matcher import (
|
|
14
|
+
PatternMatcher,
|
|
15
|
+
PatternMatch,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CustomQueryExecutor:
|
|
20
|
+
"""
|
|
21
|
+
Custom Query Executor
|
|
22
|
+
|
|
23
|
+
Executes custom queries with:
|
|
24
|
+
- Pattern matching
|
|
25
|
+
- Result projection
|
|
26
|
+
- Aggregation
|
|
27
|
+
- Grouping
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, graph_store: GraphStore):
|
|
31
|
+
"""
|
|
32
|
+
Initialize custom query executor
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
graph_store: Graph storage backend
|
|
36
|
+
"""
|
|
37
|
+
self.graph_store = graph_store
|
|
38
|
+
self.pattern_matcher = PatternMatcher(graph_store)
|
|
39
|
+
|
|
40
|
+
async def execute(self, query: GraphQuery, start_entity_id: Optional[str] = None) -> Dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Execute a custom query
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
query: Graph query with custom patterns
|
|
46
|
+
start_entity_id: Optional starting entity ID
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Query results with matches, projections, and aggregations
|
|
50
|
+
"""
|
|
51
|
+
# Execute pattern matching
|
|
52
|
+
matches = await self._execute_pattern_matching(query, start_entity_id)
|
|
53
|
+
|
|
54
|
+
# Apply projection if specified
|
|
55
|
+
if query.projection:
|
|
56
|
+
projected_results = self._apply_projection(matches, query.projection)
|
|
57
|
+
else:
|
|
58
|
+
projected_results = [self._match_to_dict(match) for match in matches]
|
|
59
|
+
|
|
60
|
+
# Apply aggregation if specified
|
|
61
|
+
if query.aggregations:
|
|
62
|
+
aggregated_results = self._apply_aggregation(projected_results, query.aggregations, query.group_by)
|
|
63
|
+
return {
|
|
64
|
+
"matches": len(matches),
|
|
65
|
+
"results": aggregated_results,
|
|
66
|
+
"aggregated": True,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
"matches": len(matches),
|
|
71
|
+
"results": projected_results,
|
|
72
|
+
"aggregated": False,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async def _execute_pattern_matching(self, query: GraphQuery, start_entity_id: Optional[str]) -> List[PatternMatch]:
|
|
76
|
+
"""
|
|
77
|
+
Execute pattern matching based on query
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
query: Graph query
|
|
81
|
+
start_entity_id: Optional starting entity ID
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of pattern matches
|
|
85
|
+
"""
|
|
86
|
+
max_matches = query.max_results or 100
|
|
87
|
+
|
|
88
|
+
# Single pattern
|
|
89
|
+
if query.pattern:
|
|
90
|
+
return await self.pattern_matcher.match_pattern(query.pattern, start_entity_id, max_matches)
|
|
91
|
+
|
|
92
|
+
# Multiple patterns (required)
|
|
93
|
+
if query.patterns:
|
|
94
|
+
if query.optional_patterns:
|
|
95
|
+
# Required + optional patterns
|
|
96
|
+
return await self.pattern_matcher.match_optional_patterns(
|
|
97
|
+
query.patterns,
|
|
98
|
+
query.optional_patterns,
|
|
99
|
+
start_entity_id,
|
|
100
|
+
max_matches,
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
# Only required patterns
|
|
104
|
+
return await self.pattern_matcher.match_multiple_patterns(query.patterns, start_entity_id, max_matches)
|
|
105
|
+
|
|
106
|
+
# No patterns specified
|
|
107
|
+
return []
|
|
108
|
+
|
|
109
|
+
def _match_to_dict(self, match: PatternMatch) -> Dict[str, Any]:
|
|
110
|
+
"""
|
|
111
|
+
Convert pattern match to dictionary
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
match: Pattern match
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Dictionary representation
|
|
118
|
+
"""
|
|
119
|
+
return {
|
|
120
|
+
"entities": [
|
|
121
|
+
{
|
|
122
|
+
"id": entity.id,
|
|
123
|
+
"type": entity.entity_type,
|
|
124
|
+
"properties": entity.properties,
|
|
125
|
+
}
|
|
126
|
+
for entity in match.entities
|
|
127
|
+
],
|
|
128
|
+
"relations": [
|
|
129
|
+
{
|
|
130
|
+
"id": relation.id,
|
|
131
|
+
"type": relation.relation_type,
|
|
132
|
+
"source": relation.source_id,
|
|
133
|
+
"target": relation.target_id,
|
|
134
|
+
"properties": relation.properties,
|
|
135
|
+
}
|
|
136
|
+
for relation in match.relations
|
|
137
|
+
],
|
|
138
|
+
"score": match.score,
|
|
139
|
+
"bindings": match.bindings,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
def _apply_projection(self, matches: List[PatternMatch], projection: List[str]) -> List[Dict[str, Any]]:
|
|
143
|
+
"""
|
|
144
|
+
Apply projection to matches
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
matches: Pattern matches
|
|
148
|
+
projection: Fields to project
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Projected results
|
|
152
|
+
"""
|
|
153
|
+
projected = []
|
|
154
|
+
|
|
155
|
+
for match in matches:
|
|
156
|
+
result = {}
|
|
157
|
+
|
|
158
|
+
for field in projection:
|
|
159
|
+
value = self._extract_field(match, field)
|
|
160
|
+
result[field] = value
|
|
161
|
+
|
|
162
|
+
projected.append(result)
|
|
163
|
+
|
|
164
|
+
return projected
|
|
165
|
+
|
|
166
|
+
def _extract_field(self, match: PatternMatch, field: str) -> Any:
|
|
167
|
+
"""
|
|
168
|
+
Extract a field value from a match
|
|
169
|
+
|
|
170
|
+
Supports dot notation for nested fields:
|
|
171
|
+
- "id" -> first entity's ID
|
|
172
|
+
- "entities[0].name" -> first entity's name
|
|
173
|
+
- "entities[0].properties.age" -> first entity's age property
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
match: Pattern match
|
|
177
|
+
field: Field path to extract
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Field value
|
|
181
|
+
"""
|
|
182
|
+
# Handle simple fields
|
|
183
|
+
if field == "score":
|
|
184
|
+
return match.score
|
|
185
|
+
|
|
186
|
+
if field == "entity_count":
|
|
187
|
+
return len(match.entities)
|
|
188
|
+
|
|
189
|
+
if field == "relation_count":
|
|
190
|
+
return len(match.relations)
|
|
191
|
+
|
|
192
|
+
# Handle entity fields
|
|
193
|
+
if field.startswith("entities"):
|
|
194
|
+
# Parse entities[0].name or entities[0].properties.age
|
|
195
|
+
parts = field.split(".")
|
|
196
|
+
|
|
197
|
+
# Extract index
|
|
198
|
+
if "[" in parts[0]:
|
|
199
|
+
index_str = parts[0].split("[")[1].split("]")[0]
|
|
200
|
+
index = int(index_str)
|
|
201
|
+
|
|
202
|
+
if index >= len(match.entities):
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
entity = match.entities[index]
|
|
206
|
+
|
|
207
|
+
# Extract nested field
|
|
208
|
+
if len(parts) == 1:
|
|
209
|
+
return {
|
|
210
|
+
"id": entity.id,
|
|
211
|
+
"type": entity.entity_type,
|
|
212
|
+
"properties": entity.properties,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if parts[1] == "id":
|
|
216
|
+
return entity.id
|
|
217
|
+
elif parts[1] == "type":
|
|
218
|
+
return entity.entity_type
|
|
219
|
+
elif parts[1] == "properties" and len(parts) > 2:
|
|
220
|
+
return entity.properties.get(parts[2])
|
|
221
|
+
elif parts[1] == "properties":
|
|
222
|
+
return entity.properties
|
|
223
|
+
|
|
224
|
+
# Handle relation fields
|
|
225
|
+
if field.startswith("relations"):
|
|
226
|
+
parts = field.split(".")
|
|
227
|
+
|
|
228
|
+
if "[" in parts[0]:
|
|
229
|
+
index_str = parts[0].split("[")[1].split("]")[0]
|
|
230
|
+
index = int(index_str)
|
|
231
|
+
|
|
232
|
+
if index >= len(match.relations):
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
relation = match.relations[index]
|
|
236
|
+
|
|
237
|
+
if len(parts) == 1:
|
|
238
|
+
return {
|
|
239
|
+
"id": relation.id,
|
|
240
|
+
"type": relation.relation_type,
|
|
241
|
+
"source": relation.source_id,
|
|
242
|
+
"target": relation.target_id,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if parts[1] == "id":
|
|
246
|
+
return relation.id
|
|
247
|
+
elif parts[1] == "type":
|
|
248
|
+
return relation.relation_type
|
|
249
|
+
elif parts[1] == "source":
|
|
250
|
+
return relation.source_id
|
|
251
|
+
elif parts[1] == "target":
|
|
252
|
+
return relation.target_id
|
|
253
|
+
elif parts[1] == "properties" and len(parts) > 2:
|
|
254
|
+
return relation.properties.get(parts[2])
|
|
255
|
+
|
|
256
|
+
# Handle bindings
|
|
257
|
+
if field.startswith("bindings."):
|
|
258
|
+
binding_name = field.split(".")[1]
|
|
259
|
+
return match.bindings.get(binding_name)
|
|
260
|
+
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
def _apply_aggregation(
|
|
264
|
+
self,
|
|
265
|
+
results: List[Dict[str, Any]],
|
|
266
|
+
aggregations: Dict[str, str],
|
|
267
|
+
group_by: Optional[List[str]] = None,
|
|
268
|
+
) -> List[Dict[str, Any]]:
|
|
269
|
+
"""
|
|
270
|
+
Apply aggregations to results
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
results: Projected results
|
|
274
|
+
aggregations: Aggregation functions (e.g., {"count": "COUNT", "avg_age": "AVG(age)"})
|
|
275
|
+
group_by: Optional fields to group by
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Aggregated results
|
|
279
|
+
"""
|
|
280
|
+
if group_by:
|
|
281
|
+
# Group results
|
|
282
|
+
groups: Dict[Any, List[Dict[str, Any]]] = {}
|
|
283
|
+
|
|
284
|
+
for result in results:
|
|
285
|
+
# Create group key
|
|
286
|
+
key_parts = []
|
|
287
|
+
for field in group_by:
|
|
288
|
+
key_parts.append(str(result.get(field, "")))
|
|
289
|
+
key = tuple(key_parts)
|
|
290
|
+
|
|
291
|
+
if key not in groups:
|
|
292
|
+
groups[key] = []
|
|
293
|
+
groups[key].append(result)
|
|
294
|
+
|
|
295
|
+
# Aggregate each group
|
|
296
|
+
aggregated = []
|
|
297
|
+
|
|
298
|
+
for key, group_results in groups.items():
|
|
299
|
+
agg_result = {}
|
|
300
|
+
|
|
301
|
+
# Add group by fields
|
|
302
|
+
for i, field in enumerate(group_by):
|
|
303
|
+
agg_result[field] = key[i]
|
|
304
|
+
|
|
305
|
+
# Apply aggregations
|
|
306
|
+
for agg_name, agg_func in aggregations.items():
|
|
307
|
+
agg_result[agg_name] = self._compute_aggregation(group_results, agg_func)
|
|
308
|
+
|
|
309
|
+
aggregated.append(agg_result)
|
|
310
|
+
|
|
311
|
+
return aggregated
|
|
312
|
+
else:
|
|
313
|
+
# Aggregate all results
|
|
314
|
+
agg_result = {}
|
|
315
|
+
|
|
316
|
+
for agg_name, agg_func in aggregations.items():
|
|
317
|
+
agg_result[agg_name] = self._compute_aggregation(results, agg_func)
|
|
318
|
+
|
|
319
|
+
return [agg_result]
|
|
320
|
+
|
|
321
|
+
def _compute_aggregation(self, results: List[Dict[str, Any]], agg_func: str) -> Any:
|
|
322
|
+
"""
|
|
323
|
+
Compute an aggregation function
|
|
324
|
+
|
|
325
|
+
Supports:
|
|
326
|
+
- COUNT: Count of results
|
|
327
|
+
- SUM(field): Sum of field values
|
|
328
|
+
- AVG(field): Average of field values
|
|
329
|
+
- MIN(field): Minimum field value
|
|
330
|
+
- MAX(field): Maximum field value
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
results: Results to aggregate
|
|
334
|
+
agg_func: Aggregation function string
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Aggregated value
|
|
338
|
+
"""
|
|
339
|
+
if agg_func == "COUNT":
|
|
340
|
+
return len(results)
|
|
341
|
+
|
|
342
|
+
# Parse function and field
|
|
343
|
+
if "(" in agg_func:
|
|
344
|
+
func_name = agg_func.split("(")[0]
|
|
345
|
+
field = agg_func.split("(")[1].split(")")[0]
|
|
346
|
+
|
|
347
|
+
# Extract field values
|
|
348
|
+
values = []
|
|
349
|
+
for result in results:
|
|
350
|
+
value = result.get(field)
|
|
351
|
+
if value is not None and isinstance(value, (int, float)):
|
|
352
|
+
values.append(value)
|
|
353
|
+
|
|
354
|
+
if not values:
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
if func_name == "SUM":
|
|
358
|
+
return sum(values)
|
|
359
|
+
elif func_name == "AVG":
|
|
360
|
+
return sum(values) / len(values)
|
|
361
|
+
elif func_name == "MIN":
|
|
362
|
+
return min(values)
|
|
363
|
+
elif func_name == "MAX":
|
|
364
|
+
return max(values)
|
|
365
|
+
|
|
366
|
+
return None
|