Skip to main content

Core weight routing primitives for multi-user scale integrations

Project description

multi-user-scale-core

PyPI version

Not every smart scale app or integration surfaces who's standing on the scale. This library solves that: given a weight reading and a set of users with history, it returns a ranked list of likely owners. Pure Python, no runtime dependencies. Fully typed (PEP 561).

Features

  • WeightRouter: Route incoming weight measurements to users using adaptive tolerance and competitive pruning.
  • Adaptive tolerance: Exponentially-weighted reference weight, variance-based tolerance, and recency scaling that automatically widens the window when a user hasn't weighed in recently.
  • Dormant User Retention: Users inactive past the retention window retain their baseline for future matching with an expanded tolerance margin.
  • Persistence: to_dict() / from_dict() for saving and restoring router state across restarts.
  • Models: WeightMeasurement, UserProfile, RouterConfig, MeasurementCandidate.

Buy Me A Coffee

Installation

Requires Python 3.10+. Install using pip:

pip install multi-user-scale-core

Quick Start

from multi_user_scale_core import RouterConfig, UserProfile, WeightMeasurement, WeightRouter
from datetime import datetime, timezone

router = WeightRouter(config=RouterConfig())
router.set_users([
    UserProfile(user_id="alice", display_name="Alice"),
    UserProfile(user_id="bob", display_name="Bob"),
])

# Evaluate an incoming measurement (e.g. from a scale sensor)
measurement = WeightMeasurement(
    weight_kg=75.2,
    timestamp=datetime.now(tz=timezone.utc),
    source_id="sensor.scale",
)
candidates = router.evaluate_measurement(measurement)
# candidates: list[MeasurementCandidate], ordered by match quality.
# Matched users come first, sorted by proximity to their reference weight.
# Users with no history yet are appended at the end.
#
# Matched candidates include:
#   .reference_weight_kg  — the weighted-average reference used for comparison
#   .tolerance_kg         — the tolerance band that accepted this reading
# No-history candidates have both fields as None.

# Once confirmed (e.g. by the user), record the measurement
router.record_measurement_for_user("alice", measurement)

Usage

Reassigning and removing measurements

# Move the latest measurement from alice to bob (e.g. after user correction)
router.reassign_measurement("alice", "bob")

# Move a specific measurement by ID
router.reassign_measurement("alice", "bob", measurement_id="abc123")

# Remove the latest measurement for a user
router.remove_measurement("alice")

# Remove a specific measurement by ID
router.remove_measurement("alice", measurement_id="abc123")

Managing users

router.set_users([
    UserProfile(user_id="alice", display_name="Alice"),
    UserProfile(user_id="bob", display_name="Bob"),
])

Note: set_users() replaces the entire user list. History for any user not present in the new list is permanently discarded. Call to_dict() first if you need to preserve that history.

Persistence

# Serialise state (e.g. to Home Assistant config entry data)
payload = router.to_dict()

# Restore state
router = WeightRouter.from_dict(payload)

# Inject a custom clock (useful in tests or when the stored "now" matters
# for pruning stale history on first mutation after restore)
router = WeightRouter.from_dict(payload, now_provider=lambda: my_fixed_time)

to_dict() includes a "now" snapshot timestamp for human inspection. It is not used during from_dict() restoration.

Configuration

config = RouterConfig(
    history_retention_days=90,       # drop measurements older than this (retaining the most recent 1)
    max_history_size=100,            # cap per-user history length
    tolerance_percentage=0.04,       # base tolerance as fraction of body weight
    min_tolerance_kg=1.5,            # floor on tolerance regardless of body weight
    variance_window_days=30,         # window for variance-based adaptive tolerance
    reference_window_days=7,         # window for computing the reference weight
    min_measurements_for_adaptive=5, # minimum history needed for variance adaptation
    enable_pruning=True,             # drop candidates significantly worse than the best match
    prune_margin_kg=3.0,             # margin above the best match to start pruning
)

Default tolerance constants are exported for convenience:

from multi_user_scale_core import (
    DEFAULT_TOLERANCE_PERCENTAGE,  # 0.04
    MIN_TOLERANCE_KG,              # 1.5
    MAX_TOLERANCE_KG,              # 5.0
    MIN_MEASUREMENTS_FOR_ADAPTIVE, # 5
    REFERENCE_WINDOW_DAYS,         # 7
    VARIANCE_WINDOW_DAYS,          # 30
)

Error handling

All errors inherit from RouterError:

from multi_user_scale_core import (
    DuplicateMeasurementError,    # measurement_id already exists in history
    MeasurementNotFoundError,     # referenced measurement does not exist
    MeasurementValidationError,   # weight is NaN, infinite, or not a number
    RouterError,                  # base class
    UserNotFoundError,            # user_id not registered with set_users()
)

Compatibility

  • Python 3.10+
  • No runtime dependencies

Support the Project

If you find this project helpful, consider buying me a coffee! Your support helps maintain and improve this library.

Buy Me A Coffee

License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

multi_user_scale_core-0.1.2.tar.gz (17.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

multi_user_scale_core-0.1.2-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

Details for the file multi_user_scale_core-0.1.2.tar.gz.

File metadata

  • Download URL: multi_user_scale_core-0.1.2.tar.gz
  • Upload date:
  • Size: 17.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for multi_user_scale_core-0.1.2.tar.gz
Algorithm Hash digest
SHA256 55ee5350f71ccb7559cb155aa105e4b25fa4b4ba1c095f2f5615781039de3ac1
MD5 612009c21f60ee12e32d39f8799d9f50
BLAKE2b-256 a4c75dde40833f26659514cc06d8e5b4f0c2864f60294b504f00975b4ba8e2e2

See more details on using hashes here.

File details

Details for the file multi_user_scale_core-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for multi_user_scale_core-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 09a8ef6f0d07a8e1de6b08403e9feb5c9b84951cb76574323383586cddac07af
MD5 4bd86dc60903b0b7f9546c6ae6f11dcf
BLAKE2b-256 b630567dea4bd14926bc9e843129d072353e03b40830a74ccef3f1d6b8665863

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page