@remnic/core 9.63.12 → 9.64.1
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.
- package/dist/access-admin-ops-surface.d.ts +11 -11
- package/dist/access-admin-ops-surface.js +15 -15
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +16 -16
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +15 -15
- package/dist/access-cli.js +49 -49
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +11 -11
- package/dist/access-extraction-force-flush.js +15 -15
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http-lifecycle-flush.d.ts +11 -11
- package/dist/access-http-offline-stream.d.ts +11 -11
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +17 -17
- package/dist/access-http.d.ts +12 -12
- package/dist/access-http.js +26 -26
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +15 -15
- package/dist/access-lcm-surface.d.ts +11 -11
- package/dist/access-lcm-surface.js +15 -15
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +20 -20
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-CrF0FKaz.d.ts → access-namespace-preflight-Dj9uR4Cq.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +15 -15
- package/dist/access-observe-write-surface.d.ts +11 -11
- package/dist/access-observe-write-surface.js +15 -15
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-operations-batch.js +16 -16
- package/dist/access-operations.d.ts +11 -11
- package/dist/access-operations.js +19 -19
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +15 -15
- package/dist/access-recall-response.d.ts +11 -11
- package/dist/access-recall-response.js +15 -15
- package/dist/access-recall-surface.d.ts +11 -11
- package/dist/access-recall-surface.js +15 -15
- package/dist/{access-service-U6jiERic.d.ts → access-service-Bd0lzaHp.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +11 -11
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +15 -15
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-surface-catalog.js +17 -17
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-C0KIwZqR.d.ts → catalog-rKYbN7zF.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-5SZ6CLGJ.js → chunk-2RZCOG63.js} +3 -2
- package/dist/{chunk-5SZ6CLGJ.js.map → chunk-2RZCOG63.js.map} +1 -1
- package/dist/{chunk-7DRSWPUV.js → chunk-3CYVTO6A.js} +3 -3
- package/dist/{chunk-LJQGQUNS.js → chunk-4FSCGYTQ.js} +2 -2
- package/dist/{chunk-HP6B732E.js → chunk-4KKVDOCD.js} +24 -24
- package/dist/{chunk-36XS2ASP.js → chunk-5Q5BEWJ3.js} +2 -2
- package/dist/{chunk-4XN35YQD.js → chunk-5TBO7CDR.js} +29 -1
- package/dist/chunk-5TBO7CDR.js.map +1 -0
- package/dist/{chunk-BYFKJXIR.js → chunk-6I6IOMKS.js} +2 -2
- package/dist/{chunk-YI25J2JD.js → chunk-7D3TTGHY.js} +22 -13
- package/dist/chunk-7D3TTGHY.js.map +1 -0
- package/dist/{chunk-4IIDTPSK.js → chunk-7X35RPN3.js} +2 -2
- package/dist/{chunk-FEP45MYY.js → chunk-AGC3Q53R.js} +2 -2
- package/dist/{chunk-ZWYXSF2T.js → chunk-AL43367I.js} +96 -4
- package/dist/chunk-AL43367I.js.map +1 -0
- package/dist/{chunk-WCWIV2JH.js → chunk-B2GICNOJ.js} +2 -2
- package/dist/{chunk-BTBAHTQ5.js → chunk-CI6YIT5S.js} +2 -2
- package/dist/{chunk-CUDSA46O.js → chunk-CUIAHZPZ.js} +2 -2
- package/dist/{chunk-BTUIHRRR.js → chunk-D267K4V5.js} +2 -2
- package/dist/{chunk-QICOU4RY.js → chunk-DLZBOEJY.js} +2 -2
- package/dist/{chunk-AR253JBM.js → chunk-DPPTG6VJ.js} +1 -1
- package/dist/chunk-DPPTG6VJ.js.map +1 -0
- package/dist/{chunk-BPLPGOUJ.js → chunk-DRS3OX77.js} +2 -2
- package/dist/{chunk-3F3CVVDS.js → chunk-DTQ3OB7O.js} +2 -2
- package/dist/{chunk-Z4UPRZR6.js → chunk-DZ2AVS2H.js} +2 -2
- package/dist/{chunk-YINSSARY.js → chunk-EAWJSY2Z.js} +1 -1
- package/dist/{chunk-NWHKHBL4.js → chunk-FMGTFMT3.js} +9 -9
- package/dist/{chunk-57WMVU4D.js → chunk-GSQDPL45.js} +3 -3
- package/dist/{chunk-UUYQWKHO.js → chunk-IHPYTG6F.js} +2 -2
- package/dist/{chunk-XREXYQ5U.js → chunk-IPUUMLSM.js} +21 -21
- package/dist/{chunk-SQ3EGGII.js → chunk-J3JEW5CT.js} +2 -2
- package/dist/{chunk-VCEE4YNA.js → chunk-JE3AKPOO.js} +2 -2
- package/dist/{chunk-IUSV4XJC.js → chunk-KOE3RLNN.js} +2 -2
- package/dist/{chunk-WQ6TRWAE.js → chunk-NMQXUGTU.js} +3 -3
- package/dist/{chunk-V2YH7T6T.js → chunk-OTVHRZZD.js} +2 -2
- package/dist/{chunk-77E7LLMV.js → chunk-OWAYQK5V.js} +8 -8
- package/dist/{chunk-YGSDWO3U.js → chunk-P4SD7BSA.js} +2 -2
- package/dist/{chunk-G5F4MXXC.js → chunk-Q3IRE6E4.js} +2 -2
- package/dist/{chunk-HZXO2CJ6.js → chunk-QHNO6AND.js} +2 -2
- package/dist/{chunk-5OOTHWGJ.js → chunk-RMB22Q3R.js} +2 -2
- package/dist/{chunk-BB2LH5JM.js → chunk-RNORDDD6.js} +11 -1
- package/dist/chunk-RNORDDD6.js.map +1 -0
- package/dist/{chunk-BS75WJBH.js → chunk-UIYYS2YI.js} +4 -4
- package/dist/{chunk-SYU5JOBQ.js → chunk-UWSETW6O.js} +2 -2
- package/dist/{chunk-CK3DZ6OE.js → chunk-VCNGVU62.js} +2 -2
- package/dist/{chunk-DIJCMHUE.js → chunk-VSKA2ATE.js} +2 -2
- package/dist/{chunk-PQODT53H.js → chunk-WDFXRHOZ.js} +3 -3
- package/dist/{chunk-6NMZXEJ7.js → chunk-WNO5W2TP.js} +2 -2
- package/dist/{chunk-RFFCBYBD.js → chunk-XBRGJJK7.js} +2 -2
- package/dist/{chunk-R5AWIILH.js → chunk-XFAWGUNX.js} +2 -2
- package/dist/{chunk-JASFZQ76.js → chunk-XSPRZZEX.js} +3 -3
- package/dist/{chunk-EHC3FIJ4.js → chunk-Y5ZIQFWH.js} +2 -2
- package/dist/{chunk-6IKXAAJI.js → chunk-YY63AMGP.js} +2 -2
- package/dist/{chunk-7ML7STOD.js → chunk-ZGGTIBWN.js} +2 -2
- package/dist/{chunk-X6HNXVXS.js → chunk-ZUIVJ3BB.js} +4 -4
- package/dist/{cli-BsaYHAH9.d.ts → cli-0dDmOsJ8.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +39 -39
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +4 -4
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +5 -5
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +5 -5
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-Cph4PnBe.d.ts → dependency-propagation-delivery-Cr8u-XwC.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/external-wiki-access.d.ts +11 -11
- package/dist/external-wiki-access.js +16 -16
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +11 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +8 -8
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +21 -21
- package/dist/index.js +49 -49
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/{maintenance-BNq31xpo.d.ts → maintenance-BpNjuZk4.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +6 -6
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +12 -12
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-CBuUOP2J.d.ts → orchestrator-B3ZQ8CTJ.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +49 -49
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-CW_Q4WvX.d.ts → pipeline-q5emoXD6.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{public-http-jMlmLny4.d.ts → public-http-CQN-i7Ai.d.ts} +1 -1
- package/dist/{qmd-q-cAzYuC.d.ts → qmd-YC6YGphs.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/{scope-profiles-CIr4mXgp.d.ts → scope-profiles-De8F7YQ6.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-BZ6J46j-.d.ts → semantic-consolidation-Bf-hm2LQ.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/{service-CCl4iyyY.d.ts → service-D83osSRn.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +7 -7
- package/dist/{storage-BPXmJKIt.d.ts → storage-BHWbpbhj.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +14 -14
- package/dist/support-passport/index.js +18 -18
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CltSWcND.d.ts → types-CpgsN-Gb.d.ts} +24 -3
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/config.ts +5 -6
- package/src/connectors/index.test.ts +24 -0
- package/src/connectors/index.ts +2 -0
- package/src/connectors/prime-agent.ts +38 -0
- package/src/location/cli.ts +163 -0
- package/src/location/config.test.ts +75 -0
- package/src/location/config.ts +105 -0
- package/src/location/intervals.test.ts +202 -0
- package/src/location/intervals.ts +221 -0
- package/src/location/parse-companions.ts +12 -0
- package/src/location/pipeline.test.ts +324 -0
- package/src/location/pipeline.ts +245 -0
- package/src/location/recall-exclusion.test.ts +36 -0
- package/src/location/registry.test.ts +74 -0
- package/src/location/registry.ts +54 -0
- package/src/location/store.test.ts +170 -0
- package/src/location/store.ts +478 -0
- package/src/location/types.ts +95 -0
- package/src/orchestration/generic-recall-paths.ts +17 -0
- package/src/orchestration/orchestrator-helpers.ts +22 -0
- package/src/orchestration/qmd-result-resolver.ts +1 -0
- package/src/types.ts +2 -3
- package/dist/chunk-4XN35YQD.js.map +0 -1
- package/dist/chunk-AR253JBM.js.map +0 -1
- package/dist/chunk-BB2LH5JM.js.map +0 -1
- package/dist/chunk-YI25J2JD.js.map +0 -1
- package/dist/chunk-ZWYXSF2T.js.map +0 -1
- /package/dist/{chunk-7DRSWPUV.js.map → chunk-3CYVTO6A.js.map} +0 -0
- /package/dist/{chunk-LJQGQUNS.js.map → chunk-4FSCGYTQ.js.map} +0 -0
- /package/dist/{chunk-HP6B732E.js.map → chunk-4KKVDOCD.js.map} +0 -0
- /package/dist/{chunk-36XS2ASP.js.map → chunk-5Q5BEWJ3.js.map} +0 -0
- /package/dist/{chunk-BYFKJXIR.js.map → chunk-6I6IOMKS.js.map} +0 -0
- /package/dist/{chunk-4IIDTPSK.js.map → chunk-7X35RPN3.js.map} +0 -0
- /package/dist/{chunk-FEP45MYY.js.map → chunk-AGC3Q53R.js.map} +0 -0
- /package/dist/{chunk-WCWIV2JH.js.map → chunk-B2GICNOJ.js.map} +0 -0
- /package/dist/{chunk-BTBAHTQ5.js.map → chunk-CI6YIT5S.js.map} +0 -0
- /package/dist/{chunk-CUDSA46O.js.map → chunk-CUIAHZPZ.js.map} +0 -0
- /package/dist/{chunk-BTUIHRRR.js.map → chunk-D267K4V5.js.map} +0 -0
- /package/dist/{chunk-QICOU4RY.js.map → chunk-DLZBOEJY.js.map} +0 -0
- /package/dist/{chunk-BPLPGOUJ.js.map → chunk-DRS3OX77.js.map} +0 -0
- /package/dist/{chunk-3F3CVVDS.js.map → chunk-DTQ3OB7O.js.map} +0 -0
- /package/dist/{chunk-Z4UPRZR6.js.map → chunk-DZ2AVS2H.js.map} +0 -0
- /package/dist/{chunk-YINSSARY.js.map → chunk-EAWJSY2Z.js.map} +0 -0
- /package/dist/{chunk-NWHKHBL4.js.map → chunk-FMGTFMT3.js.map} +0 -0
- /package/dist/{chunk-57WMVU4D.js.map → chunk-GSQDPL45.js.map} +0 -0
- /package/dist/{chunk-UUYQWKHO.js.map → chunk-IHPYTG6F.js.map} +0 -0
- /package/dist/{chunk-XREXYQ5U.js.map → chunk-IPUUMLSM.js.map} +0 -0
- /package/dist/{chunk-SQ3EGGII.js.map → chunk-J3JEW5CT.js.map} +0 -0
- /package/dist/{chunk-VCEE4YNA.js.map → chunk-JE3AKPOO.js.map} +0 -0
- /package/dist/{chunk-IUSV4XJC.js.map → chunk-KOE3RLNN.js.map} +0 -0
- /package/dist/{chunk-WQ6TRWAE.js.map → chunk-NMQXUGTU.js.map} +0 -0
- /package/dist/{chunk-V2YH7T6T.js.map → chunk-OTVHRZZD.js.map} +0 -0
- /package/dist/{chunk-77E7LLMV.js.map → chunk-OWAYQK5V.js.map} +0 -0
- /package/dist/{chunk-YGSDWO3U.js.map → chunk-P4SD7BSA.js.map} +0 -0
- /package/dist/{chunk-G5F4MXXC.js.map → chunk-Q3IRE6E4.js.map} +0 -0
- /package/dist/{chunk-HZXO2CJ6.js.map → chunk-QHNO6AND.js.map} +0 -0
- /package/dist/{chunk-5OOTHWGJ.js.map → chunk-RMB22Q3R.js.map} +0 -0
- /package/dist/{chunk-BS75WJBH.js.map → chunk-UIYYS2YI.js.map} +0 -0
- /package/dist/{chunk-SYU5JOBQ.js.map → chunk-UWSETW6O.js.map} +0 -0
- /package/dist/{chunk-CK3DZ6OE.js.map → chunk-VCNGVU62.js.map} +0 -0
- /package/dist/{chunk-DIJCMHUE.js.map → chunk-VSKA2ATE.js.map} +0 -0
- /package/dist/{chunk-PQODT53H.js.map → chunk-WDFXRHOZ.js.map} +0 -0
- /package/dist/{chunk-6NMZXEJ7.js.map → chunk-WNO5W2TP.js.map} +0 -0
- /package/dist/{chunk-RFFCBYBD.js.map → chunk-XBRGJJK7.js.map} +0 -0
- /package/dist/{chunk-R5AWIILH.js.map → chunk-XFAWGUNX.js.map} +0 -0
- /package/dist/{chunk-JASFZQ76.js.map → chunk-XSPRZZEX.js.map} +0 -0
- /package/dist/{chunk-EHC3FIJ4.js.map → chunk-Y5ZIQFWH.js.map} +0 -0
- /package/dist/{chunk-6IKXAAJI.js.map → chunk-YY63AMGP.js.map} +0 -0
- /package/dist/{chunk-7ML7STOD.js.map → chunk-ZGGTIBWN.js.map} +0 -0
- /package/dist/{chunk-X6HNXVXS.js.map → chunk-ZUIVJ3BB.js.map} +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
bucketObservationsByDay,
|
|
6
|
+
dominantPlace,
|
|
7
|
+
intervalOverlapMs,
|
|
8
|
+
localDayKey,
|
|
9
|
+
locationDayWindow,
|
|
10
|
+
observationSegments,
|
|
11
|
+
} from "./intervals.js";
|
|
12
|
+
import type { LocationObservation, LocationSegment } from "./types.js";
|
|
13
|
+
|
|
14
|
+
const HOUR_MS = 3_600_000;
|
|
15
|
+
|
|
16
|
+
function observation(observedAtUtc: string, placeId: string, label = placeId): LocationObservation {
|
|
17
|
+
return { observedAtUtc, place: { id: placeId, label } };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function segment(startUtc: string, endUtc: string, placeId: string): LocationSegment {
|
|
21
|
+
return { startUtc, endUtc, place: { id: placeId, label: placeId } };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test("intervalOverlapMs is 0 for disjoint intervals and exact for partial overlap", () => {
|
|
25
|
+
assert.equal(
|
|
26
|
+
intervalOverlapMs(
|
|
27
|
+
{ startUtc: "2026-08-17T09:00:00Z", endUtc: "2026-08-17T10:00:00Z" },
|
|
28
|
+
{ startUtc: "2026-08-17T10:00:00Z", endUtc: "2026-08-17T11:00:00Z" },
|
|
29
|
+
),
|
|
30
|
+
0,
|
|
31
|
+
"half-open intervals that touch at one instant do not overlap",
|
|
32
|
+
);
|
|
33
|
+
assert.equal(
|
|
34
|
+
intervalOverlapMs(
|
|
35
|
+
{ startUtc: "2026-08-17T09:00:00Z", endUtc: "2026-08-17T10:30:00Z" },
|
|
36
|
+
{ startUtc: "2026-08-17T10:00:00Z", endUtc: "2026-08-17T11:00:00Z" },
|
|
37
|
+
),
|
|
38
|
+
30 * 60_000,
|
|
39
|
+
);
|
|
40
|
+
assert.equal(
|
|
41
|
+
intervalOverlapMs(
|
|
42
|
+
{ startUtc: "2026-08-17T09:00:00Z", endUtc: "2026-08-17T12:00:00Z" },
|
|
43
|
+
{ startUtc: "2026-08-17T09:30:00Z", endUtc: "2026-08-17T10:00:00Z" },
|
|
44
|
+
),
|
|
45
|
+
30 * 60_000,
|
|
46
|
+
"a contained interval overlaps by its own length",
|
|
47
|
+
);
|
|
48
|
+
assert.equal(
|
|
49
|
+
intervalOverlapMs(
|
|
50
|
+
{ startUtc: "2026-08-17T09:00:00Z", endUtc: "2026-08-17T10:00:00Z" },
|
|
51
|
+
{ startUtc: "2026-08-17T09:00:00Z", endUtc: "2026-08-17T10:00:00Z" },
|
|
52
|
+
),
|
|
53
|
+
HOUR_MS,
|
|
54
|
+
"identical intervals overlap by their full length",
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("locationDayWindow produces 23h spring-forward and 25h fall-back days in America/Chicago", () => {
|
|
59
|
+
// US DST 2026: spring forward 2026-03-08, fall back 2026-11-01.
|
|
60
|
+
const spring = locationDayWindow("2026-03-08", "America/Chicago");
|
|
61
|
+
assert.equal(
|
|
62
|
+
Date.parse(spring.endUtc) - Date.parse(spring.startUtc),
|
|
63
|
+
23 * HOUR_MS,
|
|
64
|
+
"spring-forward local day is 23 hours",
|
|
65
|
+
);
|
|
66
|
+
assert.equal(spring.startUtc, "2026-03-08T06:00:00.000Z");
|
|
67
|
+
|
|
68
|
+
const fall = locationDayWindow("2026-11-01", "America/Chicago");
|
|
69
|
+
assert.equal(
|
|
70
|
+
Date.parse(fall.endUtc) - Date.parse(fall.startUtc),
|
|
71
|
+
25 * HOUR_MS,
|
|
72
|
+
"fall-back local day is 25 hours",
|
|
73
|
+
);
|
|
74
|
+
assert.equal(fall.startUtc, "2026-11-01T05:00:00.000Z");
|
|
75
|
+
|
|
76
|
+
const utc = locationDayWindow("2026-08-17", "UTC");
|
|
77
|
+
assert.equal(Date.parse(utc.endUtc) - Date.parse(utc.startUtc), 24 * HOUR_MS);
|
|
78
|
+
assert.throws(() => locationDayWindow("2026-02-30", "UTC"), RangeError);
|
|
79
|
+
assert.throws(() => locationDayWindow("2026-8-7", "UTC"), RangeError);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("localDayKey buckets midnight exactly on the owning day (half-open)", () => {
|
|
83
|
+
assert.equal(localDayKey("2026-08-17T00:00:00.000Z", "UTC"), "2026-08-17");
|
|
84
|
+
assert.equal(localDayKey("2026-08-17T23:59:59.999Z", "UTC"), "2026-08-17");
|
|
85
|
+
assert.equal(localDayKey("2026-08-18T00:00:00.000Z", "UTC"), "2026-08-18");
|
|
86
|
+
// Zones east of UTC roll over mid-UTC-afternoon.
|
|
87
|
+
assert.equal(localDayKey("2026-08-17T18:30:00.000Z", "Asia/Tokyo"), "2026-08-18");
|
|
88
|
+
assert.throws(() => localDayKey("not-an-instant", "UTC"), RangeError);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("bucketObservationsByDay sorts within each local day bucket", () => {
|
|
92
|
+
const buckets = bucketObservationsByDay(
|
|
93
|
+
[
|
|
94
|
+
observation("2026-08-17T20:00:00Z", "b"),
|
|
95
|
+
observation("2026-08-17T09:00:00Z", "a"),
|
|
96
|
+
observation("2026-08-18T01:00:00Z", "c"),
|
|
97
|
+
],
|
|
98
|
+
"UTC",
|
|
99
|
+
);
|
|
100
|
+
assert.deepEqual([...buckets.keys()].sort(), ["2026-08-17", "2026-08-18"]);
|
|
101
|
+
assert.deepEqual(
|
|
102
|
+
buckets.get("2026-08-17")!.map((item) => item.observedAtUtc),
|
|
103
|
+
["2026-08-17T09:00:00Z", "2026-08-17T20:00:00Z"],
|
|
104
|
+
);
|
|
105
|
+
assert.throws(
|
|
106
|
+
() => bucketObservationsByDay([observation("garbage", "a")], "UTC"),
|
|
107
|
+
RangeError,
|
|
108
|
+
"a malformed instant is rejected loudly, never bucketed",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("observationSegments attributes dwell to the next observation and caps at window end", () => {
|
|
113
|
+
const window = locationDayWindow("2026-08-17", "UTC");
|
|
114
|
+
const segments = observationSegments(
|
|
115
|
+
[observation("2026-08-17T09:00:00Z", "home"), observation("2026-08-17T09:30:00Z", "office")],
|
|
116
|
+
window,
|
|
117
|
+
{ retainCoordinates: false },
|
|
118
|
+
);
|
|
119
|
+
assert.deepEqual(
|
|
120
|
+
segments.map((item) => [item.startUtc, item.endUtc, item.place.id]),
|
|
121
|
+
[
|
|
122
|
+
["2026-08-17T09:00:00.000Z", "2026-08-17T09:30:00.000Z", "home"],
|
|
123
|
+
["2026-08-17T09:30:00.000Z", window.endUtc, "office"],
|
|
124
|
+
],
|
|
125
|
+
"the last observation dwells until the day window ends, never across it",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("observationSegments drops coordinates unless retention is enabled", () => {
|
|
130
|
+
const window = locationDayWindow("2026-08-17", "UTC");
|
|
131
|
+
const withCoords: LocationObservation[] = [
|
|
132
|
+
{
|
|
133
|
+
observedAtUtc: "2026-08-17T09:00:00Z",
|
|
134
|
+
place: { id: "home", label: "Home", latitude: 41.8781, longitude: -87.6298 },
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
const stripped = observationSegments(withCoords, window, { retainCoordinates: false });
|
|
138
|
+
assert.equal(stripped[0]!.place.latitude, undefined);
|
|
139
|
+
assert.equal(stripped[0]!.place.longitude, undefined);
|
|
140
|
+
const kept = observationSegments(withCoords, window, { retainCoordinates: true });
|
|
141
|
+
assert.equal(kept[0]!.place.latitude, 41.8781);
|
|
142
|
+
assert.equal(kept[0]!.place.longitude, -87.6298);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("observationSegments rejects observations outside the requested window", () => {
|
|
146
|
+
const window = locationDayWindow("2026-08-17", "UTC");
|
|
147
|
+
assert.throws(
|
|
148
|
+
() => observationSegments([observation("2026-08-18T00:00:00Z", "home")], window, { retainCoordinates: false }),
|
|
149
|
+
RangeError,
|
|
150
|
+
"the window end is exclusive",
|
|
151
|
+
);
|
|
152
|
+
assert.throws(
|
|
153
|
+
() =>
|
|
154
|
+
observationSegments([observation("2026-08-16T23:59:59.999Z", "home")], window, { retainCoordinates: false }),
|
|
155
|
+
RangeError,
|
|
156
|
+
);
|
|
157
|
+
assert.throws(
|
|
158
|
+
() =>
|
|
159
|
+
observationSegments(
|
|
160
|
+
[observation("2026-08-17T09:00:00Z", "home")],
|
|
161
|
+
{ startUtc: "2026-08-17T10:00:00Z", endUtc: "2026-08-17T09:00:00Z" },
|
|
162
|
+
{ retainCoordinates: false },
|
|
163
|
+
),
|
|
164
|
+
RangeError,
|
|
165
|
+
"an inverted window is rejected",
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("dominantPlace breaks duration ties by earlier first start, then place id", () => {
|
|
170
|
+
const tieEarlierStart = [
|
|
171
|
+
segment("2026-08-17T09:00:00Z", "2026-08-17T10:00:00Z", "gym"),
|
|
172
|
+
segment("2026-08-17T10:00:00Z", "2026-08-17T11:00:00Z", "alpha"),
|
|
173
|
+
segment("2026-08-17T11:00:00Z", "2026-08-17T12:00:00Z", "alpha"),
|
|
174
|
+
];
|
|
175
|
+
assert.equal(dominantPlace(tieEarlierStart)?.place.id, "alpha");
|
|
176
|
+
|
|
177
|
+
const tieSameStartLexicographic = [
|
|
178
|
+
segment("2026-08-17T09:00:00Z", "2026-08-17T10:00:00Z", "zeta"),
|
|
179
|
+
segment("2026-08-17T09:00:00Z", "2026-08-17T10:00:00Z", "beta"),
|
|
180
|
+
];
|
|
181
|
+
assert.equal(
|
|
182
|
+
dominantPlace(tieSameStartLexicographic)?.place.id,
|
|
183
|
+
"beta",
|
|
184
|
+
"equal totals and equal first starts fall back to the smaller place id",
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Deterministic across orderings (insertion order must never win).
|
|
188
|
+
const shuffled = [...tieEarlierStart].reverse();
|
|
189
|
+
assert.equal(dominantPlace(shuffled)?.place.id, "alpha");
|
|
190
|
+
assert.equal(dominantPlace([]), null);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("dominantPlace unions overlapping segments so two sources do not double-count", () => {
|
|
194
|
+
const overlapping = [
|
|
195
|
+
segment("2026-08-17T09:00:00Z", "2026-08-17T12:00:00Z", "home"),
|
|
196
|
+
segment("2026-08-17T10:00:00Z", "2026-08-17T11:00:00Z", "office"),
|
|
197
|
+
segment("2026-08-17T14:00:00Z", "2026-08-17T18:00:00Z", "office"),
|
|
198
|
+
];
|
|
199
|
+
const dominant = dominantPlace(overlapping)!;
|
|
200
|
+
assert.equal(dominant.place.id, "office");
|
|
201
|
+
assert.equal(dominant.totalMs, 5 * HOUR_MS, "1h overlapping office + 4h afternoon office");
|
|
202
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Location interval math (issue #2044).
|
|
3
|
+
*
|
|
4
|
+
* Pure, deterministic helpers over UTC ISO-8601 instants: half-open
|
|
5
|
+
* [start, end) day windows (reusing the DST-correct activity day-window), IANA
|
|
6
|
+
* local-day bucketing, overlap, observation→segment dwell, and dominant-place
|
|
7
|
+
* selection with stable tie-breaking. No IO, no clocks, no randomness.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { activityDayWindow, isValidActivityDate } from "../activity/digest.js";
|
|
11
|
+
import type { LocationObservation, LocationPlace, LocationSegment } from "./types.js";
|
|
12
|
+
|
|
13
|
+
export function isValidLocationDate(date: string): boolean {
|
|
14
|
+
return isValidActivityDate(date);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Half-open [start, end) UTC ISO bounds of a local day. */
|
|
18
|
+
export function locationDayWindow(date: string, timezone: string): { startUtc: string; endUtc: string } {
|
|
19
|
+
if (!isValidActivityDate(date)) {
|
|
20
|
+
throw new RangeError(`Invalid location date "${date}"; expected a real YYYY-MM-DD day.`);
|
|
21
|
+
}
|
|
22
|
+
return activityDayWindow(date, timezone);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertFiniteInstant(iso: string, what: string): number {
|
|
26
|
+
const ms = Date.parse(iso);
|
|
27
|
+
if (typeof iso !== "string" || !Number.isFinite(ms)) {
|
|
28
|
+
throw new RangeError(`${what} must be a UTC ISO-8601 instant; got "${String(iso)}"`);
|
|
29
|
+
}
|
|
30
|
+
return ms;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The IANA local calendar day (YYYY-MM-DD) an instant falls in. */
|
|
34
|
+
export function localDayKey(instantUtc: string, timezone: string): string {
|
|
35
|
+
assertFiniteInstant(instantUtc, "instantUtc");
|
|
36
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
37
|
+
timeZone: timezone,
|
|
38
|
+
year: "numeric",
|
|
39
|
+
month: "2-digit",
|
|
40
|
+
day: "2-digit",
|
|
41
|
+
}).format(new Date(instantUtc));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function compareObservations(a: LocationObservation, b: LocationObservation): number {
|
|
45
|
+
if (a.observedAtUtc !== b.observedAtUtc) {
|
|
46
|
+
return a.observedAtUtc < b.observedAtUtc ? -1 : 1;
|
|
47
|
+
}
|
|
48
|
+
// Total comparator (§12): equal-key observations fall back to place id so
|
|
49
|
+
// ordering — and therefore dwell attribution — is deterministic.
|
|
50
|
+
if (a.place.id !== b.place.id) {
|
|
51
|
+
return a.place.id < b.place.id ? -1 : 1;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Bucket observations into their IANA local days, sorted within each day. */
|
|
57
|
+
export function bucketObservationsByDay(
|
|
58
|
+
observations: readonly LocationObservation[],
|
|
59
|
+
timezone: string,
|
|
60
|
+
): Map<string, LocationObservation[]> {
|
|
61
|
+
const buckets = new Map<string, LocationObservation[]>();
|
|
62
|
+
for (const observation of observations) {
|
|
63
|
+
assertFiniteInstant(observation.observedAtUtc, "observation observedAtUtc");
|
|
64
|
+
const key = localDayKey(observation.observedAtUtc, timezone);
|
|
65
|
+
const bucket = buckets.get(key);
|
|
66
|
+
if (bucket === undefined) buckets.set(key, [observation]);
|
|
67
|
+
else bucket.push(observation);
|
|
68
|
+
}
|
|
69
|
+
for (const bucket of buckets.values()) bucket.sort(compareObservations);
|
|
70
|
+
return buckets;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Overlap in ms of two half-open intervals; 0 when disjoint. */
|
|
74
|
+
export function intervalOverlapMs(
|
|
75
|
+
a: { startUtc: string; endUtc: string },
|
|
76
|
+
b: { startUtc: string; endUtc: string },
|
|
77
|
+
): number {
|
|
78
|
+
const overlap =
|
|
79
|
+
Math.min(Date.parse(a.endUtc), Date.parse(b.endUtc)) - Math.max(Date.parse(a.startUtc), Date.parse(b.startUtc));
|
|
80
|
+
return Math.max(0, overlap);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function placeForSegment(place: LocationPlace, retainCoordinates: boolean): LocationPlace {
|
|
84
|
+
if (retainCoordinates || place.latitude === undefined || place.longitude === undefined) {
|
|
85
|
+
return place;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
id: place.id,
|
|
89
|
+
label: place.label,
|
|
90
|
+
...(place.kind === undefined ? {} : { kind: place.kind }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Derive place-visit segments from a day's observations: each observation
|
|
96
|
+
* covers [its instant, the next observation's instant ∩ window end). The last
|
|
97
|
+
* observation is capped by the window end, so dwell never crosses the day
|
|
98
|
+
* boundary. Out-of-window observations are a provider contract violation and
|
|
99
|
+
* are rejected loudly rather than silently reshaping the day. Coordinates are
|
|
100
|
+
* stripped unless `retainCoordinates` is set.
|
|
101
|
+
*/
|
|
102
|
+
export function observationSegments(
|
|
103
|
+
observations: readonly LocationObservation[],
|
|
104
|
+
window: { startUtc: string; endUtc: string },
|
|
105
|
+
options: { retainCoordinates: boolean },
|
|
106
|
+
): LocationSegment[] {
|
|
107
|
+
const startMs = assertFiniteInstant(window.startUtc, "window startUtc");
|
|
108
|
+
const endMs = assertFiniteInstant(window.endUtc, "window endUtc");
|
|
109
|
+
if (startMs >= endMs) {
|
|
110
|
+
throw new RangeError("location window must satisfy startUtc < endUtc");
|
|
111
|
+
}
|
|
112
|
+
const ordered = [...observations].sort(compareObservations);
|
|
113
|
+
const segments: LocationSegment[] = [];
|
|
114
|
+
for (let index = 0; index < ordered.length; index += 1) {
|
|
115
|
+
const current = ordered[index]!;
|
|
116
|
+
const currentMs = Date.parse(current.observedAtUtc);
|
|
117
|
+
if (currentMs < startMs || currentMs >= endMs) {
|
|
118
|
+
throw new RangeError(
|
|
119
|
+
`location observation at "${current.observedAtUtc}" falls outside the requested [${window.startUtc}, ${window.endUtc}) window`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const nextMs =
|
|
123
|
+
index + 1 < ordered.length ? Date.parse(ordered[index + 1]!.observedAtUtc) : endMs;
|
|
124
|
+
const segmentEndMs = Math.min(nextMs, endMs);
|
|
125
|
+
if (currentMs >= segmentEndMs) continue;
|
|
126
|
+
segments.push({
|
|
127
|
+
startUtc: new Date(currentMs).toISOString(),
|
|
128
|
+
endUtc: new Date(segmentEndMs).toISOString(),
|
|
129
|
+
place: placeForSegment(current.place, options.retainCoordinates),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return segments;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Total time a place was occupied (union of its segment intervals, ms). */
|
|
136
|
+
export interface PlaceDuration {
|
|
137
|
+
place: LocationPlace;
|
|
138
|
+
totalMs: number;
|
|
139
|
+
/** Earliest segment start contributing to this place's total. */
|
|
140
|
+
firstStartUtc: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function unionIntervalMs(segments: readonly LocationSegment[]): number {
|
|
144
|
+
// Merge overlapping [start, end) intervals per place so two providers
|
|
145
|
+
// observing the same visit do not double-count its duration.
|
|
146
|
+
const bounds = segments
|
|
147
|
+
.map((segment) => ({ start: Date.parse(segment.startUtc), end: Date.parse(segment.endUtc) }))
|
|
148
|
+
.sort((a, b) => (a.start !== b.start ? a.start - b.start : a.end - b.end));
|
|
149
|
+
let total = 0;
|
|
150
|
+
let sweepStart: number | null = null;
|
|
151
|
+
let sweepEnd = 0;
|
|
152
|
+
for (const bound of bounds) {
|
|
153
|
+
if (sweepStart === null || bound.start > sweepEnd) {
|
|
154
|
+
if (sweepStart !== null) total += sweepEnd - sweepStart;
|
|
155
|
+
sweepStart = bound.start;
|
|
156
|
+
sweepEnd = bound.end;
|
|
157
|
+
} else if (bound.end > sweepEnd) {
|
|
158
|
+
sweepEnd = bound.end;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (sweepStart !== null) total += sweepEnd - sweepStart;
|
|
162
|
+
return total;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Per-place totals across every segment given (multi-source safe). */
|
|
166
|
+
export function placeDurations(segments: readonly LocationSegment[]): PlaceDuration[] {
|
|
167
|
+
const byPlace = new Map<string, { place: LocationPlace; segments: LocationSegment[]; firstStartUtc: string }>();
|
|
168
|
+
for (const segment of segments) {
|
|
169
|
+
const existing = byPlace.get(segment.place.id);
|
|
170
|
+
if (existing === undefined) {
|
|
171
|
+
byPlace.set(segment.place.id, { place: segment.place, segments: [segment], firstStartUtc: segment.startUtc });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
existing.segments.push(segment);
|
|
175
|
+
if (segment.startUtc < existing.firstStartUtc) existing.firstStartUtc = segment.startUtc;
|
|
176
|
+
// Two providers may label the same place id differently; keep the
|
|
177
|
+
// lexicographically smallest label so rendering stays deterministic.
|
|
178
|
+
if (segment.place.label < existing.place.label) existing.place = segment.place;
|
|
179
|
+
}
|
|
180
|
+
const durations: PlaceDuration[] = [...byPlace.values()].map((entry) => ({
|
|
181
|
+
place: entry.place,
|
|
182
|
+
totalMs: unionIntervalMs(entry.segments),
|
|
183
|
+
firstStartUtc: entry.firstStartUtc,
|
|
184
|
+
}));
|
|
185
|
+
durations.sort((a, b) => {
|
|
186
|
+
if (a.place.id !== b.place.id) return a.place.id < b.place.id ? -1 : 1;
|
|
187
|
+
return 0;
|
|
188
|
+
});
|
|
189
|
+
return durations;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface DominantPlaceResult {
|
|
193
|
+
place: LocationPlace;
|
|
194
|
+
totalMs: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The place with the longest total occupancy. Ties are stable and
|
|
199
|
+
* deterministic: earlier first start wins, then the lexicographically smaller
|
|
200
|
+
* place id — never insertion order.
|
|
201
|
+
*/
|
|
202
|
+
export function dominantPlace(segments: readonly LocationSegment[]): DominantPlaceResult | null {
|
|
203
|
+
const durations = placeDurations(segments);
|
|
204
|
+
let best: PlaceDuration | null = null;
|
|
205
|
+
for (const duration of durations) {
|
|
206
|
+
if (best === null) {
|
|
207
|
+
best = duration;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (duration.totalMs !== best.totalMs) {
|
|
211
|
+
if (duration.totalMs > best.totalMs) best = duration;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (duration.firstStartUtc !== best.firstStartUtc) {
|
|
215
|
+
if (duration.firstStartUtc < best.firstStartUtc) best = duration;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (duration.place.id < best.place.id) best = duration;
|
|
219
|
+
}
|
|
220
|
+
return best === null ? null : { place: best.place, totalMs: best.totalMs };
|
|
221
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { parseActivityConfig } from "../activity/config.js";
|
|
2
|
+
import { parseMeetingsConfig } from "../meetings/config.js";
|
|
3
|
+
import { parseProvenanceConfig } from "../provenance.js";
|
|
4
|
+
|
|
5
|
+
/** Parse capture-adjacent config blocks without location (#2044). */
|
|
6
|
+
export function parseCaptureCompanionConfigs(cfg: Record<string, unknown>) {
|
|
7
|
+
return {
|
|
8
|
+
activity: parseActivityConfig(cfg.activity),
|
|
9
|
+
meetings: parseMeetingsConfig(cfg.meetings),
|
|
10
|
+
provenance: parseProvenanceConfig(cfg.provenance),
|
|
11
|
+
};
|
|
12
|
+
}
|