Skip to content

popoto.fields.decaying_sorted_field

popoto.fields.decaying_sorted_field

DecayingSortedField — time-weighted scoring via Lua decay computation.

This module provides a SortedField subclass where records lose relevance over time following power-law decay: base_score * elapsed_days^(-decay_rate).

The sorted set stores timestamps as scores. A Lua script computes decay-ranked results at query time, reading base scores from each member's model hash via cmsgpack.

Design
  • Timestamps are always stored as scores (auto_now=True behavior)
  • Decay computation happens server-side in Lua (no round trips)
  • Base scores come from a companion field on the same model hash
  • When base_score_field is None, all items have equal base score (1.0)
Example

class Memory(Model): key = UniqueKeyField() content = StringField() strength = FloatField(default=1.0) last_accessed = DecayingSortedField( decay_rate=0.5, base_score_field="strength", )

Query top-10 memories by decayed relevance

top = Memory.query.top_by_decay("last_accessed", n=10)

Refresh a memory's decay clock without full save

memory.touch("last_accessed")

DecayingSortedField

Bases: SortedFieldMixin, Field

A SortedField subclass where records lose relevance over time.

Stores timestamps as sorted set scores. A Lua script computes decay-ranked results at query time using power-law decay: decayed_score = base_score * elapsed_days ^ (-decay_rate)

With decay_rate=0.5, a record scores 1.0 after 1 day, 0.5 after 4 days, and 0.1 after 100 days.

Parameters:

Name Type Description Default
decay_rate

Controls how fast scores drop. Higher = faster decay. Default 0.5. Must be > 0.

required
base_score_field

Name of a companion field whose value multiplies the decay curve. When None, base score is 1.0.

required
partition_by

Partition the sorted set by key field values. Inherited from SortedFieldMixin.

required
Source code in src/popoto/fields/decaying_sorted_field.py
class DecayingSortedField(SortedFieldMixin, Field):
    """A SortedField subclass where records lose relevance over time.

    Stores timestamps as sorted set scores. A Lua script computes
    decay-ranked results at query time using power-law decay:
        decayed_score = base_score * elapsed_days ^ (-decay_rate)

    With decay_rate=0.5, a record scores 1.0 after 1 day, 0.5 after
    4 days, and 0.1 after 100 days.

    Args:
        decay_rate: Controls how fast scores drop. Higher = faster decay.
            Default 0.5. Must be > 0.
        base_score_field: Name of a companion field whose value multiplies
            the decay curve. When None, base score is 1.0.
        partition_by: Partition the sorted set by key field values.
            Inherited from SortedFieldMixin.
    """

    def __init__(self, **kwargs):
        decay_rate = kwargs.pop("decay_rate", None)
        self.decay_rate = decay_rate if decay_rate is not None else Defaults.DECAY_RATE
        self.base_score_field = kwargs.pop("base_score_field", None)

        if self.decay_rate <= 0:
            raise ModelException(
                "decay_rate must be > 0 (got {})".format(self.decay_rate)
            )

        # Force type=float and auto_now=True behavior
        kwargs["type"] = float
        kwargs["auto_now"] = True
        kwargs["sorted"] = True
        super().__init__(**kwargs)