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,891 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Units — Self-Discoverable Intelligence Layer
|
|
3
|
+
=====================================================
|
|
4
|
+
A ContextUnit (CU) is a synthesized intelligence object that bundles related
|
|
5
|
+
facts, entities, and events into a single addressable knowledge packet.
|
|
6
|
+
|
|
7
|
+
CUs answer questions, expose what they know, and hint at what to look for next.
|
|
8
|
+
|
|
9
|
+
Key functions:
|
|
10
|
+
- cluster_facts_for_cus() — group facts by shared keywords into candidate clusters
|
|
11
|
+
- build_context_unit() — build a CU dict from a topic + facts + entities
|
|
12
|
+
- match_intent() — find CUs relevant to a natural-language query
|
|
13
|
+
- format_cu_for_agent() — render a CU as human-readable text
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
import uuid
|
|
21
|
+
from collections import Counter, defaultdict
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# ── Stop words (excluded from keyword matching) ─────────────────────────────
|
|
27
|
+
|
|
28
|
+
_STOP_WORDS = frozenset({
|
|
29
|
+
"a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
|
|
30
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "could",
|
|
31
|
+
"should", "may", "might", "shall", "can", "need", "dare", "ought",
|
|
32
|
+
"used", "to", "of", "in", "on", "at", "by", "for", "with", "about",
|
|
33
|
+
"against", "between", "into", "through", "during", "before", "after",
|
|
34
|
+
"above", "below", "from", "up", "down", "out", "off", "over", "under",
|
|
35
|
+
"again", "then", "once", "that", "this", "these", "those", "what",
|
|
36
|
+
"which", "who", "whom", "when", "where", "why", "how", "and", "but",
|
|
37
|
+
"or", "nor", "not", "so", "yet", "both", "either", "neither", "if",
|
|
38
|
+
"as", "it", "its", "he", "she", "they", "we", "i", "you", "me", "him",
|
|
39
|
+
"her", "us", "them", "my", "your", "his", "our", "their", "there",
|
|
40
|
+
"says", "said", "say", "s", "whats", "happening", "happening",
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _tokenize(text: str) -> list[str]:
|
|
45
|
+
"""Lowercase, split on non-word chars, strip stop words."""
|
|
46
|
+
tokens = re.findall(r"[a-z]+", text.lower())
|
|
47
|
+
return [t for t in tokens if t not in _STOP_WORDS and len(t) > 2]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _extract_features(text: str) -> list[str]:
|
|
51
|
+
"""Extract unigrams + bigrams + capitalized entities for richer TF-IDF."""
|
|
52
|
+
# Unigrams (lowercase, no stop words)
|
|
53
|
+
words = re.findall(r"[a-z]+", text.lower())
|
|
54
|
+
unigrams = [w for w in words if w not in _STOP_WORDS and len(w) > 2]
|
|
55
|
+
|
|
56
|
+
# Bigrams — capture phrases like "nuclear_talks", "climate_summit"
|
|
57
|
+
bigrams = [f"{unigrams[i]}_{unigrams[i+1]}" for i in range(len(unigrams) - 1)]
|
|
58
|
+
|
|
59
|
+
# Capitalized words from original text — proper nouns (Iran, India, OpenAI, etc.)
|
|
60
|
+
entities = re.findall(r"\b[A-Z][a-z]{2,}\b", text)
|
|
61
|
+
entity_features = [f"ENT_{e.lower()}" for e in entities]
|
|
62
|
+
|
|
63
|
+
return unigrams + bigrams + entity_features
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── Clustering ───────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cluster_facts_for_cus(
|
|
70
|
+
facts: List[Dict[str, Any]],
|
|
71
|
+
min_cluster_size: int = 3,
|
|
72
|
+
max_clusters: int = 15,
|
|
73
|
+
db=None,
|
|
74
|
+
) -> List[Dict[str, Any]]:
|
|
75
|
+
"""
|
|
76
|
+
Group facts into clusters using graph-aware entity co-occurrence.
|
|
77
|
+
|
|
78
|
+
Strategy (best → fallback):
|
|
79
|
+
1. **Graph-aware** (if db provided): facts sharing entities (via MENTIONS edges)
|
|
80
|
+
cluster together. A fact about "Iran" and "Nuclear talks" clusters with
|
|
81
|
+
another fact about "Iran" and "IAEA" because they share the Iran entity.
|
|
82
|
+
2. **Embedding-based**: cached Qdrant vectors + k-means.
|
|
83
|
+
3. **TF-IDF**: unigram+bigram+entity features + k-means.
|
|
84
|
+
|
|
85
|
+
Returns a list of cluster dicts:
|
|
86
|
+
{"topic": str, "facts": [fact_dict, ...]}
|
|
87
|
+
"""
|
|
88
|
+
if not facts:
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
import math
|
|
92
|
+
n = len(facts)
|
|
93
|
+
if n < 30:
|
|
94
|
+
k = max(2, int(math.sqrt(n)))
|
|
95
|
+
else:
|
|
96
|
+
k = max(3, min(max_clusters, max(n // 15, int(math.sqrt(n)))))
|
|
97
|
+
|
|
98
|
+
# Strategy 1: Graph-aware clustering via entity co-occurrence
|
|
99
|
+
if db is not None:
|
|
100
|
+
clusters = _cluster_with_graph(facts, db, k, min_cluster_size)
|
|
101
|
+
if clusters:
|
|
102
|
+
logger.info("[CU] Graph-aware clustering: %d facts → %d clusters", n, len(clusters))
|
|
103
|
+
return clusters
|
|
104
|
+
|
|
105
|
+
# Strategy 2: Embedding-based clustering
|
|
106
|
+
texts = [
|
|
107
|
+
fact.get("properties", {}).get("statement", "")
|
|
108
|
+
or fact.get("properties", {}).get("name", "")
|
|
109
|
+
for fact in facts
|
|
110
|
+
]
|
|
111
|
+
clusters = _cluster_with_embeddings(facts, texts, k, min_cluster_size)
|
|
112
|
+
if clusters:
|
|
113
|
+
logger.info("[CU] Embedding-based clustering: %d facts → %d clusters", n, len(clusters))
|
|
114
|
+
return clusters
|
|
115
|
+
|
|
116
|
+
# Strategy 3: TF-IDF fallback
|
|
117
|
+
clusters = _cluster_with_tfidf(facts, texts, k, min_cluster_size)
|
|
118
|
+
logger.info("[CU] TF-IDF clustering: %d facts → %d clusters", n, len(clusters))
|
|
119
|
+
return clusters
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _cluster_with_graph(
|
|
123
|
+
facts: List[Dict], db, k: int, min_size: int,
|
|
124
|
+
) -> List[Dict[str, Any]]:
|
|
125
|
+
"""
|
|
126
|
+
Cluster facts using graph structure — entity co-occurrence.
|
|
127
|
+
|
|
128
|
+
For each fact, find which entities it connects to via MENTIONS edges
|
|
129
|
+
(or reverse: entities that MENTION this fact). Facts sharing entities
|
|
130
|
+
naturally cluster together.
|
|
131
|
+
|
|
132
|
+
Builds a fact × entity binary matrix, then runs k-means on it.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
import numpy as np
|
|
136
|
+
except ImportError:
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
# Build fact_id → index mapping
|
|
140
|
+
fact_ids = [f.get("id", "") for f in facts]
|
|
141
|
+
fact_idx = {fid: i for i, fid in enumerate(fact_ids) if fid}
|
|
142
|
+
|
|
143
|
+
if not fact_idx:
|
|
144
|
+
return []
|
|
145
|
+
|
|
146
|
+
# Build fact→entity connections via 2-hop: Fact ←STATES← Passage →MENTIONS→ Entity
|
|
147
|
+
# Step 1: Scan edges to build passage→facts and passage→entities maps
|
|
148
|
+
entity_set: Dict[str, int] = {} # entity_id → column index
|
|
149
|
+
fact_entities: Dict[int, List[int]] = defaultdict(list) # fact_row → [entity_cols]
|
|
150
|
+
entity_names: Dict[int, str] = {}
|
|
151
|
+
|
|
152
|
+
passage_to_facts: Dict[str, List[int]] = defaultdict(list) # passage_id → [fact rows]
|
|
153
|
+
passage_to_entities: Dict[str, List[str]] = defaultdict(list) # passage_id → [entity_ids]
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
all_edges = db.get_all_edges()
|
|
157
|
+
except Exception:
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
for edge in all_edges:
|
|
161
|
+
src = getattr(edge, 'source', '')
|
|
162
|
+
tgt = getattr(edge, 'target', '')
|
|
163
|
+
label = getattr(edge, 'label', '') or getattr(edge, 'edge_type', '')
|
|
164
|
+
|
|
165
|
+
# Passage →STATES→ Fact (or Fact →DERIVED_FROM→ Passage)
|
|
166
|
+
if label == "STATES" and tgt in fact_idx:
|
|
167
|
+
passage_to_facts[src].append(fact_idx[tgt])
|
|
168
|
+
elif label == "DERIVED_FROM" and src in fact_idx:
|
|
169
|
+
passage_to_facts[tgt].append(fact_idx[src])
|
|
170
|
+
|
|
171
|
+
# Passage →MENTIONS→ Entity
|
|
172
|
+
if label == "MENTIONS":
|
|
173
|
+
passage_to_entities[src].append(tgt)
|
|
174
|
+
|
|
175
|
+
# Direct Fact →MENTIONS→ Entity (if any exist)
|
|
176
|
+
if label == "MENTIONS" and src in fact_idx:
|
|
177
|
+
eid = tgt
|
|
178
|
+
if eid not in entity_set:
|
|
179
|
+
entity_set[eid] = len(entity_set)
|
|
180
|
+
fact_entities[fact_idx[src]].append(entity_set[eid])
|
|
181
|
+
|
|
182
|
+
# Step 2: Bridge — facts inherit entities from their source passage
|
|
183
|
+
for passage_id, fact_rows in passage_to_facts.items():
|
|
184
|
+
entity_ids = passage_to_entities.get(passage_id, [])
|
|
185
|
+
for eid in entity_ids:
|
|
186
|
+
if eid not in entity_set:
|
|
187
|
+
entity_set[eid] = len(entity_set)
|
|
188
|
+
col = entity_set[eid]
|
|
189
|
+
for row in fact_rows:
|
|
190
|
+
fact_entities[row].append(col)
|
|
191
|
+
|
|
192
|
+
# Step 3: Resolve entity names
|
|
193
|
+
# Build a complete ID→name map from all entity nodes upfront.
|
|
194
|
+
# This handles merged/deduplicated entities where edge targets point to
|
|
195
|
+
# pre-merge IDs that have empty properties (ghost nodes).
|
|
196
|
+
_all_entity_names: Dict[str, str] = {}
|
|
197
|
+
for _label in ("Person", "Organization", "Location", "Event"):
|
|
198
|
+
try:
|
|
199
|
+
for _n in db.get_all_nodes(label=_label):
|
|
200
|
+
_nid = _n.id if hasattr(_n, 'id') else ""
|
|
201
|
+
_props = _n.properties if hasattr(_n, 'properties') else {}
|
|
202
|
+
_name = _props.get("name") or _props.get("title") or ""
|
|
203
|
+
_name = _name.split("\n")[0].strip()[:60]
|
|
204
|
+
if _nid and _name:
|
|
205
|
+
_all_entity_names[_nid] = _name
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
for eid, col in entity_set.items():
|
|
210
|
+
# Try the pre-built map first (fast, handles merged entities)
|
|
211
|
+
name = _all_entity_names.get(eid, "")
|
|
212
|
+
if not name:
|
|
213
|
+
# Fallback: direct node lookup (handles non-standard labels)
|
|
214
|
+
try:
|
|
215
|
+
node = db.get_node(eid)
|
|
216
|
+
if node:
|
|
217
|
+
props = node.properties if hasattr(node, 'properties') else {}
|
|
218
|
+
name = props.get("name") or props.get("title") or ""
|
|
219
|
+
name = name.split("\n")[0].strip()[:60]
|
|
220
|
+
except Exception:
|
|
221
|
+
pass
|
|
222
|
+
entity_names[col] = name if name else eid[:12]
|
|
223
|
+
|
|
224
|
+
if not entity_set or not fact_entities:
|
|
225
|
+
logger.debug("[CU] No graph edges found for facts — skipping graph clustering")
|
|
226
|
+
return []
|
|
227
|
+
|
|
228
|
+
# Build binary fact × entity matrix
|
|
229
|
+
n_facts = len(facts)
|
|
230
|
+
n_entities = len(entity_set)
|
|
231
|
+
mat = np.zeros((n_facts, n_entities), dtype=np.float32)
|
|
232
|
+
|
|
233
|
+
for row, cols in fact_entities.items():
|
|
234
|
+
for col in cols:
|
|
235
|
+
mat[row, col] = 1.0
|
|
236
|
+
|
|
237
|
+
# Weight by IDF — entities mentioned by many facts are less discriminative
|
|
238
|
+
import math as _math
|
|
239
|
+
for col in range(n_entities):
|
|
240
|
+
doc_freq = np.sum(mat[:, col] > 0)
|
|
241
|
+
if doc_freq > 0:
|
|
242
|
+
mat[:, col] *= _math.log(n_facts / doc_freq + 1)
|
|
243
|
+
|
|
244
|
+
# Normalize rows
|
|
245
|
+
norms = np.linalg.norm(mat, axis=1, keepdims=True)
|
|
246
|
+
norms[norms == 0] = 1.0
|
|
247
|
+
mat = mat / norms
|
|
248
|
+
|
|
249
|
+
# Filter out facts with zero entity connections (no graph signal)
|
|
250
|
+
connected = np.where(np.any(mat > 0, axis=1))[0]
|
|
251
|
+
if len(connected) < max(k, n_facts // 3):
|
|
252
|
+
logger.debug("[CU] Only %d/%d facts have graph connections — insufficient", len(connected), n_facts)
|
|
253
|
+
return []
|
|
254
|
+
|
|
255
|
+
labels = _kmeans(mat, k, max_iter=25)
|
|
256
|
+
|
|
257
|
+
# Build clusters with entity-derived topics
|
|
258
|
+
clusters = _build_clusters_from_labels(facts, list(range(n_facts)), labels, min_size)
|
|
259
|
+
|
|
260
|
+
# Enrich topic names with top entity names from each cluster
|
|
261
|
+
for cluster in clusters:
|
|
262
|
+
entity_counts: Counter = Counter()
|
|
263
|
+
for fact in cluster["facts"]:
|
|
264
|
+
fid = fact.get("id", "")
|
|
265
|
+
if fid in fact_idx:
|
|
266
|
+
for col in fact_entities.get(fact_idx[fid], []):
|
|
267
|
+
name = entity_names.get(col, "")
|
|
268
|
+
if name and name != "same-source":
|
|
269
|
+
entity_counts[name] += 1
|
|
270
|
+
if entity_counts:
|
|
271
|
+
# Filter out entity-ID-like names (ent_xxxx) — they're unresolved
|
|
272
|
+
real_names = [(n, c) for n, c in entity_counts.most_common(10)
|
|
273
|
+
if not n.startswith("ent_") and len(n) > 2]
|
|
274
|
+
if real_names:
|
|
275
|
+
top_entities = [name for name, _ in real_names[:3]]
|
|
276
|
+
cluster["topic"] = " / ".join(top_entities)
|
|
277
|
+
cluster["key_entities"] = [name for name, _ in real_names[:5]]
|
|
278
|
+
else:
|
|
279
|
+
# All entity names unresolved — fall back to keyword topic
|
|
280
|
+
cluster["topic"] = _derive_topic(cluster["facts"])
|
|
281
|
+
# Final safety: if topic still contains ent_ IDs, override with keywords
|
|
282
|
+
if "ent_" in cluster.get("topic", ""):
|
|
283
|
+
cluster["topic"] = _derive_topic(cluster["facts"])
|
|
284
|
+
|
|
285
|
+
# Final cleanup: replace any remaining ent_ ID topics with keyword-derived topics
|
|
286
|
+
for cluster in clusters:
|
|
287
|
+
if "ent_" in cluster.get("topic", ""):
|
|
288
|
+
cluster["topic"] = _derive_topic(cluster["facts"])
|
|
289
|
+
|
|
290
|
+
return clusters
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _cluster_with_embeddings(
|
|
294
|
+
facts: List[Dict], texts: List[str], k: int, min_size: int
|
|
295
|
+
) -> List[Dict[str, Any]]:
|
|
296
|
+
"""Cluster facts using cached embedding vectors + k-means."""
|
|
297
|
+
try:
|
|
298
|
+
from ..search.embedding_cache import get_embedding_cache
|
|
299
|
+
cache = get_embedding_cache()
|
|
300
|
+
except Exception:
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
# Retrieve cached embeddings
|
|
304
|
+
vectors = []
|
|
305
|
+
indices = [] # which facts have embeddings
|
|
306
|
+
for i, text in enumerate(texts):
|
|
307
|
+
if not text.strip():
|
|
308
|
+
continue
|
|
309
|
+
vec = cache.get(text)
|
|
310
|
+
if vec is not None:
|
|
311
|
+
vectors.append(vec)
|
|
312
|
+
indices.append(i)
|
|
313
|
+
|
|
314
|
+
# Need at least 50% coverage and enough for k-means
|
|
315
|
+
if len(vectors) < max(k, len(facts) // 2):
|
|
316
|
+
return []
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
import numpy as np
|
|
320
|
+
mat = np.array(vectors, dtype=np.float32)
|
|
321
|
+
# Normalize for cosine distance
|
|
322
|
+
norms = np.linalg.norm(mat, axis=1, keepdims=True)
|
|
323
|
+
norms[norms == 0] = 1.0
|
|
324
|
+
mat = mat / norms
|
|
325
|
+
|
|
326
|
+
labels = _kmeans(mat, k, max_iter=20)
|
|
327
|
+
|
|
328
|
+
return _build_clusters_from_labels(facts, indices, labels, min_size)
|
|
329
|
+
except Exception as e:
|
|
330
|
+
logger.debug("[CU] Embedding clustering failed: %s", e)
|
|
331
|
+
return []
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _cluster_with_tfidf(
|
|
335
|
+
facts: List[Dict], texts: List[str], k: int, min_size: int, _depth: int = 0,
|
|
336
|
+
) -> List[Dict[str, Any]]:
|
|
337
|
+
"""Cluster facts using TF-IDF vectors + k-means. Pure Python, no sklearn."""
|
|
338
|
+
# Build TF-IDF matrix with unigrams + bigrams + entity features
|
|
339
|
+
doc_tokens = [_extract_features(t) for t in texts]
|
|
340
|
+
# Vocabulary: tokens that appear in 2+ docs but <40% of docs
|
|
341
|
+
doc_freq: Dict[str, int] = Counter()
|
|
342
|
+
for tokens in doc_tokens:
|
|
343
|
+
for t in set(tokens):
|
|
344
|
+
doc_freq[t] += 1
|
|
345
|
+
|
|
346
|
+
n_docs = len(doc_tokens)
|
|
347
|
+
# Include all terms appearing in 2+ docs. IDF naturally down-weights common ones.
|
|
348
|
+
# Only exclude terms in >80% of docs (truly universal noise like "report", "news").
|
|
349
|
+
max_df = max(int(n_docs * 0.8), 3)
|
|
350
|
+
vocab = {t: idx for idx, (t, df) in enumerate(
|
|
351
|
+
(t, df) for t, df in doc_freq.items() if 2 <= df <= max_df
|
|
352
|
+
)}
|
|
353
|
+
|
|
354
|
+
if len(vocab) < 3:
|
|
355
|
+
# Not enough distinctive terms — return single cluster
|
|
356
|
+
return [{"topic": _derive_topic(facts), "facts": facts}]
|
|
357
|
+
|
|
358
|
+
import math as _math
|
|
359
|
+
|
|
360
|
+
# Build sparse TF-IDF vectors as dicts
|
|
361
|
+
idf = {t: _math.log(n_docs / df) for t, df in doc_freq.items() if t in vocab}
|
|
362
|
+
vectors = []
|
|
363
|
+
indices = []
|
|
364
|
+
for i, tokens in enumerate(doc_tokens):
|
|
365
|
+
if not tokens:
|
|
366
|
+
continue
|
|
367
|
+
tf = Counter(tokens)
|
|
368
|
+
vec = {}
|
|
369
|
+
for t, count in tf.items():
|
|
370
|
+
if t in vocab:
|
|
371
|
+
vec[vocab[t]] = (count / len(tokens)) * idf.get(t, 1.0)
|
|
372
|
+
if vec:
|
|
373
|
+
vectors.append(vec)
|
|
374
|
+
indices.append(i)
|
|
375
|
+
|
|
376
|
+
if len(vectors) < k:
|
|
377
|
+
return [{"topic": _derive_topic(facts), "facts": facts}]
|
|
378
|
+
|
|
379
|
+
# Convert sparse to dense for k-means
|
|
380
|
+
try:
|
|
381
|
+
import numpy as np
|
|
382
|
+
dim = len(vocab)
|
|
383
|
+
mat = np.zeros((len(vectors), dim), dtype=np.float32)
|
|
384
|
+
for row, vec in enumerate(vectors):
|
|
385
|
+
for col, val in vec.items():
|
|
386
|
+
mat[row, col] = val
|
|
387
|
+
# Normalize
|
|
388
|
+
norms = np.linalg.norm(mat, axis=1, keepdims=True)
|
|
389
|
+
norms[norms == 0] = 1.0
|
|
390
|
+
mat = mat / norms
|
|
391
|
+
|
|
392
|
+
labels = _kmeans(mat, k, max_iter=20)
|
|
393
|
+
return _build_clusters_from_labels(facts, indices, labels, min_size, _depth=_depth)
|
|
394
|
+
except ImportError:
|
|
395
|
+
# No numpy — pure Python fallback with simple keyword grouping
|
|
396
|
+
return _cluster_keyword_fallback(facts, min_size)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _kmeans(mat, k: int, max_iter: int = 20) -> list:
|
|
400
|
+
"""Lightweight k-means on a numpy matrix. Returns list of cluster labels."""
|
|
401
|
+
import numpy as np
|
|
402
|
+
n = mat.shape[0]
|
|
403
|
+
k = min(k, n)
|
|
404
|
+
|
|
405
|
+
# K-means++ initialization
|
|
406
|
+
rng = np.random.RandomState(42)
|
|
407
|
+
centroids = [mat[rng.randint(n)]]
|
|
408
|
+
for _ in range(k - 1):
|
|
409
|
+
dists = np.array([min(np.dot(mat[i] - c, mat[i] - c) for c in centroids) for i in range(n)])
|
|
410
|
+
probs = dists / (dists.sum() + 1e-12)
|
|
411
|
+
idx = rng.choice(n, p=probs)
|
|
412
|
+
centroids.append(mat[idx])
|
|
413
|
+
centroids = np.array(centroids)
|
|
414
|
+
|
|
415
|
+
labels = np.zeros(n, dtype=int)
|
|
416
|
+
for _ in range(max_iter):
|
|
417
|
+
# Assign
|
|
418
|
+
# Cosine similarity = dot product on normalized vectors
|
|
419
|
+
sims = mat @ centroids.T # (n, k)
|
|
420
|
+
new_labels = np.argmax(sims, axis=1)
|
|
421
|
+
if np.array_equal(new_labels, labels):
|
|
422
|
+
break
|
|
423
|
+
labels = new_labels
|
|
424
|
+
# Update centroids
|
|
425
|
+
for j in range(k):
|
|
426
|
+
members = mat[labels == j]
|
|
427
|
+
if len(members) > 0:
|
|
428
|
+
c = members.mean(axis=0)
|
|
429
|
+
norm = np.linalg.norm(c)
|
|
430
|
+
centroids[j] = c / norm if norm > 0 else c
|
|
431
|
+
|
|
432
|
+
return labels.tolist()
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _build_clusters_from_labels(
|
|
436
|
+
facts: List[Dict], indices: List[int], labels: list, min_size: int,
|
|
437
|
+
_depth: int = 0,
|
|
438
|
+
) -> List[Dict[str, Any]]:
|
|
439
|
+
"""Group facts by cluster labels and derive topics.
|
|
440
|
+
Recursively splits oversized clusters (>40% of total)."""
|
|
441
|
+
groups: Dict[int, List[Dict]] = defaultdict(list)
|
|
442
|
+
for idx, label in zip(indices, labels):
|
|
443
|
+
groups[label].append(facts[idx])
|
|
444
|
+
|
|
445
|
+
# Also assign un-indexed facts to nearest cluster by keyword overlap
|
|
446
|
+
indexed_set = set(indices)
|
|
447
|
+
for i, fact in enumerate(facts):
|
|
448
|
+
if i not in indexed_set:
|
|
449
|
+
if groups:
|
|
450
|
+
best = max(groups, key=lambda g: len(groups[g]))
|
|
451
|
+
groups[best].append(fact)
|
|
452
|
+
|
|
453
|
+
total = len(facts)
|
|
454
|
+
max_cluster_pct = 0.4 # No single cluster should be >40% of total
|
|
455
|
+
|
|
456
|
+
clusters = []
|
|
457
|
+
for label, cluster_facts in sorted(groups.items(), key=lambda x: -len(x[1])):
|
|
458
|
+
if len(cluster_facts) < min_size:
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
# Recursively split oversized clusters (max 2 levels deep)
|
|
462
|
+
if len(cluster_facts) > total * max_cluster_pct and _depth < 2 and len(cluster_facts) >= 6:
|
|
463
|
+
sub_texts = [
|
|
464
|
+
f.get("properties", {}).get("statement", "")
|
|
465
|
+
or f.get("properties", {}).get("name", "")
|
|
466
|
+
for f in cluster_facts
|
|
467
|
+
]
|
|
468
|
+
import math
|
|
469
|
+
sub_k = max(2, min(5, len(cluster_facts) // 10))
|
|
470
|
+
sub_clusters = _cluster_with_tfidf(cluster_facts, sub_texts, sub_k, min_size, _depth=_depth + 1)
|
|
471
|
+
if len(sub_clusters) > 1:
|
|
472
|
+
clusters.extend(sub_clusters)
|
|
473
|
+
continue
|
|
474
|
+
|
|
475
|
+
topic = _derive_topic(cluster_facts)
|
|
476
|
+
clusters.append({"topic": topic, "facts": cluster_facts})
|
|
477
|
+
|
|
478
|
+
# If min_size filtered everything, return top clusters with relaxed threshold
|
|
479
|
+
if not clusters and groups:
|
|
480
|
+
for label, cluster_facts in sorted(groups.items(), key=lambda x: -len(x[1])):
|
|
481
|
+
if len(cluster_facts) >= 2:
|
|
482
|
+
topic = _derive_topic(cluster_facts)
|
|
483
|
+
clusters.append({"topic": topic, "facts": cluster_facts})
|
|
484
|
+
if len(clusters) >= 3:
|
|
485
|
+
break
|
|
486
|
+
|
|
487
|
+
return clusters
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _derive_topic(facts: List[Dict]) -> str:
|
|
491
|
+
"""Derive a human-readable topic label from the most frequent keywords."""
|
|
492
|
+
all_text = " ".join(
|
|
493
|
+
f.get("properties", {}).get("statement", "") for f in facts
|
|
494
|
+
)
|
|
495
|
+
freq = Counter(_tokenize(all_text))
|
|
496
|
+
# Filter out very common words within this cluster (>60% of facts)
|
|
497
|
+
n = len(facts)
|
|
498
|
+
top = [(kw, c) for kw, c in freq.most_common(10) if c < n * 0.6]
|
|
499
|
+
if not top:
|
|
500
|
+
top = freq.most_common(3)
|
|
501
|
+
top_kws = [kw for kw, _ in top[:3]]
|
|
502
|
+
return " ".join(w.capitalize() for w in top_kws) or "General"
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _cluster_keyword_fallback(facts: List[Dict], min_size: int) -> List[Dict[str, Any]]:
|
|
506
|
+
"""Simple keyword-based grouping when numpy is unavailable. Uses TF-IDF scores
|
|
507
|
+
to pick the top distinctive keyword per fact, then groups by that keyword."""
|
|
508
|
+
doc_tokens = [_tokenize(
|
|
509
|
+
f.get("properties", {}).get("statement", "") or f.get("properties", {}).get("name", "")
|
|
510
|
+
) for f in facts]
|
|
511
|
+
|
|
512
|
+
doc_freq: Dict[str, int] = Counter()
|
|
513
|
+
for tokens in doc_tokens:
|
|
514
|
+
for t in set(tokens):
|
|
515
|
+
doc_freq[t] += 1
|
|
516
|
+
|
|
517
|
+
import math as _math
|
|
518
|
+
n_docs = len(facts)
|
|
519
|
+
idf = {t: _math.log(n_docs / df) for t, df in doc_freq.items() if df >= 2}
|
|
520
|
+
|
|
521
|
+
groups: Dict[str, List[Dict]] = defaultdict(list)
|
|
522
|
+
for i, tokens in enumerate(doc_tokens):
|
|
523
|
+
if not tokens:
|
|
524
|
+
groups["general"].append(facts[i])
|
|
525
|
+
continue
|
|
526
|
+
tf = Counter(tokens)
|
|
527
|
+
# Pick the keyword with highest TF-IDF score as this fact's "topic"
|
|
528
|
+
best_kw = max(
|
|
529
|
+
(t for t in tokens if t in idf),
|
|
530
|
+
key=lambda t: (tf[t] / len(tokens)) * idf[t],
|
|
531
|
+
default="general",
|
|
532
|
+
)
|
|
533
|
+
groups[best_kw].append(facts[i])
|
|
534
|
+
|
|
535
|
+
# Merge tiny groups into "other"
|
|
536
|
+
clusters = []
|
|
537
|
+
other: List[Dict] = []
|
|
538
|
+
for kw, group_facts in sorted(groups.items(), key=lambda x: -len(x[1])):
|
|
539
|
+
if len(group_facts) >= min_size:
|
|
540
|
+
clusters.append({"topic": kw.capitalize(), "facts": group_facts})
|
|
541
|
+
else:
|
|
542
|
+
other.extend(group_facts)
|
|
543
|
+
if other and len(other) >= min_size:
|
|
544
|
+
clusters.append({"topic": _derive_topic(other), "facts": other})
|
|
545
|
+
|
|
546
|
+
return clusters or [{"topic": _derive_topic(facts), "facts": facts}]
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# ── CU Builder ───────────────────────────────────────────────────────────────
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def build_context_unit(
|
|
553
|
+
topic: str,
|
|
554
|
+
facts: List[Dict[str, Any]],
|
|
555
|
+
entities: Optional[List[Dict[str, Any]]] = None,
|
|
556
|
+
events: Optional[List[Dict[str, Any]]] = None,
|
|
557
|
+
use_llm: bool = False,
|
|
558
|
+
llm_client: Any = None,
|
|
559
|
+
cu_config: Any = None,
|
|
560
|
+
) -> Dict[str, Any]:
|
|
561
|
+
"""
|
|
562
|
+
Build a ContextUnit dict from a topic, facts, entities, and events.
|
|
563
|
+
|
|
564
|
+
Parameters
|
|
565
|
+
----------
|
|
566
|
+
topic : Human-readable label for this CU's subject matter.
|
|
567
|
+
facts : List of Fact node dicts (with 'id' and 'properties').
|
|
568
|
+
entities : List of Person/Organization/Location node dicts.
|
|
569
|
+
events : List of Event node dicts.
|
|
570
|
+
use_llm : If True and llm_client is provided, synthesize claim via LLM.
|
|
571
|
+
llm_client : Optional LLM client (must expose .complete(prompt) → str).
|
|
572
|
+
cu_config : Optional ContextUnitConfig from schema (domain-aware CU rules).
|
|
573
|
+
|
|
574
|
+
Returns
|
|
575
|
+
-------
|
|
576
|
+
A ContextUnit dict with keys:
|
|
577
|
+
id, label, properties (topic, claim, confidence, questions_answered,
|
|
578
|
+
next_clues, created_at), evidence_ids, actor_ids
|
|
579
|
+
"""
|
|
580
|
+
entities = entities or []
|
|
581
|
+
events = events or []
|
|
582
|
+
|
|
583
|
+
# Extract raw text
|
|
584
|
+
statements = [
|
|
585
|
+
f.get("properties", {}).get("statement", "")
|
|
586
|
+
for f in facts
|
|
587
|
+
if f.get("properties", {}).get("statement")
|
|
588
|
+
]
|
|
589
|
+
actor_names = [
|
|
590
|
+
e.get("properties", {}).get("name", "")
|
|
591
|
+
for e in entities
|
|
592
|
+
if e.get("properties", {}).get("name")
|
|
593
|
+
]
|
|
594
|
+
event_names = [
|
|
595
|
+
ev.get("properties", {}).get("name", "")
|
|
596
|
+
for ev in events
|
|
597
|
+
if ev.get("properties", {}).get("name")
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
evidence_ids = [f.get("id") for f in facts if f.get("id")]
|
|
601
|
+
actor_ids = [e.get("id") for e in entities if e.get("id")]
|
|
602
|
+
|
|
603
|
+
# Build properties via LLM or heuristic
|
|
604
|
+
if use_llm and llm_client is not None:
|
|
605
|
+
props = _build_cu_with_llm(topic, statements, actor_names, event_names, llm_client)
|
|
606
|
+
else:
|
|
607
|
+
props = _build_cu_heuristic(topic, statements, actor_names, event_names)
|
|
608
|
+
|
|
609
|
+
# Sanitize topic — replace unresolved entity IDs with keyword-derived topic
|
|
610
|
+
if "ent_" in topic:
|
|
611
|
+
all_text = " ".join(statements[:20])
|
|
612
|
+
freq = Counter(_tokenize(all_text))
|
|
613
|
+
top_kws = [kw for kw, _ in freq.most_common(3)]
|
|
614
|
+
topic = " ".join(w.capitalize() for w in top_kws) or topic
|
|
615
|
+
|
|
616
|
+
props["topic"] = topic
|
|
617
|
+
|
|
618
|
+
# Apply domain-specific overrides from schema's context_unit config
|
|
619
|
+
# (runs after topic is set so claim_template can reference it)
|
|
620
|
+
if cu_config is not None:
|
|
621
|
+
props = _apply_cu_config(props, facts, cu_config)
|
|
622
|
+
|
|
623
|
+
cu_id = f"cu_{uuid.uuid4().hex[:12]}"
|
|
624
|
+
return {
|
|
625
|
+
"id": cu_id,
|
|
626
|
+
"label": "ContextUnit",
|
|
627
|
+
"properties": props,
|
|
628
|
+
"evidence_ids": evidence_ids,
|
|
629
|
+
"actor_ids": actor_ids,
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _build_cu_with_llm(
|
|
634
|
+
topic: str,
|
|
635
|
+
statements: List[str],
|
|
636
|
+
actor_names: List[str],
|
|
637
|
+
event_names: List[str],
|
|
638
|
+
llm_client: Any,
|
|
639
|
+
) -> Dict[str, Any]:
|
|
640
|
+
"""Synthesize CU properties using an LLM client."""
|
|
641
|
+
facts_text = "\n".join(f"- {s}" for s in statements[:10])
|
|
642
|
+
actors_text = ", ".join(actor_names) if actor_names else "unknown"
|
|
643
|
+
prompt = (
|
|
644
|
+
f"You are a news analyst. Given the following facts about '{topic}', "
|
|
645
|
+
f"write:\n1. A single-sentence claim summarising the situation.\n"
|
|
646
|
+
f"2. Two questions this cluster of facts answers.\n"
|
|
647
|
+
f"3. One next clue to follow up.\n\n"
|
|
648
|
+
f"Facts:\n{facts_text}\n\nActors: {actors_text}\n\n"
|
|
649
|
+
f"Respond as JSON with keys: claim, questions_answered (list), next_clues (list)."
|
|
650
|
+
)
|
|
651
|
+
try:
|
|
652
|
+
raw = llm_client.complete(prompt)
|
|
653
|
+
import json as _json
|
|
654
|
+
data = _json.loads(raw)
|
|
655
|
+
confidence = min(0.95, 0.5 + 0.05 * len(statements))
|
|
656
|
+
return {
|
|
657
|
+
"claim": data.get("claim", statements[0] if statements else topic),
|
|
658
|
+
"confidence": confidence,
|
|
659
|
+
"questions_answered": data.get("questions_answered", []),
|
|
660
|
+
"next_clues": data.get("next_clues", []),
|
|
661
|
+
}
|
|
662
|
+
except Exception as exc:
|
|
663
|
+
logger.warning("LLM synthesis failed, falling back to heuristic: %s", exc)
|
|
664
|
+
return _build_cu_heuristic(topic, statements, actor_names, event_names)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def _build_cu_heuristic(
|
|
668
|
+
topic: str,
|
|
669
|
+
statements: List[str],
|
|
670
|
+
actor_names: List[str],
|
|
671
|
+
event_names: List[str],
|
|
672
|
+
) -> Dict[str, Any]:
|
|
673
|
+
"""
|
|
674
|
+
Build CU properties without an LLM.
|
|
675
|
+
|
|
676
|
+
- claim : first 1-2 fact statements joined
|
|
677
|
+
- confidence : 0.3 + 0.1 * num_facts, capped at 0.95
|
|
678
|
+
- questions_answered : derived from topic and actor names
|
|
679
|
+
- next_clues : derived from actor and event names
|
|
680
|
+
"""
|
|
681
|
+
# Claim: concatenate first two statements
|
|
682
|
+
if statements:
|
|
683
|
+
claim_parts = [s.rstrip(".") for s in statements[:2]]
|
|
684
|
+
claim = "; ".join(claim_parts) + "."
|
|
685
|
+
else:
|
|
686
|
+
claim = f"Situation update on {topic}."
|
|
687
|
+
|
|
688
|
+
confidence = min(0.95, 0.3 + 0.1 * len(statements))
|
|
689
|
+
|
|
690
|
+
# Questions answered
|
|
691
|
+
questions: list[str] = [f"What is the current situation regarding {topic}?"]
|
|
692
|
+
for name in actor_names[:2]:
|
|
693
|
+
questions.append(f"What is {name}'s role in {topic}?")
|
|
694
|
+
|
|
695
|
+
# Next clues
|
|
696
|
+
next_clues: list[str] = []
|
|
697
|
+
for name in actor_names[:2]:
|
|
698
|
+
next_clues.append(f"Follow up on {name}'s statements.")
|
|
699
|
+
for name in event_names[:2]:
|
|
700
|
+
next_clues.append(f"Track developments in {name}.")
|
|
701
|
+
if not next_clues:
|
|
702
|
+
next_clues.append(f"Search for latest updates on {topic}.")
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
"claim": claim,
|
|
706
|
+
"confidence": confidence,
|
|
707
|
+
"questions_answered": questions,
|
|
708
|
+
"next_clues": next_clues,
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _apply_cu_config(
|
|
713
|
+
props: Dict[str, Any],
|
|
714
|
+
facts: List[Dict[str, Any]],
|
|
715
|
+
cu_config: Any,
|
|
716
|
+
) -> Dict[str, Any]:
|
|
717
|
+
"""Apply domain-specific ContextUnitConfig overrides to CU properties.
|
|
718
|
+
|
|
719
|
+
- confidence_weights: replaces flat 0.1-per-fact with evidence-type-weighted scoring
|
|
720
|
+
- claim_template: domain-specific claim phrasing (if non-empty)
|
|
721
|
+
- domain_questions: prepends domain seed questions to questions_answered
|
|
722
|
+
"""
|
|
723
|
+
# 1. Domain-aware confidence scoring
|
|
724
|
+
weights = getattr(cu_config, "confidence_weights", {})
|
|
725
|
+
if weights:
|
|
726
|
+
total_weight = 0.0
|
|
727
|
+
matched = 0
|
|
728
|
+
for fact in facts:
|
|
729
|
+
fprops = fact.get("properties", {})
|
|
730
|
+
# Check fact's evidence_level, evidence_type, type, or label
|
|
731
|
+
ev_type = (
|
|
732
|
+
fprops.get("evidence_level")
|
|
733
|
+
or fprops.get("evidence_type")
|
|
734
|
+
or fprops.get("type", "")
|
|
735
|
+
).lower().replace(" ", "_")
|
|
736
|
+
w = weights.get(ev_type, 0.0)
|
|
737
|
+
if w > 0:
|
|
738
|
+
total_weight += w
|
|
739
|
+
matched += 1
|
|
740
|
+
else:
|
|
741
|
+
# Unclassified evidence gets a small default weight
|
|
742
|
+
total_weight += 0.05
|
|
743
|
+
# Base 0.2 + accumulated evidence weight, capped at 0.95
|
|
744
|
+
props["confidence"] = min(0.95, 0.2 + total_weight)
|
|
745
|
+
if matched > 0:
|
|
746
|
+
props["evidence_quality"] = f"{matched}/{len(facts)} typed"
|
|
747
|
+
|
|
748
|
+
# 2. Claim template
|
|
749
|
+
template = getattr(cu_config, "claim_template", "")
|
|
750
|
+
if template and props.get("claim"):
|
|
751
|
+
try:
|
|
752
|
+
props["claim"] = template.format(
|
|
753
|
+
topic=props.get("topic", ""),
|
|
754
|
+
claim=props["claim"],
|
|
755
|
+
evidence_type=props.get("evidence_quality", "mixed"),
|
|
756
|
+
fact_count=len(facts),
|
|
757
|
+
exposure="",
|
|
758
|
+
)
|
|
759
|
+
except (KeyError, IndexError):
|
|
760
|
+
pass # template has placeholders we can't fill — keep original claim
|
|
761
|
+
|
|
762
|
+
# 3. Domain questions — prepend schema-defined questions, deduplicate
|
|
763
|
+
domain_qs = getattr(cu_config, "domain_questions", [])
|
|
764
|
+
if domain_qs:
|
|
765
|
+
existing = set(props.get("questions_answered", []))
|
|
766
|
+
new_qs = [q for q in domain_qs if q not in existing]
|
|
767
|
+
props["questions_answered"] = new_qs + props.get("questions_answered", [])
|
|
768
|
+
|
|
769
|
+
return props
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
# ── Intent Matching ──────────────────────────────────────────────────────────
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def match_intent(
|
|
776
|
+
query: str,
|
|
777
|
+
cus: List[Dict[str, Any]],
|
|
778
|
+
limit: int = 3,
|
|
779
|
+
) -> List[Dict[str, Any]]:
|
|
780
|
+
"""
|
|
781
|
+
Find CUs whose topic or questions_answered overlap with a query.
|
|
782
|
+
|
|
783
|
+
Scoring:
|
|
784
|
+
- Each query token matched in CU.topic contributes 2 points.
|
|
785
|
+
- Each query token matched in any questions_answered string contributes 1 point.
|
|
786
|
+
|
|
787
|
+
Returns CUs with score > 0, ranked descending, up to `limit`.
|
|
788
|
+
"""
|
|
789
|
+
query_tokens = set(_tokenize(query))
|
|
790
|
+
if not query_tokens:
|
|
791
|
+
return []
|
|
792
|
+
|
|
793
|
+
scored: list[tuple[int, dict]] = []
|
|
794
|
+
for cu in cus:
|
|
795
|
+
props = cu.get("properties", {})
|
|
796
|
+
score = 0
|
|
797
|
+
|
|
798
|
+
topic_tokens = set(_tokenize(props.get("topic", "")))
|
|
799
|
+
score += 2 * len(query_tokens & topic_tokens)
|
|
800
|
+
|
|
801
|
+
for q in props.get("questions_answered", []):
|
|
802
|
+
q_tokens = set(_tokenize(q))
|
|
803
|
+
score += len(query_tokens & q_tokens)
|
|
804
|
+
|
|
805
|
+
if score > 0:
|
|
806
|
+
scored.append((score, cu))
|
|
807
|
+
|
|
808
|
+
scored.sort(key=lambda x: x[0], reverse=True)
|
|
809
|
+
return [cu for _, cu in scored[:limit]]
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
# ── Formatter ────────────────────────────────────────────────────────────────
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def format_cu_for_agent(
|
|
816
|
+
cu: Dict[str, Any],
|
|
817
|
+
zoom: str = "full",
|
|
818
|
+
cu_config: Any = None,
|
|
819
|
+
) -> str:
|
|
820
|
+
"""Render a ContextUnit at a specific zoom level.
|
|
821
|
+
|
|
822
|
+
Zoom levels (like Google Maps tile resolution):
|
|
823
|
+
headline — ~20 tokens: topic + short claim. For instant/fast tiers.
|
|
824
|
+
summary — ~80 tokens: claim + evidence count + top questions. For standard tier.
|
|
825
|
+
full — ~200 tokens: everything including evidence IDs. For deep tier.
|
|
826
|
+
|
|
827
|
+
If cu_config has custom zoom_levels templates, those are used instead.
|
|
828
|
+
"""
|
|
829
|
+
props = cu.get("properties", {})
|
|
830
|
+
topic = props.get("topic", "Unknown")
|
|
831
|
+
claim = props.get("claim", "")
|
|
832
|
+
confidence = props.get("confidence", 0.0)
|
|
833
|
+
questions = props.get("questions_answered", [])
|
|
834
|
+
clues = props.get("next_clues", [])
|
|
835
|
+
evidence = cu.get("evidence_ids", [])
|
|
836
|
+
|
|
837
|
+
# Try schema-defined zoom template first
|
|
838
|
+
if cu_config is not None:
|
|
839
|
+
templates = getattr(cu_config, "zoom_levels", {})
|
|
840
|
+
template = templates.get(zoom, "")
|
|
841
|
+
if template:
|
|
842
|
+
try:
|
|
843
|
+
claim_short = claim.split(";")[0].split(".")[0].strip()
|
|
844
|
+
return template.format(
|
|
845
|
+
topic=topic,
|
|
846
|
+
confidence=confidence,
|
|
847
|
+
claim=claim,
|
|
848
|
+
claim_short=claim_short[:80],
|
|
849
|
+
questions="; ".join(questions[:3]),
|
|
850
|
+
questions_short="; ".join(q[:40] for q in questions[:2]),
|
|
851
|
+
clues="; ".join(clues[:2]),
|
|
852
|
+
evidence_ids=", ".join(evidence[:5]),
|
|
853
|
+
evidence_count=len(evidence),
|
|
854
|
+
)
|
|
855
|
+
except (KeyError, IndexError, ValueError):
|
|
856
|
+
pass # fall through to default formatting
|
|
857
|
+
|
|
858
|
+
# Default zoom formatting
|
|
859
|
+
if zoom == "headline":
|
|
860
|
+
claim_short = claim.split(";")[0].split(".")[0].strip()
|
|
861
|
+
return f"[{topic}] {claim_short[:80]} ({confidence:.0%})"
|
|
862
|
+
|
|
863
|
+
if zoom == "summary":
|
|
864
|
+
lines = [
|
|
865
|
+
f"[{topic}] ({confidence:.0%})",
|
|
866
|
+
f" {claim[:150]}",
|
|
867
|
+
]
|
|
868
|
+
if evidence:
|
|
869
|
+
lines.append(f" Evidence: {len(evidence)} sources")
|
|
870
|
+
if questions:
|
|
871
|
+
lines.append(f" Answers: {'; '.join(q[:40] for q in questions[:2])}")
|
|
872
|
+
return "\n".join(lines)
|
|
873
|
+
|
|
874
|
+
# full (default)
|
|
875
|
+
lines = [
|
|
876
|
+
f"[ContextUnit: {topic}]",
|
|
877
|
+
f"Claim: {claim}",
|
|
878
|
+
f"Confidence: {confidence:.0%}",
|
|
879
|
+
]
|
|
880
|
+
if questions:
|
|
881
|
+
lines.append("Answers:")
|
|
882
|
+
for q in questions:
|
|
883
|
+
lines.append(f" - {q}")
|
|
884
|
+
if clues:
|
|
885
|
+
lines.append("Next clues:")
|
|
886
|
+
for c in clues:
|
|
887
|
+
lines.append(f" - {c}")
|
|
888
|
+
if evidence:
|
|
889
|
+
lines.append(f"Evidence: {', '.join(evidence)}")
|
|
890
|
+
|
|
891
|
+
return "\n".join(lines)
|