contextsynapse 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- contextsynapse/__init__.py +107 -0
- contextsynapse/__main__.py +9 -0
- contextsynapse/a2a/__init__.py +28 -0
- contextsynapse/a2a/client.py +120 -0
- contextsynapse/a2a/discovery.py +128 -0
- contextsynapse/a2a/handlers.py +164 -0
- contextsynapse/a2a/models.py +246 -0
- contextsynapse/a2a/streaming.py +92 -0
- contextsynapse/a2a/task_manager.py +275 -0
- contextsynapse/adapters/__init__.py +98 -0
- contextsynapse/adapters/_base.py +298 -0
- contextsynapse/adapters/autogen/__init__.py +4 -0
- contextsynapse/adapters/autogen/tools.py +34 -0
- contextsynapse/adapters/crewai/__init__.py +4 -0
- contextsynapse/adapters/crewai/tools.py +63 -0
- contextsynapse/adapters/langchain/__init__.py +19 -0
- contextsynapse/adapters/langchain/message_history.py +17 -0
- contextsynapse/adapters/langchain/retriever.py +158 -0
- contextsynapse/adapters/langchain/tools.py +43 -0
- contextsynapse/adapters/langgraph/__init__.py +21 -0
- contextsynapse/adapters/langgraph/checkpoint.py +223 -0
- contextsynapse/adapters/langgraph/context_tools.py +341 -0
- contextsynapse/adapters/langgraph/message_history.py +138 -0
- contextsynapse/adapters/langgraph/tools.py +43 -0
- contextsynapse/adapters/llamaindex/__init__.py +4 -0
- contextsynapse/adapters/llamaindex/tools.py +64 -0
- contextsynapse/adapters/openai/__init__.py +24 -0
- contextsynapse/adapters/openai/tools.py +206 -0
- contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
- contextsynapse/adapters/pydantic_ai/tools.py +36 -0
- contextsynapse/adapters/swarm/__init__.py +4 -0
- contextsynapse/adapters/swarm/tools.py +44 -0
- contextsynapse/agents/__init__.py +6 -0
- contextsynapse/agents/auto_capture.py +80 -0
- contextsynapse/agents/dispatcher.py +299 -0
- contextsynapse/agents/notifications.py +112 -0
- contextsynapse/aiql/__init__.py +71 -0
- contextsynapse/aiql/cache.py +341 -0
- contextsynapse/aiql/compiler/__init__.py +62 -0
- contextsynapse/aiql/compiler/optimizer.py +126 -0
- contextsynapse/aiql/compiler/planner.py +366 -0
- contextsynapse/aiql/compiler/statistics.py +235 -0
- contextsynapse/aiql/compiler/validator.py +425 -0
- contextsynapse/aiql/engine/__init__.py +11 -0
- contextsynapse/aiql/engine/chunk_storage.py +2584 -0
- contextsynapse/aiql/engine/executor.py +7737 -0
- contextsynapse/aiql/engine/extraction_integration.py +442 -0
- contextsynapse/aiql/engine/graph_builder.py +405 -0
- contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
- contextsynapse/aiql/engine/performance_monitor.py +289 -0
- contextsynapse/aiql/engine/postprocessing.py +365 -0
- contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
- contextsynapse/aiql/engine/redis_cache.py +106 -0
- contextsynapse/aiql/engine/streaming_json.py +366 -0
- contextsynapse/aiql/engine/streaming_parquet.py +568 -0
- contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
- contextsynapse/aiql/grammar/__init__.py +14 -0
- contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
- contextsynapse/aiql/grammar/base.py +71 -0
- contextsynapse/aiql/parser/__init__.py +46 -0
- contextsynapse/aiql/parser/aiql_parser.py +11330 -0
- contextsynapse/aiql/parser/base_parser.py +47 -0
- contextsynapse/api/__init__.py +5 -0
- contextsynapse/api/a2a_router.py +233 -0
- contextsynapse/api/admin_router.py +140 -0
- contextsynapse/api/agent_worker_router.py +978 -0
- contextsynapse/api/algorithms_router.py +317 -0
- contextsynapse/api/api.py +4144 -0
- contextsynapse/api/audit.py +204 -0
- contextsynapse/api/auth.py +441 -0
- contextsynapse/api/auth_router.py +537 -0
- contextsynapse/api/billing.py +261 -0
- contextsynapse/api/billing_router.py +275 -0
- contextsynapse/api/boundary_router.py +452 -0
- contextsynapse/api/cognition_router.py +116 -0
- contextsynapse/api/connector_router.py +237 -0
- contextsynapse/api/context_router.py +1619 -0
- contextsynapse/api/dashboard_router.py +11367 -0
- contextsynapse/api/errors.py +183 -0
- contextsynapse/api/events.py +88 -0
- contextsynapse/api/experiment_bootstrap.py +321 -0
- contextsynapse/api/experiment_orchestrator.py +1078 -0
- contextsynapse/api/experiment_router.py +2089 -0
- contextsynapse/api/feature_gate.py +89 -0
- contextsynapse/api/federation_router.py +108 -0
- contextsynapse/api/integrations.py +567 -0
- contextsynapse/api/integrations_router.py +461 -0
- contextsynapse/api/intelligence_router.py +1028 -0
- contextsynapse/api/metering.py +155 -0
- contextsynapse/api/metrics.py +112 -0
- contextsynapse/api/models.py +38 -0
- contextsynapse/api/monitoring_router.py +174 -0
- contextsynapse/api/pipeline_router.py +1437 -0
- contextsynapse/api/playground_conn.py +116 -0
- contextsynapse/api/projects_router.py +131 -0
- contextsynapse/api/queries_router.py +282 -0
- contextsynapse/api/quota.py +95 -0
- contextsynapse/api/rate_limit.py +64 -0
- contextsynapse/api/search_router.py +261 -0
- contextsynapse/api/shield_router.py +447 -0
- contextsynapse/api/tenant_guard.py +103 -0
- contextsynapse/api/tenant_quotas.py +231 -0
- contextsynapse/api/tenants.py +305 -0
- contextsynapse/api/users.py +646 -0
- contextsynapse/api/v1.py +167 -0
- contextsynapse/api/vertical_builder_router.py +387 -0
- contextsynapse/benchmark/__init__.py +15 -0
- contextsynapse/benchmark/__main__.py +64 -0
- contextsynapse/benchmark/agents.py +338 -0
- contextsynapse/benchmark/live_ab.py +338 -0
- contextsynapse/benchmark/reporter.py +149 -0
- contextsynapse/benchmark/runner.py +264 -0
- contextsynapse/benchmark/savings.py +266 -0
- contextsynapse/benchmark/token_counter.py +212 -0
- contextsynapse/benchmark/workloads.py +289 -0
- contextsynapse/buffer/__init__.py +154 -0
- contextsynapse/buffer/base.py +257 -0
- contextsynapse/buffer/duckdb_buffer.py +246 -0
- contextsynapse/buffer/file_buffer.py +229 -0
- contextsynapse/buffer/lmdb_buffer.py +286 -0
- contextsynapse/buffer/local_buffer.py +154 -0
- contextsynapse/buffer/redis_buffer.py +211 -0
- contextsynapse/buffer/sqlite_buffer.py +266 -0
- contextsynapse/cache.py +2 -0
- contextsynapse/cli/__init__.py +40 -0
- contextsynapse/cli/cli.py +475 -0
- contextsynapse/cli/config_cli.py +177 -0
- contextsynapse/cli/scaffold.py +465 -0
- contextsynapse/cognition/__init__.py +31 -0
- contextsynapse/cognition/derivation.py +55 -0
- contextsynapse/cognition/events.py +135 -0
- contextsynapse/cognition/feedback.py +52 -0
- contextsynapse/cognition/hallucination.py +90 -0
- contextsynapse/cognition/invalidation.py +70 -0
- contextsynapse/cognition/read_tracker.py +50 -0
- contextsynapse/collaboration.py +2 -0
- contextsynapse/comparison.py +2 -0
- contextsynapse/config/__init__.py +10 -0
- contextsynapse/config/connectors/amfi_nav.yaml +18 -0
- contextsynapse/config/connectors/auto_sector.yaml +20 -0
- contextsynapse/config/connectors/banking_sector.yaml +20 -0
- contextsynapse/config/connectors/energy_sector.yaml +20 -0
- contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
- contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
- contextsynapse/config/connectors/nse_price.yaml +61 -0
- contextsynapse/config/connectors/pharma_sector.yaml +20 -0
- contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
- contextsynapse/config/context_presets/ai_research.json +25 -0
- contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
- contextsynapse/config/context_presets/claude_conversations.json +19 -0
- contextsynapse/config/context_presets/legal_compliance.json +21 -0
- contextsynapse/config/context_presets/meeting_notes.json +21 -0
- contextsynapse/config/context_presets/slack_engineering.json +21 -0
- contextsynapse/config/context_presets/tech_climate.json +24 -0
- contextsynapse/config/context_presets/toi_politics.json +25 -0
- contextsynapse/config/database_config.py +453 -0
- contextsynapse/config/deployment.py +221 -0
- contextsynapse/config/gpt_action_schema.json +164 -0
- contextsynapse/config/indicators/india_macro.yaml +213 -0
- contextsynapse/config/logging_config.py +88 -0
- contextsynapse/config/mode_config.py +223 -0
- contextsynapse/config/schemas/api_spec.yaml +48 -0
- contextsynapse/config/schemas/architecture_doc.yaml +51 -0
- contextsynapse/config/schemas/chat_history.yaml +90 -0
- contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
- contextsynapse/config/schemas/claude_conversation.yaml +148 -0
- contextsynapse/config/schemas/contract.yaml +48 -0
- contextsynapse/config/schemas/conversation.yaml +167 -0
- contextsynapse/config/schemas/database.yaml +74 -0
- contextsynapse/config/schemas/decision.yaml +72 -0
- contextsynapse/config/schemas/ecommerce.yaml +56 -0
- contextsynapse/config/schemas/education.yaml +42 -0
- contextsynapse/config/schemas/healthcare.yaml +133 -0
- contextsynapse/config/schemas/hr_document.yaml +48 -0
- contextsynapse/config/schemas/invoice.yaml +40 -0
- contextsynapse/config/schemas/knowledge_base.yaml +75 -0
- contextsynapse/config/schemas/legal.yaml +42 -0
- contextsynapse/config/schemas/marketing_content.yaml +48 -0
- contextsynapse/config/schemas/meeting_notes.yaml +51 -0
- contextsynapse/config/schemas/news_article.yaml +61 -0
- contextsynapse/config/schemas/real_estate.yaml +49 -0
- contextsynapse/config/schemas/requirements_doc.yaml +48 -0
- contextsynapse/config/schemas/research_paper.yaml +56 -0
- contextsynapse/config/schemas/resume.yaml +49 -0
- contextsynapse/config/schemas/retail.yaml +42 -0
- contextsynapse/config/schemas/rule_meta.yaml +173 -0
- contextsynapse/config/schemas/rules.yaml +72 -0
- contextsynapse/config/schemas/screener_meta.yaml +236 -0
- contextsynapse/config/schemas/sdlc.yaml +103 -0
- contextsynapse/config/schemas/session_graph.yaml +172 -0
- contextsynapse/config/schemas/support_ticket.yaml +48 -0
- contextsynapse/config/schemas/web.yaml +64 -0
- contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
- contextsynapse/config/sensors/global_risk.yaml +53 -0
- contextsynapse/config/sensors/government_policy.yaml +54 -0
- contextsynapse/config/sensors/supply_chain.yaml +50 -0
- contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
- contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
- contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
- contextsynapse/config/sensors/templates/market_data.yaml +118 -0
- contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
- contextsynapse/config/sensors/weather.yaml +52 -0
- contextsynapse/config/storage_strategy.py +202 -0
- contextsynapse/config/templates/competitive_intel.yaml +39 -0
- contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
- contextsynapse/config/templates/market_analysis.yaml +58 -0
- contextsynapse/connectors/__init__.py +15 -0
- contextsynapse/connectors/api_router.py +121 -0
- contextsynapse/connectors/base.py +357 -0
- contextsynapse/connectors/financial/audio.py +170 -0
- contextsynapse/connectors/financial/youtube.py +272 -0
- contextsynapse/connectors/github.py +164 -0
- contextsynapse/connectors/jira.py +120 -0
- contextsynapse/connectors/kafka_connector.py +124 -0
- contextsynapse/connectors/pipeline_connector.py +747 -0
- contextsynapse/connectors/registry.py +116 -0
- contextsynapse/connectors/scheduler.py +87 -0
- contextsynapse/connectors/universal.py +360 -0
- contextsynapse/context/__init__.py +96 -0
- contextsynapse/context/__main__.py +4 -0
- contextsynapse/context/acl.py +125 -0
- contextsynapse/context/agent_card.py +201 -0
- contextsynapse/context/agent_memory.py +814 -0
- contextsynapse/context/agents.py +472 -0
- contextsynapse/context/agents_redis.py +391 -0
- contextsynapse/context/assembled.py +204 -0
- contextsynapse/context/attribution.py +113 -0
- contextsynapse/context/blob.py +184 -0
- contextsynapse/context/boundaries.py +187 -0
- contextsynapse/context/boundary.py +507 -0
- contextsynapse/context/bundle.py +155 -0
- contextsynapse/context/cli.py +519 -0
- contextsynapse/context/collaboration.py +165 -0
- contextsynapse/context/compiler.py +604 -0
- contextsynapse/context/composite.py +536 -0
- contextsynapse/context/compression.py +147 -0
- contextsynapse/context/context_manager.py +888 -0
- contextsynapse/context/context_schema.py +90 -0
- contextsynapse/context/context_schema.yaml +105 -0
- contextsynapse/context/context_units.py +891 -0
- contextsynapse/context/conversation.py +488 -0
- contextsynapse/context/dedup.py +207 -0
- contextsynapse/context/document_processor.py +698 -0
- contextsynapse/context/embedding_hooks.py +338 -0
- contextsynapse/context/execution_schema.py +274 -0
- contextsynapse/context/fan_out.py +138 -0
- contextsynapse/context/frozen.py +80 -0
- contextsynapse/context/gravity.py +227 -0
- contextsynapse/context/hub.py +1401 -0
- contextsynapse/context/ingest.py +391 -0
- contextsynapse/context/injection.py +226 -0
- contextsynapse/context/interaction_graphifier.py +562 -0
- contextsynapse/context/layers.py +574 -0
- contextsynapse/context/projection.py +1195 -0
- contextsynapse/context/promotion.py +634 -0
- contextsynapse/context/proof.py +131 -0
- contextsynapse/context/propagation.py +577 -0
- contextsynapse/context/pubsub.py +135 -0
- contextsynapse/context/quality.py +1054 -0
- contextsynapse/context/quality_tracker.py +270 -0
- contextsynapse/context/scoping.py +370 -0
- contextsynapse/context/seed.py +992 -0
- contextsynapse/context/session.py +1142 -0
- contextsynapse/context/session_graph.py +593 -0
- contextsynapse/context/session_memory.py +309 -0
- contextsynapse/context/session_resolver.py +111 -0
- contextsynapse/context/skill_learning.py +237 -0
- contextsynapse/context/smart_budget.py +129 -0
- contextsynapse/context/source_policy.py +242 -0
- contextsynapse/context/store_factory.py +53 -0
- contextsynapse/context/sync.py +648 -0
- contextsynapse/context/token.py +236 -0
- contextsynapse/context/vector_integration.py +297 -0
- contextsynapse/context/webhooks.py +194 -0
- contextsynapse/context/working_memory.py +224 -0
- contextsynapse/core/__init__.py +18 -0
- contextsynapse/core/archiver.py +237 -0
- contextsynapse/core/change_stream.py +282 -0
- contextsynapse/core/checkpoint.py +310 -0
- contextsynapse/core/cloud_storage.py +387 -0
- contextsynapse/core/context_state.py +651 -0
- contextsynapse/core/cron.py +324 -0
- contextsynapse/core/datasource.py +643 -0
- contextsynapse/core/db.py +289 -0
- contextsynapse/core/federation.py +283 -0
- contextsynapse/core/graph_coordinator.py +201 -0
- contextsynapse/core/graph_intelligence.py +388 -0
- contextsynapse/core/graph_structures.py +106 -0
- contextsynapse/core/graph_sync.py +157 -0
- contextsynapse/core/hybrid_graph_storage.py +2998 -0
- contextsynapse/core/lifecycle.py +691 -0
- contextsynapse/core/multiworker.py +378 -0
- contextsynapse/core/pruning.py +312 -0
- contextsynapse/core/redis_registry.py +524 -0
- contextsynapse/core/registry.py +1043 -0
- contextsynapse/core/registry_factory.py +51 -0
- contextsynapse/core/registry_metadata.py +294 -0
- contextsynapse/core/replay.py +354 -0
- contextsynapse/core/replication.py +339 -0
- contextsynapse/core/time_travel.py +260 -0
- contextsynapse/core/write_behind.py +152 -0
- contextsynapse/core/write_context.py +39 -0
- contextsynapse/db/__init__.py +1 -0
- contextsynapse/db/postgres.py +267 -0
- contextsynapse/db/profile.py +236 -0
- contextsynapse/db/rules.py +328 -0
- contextsynapse/demo/__init__.py +0 -0
- contextsynapse/demo/report.py +147 -0
- contextsynapse/demo/runner.py +520 -0
- contextsynapse/demo/scenario.py +165 -0
- contextsynapse/demo/session_demo.py +280 -0
- contextsynapse/demo/session_report.py +143 -0
- contextsynapse/demo/universal_runner.py +257 -0
- contextsynapse/engine/__init__.py +15 -0
- contextsynapse/engine/core.py +555 -0
- contextsynapse/extraction/__init__.py +69 -0
- contextsynapse/extraction/cli.py +274 -0
- contextsynapse/extraction/config.py +107 -0
- contextsynapse/extraction/examples/example_usage.py +118 -0
- contextsynapse/extraction/examples/sample_manifest.json +53 -0
- contextsynapse/extraction/examples/sample_normalized.json +185 -0
- contextsynapse/extraction/extract_engine.py +440 -0
- contextsynapse/extraction/extractors/__init__.py +37 -0
- contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
- contextsynapse/extraction/extractors/csv_extractor.py +83 -0
- contextsynapse/extraction/extractors/docx_extractor.py +129 -0
- contextsynapse/extraction/extractors/excel_extractor.py +152 -0
- contextsynapse/extraction/extractors/html_extractor.py +146 -0
- contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
- contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
- contextsynapse/extraction/extractors/register_extractors.py +63 -0
- contextsynapse/extraction/extractors/text_extractor.py +65 -0
- contextsynapse/extraction/extractors/txt_extractor.py +120 -0
- contextsynapse/extraction/extractors/website_extractor.py +246 -0
- contextsynapse/extraction/fact_extractor.py +236 -0
- contextsynapse/extraction/file_manager.py +353 -0
- contextsynapse/extraction/hierarchy.py +239 -0
- contextsynapse/extraction/id_generator.py +450 -0
- contextsynapse/extraction/layout_detector.py +409 -0
- contextsynapse/extraction/llm_entity_extractor.py +301 -0
- contextsynapse/extraction/normalized_store.py +480 -0
- contextsynapse/extraction/normalizer.py +594 -0
- contextsynapse/extraction/parquet_writer.py +598 -0
- contextsynapse/extraction/post_processors/__init__.py +40 -0
- contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
- contextsynapse/extraction/ray_normalization.py +233 -0
- contextsynapse/extraction/ray_runner.py +207 -0
- contextsynapse/extraction/redis_schema_registry.py +222 -0
- contextsynapse/extraction/registry.py +204 -0
- contextsynapse/extraction/schema_loader.py +663 -0
- contextsynapse/extraction/schema_registry.py +230 -0
- contextsynapse/extraction/semantic_normalizer.py +245 -0
- contextsynapse/extraction/version_manager.py +314 -0
- contextsynapse/gateway/__init__.py +24 -0
- contextsynapse/gateway/action_emitter.py +212 -0
- contextsynapse/gateway/cost_tracker.py +508 -0
- contextsynapse/gateway/gateway.py +377 -0
- contextsynapse/gateway/policy.py +303 -0
- contextsynapse/governance/__init__.py +13 -0
- contextsynapse/governance/layer.py +513 -0
- contextsynapse/ingestion/__init__.py +15 -0
- contextsynapse/ingestion/amplifier.py +288 -0
- contextsynapse/ingestion/async_ingest.py +463 -0
- contextsynapse/ingestion/chunker.py +303 -0
- contextsynapse/ingestion/chunking.py +58 -0
- contextsynapse/ingestion/cleaner.py +190 -0
- contextsynapse/ingestion/cleanse.py +252 -0
- contextsynapse/ingestion/connectors/__init__.py +38 -0
- contextsynapse/ingestion/connectors/base.py +300 -0
- contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
- contextsynapse/ingestion/connectors/claude_memory.py +274 -0
- contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
- contextsynapse/ingestion/content_detector.py +80 -0
- contextsynapse/ingestion/content_monitor.py +502 -0
- contextsynapse/ingestion/dedup.py +106 -0
- contextsynapse/ingestion/feed.py +640 -0
- contextsynapse/ingestion/filters.py +461 -0
- contextsynapse/ingestion/graph_builder.py +705 -0
- contextsynapse/ingestion/input_classifier.py +307 -0
- contextsynapse/ingestion/job_manager.py +571 -0
- contextsynapse/ingestion/llm_extractor.py +610 -0
- contextsynapse/ingestion/orchestrator.py +116 -0
- contextsynapse/ingestion/parsers/__init__.py +19 -0
- contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
- contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
- contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
- contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
- contextsynapse/ingestion/pipeline_context.py +228 -0
- contextsynapse/ingestion/pipeline_store.py +1195 -0
- contextsynapse/ingestion/queue.py +133 -0
- contextsynapse/ingestion/reprocess.py +179 -0
- contextsynapse/ingestion/scenario_router.py +224 -0
- contextsynapse/ingestion/schema_extractor.py +759 -0
- contextsynapse/ingestion/schema_validator.py +274 -0
- contextsynapse/ingestion/sdlc_ingest.py +87 -0
- contextsynapse/ingestion/smart_ingest.py +2451 -0
- contextsynapse/ingestion/source_connector.py +171 -0
- contextsynapse/ingestion/stage_executor.py +2095 -0
- contextsynapse/ingestion/stages/__init__.py +9 -0
- contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
- contextsynapse/ingestion/strategies.py +386 -0
- contextsynapse/ingestion/universal/__init__.py +4 -0
- contextsynapse/ingestion/universal/_operator_registry.py +59 -0
- contextsynapse/ingestion/universal/ingest_content.py +32 -0
- contextsynapse/ingestion/universal/operators/__init__.py +1 -0
- contextsynapse/ingestion/universal/operators/base.py +19 -0
- contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
- contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
- contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
- contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
- contextsynapse/ingestion/universal/operators/embed.py +80 -0
- contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
- contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
- contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
- contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
- contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
- contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
- contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
- contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
- contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
- contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
- contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
- contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
- contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
- contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
- contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
- contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
- contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
- contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
- contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
- contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
- contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
- contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
- contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
- contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
- contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
- contextsynapse/ingestion/universal/shared_signals.py +161 -0
- contextsynapse/ingestion/universal/stage_executor.py +219 -0
- contextsynapse/ingestion/web_crawler.py +265 -0
- contextsynapse/intelligence/__init__.py +53 -0
- contextsynapse/intelligence/alerts.py +198 -0
- contextsynapse/intelligence/collector.py +655 -0
- contextsynapse/intelligence/comparison.py +198 -0
- contextsynapse/intelligence/config.py +56 -0
- contextsynapse/intelligence/conflict_detector.py +182 -0
- contextsynapse/intelligence/context_gaps.py +192 -0
- contextsynapse/intelligence/context_radar.py +135 -0
- contextsynapse/intelligence/correlation_engine.py +416 -0
- contextsynapse/intelligence/correlation_template.py +120 -0
- contextsynapse/intelligence/dedup.py +296 -0
- contextsynapse/intelligence/event_bus.py +91 -0
- contextsynapse/intelligence/feedback_loop.py +101 -0
- contextsynapse/intelligence/freshness.py +307 -0
- contextsynapse/intelligence/fundamental_feed.py +551 -0
- contextsynapse/intelligence/geo_data.py +170 -0
- contextsynapse/intelligence/geo_reference.py +112 -0
- contextsynapse/intelligence/graph_fusion.py +465 -0
- contextsynapse/intelligence/heuristics.py +176 -0
- contextsynapse/intelligence/impact_tracker.py +336 -0
- contextsynapse/intelligence/macro_indicators.py +319 -0
- contextsynapse/intelligence/ocr.py +104 -0
- contextsynapse/intelligence/persistence.py +267 -0
- contextsynapse/intelligence/pipeline_health.py +130 -0
- contextsynapse/intelligence/price_feed.py +221 -0
- contextsynapse/intelligence/rate_limiter.py +130 -0
- contextsynapse/intelligence/reactive.py +411 -0
- contextsynapse/intelligence/runtime_context.py +390 -0
- contextsynapse/intelligence/sentiment_decay.py +426 -0
- contextsynapse/intelligence/session.py +277 -0
- contextsynapse/intelligence/signal_hierarchy.py +438 -0
- contextsynapse/intelligence/signals.py +185 -0
- contextsynapse/intelligence/source_watcher.py +132 -0
- contextsynapse/intelligence/table_enricher.py +256 -0
- contextsynapse/intelligence/tagger.py +319 -0
- contextsynapse/intelligence/timeseries.py +209 -0
- contextsynapse/intelligence/tracker.py +318 -0
- contextsynapse/intelligence/watchdog.py +392 -0
- contextsynapse/intelligence/watchdog_manager.py +263 -0
- contextsynapse/intelligence/webhook_receiver.py +165 -0
- contextsynapse/intelligence/weight_learner.py +234 -0
- contextsynapse/llm/__init__.py +8 -0
- contextsynapse/llm/client.py +348 -0
- contextsynapse/llm/rate_limiter.py +166 -0
- contextsynapse/logging_config.py +2 -0
- contextsynapse/marketplace/__init__.py +7 -0
- contextsynapse/marketplace/registry.py +173 -0
- contextsynapse/mcp/__init__.py +11 -0
- contextsynapse/mcp/__main__.py +4 -0
- contextsynapse/mcp/auth_middleware.py +212 -0
- contextsynapse/mcp/connection_pool.py +128 -0
- contextsynapse/mcp/server.py +986 -0
- contextsynapse/mcp/session_context.py +67 -0
- contextsynapse/memory/__init__.py +34 -0
- contextsynapse/memory/engine.py +475 -0
- contextsynapse/memory/temporal.py +327 -0
- contextsynapse/metadata/__init__.py +12 -0
- contextsynapse/metadata/metadata_db.py +272 -0
- contextsynapse/metadata/metadata_query_tool.py +300 -0
- contextsynapse/metadata/metadata_tracker.py +425 -0
- contextsynapse/models/__init__.py +27 -0
- contextsynapse/models/embedding_service.py +360 -0
- contextsynapse/models/model_registry.py +228 -0
- contextsynapse/multiworker.py +2 -0
- contextsynapse/pipelines/__init__.py +1 -0
- contextsynapse/pipelines/executor.py +851 -0
- contextsynapse/pipelines/filters.py +189 -0
- contextsynapse/pipelines/models.py +176 -0
- contextsynapse/pipelines/scheduler.py +268 -0
- contextsynapse/playground/__init__.py +1 -0
- contextsynapse/playground/templates.py +85 -0
- contextsynapse/plugins/__init__.py +43 -0
- contextsynapse/plugins/api.py +66 -0
- contextsynapse/plugins/base.py +350 -0
- contextsynapse/plugins/domain_interface.py +508 -0
- contextsynapse/plugins/domain_loader.py +241 -0
- contextsynapse/plugins/loader.py +148 -0
- contextsynapse/plugins/registry.py +265 -0
- contextsynapse/project/__init__.py +11 -0
- contextsynapse/project/code_context.py +807 -0
- contextsynapse/project/connectors/__init__.py +4 -0
- contextsynapse/project/connectors/github_connector.py +113 -0
- contextsynapse/project/connectors/jira_connector.py +160 -0
- contextsynapse/project/graph.py +1023 -0
- contextsynapse/project/project_context.py +545 -0
- contextsynapse/project/scanner_registry.py +61 -0
- contextsynapse/project/schema_manager.py +322 -0
- contextsynapse/project/schemas/knowledge.yaml +59 -0
- contextsynapse/project/schemas/sdlc.yaml +369 -0
- contextsynapse/project/sdlc_schema.py +172 -0
- contextsynapse/project/spec_parser.py +85 -0
- contextsynapse/project/task_lock.py +181 -0
- contextsynapse/project/task_stream.py +203 -0
- contextsynapse/project/team_tools.py +537 -0
- contextsynapse/py.typed +0 -0
- contextsynapse/realtime/__init__.py +6 -0
- contextsynapse/realtime/collaboration.py +147 -0
- contextsynapse/rules/__init__.py +10 -0
- contextsynapse/rules/engine.py +116 -0
- contextsynapse/rules/evaluators/__init__.py +6 -0
- contextsynapse/rules/evaluators/aggregate.py +205 -0
- contextsynapse/rules/evaluators/custom.py +170 -0
- contextsynapse/rules/evaluators/pre_action.py +125 -0
- contextsynapse/rules/evaluators/temporal.py +129 -0
- contextsynapse/rules/loader.py +52 -0
- contextsynapse/rules/models.py +124 -0
- contextsynapse/rules/universal.py +393 -0
- contextsynapse/scheduler.py +229 -0
- contextsynapse/schema/__init__.py +42 -0
- contextsynapse/schema/compiler.py +252 -0
- contextsynapse/schema/composer.py +158 -0
- contextsynapse/schema/dedup.py +73 -0
- contextsynapse/schema/derivation.py +172 -0
- contextsynapse/schema/sdl.py +326 -0
- contextsynapse/schema/validation_gate.py +124 -0
- contextsynapse/sdk/__init__.py +63 -0
- contextsynapse/sdk/agent.py +214 -0
- contextsynapse/sdk/client.py +269 -0
- contextsynapse/sdk/exceptions.py +23 -0
- contextsynapse/sdk/models.py +78 -0
- contextsynapse/sdk/session.py +408 -0
- contextsynapse/sdk/tracked.py +218 -0
- contextsynapse/sdk/worker.py +266 -0
- contextsynapse/sdk/workspace_client.py +98 -0
- contextsynapse/sdk/ws.py +41 -0
- contextsynapse/search/__init__.py +20 -0
- contextsynapse/search/embedding_cache.py +140 -0
- contextsynapse/search/enhanced_search.py +152 -0
- contextsynapse/search/fulltext.py +130 -0
- contextsynapse/search/graph_search.py +634 -0
- contextsynapse/search/lmdb_index.py +1218 -0
- contextsynapse/search/nl_to_aiql.py +199 -0
- contextsynapse/search/rag.py +1346 -0
- contextsynapse/search/rag_cache.py +167 -0
- contextsynapse/search/reasoning_chain.py +231 -0
- contextsynapse/search/redis_search.py +207 -0
- contextsynapse/search/retrieval_quality.py +314 -0
- contextsynapse/search/semantic_query.py +455 -0
- contextsynapse/search/text_resolver.py +159 -0
- contextsynapse/search/whoosh_search.py +373 -0
- contextsynapse/security/__init__.py +17 -0
- contextsynapse/security/audit.py +297 -0
- contextsynapse/security/audit_logger.py +97 -0
- contextsynapse/security/audit_trail.py +270 -0
- contextsynapse/security/auth_unified.py +284 -0
- contextsynapse/security/auto_tagger.py +230 -0
- contextsynapse/security/data_security.py +218 -0
- contextsynapse/security/encryption.py +207 -0
- contextsynapse/security/identity.py +447 -0
- contextsynapse/security/jwt_identity.py +59 -0
- contextsynapse/security/middleware.py +336 -0
- contextsynapse/security/pii.py +233 -0
- contextsynapse/security/rbac.py +96 -0
- contextsynapse/security/rls.py +205 -0
- contextsynapse/security/sanitize.py +233 -0
- contextsynapse/security/scoped_encryption.py +131 -0
- contextsynapse/security/tenant.py +401 -0
- contextsynapse/shield/__init__.py +21 -0
- contextsynapse/shield/anomaly.py +56 -0
- contextsynapse/shield/permissions.py +99 -0
- contextsynapse/shield/profile.py +103 -0
- contextsynapse/shield/shield.py +108 -0
- contextsynapse/shield/trust_engine.py +69 -0
- contextsynapse/skills/__init__.py +14 -0
- contextsynapse/skills/engine.py +233 -0
- contextsynapse/skills/loader.py +59 -0
- contextsynapse/skills/models.py +139 -0
- contextsynapse/storage/__init__.py +7 -0
- contextsynapse/storage/cache.py +208 -0
- contextsynapse/storage/columnar_store_v2.py +178 -0
- contextsynapse/storage/csr_graph_storage.py +587 -0
- contextsynapse/storage/csr_store.py +388 -0
- contextsynapse/storage/document_store.py +491 -0
- contextsynapse/storage/hnsw_index.py +294 -0
- contextsynapse/storage/hybrid_store_v2.py +278 -0
- contextsynapse/storage/lmdb_graph_storage.py +366 -0
- contextsynapse/storage/migration.py +279 -0
- contextsynapse/storage/namespace_store.py +346 -0
- contextsynapse/storage/pure_graph_storage.py +185 -0
- contextsynapse/storage/redis_graph_adapter.py +186 -0
- contextsynapse/storage/redis_graph_storage.py +375 -0
- contextsynapse/storage/router/__init__.py +29 -0
- contextsynapse/storage/router/duckdb_store.py +398 -0
- contextsynapse/storage/router/factory.py +83 -0
- contextsynapse/storage/router/interface.py +125 -0
- contextsynapse/storage/router/postgres_store.py +339 -0
- contextsynapse/storage/router/resolver.py +192 -0
- contextsynapse/storage/router/router.py +300 -0
- contextsynapse/storage/single_file_storage.py +391 -0
- contextsynapse/storage/storage_manager.py +214 -0
- contextsynapse/storage/storage_query_optimizer.py +314 -0
- contextsynapse/storage/wal.py +359 -0
- contextsynapse/temporal/__init__.py +0 -0
- contextsynapse/temporal/storage.py +106 -0
- contextsynapse/tests/__init__.py +11 -0
- contextsynapse/tests/regression/__init__.py +1 -0
- contextsynapse/tests/regression/add_metadata.py +395 -0
- contextsynapse/tests/regression/add_skip_flags.py +85 -0
- contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
- contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
- contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
- contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
- contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
- contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
- contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
- contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
- contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
- contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
- contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
- contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
- contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
- contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
- contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
- contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
- contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
- contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
- contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
- contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
- contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
- contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
- contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/evaluation_metrics.py +227 -0
- contextsynapse/tests/regression/regression_runner.py +1081 -0
- contextsynapse/tests/regression/setup_regression_data.py +212 -0
- contextsynapse/tests/regression/test_basic_only.py +84 -0
- contextsynapse/tests/regression/test_namespace_creation.py +29 -0
- contextsynapse/tests/regression/test_single_fix.py +61 -0
- contextsynapse/tests/regression/test_single_query.py +20 -0
- contextsynapse/tests/regression/validate_test_syntax.py +179 -0
- contextsynapse/tests/test_advanced_features.py +347 -0
- contextsynapse/tests/test_aiql_summary.py +283 -0
- contextsynapse/tests/test_all_aiql_queries.py +948 -0
- contextsynapse/tests/test_all_create_queries.py +213 -0
- contextsynapse/tests/test_api_vs_native.py +280 -0
- contextsynapse/tests/test_chunking_dedup.py +199 -0
- contextsynapse/tests/test_connectors.py +171 -0
- contextsynapse/tests/test_edges_and_match.py +202 -0
- contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
- contextsynapse/tests/test_name_property.py +224 -0
- contextsynapse/tests/test_node_edge_metadata.py +250 -0
- contextsynapse/tests/test_playground_conn.py +114 -0
- contextsynapse/tests/test_query_cache.py +213 -0
- contextsynapse/tests/test_source_policy_auth.py +348 -0
- contextsynapse/tests/test_temporal_and_domain.py +224 -0
- contextsynapse/tests/test_time_travel_e2e.py +571 -0
- contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
- contextsynapse/tools/__init__.py +36 -0
- contextsynapse/tools/a2a_tools.py +106 -0
- contextsynapse/tools/cognition_tools.py +118 -0
- contextsynapse/tools/context.py +905 -0
- contextsynapse/tools/formatting.py +92 -0
- contextsynapse/tools/gateway_tools.py +186 -0
- contextsynapse/tools/graph.py +1700 -0
- contextsynapse/tools/intelligence.py +235 -0
- contextsynapse/tools/memory.py +118 -0
- contextsynapse/tools/registry.py +373 -0
- contextsynapse/tools/shield_tools.py +159 -0
- contextsynapse/tools/tasks.py +341 -0
- contextsynapse/tools/workspace_tools.py +124 -0
- contextsynapse/utils/__init__.py +15 -0
- contextsynapse/utils/config_loader.py +198 -0
- contextsynapse/utils/env_loader.py +156 -0
- contextsynapse/utils/file_utils.py +288 -0
- contextsynapse/utils/logging_optimizer.py +119 -0
- contextsynapse/vector/__init__.py +30 -0
- contextsynapse/vector/chroma_vector_store.py +152 -0
- contextsynapse/vector/custom_vector_store.py +537 -0
- contextsynapse/vector/faiss_vector_store.py +296 -0
- contextsynapse/vector/qdrant_vector_store.py +163 -0
- contextsynapse/vector/vector_db.py +953 -0
- contextsynapse/vector/vector_db_manager.py +197 -0
- contextsynapse/workspace/__init__.py +27 -0
- contextsynapse/workspace/base.py +94 -0
- contextsynapse/workspace/git.py +228 -0
- contextsynapse/workspace/github.py +78 -0
- contextsynapse/workspace/local.py +73 -0
- contextsynapse/workspace/tools.py +222 -0
- contextsynapse-1.0.0.dist-info/METADATA +847 -0
- contextsynapse-1.0.0.dist-info/RECORD +776 -0
- contextsynapse-1.0.0.dist-info/WHEEL +5 -0
- contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
- contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
- contextsynapse-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
"""Context Seed Engine — seeds entity contexts using multiple context layers.
|
|
2
|
+
|
|
3
|
+
Unlike single-domain seeding, this engine:
|
|
4
|
+
1. Asks ALL registered layers if they know about the entity
|
|
5
|
+
2. Merges knowledge from every layer that responds
|
|
6
|
+
3. Builds a unified schema from all contributing layers
|
|
7
|
+
4. Sets up cross-layer dependency edges
|
|
8
|
+
5. Recommends sensors from all relevant layers
|
|
9
|
+
|
|
10
|
+
Example: seeding "Reliance"
|
|
11
|
+
- finance layer: stock data, earnings schema, price sensor
|
|
12
|
+
- macro layer: GDP dependency, RBI regulation
|
|
13
|
+
- supply_chain layer: crude oil input, ethylene supply
|
|
14
|
+
- psychology layer: retail herding patterns
|
|
15
|
+
- weather layer: monsoon impact on retail segment
|
|
16
|
+
|
|
17
|
+
All combined into ONE entity context with edges to shared entities.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import re
|
|
25
|
+
import uuid
|
|
26
|
+
from dataclasses import asdict, dataclass, field
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Result data class
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class SeedResult:
|
|
40
|
+
"""Complete result of seeding a new entity context via multiple layers."""
|
|
41
|
+
|
|
42
|
+
entity: str
|
|
43
|
+
contributing_layers: List[str] # which layers enriched this entity
|
|
44
|
+
discovery: dict # merged discovery from all layers + LLM
|
|
45
|
+
schema: dict # merged node types and edge types
|
|
46
|
+
dependency_map: dict # all dependencies across layers
|
|
47
|
+
pipeline_recommendations: list # suggested data feeds with reasons
|
|
48
|
+
sensor_suggestions: list # sensors to activate with reasons
|
|
49
|
+
cross_layer_connections: list # edges to shared/meta entities
|
|
50
|
+
estimated_signal_sources: int
|
|
51
|
+
llm_used: bool
|
|
52
|
+
timestamp: str
|
|
53
|
+
|
|
54
|
+
def summary(self) -> str:
|
|
55
|
+
"""Human-readable summary of the seed result."""
|
|
56
|
+
lines = [
|
|
57
|
+
f"Entity: {self.entity}",
|
|
58
|
+
f"Layers: {', '.join(self.contributing_layers) or 'none'}",
|
|
59
|
+
f"LLM used: {self.llm_used}",
|
|
60
|
+
f"Schema: {len(self.schema.get('node_types', []))} node types, "
|
|
61
|
+
f"{len(self.schema.get('edge_types', []))} edge types",
|
|
62
|
+
f"Dependencies: {sum(len(v) for v in self.dependency_map.values() if isinstance(v, list))} total",
|
|
63
|
+
f"Pipelines: {len(self.pipeline_recommendations)}",
|
|
64
|
+
f"Sensors: {len(self.sensor_suggestions)}",
|
|
65
|
+
f"Cross-layer connections: {len(self.cross_layer_connections)}",
|
|
66
|
+
f"Estimated signal sources: {self.estimated_signal_sources}",
|
|
67
|
+
]
|
|
68
|
+
return "\n".join(lines)
|
|
69
|
+
|
|
70
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
71
|
+
return asdict(self)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Context Seed Engine
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ContextSeedEngine:
|
|
80
|
+
"""Seeds entity contexts using ALL available context layers.
|
|
81
|
+
|
|
82
|
+
The engine asks every registered layer whether it knows about the
|
|
83
|
+
entity, collects and merges their knowledge, optionally enriches
|
|
84
|
+
via LLM, and produces a unified context seed that includes schema,
|
|
85
|
+
dependency map, pipeline recommendations, and sensor suggestions
|
|
86
|
+
from ALL contributing layers.
|
|
87
|
+
|
|
88
|
+
Usage::
|
|
89
|
+
|
|
90
|
+
from contextsynapse.context.layers import ContextLayer
|
|
91
|
+
from contextsynapse.context.seed import ContextSeedEngine
|
|
92
|
+
|
|
93
|
+
layers = [
|
|
94
|
+
ContextLayer.from_yaml("layers/finance.yaml"),
|
|
95
|
+
ContextLayer.from_yaml("layers/macro.yaml"),
|
|
96
|
+
ContextLayer.from_yaml("layers/supply_chain.yaml"),
|
|
97
|
+
]
|
|
98
|
+
engine = ContextSeedEngine(registry=my_registry, layers=layers)
|
|
99
|
+
result = engine.seed("Reliance Industries", sector="Conglomerate")
|
|
100
|
+
engine.apply(result)
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
registry,
|
|
106
|
+
layers: Optional[List] = None,
|
|
107
|
+
llm_client=None,
|
|
108
|
+
):
|
|
109
|
+
"""
|
|
110
|
+
Args:
|
|
111
|
+
registry: GraphRegistry (or RedisGraphRegistry) for graph access.
|
|
112
|
+
layers: List of ContextLayer instances. If None, attempts to
|
|
113
|
+
load all registered layers via plugin discovery.
|
|
114
|
+
llm_client: Optional LLM client with ``generate_json()`` method.
|
|
115
|
+
If None, uses layer data only (no LLM cost).
|
|
116
|
+
"""
|
|
117
|
+
self.registry = registry
|
|
118
|
+
self.llm_client = llm_client
|
|
119
|
+
|
|
120
|
+
if layers is not None:
|
|
121
|
+
self.layers = list(layers)
|
|
122
|
+
else:
|
|
123
|
+
self.layers = self._discover_layers()
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
# Public API
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def seed(
|
|
130
|
+
self,
|
|
131
|
+
entity_name: str,
|
|
132
|
+
entity_type: str = "company",
|
|
133
|
+
ticker: Optional[str] = None,
|
|
134
|
+
sector: Optional[str] = None,
|
|
135
|
+
exchange: str = "NSE",
|
|
136
|
+
extra_context: str = "",
|
|
137
|
+
) -> SeedResult:
|
|
138
|
+
"""Seed an entity context using all available layers.
|
|
139
|
+
|
|
140
|
+
Steps:
|
|
141
|
+
1. Ask each layer: do you know about this entity?
|
|
142
|
+
2. For layers that respond, collect their entity data
|
|
143
|
+
3. If LLM available, run discovery with merged prompt
|
|
144
|
+
4. Generate schema from all contributing layers
|
|
145
|
+
5. Build dependency map from discovery + layer cross-edges
|
|
146
|
+
6. Generate pipeline recommendations (each with WHY)
|
|
147
|
+
7. Generate sensor suggestions (each with REASON)
|
|
148
|
+
8. Identify cross-layer connections
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
entity_name: Human-readable entity name.
|
|
152
|
+
entity_type: Type of entity (default "company").
|
|
153
|
+
ticker: Optional identifier.
|
|
154
|
+
sector: Optional sector override. Auto-classified if omitted.
|
|
155
|
+
exchange: Market identifier (default "NSE").
|
|
156
|
+
extra_context: Additional text context for LLM discovery.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
SeedResult with all seeding artifacts.
|
|
160
|
+
"""
|
|
161
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
162
|
+
|
|
163
|
+
# Step 1+2: Ask all layers and collect knowledge
|
|
164
|
+
contributing_layers: List[str] = []
|
|
165
|
+
merged_entity_data: Dict[str, Any] = {}
|
|
166
|
+
|
|
167
|
+
for layer in self.layers:
|
|
168
|
+
if layer.enriches(entity_name):
|
|
169
|
+
contributing_layers.append(layer.layer_id)
|
|
170
|
+
layer_data = layer.get_entity_data(entity_name)
|
|
171
|
+
if layer_data:
|
|
172
|
+
# Merge: later layers extend, don't overwrite lists
|
|
173
|
+
_merge_dicts(merged_entity_data, layer_data, layer.layer_id)
|
|
174
|
+
|
|
175
|
+
# Auto-classify sector from layers if not provided
|
|
176
|
+
if not sector:
|
|
177
|
+
for layer in self.layers:
|
|
178
|
+
if layer.layer_id in contributing_layers:
|
|
179
|
+
classified = layer.classify(entity_name)
|
|
180
|
+
if classified and classified != "uncategorized":
|
|
181
|
+
sector = classified
|
|
182
|
+
break
|
|
183
|
+
if not sector:
|
|
184
|
+
sector = merged_entity_data.get("sector", "unknown")
|
|
185
|
+
|
|
186
|
+
entity_info = {
|
|
187
|
+
"entity_name": entity_name,
|
|
188
|
+
"entity_type": entity_type,
|
|
189
|
+
"ticker": ticker or merged_entity_data.get("ticker", ""),
|
|
190
|
+
"sector": sector,
|
|
191
|
+
"exchange": exchange,
|
|
192
|
+
"extra_context": extra_context,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Step 3: LLM discovery (optional) merged with layer data
|
|
196
|
+
discovery, llm_used = self._discover(entity_info, merged_entity_data)
|
|
197
|
+
|
|
198
|
+
# Step 4: Merge schema from all contributing layers
|
|
199
|
+
schema = self._merge_schema(contributing_layers)
|
|
200
|
+
|
|
201
|
+
# Step 5: Build dependency map from discovery + layer cross-edges
|
|
202
|
+
dependency_map = self._build_dependency_map(discovery, contributing_layers)
|
|
203
|
+
|
|
204
|
+
# Step 6: Pipeline recommendations from all layers
|
|
205
|
+
pipeline_recs = self._generate_pipeline_recommendations(
|
|
206
|
+
entity_name, dependency_map, sector, contributing_layers
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Step 7: Sensor suggestions from all contributing layers
|
|
210
|
+
sensor_suggestions = self._generate_sensor_suggestions(
|
|
211
|
+
entity_name, sector, contributing_layers
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Step 8: Cross-layer connections
|
|
215
|
+
cross_layer_connections = self._find_cross_layer_connections(
|
|
216
|
+
entity_name, entity_type, contributing_layers
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Estimate total source count
|
|
220
|
+
estimated_sources = (
|
|
221
|
+
len(pipeline_recs)
|
|
222
|
+
+ sum(1 for s in sensor_suggestions if s.get("activate"))
|
|
223
|
+
+ len(cross_layer_connections)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return SeedResult(
|
|
227
|
+
entity=entity_name,
|
|
228
|
+
contributing_layers=contributing_layers,
|
|
229
|
+
discovery=discovery,
|
|
230
|
+
schema=schema,
|
|
231
|
+
dependency_map=dependency_map,
|
|
232
|
+
pipeline_recommendations=pipeline_recs,
|
|
233
|
+
sensor_suggestions=sensor_suggestions,
|
|
234
|
+
cross_layer_connections=cross_layer_connections,
|
|
235
|
+
estimated_signal_sources=estimated_sources,
|
|
236
|
+
llm_used=llm_used,
|
|
237
|
+
timestamp=now,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def apply(self, seed_result: SeedResult, context_manager=None) -> Dict[str, Any]:
|
|
241
|
+
"""Apply seed result to the graph.
|
|
242
|
+
|
|
243
|
+
Creates:
|
|
244
|
+
- Entity root node
|
|
245
|
+
- KB nodes (people, brands, aliases)
|
|
246
|
+
- Dependency nodes + edges
|
|
247
|
+
- Schema definition node
|
|
248
|
+
- Cross-layer edges to shared entities
|
|
249
|
+
- SeedManifest node
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
seed_result: The SeedResult from seed().
|
|
253
|
+
context_manager: Optional ContextManager for formal context creation.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Dict with {namespace, nodes_created, edges_created}.
|
|
257
|
+
"""
|
|
258
|
+
from contextsynapse.core.graph_structures import GraphEdge, GraphNode
|
|
259
|
+
|
|
260
|
+
entity_slug = _slugify(seed_result.entity)
|
|
261
|
+
namespace = entity_slug
|
|
262
|
+
|
|
263
|
+
# Get or create graph
|
|
264
|
+
graph = self.registry.get_graph(namespace, load_if_missing=True)
|
|
265
|
+
if graph is None:
|
|
266
|
+
graph = self.registry.create_graph(namespace)
|
|
267
|
+
|
|
268
|
+
nodes_created = 0
|
|
269
|
+
edges_created = 0
|
|
270
|
+
|
|
271
|
+
# --- Entity root node ---
|
|
272
|
+
entity_root_id = f"{entity_slug}_root"
|
|
273
|
+
graph.add_node(GraphNode(
|
|
274
|
+
id=entity_root_id,
|
|
275
|
+
label="Entity",
|
|
276
|
+
properties={
|
|
277
|
+
"name": seed_result.entity,
|
|
278
|
+
"contributing_layers": seed_result.contributing_layers,
|
|
279
|
+
"sector": seed_result.discovery.get("sector", ""),
|
|
280
|
+
"seeded_at": seed_result.timestamp,
|
|
281
|
+
},
|
|
282
|
+
))
|
|
283
|
+
nodes_created += 1
|
|
284
|
+
|
|
285
|
+
# --- People ---
|
|
286
|
+
for person in seed_result.discovery.get("people", []):
|
|
287
|
+
name = person if isinstance(person, str) else person.get("name", str(person))
|
|
288
|
+
title = "" if isinstance(person, str) else person.get("title", "")
|
|
289
|
+
pid = f"{entity_slug}_person_{_slugify(name)}"
|
|
290
|
+
graph.add_node(GraphNode(
|
|
291
|
+
id=pid,
|
|
292
|
+
label="Person",
|
|
293
|
+
properties={"name": name, "title": title, "entity": seed_result.entity},
|
|
294
|
+
))
|
|
295
|
+
graph.add_edge(GraphEdge(
|
|
296
|
+
id=f"e_{entity_root_id}_{pid}",
|
|
297
|
+
source=entity_root_id,
|
|
298
|
+
target=pid,
|
|
299
|
+
label="HAS_PERSON",
|
|
300
|
+
))
|
|
301
|
+
nodes_created += 1
|
|
302
|
+
edges_created += 1
|
|
303
|
+
|
|
304
|
+
# --- Brands / subsidiaries ---
|
|
305
|
+
for brand in seed_result.discovery.get("brands", []):
|
|
306
|
+
name = brand if isinstance(brand, str) else brand.get("name", str(brand))
|
|
307
|
+
bid = f"{entity_slug}_brand_{_slugify(name)}"
|
|
308
|
+
graph.add_node(GraphNode(
|
|
309
|
+
id=bid,
|
|
310
|
+
label="Brand",
|
|
311
|
+
properties={"name": name, "entity": seed_result.entity},
|
|
312
|
+
))
|
|
313
|
+
graph.add_edge(GraphEdge(
|
|
314
|
+
id=f"e_{entity_root_id}_{bid}",
|
|
315
|
+
source=entity_root_id,
|
|
316
|
+
target=bid,
|
|
317
|
+
label="HAS_BRAND",
|
|
318
|
+
))
|
|
319
|
+
nodes_created += 1
|
|
320
|
+
edges_created += 1
|
|
321
|
+
|
|
322
|
+
# --- Aliases ---
|
|
323
|
+
aliases = seed_result.discovery.get("aliases", [])
|
|
324
|
+
if aliases:
|
|
325
|
+
alias_id = f"{entity_slug}_aliases"
|
|
326
|
+
graph.add_node(GraphNode(
|
|
327
|
+
id=alias_id,
|
|
328
|
+
label="AliasSet",
|
|
329
|
+
properties={"aliases": aliases, "entity": seed_result.entity},
|
|
330
|
+
))
|
|
331
|
+
graph.add_edge(GraphEdge(
|
|
332
|
+
id=f"e_{entity_root_id}_{alias_id}",
|
|
333
|
+
source=entity_root_id,
|
|
334
|
+
target=alias_id,
|
|
335
|
+
label="HAS_ALIASES",
|
|
336
|
+
))
|
|
337
|
+
nodes_created += 1
|
|
338
|
+
edges_created += 1
|
|
339
|
+
|
|
340
|
+
# --- Dependency map nodes ---
|
|
341
|
+
dep_label_map = {
|
|
342
|
+
"supply_chain": ("SupplyChainInput", "DEPENDS_ON"),
|
|
343
|
+
"regulatory": ("Regulator", "REGULATED_BY"),
|
|
344
|
+
"competitive": ("Competitor", "COMPETES_WITH"),
|
|
345
|
+
"macro": ("MacroDependency", "AFFECTED_BY"),
|
|
346
|
+
}
|
|
347
|
+
for category, (node_label, edge_label) in dep_label_map.items():
|
|
348
|
+
for dep in seed_result.dependency_map.get(category, []):
|
|
349
|
+
dep_name = dep if isinstance(dep, str) else dep.get("name", str(dep))
|
|
350
|
+
dep_layer = "" if isinstance(dep, str) else dep.get("layer", "")
|
|
351
|
+
dep_id = f"{entity_slug}_{category}_{_slugify(dep_name)}"
|
|
352
|
+
dep_props = {
|
|
353
|
+
"name": dep_name,
|
|
354
|
+
"category": category,
|
|
355
|
+
"entity": seed_result.entity,
|
|
356
|
+
}
|
|
357
|
+
if dep_layer:
|
|
358
|
+
dep_props["source_layer"] = dep_layer
|
|
359
|
+
if isinstance(dep, dict):
|
|
360
|
+
dep_props.update({k: v for k, v in dep.items() if k not in ("name", "layer")})
|
|
361
|
+
|
|
362
|
+
graph.add_node(GraphNode(
|
|
363
|
+
id=dep_id,
|
|
364
|
+
label=node_label,
|
|
365
|
+
properties=dep_props,
|
|
366
|
+
))
|
|
367
|
+
graph.add_edge(GraphEdge(
|
|
368
|
+
id=f"e_{entity_root_id}_{dep_id}",
|
|
369
|
+
source=entity_root_id,
|
|
370
|
+
target=dep_id,
|
|
371
|
+
label=edge_label,
|
|
372
|
+
))
|
|
373
|
+
nodes_created += 1
|
|
374
|
+
edges_created += 1
|
|
375
|
+
|
|
376
|
+
# --- Cross-layer connection nodes ---
|
|
377
|
+
for conn in seed_result.cross_layer_connections:
|
|
378
|
+
shared_name = conn.get("shared_entity", "")
|
|
379
|
+
if not shared_name:
|
|
380
|
+
continue
|
|
381
|
+
shared_id = f"shared_{_slugify(shared_name)}"
|
|
382
|
+
# Create shared entity node (idempotent — add_node can upsert)
|
|
383
|
+
graph.add_node(GraphNode(
|
|
384
|
+
id=shared_id,
|
|
385
|
+
label="SharedEntity",
|
|
386
|
+
properties={
|
|
387
|
+
"name": shared_name,
|
|
388
|
+
"entity_type": conn.get("entity_type", ""),
|
|
389
|
+
"linked_layers": conn.get("layers", []),
|
|
390
|
+
},
|
|
391
|
+
))
|
|
392
|
+
edge_type = conn.get("edge_type", "CONNECTED_TO")
|
|
393
|
+
graph.add_edge(GraphEdge(
|
|
394
|
+
id=f"e_{entity_root_id}_{shared_id}",
|
|
395
|
+
source=entity_root_id,
|
|
396
|
+
target=shared_id,
|
|
397
|
+
label=edge_type,
|
|
398
|
+
properties={
|
|
399
|
+
"propagation_weight": conn.get("propagation_weight", 0.7),
|
|
400
|
+
},
|
|
401
|
+
))
|
|
402
|
+
nodes_created += 1
|
|
403
|
+
edges_created += 1
|
|
404
|
+
|
|
405
|
+
# --- Schema node ---
|
|
406
|
+
schema_id = f"{entity_slug}_schema"
|
|
407
|
+
graph.add_node(GraphNode(
|
|
408
|
+
id=schema_id,
|
|
409
|
+
label="SchemaDefinition",
|
|
410
|
+
properties={
|
|
411
|
+
"entity": seed_result.entity,
|
|
412
|
+
"contributing_layers": seed_result.contributing_layers,
|
|
413
|
+
"schema": json.dumps(seed_result.schema),
|
|
414
|
+
"created_at": seed_result.timestamp,
|
|
415
|
+
},
|
|
416
|
+
))
|
|
417
|
+
nodes_created += 1
|
|
418
|
+
|
|
419
|
+
# --- SeedManifest node ---
|
|
420
|
+
manifest_id = f"{entity_slug}_seed_manifest"
|
|
421
|
+
graph.add_node(GraphNode(
|
|
422
|
+
id=manifest_id,
|
|
423
|
+
label="SeedManifest",
|
|
424
|
+
properties={
|
|
425
|
+
"entity": seed_result.entity,
|
|
426
|
+
"contributing_layers": seed_result.contributing_layers,
|
|
427
|
+
"llm_used": seed_result.llm_used,
|
|
428
|
+
"estimated_signal_sources": seed_result.estimated_signal_sources,
|
|
429
|
+
"pipeline_count": len(seed_result.pipeline_recommendations),
|
|
430
|
+
"sensor_count": len(seed_result.sensor_suggestions),
|
|
431
|
+
"cross_layer_count": len(seed_result.cross_layer_connections),
|
|
432
|
+
"dependency_count": sum(
|
|
433
|
+
len(v) for v in seed_result.dependency_map.values()
|
|
434
|
+
if isinstance(v, list)
|
|
435
|
+
),
|
|
436
|
+
"seeded_at": seed_result.timestamp,
|
|
437
|
+
"seed_result_json": json.dumps(seed_result.to_dict()),
|
|
438
|
+
},
|
|
439
|
+
))
|
|
440
|
+
nodes_created += 1
|
|
441
|
+
|
|
442
|
+
# Optionally create a formal Context
|
|
443
|
+
if context_manager is not None:
|
|
444
|
+
try:
|
|
445
|
+
layer_tags = list(seed_result.contributing_layers) + ["auto-seeded"]
|
|
446
|
+
context_manager.create_context(
|
|
447
|
+
name=seed_result.entity,
|
|
448
|
+
description=(
|
|
449
|
+
f"Auto-seeded context for {seed_result.entity} "
|
|
450
|
+
f"(layers: {', '.join(seed_result.contributing_layers)})"
|
|
451
|
+
),
|
|
452
|
+
context_type="knowledge_base",
|
|
453
|
+
source="seed:multi_layer",
|
|
454
|
+
sensitivity="internal",
|
|
455
|
+
tags=layer_tags,
|
|
456
|
+
graph_namespace=namespace,
|
|
457
|
+
)
|
|
458
|
+
except Exception as exc:
|
|
459
|
+
logger.warning(
|
|
460
|
+
"Could not create formal context for '%s': %s",
|
|
461
|
+
seed_result.entity, exc,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
logger.info(
|
|
465
|
+
"Applied seed for '%s' [layers: %s]: %d nodes, %d edges in namespace '%s'",
|
|
466
|
+
seed_result.entity,
|
|
467
|
+
", ".join(seed_result.contributing_layers),
|
|
468
|
+
nodes_created,
|
|
469
|
+
edges_created,
|
|
470
|
+
namespace,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
"namespace": namespace,
|
|
475
|
+
"nodes_created": nodes_created,
|
|
476
|
+
"edges_created": edges_created,
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
def seed_shared_entity(
|
|
480
|
+
self, entity_name: str, entity_type: str
|
|
481
|
+
) -> Dict[str, Any]:
|
|
482
|
+
"""Seed a shared/meta entity that acts as a bridge across layers.
|
|
483
|
+
|
|
484
|
+
Shared entities (e.g. "crude_oil", "interest_rates", "monsoon")
|
|
485
|
+
exist in a dedicated ``_shared`` namespace and are referenced by
|
|
486
|
+
multiple entity contexts via cross-layer edges.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
entity_name: Name of the shared entity.
|
|
490
|
+
entity_type: Type label (e.g. "commodity", "policy", "weather").
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Dict with {namespace, node_id, contributing_layers}.
|
|
494
|
+
"""
|
|
495
|
+
from contextsynapse.core.graph_structures import GraphNode
|
|
496
|
+
|
|
497
|
+
namespace = "_shared"
|
|
498
|
+
graph = self.registry.get_graph(namespace, load_if_missing=True)
|
|
499
|
+
if graph is None:
|
|
500
|
+
graph = self.registry.create_graph(namespace)
|
|
501
|
+
|
|
502
|
+
node_id = f"shared_{_slugify(entity_name)}"
|
|
503
|
+
|
|
504
|
+
# Determine which layers know about this shared entity
|
|
505
|
+
contributing = [
|
|
506
|
+
layer.layer_id for layer in self.layers
|
|
507
|
+
if layer.enriches(entity_name)
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
graph.add_node(GraphNode(
|
|
511
|
+
id=node_id,
|
|
512
|
+
label="SharedEntity",
|
|
513
|
+
properties={
|
|
514
|
+
"name": entity_name,
|
|
515
|
+
"entity_type": entity_type,
|
|
516
|
+
"contributing_layers": contributing,
|
|
517
|
+
"seeded_at": datetime.now(timezone.utc).isoformat(),
|
|
518
|
+
},
|
|
519
|
+
))
|
|
520
|
+
|
|
521
|
+
logger.info(
|
|
522
|
+
"Seeded shared entity '%s' (%s) in _shared namespace, layers: %s",
|
|
523
|
+
entity_name, entity_type, ", ".join(contributing) or "none",
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
"namespace": namespace,
|
|
528
|
+
"node_id": node_id,
|
|
529
|
+
"contributing_layers": contributing,
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
def get_seeded_entities(self) -> List[Dict[str, Any]]:
|
|
533
|
+
"""List all seeded entities with their contributing layers.
|
|
534
|
+
|
|
535
|
+
Scans the registry for graphs that contain a SeedManifest node.
|
|
536
|
+
"""
|
|
537
|
+
results = []
|
|
538
|
+
graphs = self.registry.list_graphs()
|
|
539
|
+
for graph_info in graphs:
|
|
540
|
+
namespace = graph_info.get("name", "")
|
|
541
|
+
if not namespace or namespace.startswith("_"):
|
|
542
|
+
continue
|
|
543
|
+
|
|
544
|
+
graph = self.registry.get_graph(namespace, load_if_missing=False)
|
|
545
|
+
if graph is None:
|
|
546
|
+
continue
|
|
547
|
+
|
|
548
|
+
try:
|
|
549
|
+
manifests = graph.get_all_nodes(label="SeedManifest")
|
|
550
|
+
except Exception:
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
for node in manifests:
|
|
554
|
+
props = node.properties or {}
|
|
555
|
+
results.append({
|
|
556
|
+
"entity": props.get("entity", namespace),
|
|
557
|
+
"namespace": namespace,
|
|
558
|
+
"contributing_layers": props.get("contributing_layers", []),
|
|
559
|
+
"llm_used": props.get("llm_used", False),
|
|
560
|
+
"estimated_signal_sources": props.get("estimated_signal_sources", 0),
|
|
561
|
+
"seeded_at": props.get("seeded_at", ""),
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
return results
|
|
565
|
+
|
|
566
|
+
# ------------------------------------------------------------------
|
|
567
|
+
# Internal: discovery
|
|
568
|
+
# ------------------------------------------------------------------
|
|
569
|
+
|
|
570
|
+
def _discover(
|
|
571
|
+
self,
|
|
572
|
+
entity_info: Dict[str, Any],
|
|
573
|
+
merged_layer_data: Dict[str, Any],
|
|
574
|
+
) -> tuple:
|
|
575
|
+
"""Attempt LLM discovery merged with layer data.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
(discovery_dict, llm_used_bool)
|
|
579
|
+
"""
|
|
580
|
+
# Try LLM discovery if client available and any layer has a seed prompt
|
|
581
|
+
if self.llm_client:
|
|
582
|
+
seed_prompts = [
|
|
583
|
+
layer.seed_prompt_template
|
|
584
|
+
for layer in self.layers
|
|
585
|
+
if layer.seed_prompt_template
|
|
586
|
+
]
|
|
587
|
+
if seed_prompts:
|
|
588
|
+
try:
|
|
589
|
+
llm_result = self._llm_discover(entity_info, seed_prompts)
|
|
590
|
+
if llm_result and isinstance(llm_result, dict):
|
|
591
|
+
# Merge LLM result with layer data (layer data takes priority for lists)
|
|
592
|
+
merged = dict(llm_result)
|
|
593
|
+
_merge_dicts(merged, merged_layer_data, source="layer_data")
|
|
594
|
+
logger.info(
|
|
595
|
+
"LLM discovery succeeded for '%s' (%d keys)",
|
|
596
|
+
entity_info["entity_name"],
|
|
597
|
+
len(merged),
|
|
598
|
+
)
|
|
599
|
+
return merged, True
|
|
600
|
+
except Exception as exc:
|
|
601
|
+
logger.warning(
|
|
602
|
+
"LLM discovery failed for '%s': %s — using layer data",
|
|
603
|
+
entity_info["entity_name"],
|
|
604
|
+
exc,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# Fallback: use merged layer data only
|
|
608
|
+
if merged_layer_data:
|
|
609
|
+
logger.info(
|
|
610
|
+
"Using layer data for '%s' (%d keys)",
|
|
611
|
+
entity_info["entity_name"],
|
|
612
|
+
len(merged_layer_data),
|
|
613
|
+
)
|
|
614
|
+
return merged_layer_data, False
|
|
615
|
+
|
|
616
|
+
# Minimal discovery
|
|
617
|
+
logger.info(
|
|
618
|
+
"No discovery data for '%s', using entity info only",
|
|
619
|
+
entity_info["entity_name"],
|
|
620
|
+
)
|
|
621
|
+
return {
|
|
622
|
+
"name": entity_info["entity_name"],
|
|
623
|
+
"entity_type": entity_info.get("entity_type", "unknown"),
|
|
624
|
+
"sector": entity_info.get("sector", "unknown"),
|
|
625
|
+
"ticker": entity_info.get("ticker", ""),
|
|
626
|
+
"people": [],
|
|
627
|
+
"brands": [],
|
|
628
|
+
"aliases": [],
|
|
629
|
+
"supply_chain_inputs": [],
|
|
630
|
+
"regulators": [],
|
|
631
|
+
"competitors": [],
|
|
632
|
+
"macro_dependencies": [],
|
|
633
|
+
}, False
|
|
634
|
+
|
|
635
|
+
def _llm_discover(
|
|
636
|
+
self,
|
|
637
|
+
entity_info: Dict[str, Any],
|
|
638
|
+
seed_prompts: List[str],
|
|
639
|
+
) -> Dict[str, Any]:
|
|
640
|
+
"""Call LLM with merged seed prompts from all layers."""
|
|
641
|
+
# Combine prompts from all layers
|
|
642
|
+
combined_prompt = "\n\n".join(seed_prompts)
|
|
643
|
+
for key, value in entity_info.items():
|
|
644
|
+
combined_prompt = combined_prompt.replace("{{ " + key + " }}", str(value))
|
|
645
|
+
combined_prompt = combined_prompt.replace("{{" + key + "}}", str(value))
|
|
646
|
+
combined_prompt = combined_prompt.replace("{" + key + "}", str(value))
|
|
647
|
+
|
|
648
|
+
system = (
|
|
649
|
+
"You are a multi-domain knowledge expert. "
|
|
650
|
+
"Return a JSON object with discovery data for the entity. "
|
|
651
|
+
"Include: people (key people with name+title), brands (subsidiaries/brands), "
|
|
652
|
+
"aliases (alternative names/tickers), supply_chain_inputs (upstream dependencies), "
|
|
653
|
+
"regulators (regulatory bodies), competitors (direct competitors), "
|
|
654
|
+
"macro_dependencies (macro factors), sector (industry sector), "
|
|
655
|
+
"key_entities (other relevant entities)."
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
return self.llm_client.generate_json(combined_prompt, system=system)
|
|
659
|
+
|
|
660
|
+
# ------------------------------------------------------------------
|
|
661
|
+
# Internal: schema merging
|
|
662
|
+
# ------------------------------------------------------------------
|
|
663
|
+
|
|
664
|
+
def _merge_schema(self, contributing_layer_ids: List[str]) -> Dict[str, Any]:
|
|
665
|
+
"""Merge node and edge schemas from all contributing layers."""
|
|
666
|
+
node_types: List[Dict[str, Any]] = []
|
|
667
|
+
edge_types: List[Dict[str, Any]] = []
|
|
668
|
+
seen_node_types: set = set()
|
|
669
|
+
seen_edge_types: set = set()
|
|
670
|
+
|
|
671
|
+
for layer in self.layers:
|
|
672
|
+
if layer.layer_id not in contributing_layer_ids:
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
for ns in layer.node_schemas:
|
|
676
|
+
if ns.type_name not in seen_node_types:
|
|
677
|
+
node_types.append(ns.to_dict())
|
|
678
|
+
seen_node_types.add(ns.type_name)
|
|
679
|
+
|
|
680
|
+
for es in layer.edge_schemas:
|
|
681
|
+
key = (es.edge_type, tuple(es.from_types), tuple(es.to_types))
|
|
682
|
+
if key not in seen_edge_types:
|
|
683
|
+
edge_types.append(es.to_dict())
|
|
684
|
+
seen_edge_types.add(key)
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
"node_types": node_types,
|
|
688
|
+
"edge_types": edge_types,
|
|
689
|
+
"contributing_layers": list(contributing_layer_ids),
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
# ------------------------------------------------------------------
|
|
693
|
+
# Internal: dependency map
|
|
694
|
+
# ------------------------------------------------------------------
|
|
695
|
+
|
|
696
|
+
def _build_dependency_map(
|
|
697
|
+
self,
|
|
698
|
+
discovery: Dict[str, Any],
|
|
699
|
+
contributing_layer_ids: List[str],
|
|
700
|
+
) -> Dict[str, List]:
|
|
701
|
+
"""Extract dependency categories from discovery data + layer cross-edges."""
|
|
702
|
+
dep_map: Dict[str, List] = {
|
|
703
|
+
"supply_chain": [],
|
|
704
|
+
"regulatory": [],
|
|
705
|
+
"competitive": [],
|
|
706
|
+
"macro": [],
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
# From discovery data (same logic as before)
|
|
710
|
+
key_mapping = {
|
|
711
|
+
"supply_chain": ("supply_chain_inputs", "supply_chain", "inputs", "upstream"),
|
|
712
|
+
"regulatory": ("regulators", "regulatory_bodies", "regulatory"),
|
|
713
|
+
"competitive": ("competitors", "competition", "competitive"),
|
|
714
|
+
"macro": ("macro_dependencies", "macro_factors", "macro"),
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
for category, keys in key_mapping.items():
|
|
718
|
+
for key in keys:
|
|
719
|
+
items = discovery.get(key, [])
|
|
720
|
+
if items:
|
|
721
|
+
dep_map[category] = _normalize_list(items)
|
|
722
|
+
break
|
|
723
|
+
|
|
724
|
+
# Enrich with cross-layer edge definitions
|
|
725
|
+
for layer in self.layers:
|
|
726
|
+
if layer.layer_id not in contributing_layer_ids:
|
|
727
|
+
continue
|
|
728
|
+
for cle in layer.cross_layer_edges:
|
|
729
|
+
dep_entry = {
|
|
730
|
+
"name": cle.from_entity_type,
|
|
731
|
+
"edge_type": cle.edge_type,
|
|
732
|
+
"propagation_weight": cle.propagation_weight,
|
|
733
|
+
"description": cle.description,
|
|
734
|
+
"layer": layer.layer_id,
|
|
735
|
+
}
|
|
736
|
+
# Categorize the cross-layer edge
|
|
737
|
+
if cle.edge_type in ("DEPENDS_ON", "SUPPLIES_TO"):
|
|
738
|
+
dep_map["supply_chain"].append(dep_entry)
|
|
739
|
+
elif cle.edge_type in ("REGULATED_BY",):
|
|
740
|
+
dep_map["regulatory"].append(dep_entry)
|
|
741
|
+
elif cle.edge_type in ("COMPETES_WITH",):
|
|
742
|
+
dep_map["competitive"].append(dep_entry)
|
|
743
|
+
else:
|
|
744
|
+
dep_map["macro"].append(dep_entry)
|
|
745
|
+
|
|
746
|
+
return dep_map
|
|
747
|
+
|
|
748
|
+
# ------------------------------------------------------------------
|
|
749
|
+
# Internal: pipeline recommendations
|
|
750
|
+
# ------------------------------------------------------------------
|
|
751
|
+
|
|
752
|
+
def _generate_pipeline_recommendations(
|
|
753
|
+
self,
|
|
754
|
+
entity_name: str,
|
|
755
|
+
dependency_map: Dict[str, List],
|
|
756
|
+
sector: str,
|
|
757
|
+
contributing_layer_ids: List[str],
|
|
758
|
+
) -> List[Dict[str, Any]]:
|
|
759
|
+
"""Generate pipeline recommendations from dependencies."""
|
|
760
|
+
recommendations = []
|
|
761
|
+
|
|
762
|
+
category_config = {
|
|
763
|
+
"supply_chain": {
|
|
764
|
+
"source_type": "news",
|
|
765
|
+
"interval": "6h",
|
|
766
|
+
"priority": "high",
|
|
767
|
+
"why_template": "Supply chain disruption in {dep} directly impacts {entity}",
|
|
768
|
+
},
|
|
769
|
+
"regulatory": {
|
|
770
|
+
"source_type": "regulatory_filing",
|
|
771
|
+
"interval": "daily",
|
|
772
|
+
"priority": "high",
|
|
773
|
+
"why_template": "Regulatory actions by {dep} affect compliance posture of {entity}",
|
|
774
|
+
},
|
|
775
|
+
"competitive": {
|
|
776
|
+
"source_type": "news",
|
|
777
|
+
"interval": "12h",
|
|
778
|
+
"priority": "medium",
|
|
779
|
+
"why_template": "Competitive moves by {dep} affect market position of {entity}",
|
|
780
|
+
},
|
|
781
|
+
"macro": {
|
|
782
|
+
"source_type": "data_feed",
|
|
783
|
+
"interval": "daily",
|
|
784
|
+
"priority": "medium",
|
|
785
|
+
"why_template": "{dep} is a macro factor influencing {entity} performance",
|
|
786
|
+
},
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
for category, deps in dependency_map.items():
|
|
790
|
+
config = category_config.get(category, {
|
|
791
|
+
"source_type": "news",
|
|
792
|
+
"interval": "daily",
|
|
793
|
+
"priority": "low",
|
|
794
|
+
"why_template": "{dep} is related to {entity}",
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
for dep in deps:
|
|
798
|
+
dep_name = dep if isinstance(dep, str) else dep.get("name", str(dep))
|
|
799
|
+
dep_layer = "" if isinstance(dep, str) else dep.get("layer", "")
|
|
800
|
+
search_terms = [dep_name, entity_name]
|
|
801
|
+
|
|
802
|
+
recommendations.append({
|
|
803
|
+
"name": f"{_slugify(dep_name)}_{category}_pipeline",
|
|
804
|
+
"source_type": config["source_type"],
|
|
805
|
+
"interval": config["interval"],
|
|
806
|
+
"search_terms": search_terms,
|
|
807
|
+
"priority": config["priority"],
|
|
808
|
+
"why": config["why_template"].format(dep=dep_name, entity=entity_name),
|
|
809
|
+
"category": category,
|
|
810
|
+
"source_layer": dep_layer,
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
return recommendations
|
|
814
|
+
|
|
815
|
+
# ------------------------------------------------------------------
|
|
816
|
+
# Internal: sensor suggestions
|
|
817
|
+
# ------------------------------------------------------------------
|
|
818
|
+
|
|
819
|
+
def _generate_sensor_suggestions(
|
|
820
|
+
self,
|
|
821
|
+
entity_name: str,
|
|
822
|
+
sector: str,
|
|
823
|
+
contributing_layer_ids: List[str],
|
|
824
|
+
) -> List[Dict[str, Any]]:
|
|
825
|
+
"""Collect sensor suggestions from all contributing layers."""
|
|
826
|
+
suggestions = []
|
|
827
|
+
|
|
828
|
+
for layer in self.layers:
|
|
829
|
+
if layer.layer_id not in contributing_layer_ids:
|
|
830
|
+
continue
|
|
831
|
+
|
|
832
|
+
relevant_sensors = layer.get_sensors_for(entity_name, sector=sector)
|
|
833
|
+
for sensor in relevant_sensors:
|
|
834
|
+
suggestions.append({
|
|
835
|
+
"sensor": sensor.name,
|
|
836
|
+
"activate": True,
|
|
837
|
+
"reason": (
|
|
838
|
+
f"{sensor.description} — relevant to {entity_name} "
|
|
839
|
+
f"(from {layer.layer_id} layer)"
|
|
840
|
+
),
|
|
841
|
+
"interval": sensor.interval,
|
|
842
|
+
"type": sensor.sensor_type,
|
|
843
|
+
"layer": layer.layer_id,
|
|
844
|
+
"weight": sensor.weight,
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
# Also include sensors from the layer that were NOT relevant,
|
|
848
|
+
# with activate=False for transparency
|
|
849
|
+
all_sensor_names = {s.name for s in layer.sensors}
|
|
850
|
+
relevant_names = {s.name for s in relevant_sensors}
|
|
851
|
+
for sensor in layer.sensors:
|
|
852
|
+
if sensor.name not in relevant_names:
|
|
853
|
+
suggestions.append({
|
|
854
|
+
"sensor": sensor.name,
|
|
855
|
+
"activate": False,
|
|
856
|
+
"reason": (
|
|
857
|
+
f"{sensor.name} has low relevance to {entity_name} "
|
|
858
|
+
f"— skip to save cost (from {layer.layer_id} layer)"
|
|
859
|
+
),
|
|
860
|
+
"interval": sensor.interval,
|
|
861
|
+
"type": sensor.sensor_type,
|
|
862
|
+
"layer": layer.layer_id,
|
|
863
|
+
"weight": sensor.weight,
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
return suggestions
|
|
867
|
+
|
|
868
|
+
# ------------------------------------------------------------------
|
|
869
|
+
# Internal: cross-layer connections
|
|
870
|
+
# ------------------------------------------------------------------
|
|
871
|
+
|
|
872
|
+
def _find_cross_layer_connections(
|
|
873
|
+
self,
|
|
874
|
+
entity_name: str,
|
|
875
|
+
entity_type: str,
|
|
876
|
+
contributing_layer_ids: List[str],
|
|
877
|
+
) -> List[Dict[str, Any]]:
|
|
878
|
+
"""Identify cross-layer connections for this entity.
|
|
879
|
+
|
|
880
|
+
Looks at all cross_layer_edges from contributing layers and finds
|
|
881
|
+
shared entities that this entity should connect to.
|
|
882
|
+
"""
|
|
883
|
+
connections = []
|
|
884
|
+
seen_shared: set = set()
|
|
885
|
+
|
|
886
|
+
for layer in self.layers:
|
|
887
|
+
if layer.layer_id not in contributing_layer_ids:
|
|
888
|
+
continue
|
|
889
|
+
|
|
890
|
+
for cle in layer.cross_layer_edges:
|
|
891
|
+
# If entity type matches the edge's to_entity_type,
|
|
892
|
+
# the from_entity_type is a shared entity to connect to
|
|
893
|
+
if cle.to_entity_type == entity_type or cle.to_entity_type in ("*", "any"):
|
|
894
|
+
shared_key = f"{cle.from_entity_type}:{cle.edge_type}"
|
|
895
|
+
if shared_key not in seen_shared:
|
|
896
|
+
connections.append({
|
|
897
|
+
"shared_entity": cle.from_entity_type,
|
|
898
|
+
"entity_type": cle.from_entity_type,
|
|
899
|
+
"edge_type": cle.edge_type,
|
|
900
|
+
"propagation_weight": cle.propagation_weight,
|
|
901
|
+
"description": cle.description,
|
|
902
|
+
"layers": [layer.layer_id],
|
|
903
|
+
})
|
|
904
|
+
seen_shared.add(shared_key)
|
|
905
|
+
|
|
906
|
+
# If entity type matches the edge's from_entity_type,
|
|
907
|
+
# the to_entity_type is downstream
|
|
908
|
+
if cle.from_entity_type == entity_type or cle.from_entity_type in ("*", "any"):
|
|
909
|
+
shared_key = f"{cle.to_entity_type}:{cle.edge_type}"
|
|
910
|
+
if shared_key not in seen_shared:
|
|
911
|
+
connections.append({
|
|
912
|
+
"shared_entity": cle.to_entity_type,
|
|
913
|
+
"entity_type": cle.to_entity_type,
|
|
914
|
+
"edge_type": cle.edge_type,
|
|
915
|
+
"propagation_weight": cle.propagation_weight,
|
|
916
|
+
"description": cle.description,
|
|
917
|
+
"layers": [layer.layer_id],
|
|
918
|
+
})
|
|
919
|
+
seen_shared.add(shared_key)
|
|
920
|
+
|
|
921
|
+
return connections
|
|
922
|
+
|
|
923
|
+
# ------------------------------------------------------------------
|
|
924
|
+
# Internal: layer discovery
|
|
925
|
+
# ------------------------------------------------------------------
|
|
926
|
+
|
|
927
|
+
def _discover_layers(self) -> List:
|
|
928
|
+
"""Attempt to load layers via plugin discovery (best-effort)."""
|
|
929
|
+
try:
|
|
930
|
+
from contextsynapse.plugins.loader import discover_plugins
|
|
931
|
+
plugins = discover_plugins()
|
|
932
|
+
# Look for plugins that expose context layers
|
|
933
|
+
layers = []
|
|
934
|
+
for plugin in plugins:
|
|
935
|
+
if hasattr(plugin, "get_context_layers"):
|
|
936
|
+
layers.extend(plugin.get_context_layers())
|
|
937
|
+
return layers
|
|
938
|
+
except Exception as exc:
|
|
939
|
+
logger.debug("No layers discovered via plugins: %s", exc)
|
|
940
|
+
return []
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
# ---------------------------------------------------------------------------
|
|
944
|
+
# Helpers
|
|
945
|
+
# ---------------------------------------------------------------------------
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def _slugify(text: str) -> str:
|
|
949
|
+
"""Convert text to a URL/ID-safe slug."""
|
|
950
|
+
slug = text.lower().strip()
|
|
951
|
+
slug = re.sub(r"[^a-z0-9]+", "_", slug)
|
|
952
|
+
slug = slug.strip("_")
|
|
953
|
+
return slug or "entity"
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _normalize_list(items) -> List:
|
|
957
|
+
"""Ensure items is a list of dicts or strings."""
|
|
958
|
+
if not isinstance(items, list):
|
|
959
|
+
return [items] if items else []
|
|
960
|
+
result = []
|
|
961
|
+
for item in items:
|
|
962
|
+
if isinstance(item, (str, dict)):
|
|
963
|
+
result.append(item)
|
|
964
|
+
else:
|
|
965
|
+
result.append(str(item))
|
|
966
|
+
return result
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def _merge_dicts(
|
|
970
|
+
target: Dict[str, Any],
|
|
971
|
+
source: Dict[str, Any],
|
|
972
|
+
source_label: str = "",
|
|
973
|
+
) -> None:
|
|
974
|
+
"""Merge source into target, extending lists instead of overwriting."""
|
|
975
|
+
for key, value in source.items():
|
|
976
|
+
if key in target:
|
|
977
|
+
existing = target[key]
|
|
978
|
+
if isinstance(existing, list) and isinstance(value, list):
|
|
979
|
+
# Extend lists, avoiding exact duplicates
|
|
980
|
+
existing_set = {
|
|
981
|
+
json.dumps(v, sort_keys=True) if isinstance(v, dict) else str(v)
|
|
982
|
+
for v in existing
|
|
983
|
+
}
|
|
984
|
+
for v in value:
|
|
985
|
+
v_key = json.dumps(v, sort_keys=True) if isinstance(v, dict) else str(v)
|
|
986
|
+
if v_key not in existing_set:
|
|
987
|
+
existing.append(v)
|
|
988
|
+
elif isinstance(existing, dict) and isinstance(value, dict):
|
|
989
|
+
_merge_dicts(existing, value, source_label)
|
|
990
|
+
# For scalar values, keep the existing value (first layer wins)
|
|
991
|
+
else:
|
|
992
|
+
target[key] = value
|