Skip to main content

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
    keep_history_forever=False,      # when True, never prune history (retention/size ignored)
)

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.

Release files for multi-user-scale-core 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for multi-user-scale-core 0.2.0
File Size Uploaded
multi_user_scale_core-0.2.0.tar.gz 18.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for multi-user-scale-core 0.2.0
File Interpreter ABI Platform
multi_user_scale_core-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 30.4 kB

Release files / multi_user_scale_core-0.2.0.tar.gz

Download URL multi_user_scale_core-0.2.0.tar.gz
Size 18.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e611c57fd1028f3be5559879fa1c5e8ad22b73aa749966a5ead61748ef7b511a
BLAKE2b-256 checksum
How to use checksums
5acb2608034d81354758420bcc345ea73851534096efdb9615905300b792a95a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / multi_user_scale_core-0.2.0-py3-none-any.whl

Download URL multi_user_scale_core-0.2.0-py3-none-any.whl
Size 12.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
325d795154a0ff9e444822a23f3e30c40a49a486311cfff774163b1b6454c768
BLAKE2b-256 checksum
How to use checksums
26ef0b06923015a5ba45dc3adad5049969b982ee18da935f82685a0915ebbf7a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page