Skip to content
C.W.K.
Stream
Lesson 03 of 05 · published

The Profile You Can See, and What It Caught Within the Hour

~12 min · transparency, debugging, ranking, product-design

Level 0Unsorted
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Cards Had Explained Themselves for Rounds; the Scorer Never Had

Per-card explanations shipped early — each article could say which of its features matched. What did not exist was a view of the model itself: the whole profile, every learned weight, ranked. It was added as a product feature, and it behaved like a diagnostic instrument.

Within an hour of looking at it, the strongest positive signals included the words is and of, sitting near the top with substantial weights. Nothing was broken in a way any test could express. Every score was arithmetically correct. The model had simply learned that grammatical words appear in articles the reader opens — which is true, and useless.

Why It Mattered Even Though the Scores Barely Moved

The instinct is to shrug: these tokens appear in nearly everything, so they add a near-constant to every candidate and barely change the ordering. That reasoning is correct about the score and wrong about the consequence, and the difference is the match cap from two lessons ago.

An article banks a limited number of keyword matches. Slots are spent in iteration order, not in order of usefulness. Two slots taken by grammatical particles are two not available to the actual subjects — so the tokens that genuinely discriminate never get counted for that article at all. The damage is not in the arithmetic; it is in the budget.

The Guard Existed, on the Other Path

The uncomfortable part: the fix already existed in the codebase. Muting had learned the same lesson earlier and had a document-frequency guard for exactly this. Ranking did not use it — the guard had been wired into one consumer of the profile and not the other, because it was introduced while fixing muting and nobody asked who else read those weights.

This is the most common shape of a real bug in a mature system: not an absent idea, but a correct idea applied to one caller. When you add a guard to a shared computation, the question to ask is not "does this fix the bug I am looking at?" but "who else consumes this, and should they all be behind it?"

Ignoring Is Not Muting, and Neither Edits the Log

Showing the profile creates an obligation to let the reader correct it, and the correction needs to be the right verb. Muting says always hide this. Ignoring says stop inferring from this — the reading still happened, the article stays on the shelf, but the signal no longer feeds the model. They are opposites, and conflating them removes a reader's ability to say "yes I read that, no it does not mean what you think."

Critically, neither edits the event log. An ignored signal is dropped at the end of profile construction, not at the source, so the reading is still a fact and the switch can be flipped back.

Make the model visible and it starts debugging itself. A person recognizes nonsense in their own preferences instantly and needs no evaluation harness to do it. That is not a substitute for tests — it finds a different class of defect entirely, the kind where every assertion passes and the output is still absurd.

Code

A profile view that shows the raw weights and marks what is dropped·python
def build_profile(con, respect_ignores: bool = True) -> dict[str, float]:
    """Ignored signals are dropped at the END, not at the source.

    The profile is a projection of the event log, and the reader
    disowning a signal must not look like the reading never happened --
    `respect_ignores=False` is how the profile VIEW shows them what
    they have switched off. An ignored signal stays on screen, or it
    could never be switched back on.
    """
    profile = _accumulate_decayed_contributions(con)
    if respect_ignores:
        for key in life.ignored_signals(con):
            profile.pop(key, None)
    return profile


def profile_view(con) -> list[dict]:
    """What the reader sees. Marks what the guard drops rather than
    showing a number that no longer means anything."""
    raw = build_profile(con, respect_ignores=False)
    ignored = life.ignored_signals(con)
    too_common = life.common_tokens(con)      # the shared DF guard

    rows = []
    for key, weight in sorted(raw.items(), key=lambda kv: -abs(kv[1])):
        rows.append({
            "signal": key,
            "weight": round(weight, 1),
            "ignored": key in ignored,       # reader switched it off
            "common": key in too_common,     # guard drops it: no signal
            "mutes": weight <= MUTE_THRESHOLD and key.startswith("kw:"),
        })
    return rows

External links

Exercise

Find a derived model in your own system — a score, a set of learned weights, a cache of computed preferences — and dump its top twenty entries somewhere a human will actually look. Do not clean it up first. Then write down anything that makes you say 'that cannot be right', and for each one, check whether any existing test could have caught it.
Hint
The entries that look absurd usually are not arithmetic errors; they are the model faithfully learning something true and useless. That class of defect is invisible to tests because tests assert what the code should compute, and the code is computing it correctly. The only detector is a person who knows what the numbers are supposed to mean.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.