apsimo 1.3.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.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""World-model expectation resolvers (Mind M3a x world model).
|
|
2
|
+
|
|
3
|
+
The expectation engine keeps its store generic and lets subsystems register
|
|
4
|
+
how a class of predictions gets checked against reality. This module supplies
|
|
5
|
+
the two world-model classes:
|
|
6
|
+
|
|
7
|
+
- ``world-relationship:`` -- "this relationship is still active at the
|
|
8
|
+
horizon". ``detail`` carries ``source_id``/``target_id`` and optionally
|
|
9
|
+
``relationship_type``.
|
|
10
|
+
- ``world-property:`` -- "this entity property still holds this value at the
|
|
11
|
+
horizon". ``detail`` carries ``entity_id``/``key``/``value``.
|
|
12
|
+
|
|
13
|
+
Resolvers return True (hit), False (miss), or None when the world model
|
|
14
|
+
cannot see the subject -- the prediction then stays pending and eventually
|
|
15
|
+
goes ``unresolved``, which is excluded from calibration (never a fabricated
|
|
16
|
+
miss). The world store is fetched lazily at resolve time so boot order does
|
|
17
|
+
not matter, and the async store API is bridged the same way the briefing
|
|
18
|
+
aggregators do it (``asyncio.run`` off-loop, a worker thread on-loop).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import concurrent.futures
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
RELATIONSHIP_PREFIX = "world-relationship:"
|
|
31
|
+
PROPERTY_PREFIX = "world-property:"
|
|
32
|
+
|
|
33
|
+
# Historical edge-survival bookkeeping is self-consistency, not an
|
|
34
|
+
# independently observed causal outcome. Retain old rows/functions for audit.
|
|
35
|
+
CAUSAL_PREFIX = "world-causal:"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _world_store() -> Any:
|
|
39
|
+
try:
|
|
40
|
+
from apsimo.api.routers.host import _world_store as ws
|
|
41
|
+
return ws
|
|
42
|
+
except Exception:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _run_async(coro: Any) -> Any:
|
|
47
|
+
"""Run a store coroutine from the engine's synchronous resolver path."""
|
|
48
|
+
try:
|
|
49
|
+
asyncio.get_running_loop()
|
|
50
|
+
except RuntimeError:
|
|
51
|
+
return asyncio.run(coro)
|
|
52
|
+
# Already on an event loop (the autonomy phase) — blocking on this loop
|
|
53
|
+
# would deadlock, so complete the coroutine on its own loop in a worker.
|
|
54
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
|
|
55
|
+
return ex.submit(asyncio.run, coro).result(timeout=10.0)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _norm(v: Any) -> str:
|
|
59
|
+
return str(v if v is not None else "").strip().lower()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_relationship_still_active(prediction: Any) -> Optional[bool]:
|
|
63
|
+
"""Hit when at least one matching relationship is still active.
|
|
64
|
+
|
|
65
|
+
Causal-typed predictions are NOT resolved here (query-only guard,
|
|
66
|
+
H2.5): a causal edge must never be scored true/false through the
|
|
67
|
+
generic relationship machinery — its falsifiability path is the causal
|
|
68
|
+
subsystem's own. Returning None leaves the prediction unresolved,
|
|
69
|
+
which calibration excludes (never a fabricated hit or miss).
|
|
70
|
+
"""
|
|
71
|
+
store = _world_store()
|
|
72
|
+
detail = getattr(prediction, "detail", None) or {}
|
|
73
|
+
source_id = detail.get("source_id")
|
|
74
|
+
target_id = detail.get("target_id")
|
|
75
|
+
if store is None or not source_id or not target_id:
|
|
76
|
+
return None
|
|
77
|
+
try:
|
|
78
|
+
from apsimo.world_model.causal_policy import is_causal
|
|
79
|
+
if is_causal(detail.get("relationship_type") or ""):
|
|
80
|
+
return None
|
|
81
|
+
except Exception:
|
|
82
|
+
return None
|
|
83
|
+
rels = _run_async(store.query_relationships(
|
|
84
|
+
source_id=source_id, target_id=target_id,
|
|
85
|
+
relationship_type=detail.get("relationship_type") or None,
|
|
86
|
+
active_only=False, min_confidence=0.0, limit=50))
|
|
87
|
+
if not rels:
|
|
88
|
+
# never observed -> the world model cannot score this prediction
|
|
89
|
+
return None
|
|
90
|
+
return any(getattr(r, "is_active", False) for r in rels)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def resolve_property_unchanged(prediction: Any) -> Optional[bool]:
|
|
94
|
+
"""Hit when the entity property still equals the predicted value."""
|
|
95
|
+
store = _world_store()
|
|
96
|
+
detail = getattr(prediction, "detail", None) or {}
|
|
97
|
+
entity_id = detail.get("entity_id")
|
|
98
|
+
key = detail.get("key")
|
|
99
|
+
if store is None or not entity_id or not key or "value" not in detail:
|
|
100
|
+
return None
|
|
101
|
+
entity = _run_async(store.get_entity(entity_id, min_confidence=0.0))
|
|
102
|
+
if entity is None:
|
|
103
|
+
return None
|
|
104
|
+
props = getattr(entity, "properties", None) or {}
|
|
105
|
+
if key not in props:
|
|
106
|
+
# the property is no longer tracked at all — that is a visibility
|
|
107
|
+
# loss, not evidence the value changed; leave it unresolvable
|
|
108
|
+
return None
|
|
109
|
+
return _norm(props.get(key)) == _norm(detail.get("value"))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def resolve_causal_edge(prediction: Any) -> Optional[bool]:
|
|
113
|
+
"""Historical self-consistency only; never predictive calibration.
|
|
114
|
+
|
|
115
|
+
HIT: the edge still exists, is active, holds at least the confidence it
|
|
116
|
+
had when the prediction was made, and no opposing edge (H2.3 polarity)
|
|
117
|
+
exists on the same ordered pair.
|
|
118
|
+
MISS: the edge decayed below its creation confidence, was deleted or
|
|
119
|
+
deactivated, or acquired an active opposing edge.
|
|
120
|
+
None: the world store cannot be seen at all (never a fabricated miss).
|
|
121
|
+
"""
|
|
122
|
+
store = _world_store()
|
|
123
|
+
detail = getattr(prediction, "detail", None) or {}
|
|
124
|
+
edge_id = detail.get("edge_id")
|
|
125
|
+
source_id = detail.get("source_id")
|
|
126
|
+
target_id = detail.get("target_id")
|
|
127
|
+
rel_type = str(detail.get("relationship_type") or "").upper()
|
|
128
|
+
try:
|
|
129
|
+
conf_floor = float(detail.get("confidence_at_creation"))
|
|
130
|
+
except (TypeError, ValueError):
|
|
131
|
+
return None
|
|
132
|
+
if store is None or not edge_id or not source_id or not target_id\
|
|
133
|
+
or not rel_type:
|
|
134
|
+
return None
|
|
135
|
+
try:
|
|
136
|
+
rels = _run_async(store.query_relationships(
|
|
137
|
+
source_id=source_id, target_id=target_id,
|
|
138
|
+
relationship_type=rel_type, min_confidence=0.0, limit=50))
|
|
139
|
+
except Exception:
|
|
140
|
+
return None # store unreachable -> unseen, not a miss
|
|
141
|
+
edge = next((r for r in rels or [] if r.id == edge_id), None)
|
|
142
|
+
if edge is None:
|
|
143
|
+
# fall back to same pair + type (the claim, if not the row)
|
|
144
|
+
edge = (rels or [None])[0]
|
|
145
|
+
if edge is None:
|
|
146
|
+
return False # deleted — the claim did not survive
|
|
147
|
+
if getattr(edge, "valid_to", None) is not None:
|
|
148
|
+
return False # deactivated
|
|
149
|
+
if float(getattr(edge, "confidence", 0.0) or 0.0) < conf_floor:
|
|
150
|
+
return False # decayed below its creation confidence
|
|
151
|
+
# Opposing edge on the same ordered pair (H2.3 polarity sets)?
|
|
152
|
+
try:
|
|
153
|
+
from apsimo.world_model.causal_maintenance import (
|
|
154
|
+
NEGATIVE_CAUSAL, POSITIVE_CAUSAL,
|
|
155
|
+
)
|
|
156
|
+
opposing = (NEGATIVE_CAUSAL if rel_type in POSITIVE_CAUSAL
|
|
157
|
+
else POSITIVE_CAUSAL if rel_type in NEGATIVE_CAUSAL
|
|
158
|
+
else frozenset())
|
|
159
|
+
for opp_type in sorted(opposing):
|
|
160
|
+
opp = _run_async(store.query_relationships(
|
|
161
|
+
source_id=source_id, target_id=target_id,
|
|
162
|
+
relationship_type=opp_type, min_confidence=0.0, limit=10))
|
|
163
|
+
if any(getattr(o, "valid_to", None) is None for o in opp or []):
|
|
164
|
+
return False # opposed
|
|
165
|
+
except Exception:
|
|
166
|
+
return None # cannot verify the no-opposition clause -> unseen
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def register_world_resolvers(engine: Any) -> None:
|
|
171
|
+
"""Attach the world-model resolvers to an ExpectationEngine."""
|
|
172
|
+
if engine is None:
|
|
173
|
+
return
|
|
174
|
+
engine.register_resolver(RELATIONSHIP_PREFIX,
|
|
175
|
+
resolve_relationship_still_active)
|
|
176
|
+
engine.register_resolver(PROPERTY_PREFIX, resolve_property_unchanged)
|
|
177
|
+
# Causal edge survival is deliberately not registered as outcome truth.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""World model entity extraction from multiple document formats."""
|
|
2
|
+
|
|
3
|
+
from apsimo.world_model.extraction.base import FormatExtractor
|
|
4
|
+
from apsimo.world_model.extraction.detector import FormatDetector
|
|
5
|
+
from apsimo.world_model.extraction.pipeline import ExtractionPipeline
|
|
6
|
+
|
|
7
|
+
__all__ = ["FormatExtractor", "FormatDetector", "ExtractionPipeline"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Base class for format extractors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ExtractedEntity:
|
|
13
|
+
"""An entity extracted from a document."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
name: str,
|
|
18
|
+
entity_type: str,
|
|
19
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
20
|
+
source_offset: Optional[int] = None,
|
|
21
|
+
confidence: float = 1.0,
|
|
22
|
+
):
|
|
23
|
+
self.name = name
|
|
24
|
+
self.entity_type = entity_type
|
|
25
|
+
self.attributes = attributes or {}
|
|
26
|
+
self.source_offset = source_offset
|
|
27
|
+
self.confidence = confidence
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict:
|
|
30
|
+
return {
|
|
31
|
+
"name": self.name,
|
|
32
|
+
"type": self.entity_type,
|
|
33
|
+
"attributes": self.attributes,
|
|
34
|
+
"confidence": self.confidence,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FormatExtractor(ABC):
|
|
39
|
+
"""Base class for document format extractors.
|
|
40
|
+
|
|
41
|
+
Each extractor handles one or more MIME types and can:
|
|
42
|
+
1. Extract raw text from the document
|
|
43
|
+
2. Extract structured entities directly (without LLM)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def supported_formats(self) -> List[str]:
|
|
48
|
+
"""Return list of MIME types this extractor handles."""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
|
|
53
|
+
"""Extract raw text from content. Returns empty string if extraction fails."""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
async def extract_entities(self, content: bytes, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
|
|
57
|
+
"""Extract structured entities directly from content (no LLM).
|
|
58
|
+
|
|
59
|
+
Default implementation returns empty list. Override for formats
|
|
60
|
+
that have structured data (JSON, CSV).
|
|
61
|
+
"""
|
|
62
|
+
return []
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Lightweight post-message entity extractor.
|
|
2
|
+
|
|
3
|
+
Uses rule-based NER and regex patterns. Zero LLM calls.
|
|
4
|
+
MUST complete within 50ms for single-message inputs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from ..confidence import CONFIDENCE_BY_SOURCE, apply_extraction_adjustments
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ── Regex patterns for structured entity signals ──────────────────────────────
|
|
16
|
+
|
|
17
|
+
_EMAIL_RE = re.compile(
|
|
18
|
+
r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b"
|
|
19
|
+
)
|
|
20
|
+
_DOMAIN_RE = re.compile(
|
|
21
|
+
r"\b(?:https?://)?([A-Za-z0-9\-]+\.(?:com|org|net|io|ai|co|app|dev|tech))\b",
|
|
22
|
+
re.IGNORECASE,
|
|
23
|
+
)
|
|
24
|
+
_TICKER_RE = re.compile(r"\b([A-Z]{2,5})\b(?=\s*(?:stock|shares|ticker|NYSE|NASDAQ|$))")
|
|
25
|
+
_PHONE_RE = re.compile(
|
|
26
|
+
r"\+?\d[\d\s\-\(\)]{7,}\d"
|
|
27
|
+
)
|
|
28
|
+
_URL_RE = re.compile(
|
|
29
|
+
r"https?://[^\s<>\"']+", re.IGNORECASE
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Capitalized-word person/org heuristic (1–4 consecutive title-cased tokens).
|
|
33
|
+
# Single-word matches are filtered against _NAME_STOPWORDS before use.
|
|
34
|
+
_CAPITALIZED_NAME_RE = re.compile(
|
|
35
|
+
r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3})\b"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Common English words that appear capitalized but are not proper names.
|
|
39
|
+
# Filtered out when the regex produces a single-word match.
|
|
40
|
+
_NAME_STOPWORDS = frozenset({
|
|
41
|
+
"The", "A", "An", "This", "That", "These", "Those",
|
|
42
|
+
"He", "She", "It", "We", "They", "You", "I",
|
|
43
|
+
"His", "Her", "Its", "Our", "Their", "My", "Your",
|
|
44
|
+
"Is", "Are", "Was", "Were", "Be", "Been", "Being",
|
|
45
|
+
"Has", "Have", "Had", "Do", "Does", "Did",
|
|
46
|
+
"Will", "Would", "Could", "Should", "May", "Might", "Must",
|
|
47
|
+
"In", "At", "To", "By", "For", "Of", "On", "Off",
|
|
48
|
+
"With", "From", "About", "Into", "Through", "During",
|
|
49
|
+
"And", "Or", "But", "If", "As", "So", "Yet", "Nor",
|
|
50
|
+
"Not", "No", "Yes", "Ok", "Please", "Thanks", "Thank",
|
|
51
|
+
"Here", "There", "Now", "Then", "When", "Where", "How", "Why",
|
|
52
|
+
"What", "Which", "Who", "Whose",
|
|
53
|
+
"Also", "Just", "Very", "More", "Most", "Some", "Any",
|
|
54
|
+
"All", "Both", "Each", "Few", "Many", "Much", "Other",
|
|
55
|
+
"After", "Before", "Over", "Under", "Above", "Below",
|
|
56
|
+
"Between", "Among", "Against", "Along", "Around",
|
|
57
|
+
"Can", "Cannot", "Well", "Even", "Still", "Already",
|
|
58
|
+
"Never", "Always", "Often", "Again", "Since", "Once",
|
|
59
|
+
"Hi", "Hey", "Hello", "Okay", "Sure", "Yeah",
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
# Common org suffixes. The name is 1-4 consecutive Title-case tokens immediately
|
|
63
|
+
# before the suffix -- NOT a greedy run across lowercase words (which used to
|
|
64
|
+
# swallow whole sentences, e.g. "I met Alice at Acme Corp" -> one "company").
|
|
65
|
+
_ORG_SUFFIX_RE = re.compile(
|
|
66
|
+
r"\b((?:[A-Z][A-Za-z&.\-]*\s+){0,3}[A-Z][A-Za-z&.\-]*"
|
|
67
|
+
r"\s+(?:Inc\.?|Corp\.?|LLC|Ltd\.?|Co\.?|Group|Foundation|Institute|University|"
|
|
68
|
+
r"School|Hospital|Agency|Association|Society|Department))\b"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Location indicators
|
|
72
|
+
_LOCATION_INDICATORS = frozenset({
|
|
73
|
+
"in", "at", "from", "to", "near", "visit", "located", "based", "headquartered",
|
|
74
|
+
"office", "city", "country", "region", "state", "province",
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class ExtractionCandidate:
|
|
80
|
+
"""A candidate entity or relationship found in text."""
|
|
81
|
+
text: str # the extracted text span
|
|
82
|
+
entity_type: str # tentative entity type
|
|
83
|
+
start_char: int
|
|
84
|
+
end_char: int
|
|
85
|
+
confidence: float
|
|
86
|
+
context_window: str # surrounding text for disambiguation
|
|
87
|
+
linked_entity_id: Optional[str] = None # if resolved to existing entity
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class ExtractionResult:
|
|
92
|
+
entities: List[ExtractionCandidate] = field(default_factory=list)
|
|
93
|
+
relationships: List[dict] = field(default_factory=list)
|
|
94
|
+
extraction_ms: float = 0.0
|
|
95
|
+
source_id: str = "" # message ID that was processed
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ConversationExtractor:
|
|
99
|
+
"""Lightweight post-message entity extractor.
|
|
100
|
+
|
|
101
|
+
Uses rule-based NER and regex patterns. Zero LLM calls.
|
|
102
|
+
MUST complete within 50ms for single-message inputs.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
min_message_length: int = 20,
|
|
108
|
+
existing_entities: Optional[List[str]] = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
self._min_length = min_message_length
|
|
111
|
+
self._known_names: List[str] = list(existing_entities or [])
|
|
112
|
+
|
|
113
|
+
def _context(self, text: str, start: int, end: int, window: int = 40) -> str:
|
|
114
|
+
return text[max(0, start - window): min(len(text), end + window)]
|
|
115
|
+
|
|
116
|
+
async def extract(
|
|
117
|
+
self,
|
|
118
|
+
message_text: str,
|
|
119
|
+
source_id: str,
|
|
120
|
+
existing_entities: Optional[List[str]] = None,
|
|
121
|
+
) -> ExtractionResult:
|
|
122
|
+
"""Extract entity candidates from a single message.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
message_text: Raw message content.
|
|
126
|
+
source_id: The message ID for provenance tracking.
|
|
127
|
+
existing_entities: List of known entity names for fast linking.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
ExtractionResult with candidates and elapsed time.
|
|
131
|
+
"""
|
|
132
|
+
t0 = time.monotonic()
|
|
133
|
+
result = ExtractionResult(source_id=source_id)
|
|
134
|
+
|
|
135
|
+
if len(message_text) < self._min_length:
|
|
136
|
+
result.extraction_ms = (time.monotonic() - t0) * 1000
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
known = set(existing_entities or []) | set(self._known_names)
|
|
140
|
+
seen_spans: List[tuple] = [] # (start, end) to avoid duplicates
|
|
141
|
+
|
|
142
|
+
def _overlaps(s: int, e: int) -> bool:
|
|
143
|
+
for a, b in seen_spans:
|
|
144
|
+
if s < b and e > a:
|
|
145
|
+
return True
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
# ── Emails → person (high confidence) ────────────────────────────────
|
|
149
|
+
for m in _EMAIL_RE.finditer(message_text):
|
|
150
|
+
if _overlaps(m.start(), m.end()):
|
|
151
|
+
continue
|
|
152
|
+
adj = [0.15] # email pattern bonus
|
|
153
|
+
if m.group() in known:
|
|
154
|
+
adj.append(0.20)
|
|
155
|
+
conf = apply_extraction_adjustments(
|
|
156
|
+
CONFIDENCE_BY_SOURCE["conversation_ner"], adj
|
|
157
|
+
)
|
|
158
|
+
result.entities.append(ExtractionCandidate(
|
|
159
|
+
text=m.group(),
|
|
160
|
+
entity_type="person",
|
|
161
|
+
start_char=m.start(),
|
|
162
|
+
end_char=m.end(),
|
|
163
|
+
confidence=conf,
|
|
164
|
+
context_window=self._context(message_text, m.start(), m.end()),
|
|
165
|
+
))
|
|
166
|
+
seen_spans.append((m.start(), m.end()))
|
|
167
|
+
|
|
168
|
+
# ── URLs → product/company ─────────────────────────────────────────
|
|
169
|
+
for m in _URL_RE.finditer(message_text):
|
|
170
|
+
if _overlaps(m.start(), m.end()):
|
|
171
|
+
continue
|
|
172
|
+
conf = apply_extraction_adjustments(
|
|
173
|
+
CONFIDENCE_BY_SOURCE["conversation_ner"], [0.05]
|
|
174
|
+
)
|
|
175
|
+
result.entities.append(ExtractionCandidate(
|
|
176
|
+
text=m.group(),
|
|
177
|
+
entity_type="product",
|
|
178
|
+
start_char=m.start(),
|
|
179
|
+
end_char=m.end(),
|
|
180
|
+
confidence=conf,
|
|
181
|
+
context_window=self._context(message_text, m.start(), m.end()),
|
|
182
|
+
))
|
|
183
|
+
seen_spans.append((m.start(), m.end()))
|
|
184
|
+
|
|
185
|
+
# ── Org suffix → company ──────────────────────────────────────────
|
|
186
|
+
for m in _ORG_SUFFIX_RE.finditer(message_text):
|
|
187
|
+
full = m.group().strip()
|
|
188
|
+
if _overlaps(m.start(), m.end()):
|
|
189
|
+
continue
|
|
190
|
+
adj = []
|
|
191
|
+
if full in known:
|
|
192
|
+
adj.append(0.20)
|
|
193
|
+
conf = apply_extraction_adjustments(
|
|
194
|
+
CONFIDENCE_BY_SOURCE["conversation_ner"], adj
|
|
195
|
+
)
|
|
196
|
+
result.entities.append(ExtractionCandidate(
|
|
197
|
+
text=full,
|
|
198
|
+
entity_type="company",
|
|
199
|
+
start_char=m.start(),
|
|
200
|
+
end_char=m.end(),
|
|
201
|
+
confidence=conf,
|
|
202
|
+
context_window=self._context(message_text, m.start(), m.end()),
|
|
203
|
+
))
|
|
204
|
+
seen_spans.append((m.start(), m.end()))
|
|
205
|
+
|
|
206
|
+
# ── Capitalized names → person ────────────────────────────────────
|
|
207
|
+
for m in _CAPITALIZED_NAME_RE.finditer(message_text):
|
|
208
|
+
name = m.group().strip()
|
|
209
|
+
if _overlaps(m.start(), m.end()):
|
|
210
|
+
continue
|
|
211
|
+
# Skip single-word all-caps (likely acronym/ticker)
|
|
212
|
+
if name.isupper():
|
|
213
|
+
continue
|
|
214
|
+
is_single_word = " " not in name
|
|
215
|
+
# Skip single-word matches that are common English stopwords
|
|
216
|
+
if is_single_word and name in _NAME_STOPWORDS:
|
|
217
|
+
continue
|
|
218
|
+
adj = []
|
|
219
|
+
if name in known:
|
|
220
|
+
adj.append(0.20)
|
|
221
|
+
# Single-word proper names get lower confidence than multi-word names
|
|
222
|
+
if is_single_word:
|
|
223
|
+
adj.append(-0.10)
|
|
224
|
+
conf = apply_extraction_adjustments(
|
|
225
|
+
CONFIDENCE_BY_SOURCE["conversation_ner"], adj
|
|
226
|
+
)
|
|
227
|
+
# Heuristic: check if preceded by location indicator
|
|
228
|
+
prefix = message_text[max(0, m.start() - 30):m.start()].lower()
|
|
229
|
+
etype = "person"
|
|
230
|
+
for indicator in _LOCATION_INDICATORS:
|
|
231
|
+
if indicator in prefix.split():
|
|
232
|
+
etype = "location"
|
|
233
|
+
break
|
|
234
|
+
result.entities.append(ExtractionCandidate(
|
|
235
|
+
text=name,
|
|
236
|
+
entity_type=etype,
|
|
237
|
+
start_char=m.start(),
|
|
238
|
+
end_char=m.end(),
|
|
239
|
+
confidence=conf,
|
|
240
|
+
context_window=self._context(message_text, m.start(), m.end()),
|
|
241
|
+
))
|
|
242
|
+
seen_spans.append((m.start(), m.end()))
|
|
243
|
+
|
|
244
|
+
# ── Domain names → company ────────────────────────────────────────
|
|
245
|
+
for m in _DOMAIN_RE.finditer(message_text):
|
|
246
|
+
if _overlaps(m.start(), m.end()):
|
|
247
|
+
continue
|
|
248
|
+
conf = apply_extraction_adjustments(
|
|
249
|
+
CONFIDENCE_BY_SOURCE["conversation_ner"], [0.05]
|
|
250
|
+
)
|
|
251
|
+
result.entities.append(ExtractionCandidate(
|
|
252
|
+
text=m.group(1),
|
|
253
|
+
entity_type="company",
|
|
254
|
+
start_char=m.start(),
|
|
255
|
+
end_char=m.end(),
|
|
256
|
+
confidence=conf,
|
|
257
|
+
context_window=self._context(message_text, m.start(), m.end()),
|
|
258
|
+
))
|
|
259
|
+
seen_spans.append((m.start(), m.end()))
|
|
260
|
+
|
|
261
|
+
result.extraction_ms = (time.monotonic() - t0) * 1000
|
|
262
|
+
return result
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Format detection for document content."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Common extension to MIME type mapping
|
|
12
|
+
EXTENSION_MAP = {
|
|
13
|
+
".pdf": "application/pdf",
|
|
14
|
+
".html": "text/html",
|
|
15
|
+
".htm": "text/html",
|
|
16
|
+
".json": "application/json",
|
|
17
|
+
".csv": "text/csv",
|
|
18
|
+
".txt": "text/plain",
|
|
19
|
+
".md": "text/markdown",
|
|
20
|
+
".xml": "application/xml",
|
|
21
|
+
".yaml": "application/x-yaml",
|
|
22
|
+
".yml": "application/x-yaml",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Magic byte signatures
|
|
26
|
+
MAGIC_SIGNATURES = {
|
|
27
|
+
b"%PDF": "application/pdf",
|
|
28
|
+
b"<!DOCTYPE": "text/html",
|
|
29
|
+
b"<html": "text/html",
|
|
30
|
+
b"<?xml": "application/xml",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FormatDetector:
|
|
35
|
+
"""Detect document format from content, filename, or explicit MIME type."""
|
|
36
|
+
|
|
37
|
+
def detect(
|
|
38
|
+
self,
|
|
39
|
+
content: bytes,
|
|
40
|
+
filename: str = "",
|
|
41
|
+
mime_type: str = "",
|
|
42
|
+
) -> str:
|
|
43
|
+
"""Detect content format.
|
|
44
|
+
|
|
45
|
+
Priority: explicit mime_type > filename extension > magic bytes > fallback.
|
|
46
|
+
"""
|
|
47
|
+
# 1. Explicit MIME type wins
|
|
48
|
+
if mime_type:
|
|
49
|
+
return mime_type
|
|
50
|
+
|
|
51
|
+
# 2. Filename extension
|
|
52
|
+
if filename:
|
|
53
|
+
ext = Path(filename).suffix.lower()
|
|
54
|
+
if ext in EXTENSION_MAP:
|
|
55
|
+
return EXTENSION_MAP[ext]
|
|
56
|
+
|
|
57
|
+
# 3. Magic bytes
|
|
58
|
+
if content:
|
|
59
|
+
for sig, fmt in MAGIC_SIGNATURES.items():
|
|
60
|
+
if content[:len(sig)].startswith(sig):
|
|
61
|
+
return fmt
|
|
62
|
+
|
|
63
|
+
# JSON detection
|
|
64
|
+
stripped = content[:512].strip()
|
|
65
|
+
if stripped and stripped[0:1] in (b"{", b"["):
|
|
66
|
+
return "application/json"
|
|
67
|
+
|
|
68
|
+
# CSV detection (simple heuristic)
|
|
69
|
+
first_line = content[:512].split(b"\n")[0]
|
|
70
|
+
if b"," in first_line and first_line.count(b",") >= 2:
|
|
71
|
+
return "text/csv"
|
|
72
|
+
|
|
73
|
+
# 4. Fallback
|
|
74
|
+
return "text/plain"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Background document entity extractor.
|
|
2
|
+
|
|
3
|
+
Invoked via task queue. MAY use heavier NER models.
|
|
4
|
+
Processes structured documents and returns typed entity candidates.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from .conversation_extractor import ExtractionResult, ConversationExtractor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DocumentType(str, Enum):
|
|
15
|
+
PDF = "pdf"
|
|
16
|
+
HTML = "html"
|
|
17
|
+
EMAIL_BODY = "email_body"
|
|
18
|
+
PLAIN_TEXT = "plain_text"
|
|
19
|
+
CALENDAR_ICS = "calendar_ics"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DocumentExtractor:
|
|
23
|
+
"""Background document entity extractor.
|
|
24
|
+
|
|
25
|
+
Invoked via task queue. MAY use heavier NER models.
|
|
26
|
+
Processes structured documents and returns typed entity candidates.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
self._conv_extractor = ConversationExtractor(min_message_length=0)
|
|
31
|
+
|
|
32
|
+
async def extract_from_document(
|
|
33
|
+
self,
|
|
34
|
+
content: bytes,
|
|
35
|
+
document_type: DocumentType,
|
|
36
|
+
source_reference: str,
|
|
37
|
+
) -> ExtractionResult:
|
|
38
|
+
"""Extract entities from a document.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
content: Raw document bytes.
|
|
42
|
+
document_type: The type of document being processed.
|
|
43
|
+
source_reference: Email ID, URL, or file path for provenance.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
ExtractionResult with full entity and relationship candidates.
|
|
47
|
+
"""
|
|
48
|
+
text = self._decode_content(content, document_type)
|
|
49
|
+
return await self._conv_extractor.extract(
|
|
50
|
+
message_text=text,
|
|
51
|
+
source_id=source_reference,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def _decode_content(self, content: bytes, document_type: DocumentType) -> str:
|
|
55
|
+
"""Decode raw bytes to text based on document type."""
|
|
56
|
+
if document_type == DocumentType.HTML:
|
|
57
|
+
return self._strip_html(content.decode("utf-8", errors="replace"))
|
|
58
|
+
elif document_type == DocumentType.CALENDAR_ICS:
|
|
59
|
+
return self._parse_ics(content.decode("utf-8", errors="replace"))
|
|
60
|
+
else:
|
|
61
|
+
return content.decode("utf-8", errors="replace")
|
|
62
|
+
|
|
63
|
+
def _strip_html(self, html: str) -> str:
|
|
64
|
+
"""Minimal HTML tag stripper."""
|
|
65
|
+
import re
|
|
66
|
+
text = re.sub(r"<[^>]+>", " ", html)
|
|
67
|
+
text = re.sub(r"&[a-z]+;", " ", text)
|
|
68
|
+
return re.sub(r"\s+", " ", text).strip()
|
|
69
|
+
|
|
70
|
+
def _parse_ics(self, ics: str) -> str:
|
|
71
|
+
"""Extract displayable text from iCal content."""
|
|
72
|
+
lines = []
|
|
73
|
+
for line in ics.splitlines():
|
|
74
|
+
for prefix in ("SUMMARY:", "DESCRIPTION:", "LOCATION:", "ORGANIZER:"):
|
|
75
|
+
if line.startswith(prefix):
|
|
76
|
+
lines.append(line[len(prefix):].strip())
|
|
77
|
+
break
|
|
78
|
+
return " ".join(lines)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Extraction formats package."""
|
|
2
|
+
|
|
3
|
+
from apsimo.world_model.extraction.formats.text import TextExtractor
|
|
4
|
+
from apsimo.world_model.extraction.formats.json_fmt import JSONExtractor
|
|
5
|
+
from apsimo.world_model.extraction.formats.csv_fmt import CSVExtractor
|
|
6
|
+
|
|
7
|
+
# Optional extractors — load if dependencies are available
|
|
8
|
+
try:
|
|
9
|
+
from apsimo.world_model.extraction.formats.pdf import PDFExtractor
|
|
10
|
+
except ImportError:
|
|
11
|
+
PDFExtractor = None
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from apsimo.world_model.extraction.formats.html_fmt import HTMLExtractor
|
|
15
|
+
except ImportError:
|
|
16
|
+
HTMLExtractor = None
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"TextExtractor",
|
|
20
|
+
"JSONExtractor",
|
|
21
|
+
"CSVExtractor",
|
|
22
|
+
"PDFExtractor",
|
|
23
|
+
"HTMLExtractor",
|
|
24
|
+
]
|