# The relationship summary

`relationship_console.story` answers one person across every channel and every stage change, newest first: their record, the assembled history, the journey summary, and whatever sales holds for them. It is composed when you ask rather than stored, and the text inside it passes a name guard that removes a form of address standing where a name should be.

- **Status:** Available
- **Audience:** both
- **In the app:** #/relationships, #/timeline
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/relationships/relationship-story/

## What goes into one

- **The person** — display name, company, and the current stage with its label.
- **The history** — the assembled timeline for that person, newest first, bounded at 200 entries by default.
- **The journey summary** — where the relationship has got to and how it got there, from `journey.py`.
- **Selling** — the opportunities and demos `sales.for_person` holds, which is a question the summary asks rather than a thing it owns.

That last one has a deliberate fallback. A workspace whose database predates the sales tables still gets a person's history rather than an error — the summary degrades to what exists instead of refusing to render. It is a small decision that separates a screen which is occasionally incomplete from one which is occasionally blank.

## Composed, not kept

There is no summary document sitting in a column waiting to go stale. Every part is read at the moment of the request, so a stage moved a minute ago is in it, and a case resolved while you were reading is resolved the next time you look. The trade is query cost, which is why the assembly is set-based rather than a loop — the same lesson the [timeline](/docs/relationships/timeline/) page carries the measurements for.

It also means there is nothing to regenerate after a [merge](/docs/relationships/merging/). The merged record simply reads more, which is why a summary can look as though it grew a year of history in one step.

## The name guard

Text about a person accumulates over months: call summaries, notes, memory lines, follow-up reasons. Some of it was written before Connect learned that a respectful form of address is not a name, so a stored line can read *The prospect, Andi, is interested* where *andi* is an honorific, not a human.

| Moment | What it does |
|---|---|
| A person record is created or updated | `clean_name` returns nothing at all when the value is a role word, an honorific or an acknowledgement, so it is never stored as a display name |
| A memory is written | The title and the body are rewritten before they are saved |
| A call summary or a follow-up reason is written | The same rewrite runs on the text |
| The stored relationship is read back into a briefing | It is scrubbed again, because rows written before the guard existed are still on file |

The rewrite is positional, not a deletion of words. *The prospect, Andi, is interested* becomes *The prospect is interested*; a real name in the same position is untouched. The rejected list covers honorifics and kin terms, acknowledgements and fillers, discourse words, and role words like *caller* or *client*, in English and in Telugu and Devanagari script.

> **Note** This is why a summary sometimes refers to *the caller* where you expected a name. The alternative — a model addressing somebody as *Andi* because a note said so — is worse in front of a customer than a slightly formal line.

## What it is not

**Not a report you can edit** — Correcting the summary means correcting what it reads: the memory, the notes, the stage. Editing prose that is recomposed on every request would last until the next request.
**Not a substitute for the timeline** — The summary is oriented to *who is this and where are we*; the timeline answers *what happened, in order*.
**Not cross-workspace** — Isolation holds here as everywhere: one workspace's summary reads that workspace's records only.
**Not the same as the contact brief** — A channel is given a much shorter block with its own budget — see [the contact brief](/docs/relationships/contact-brief/).

## Questions

### How often is the summary rewritten?

It is not written on a schedule at all — it is composed each time it is asked for. There is no refresh interval, no queue, and no stale version to notice.

### Why does the summary call somebody 'the caller' when I know their name?

Because the stored text had a rejected word in the name position, or the name was never captured as one. Setting the person's display name fixes it going forward; the guard leaves genuine names alone.

### Can I stop opportunities appearing in it?

The summary asks sales what it holds for that person; it does not decide what is on the pipeline. Closing or removing the opportunity in [sales](/docs/sales/opportunities/) is what changes the answer.

## Related

- [The customer timeline](https://connectbyjbrh.com/docs/relationships/timeline/)
- [Customer 360](https://connectbyjbrh.com/docs/relationships/customer-360/)
- [The contact brief](https://connectbyjbrh.com/docs/relationships/contact-brief/)
- [What Connect remembers about a person](https://connectbyjbrh.com/docs/relationships/person-memory/)
- [Opportunities: the deal record](https://connectbyjbrh.com/docs/sales/opportunities/)
- [Merging two records](https://connectbyjbrh.com/docs/relationships/merging/)

## What this page is based on

- `backend/app/relationship_console.py` — `story` and its composition
- `backend/app/identity.py` — `clean_name`, `scrub_names` and the rejected words
- `docs-source/sources/CHANNELS.md` §5 — the written summary, names scrubbed on the way out
