User Journey: Define how tenors resolve
Table of Contents
This page documents a user journey: A tenant administrator defines how a tenor code resolves under a convention, down to the date it produces on a named calendar.
A user journey describes what one person is trying to do, before anything is built. It is a design document that outlives any sprint, so it names the screens, the entity fields each screen shows, and the operations and messages each step needs.
1. Goal
"I want every tenor code we quote to mean one date under each convention, so that a ladder, a curve or a swap that names 3M gets the date the desk means by 3M." The journey is done when each convention's set reads correctly and a worked tenor resolves to a date the person can check on a named calendar.
2. Actor and trigger
The actor is the tenant administrator, acting inside their own tenant. No narrower role holds these permissions today. The trigger is a tenor the desk reads differently, a new label, a convention whose anchor or resolution algorithm is wrong, or a swap that refused a date. The journey starts at Tenors, and ends when the person leaves the catalog. The journey keeps no state.
The design question the screen settles is that a tenor has two halves. One half is the code a person reads, with its display name, description and display order. The other half is the resolution the system computes, which depends on the convention and ends in a date on a named calendar. The journey shows both halves at once, so a person never edits a code without seeing the date it produces.
3. Steps
- See the tenors. The person opens Tenors. The system lists every code in display order: code, display name, kind, unit and multiplier, sort order and version. Beside each row it shows the date the tenor resolves to under the convention, calendar and horizon in force. The person searches the code, the display name or the description, and filters by kind. See What is missing for the resolution column.
- Open the tenor. The person picks a row. The system shows the code and the resolution side by side: on the left the code a person reads, on the right the anchor, the offset or the schedule walk, and the date that comes out. The seven columns of the tenor row sit below. The person corrects a field, states a change reason and a commentary, and saves. See What is missing for the two fields the wire does not carry.
- Set its resolution. The person opens Its resolution and chooses a convention. The system shows the convention's default anchor (`measured_from`) and its resolution algorithm, then a matrix of every tenor: whether it is in this convention's set, the anchor it uses, the offset it walks and the date it resolves to. A dim row is a tenor the convention does not resolve. The person changes the convention's description, anchor or algorithm, or the membership row. See What is missing for the read-only junction.
- Walk the schedule. The person opens The schedule and picks a schedule axis. The system shows how the axis produces dates, then expands the axis into a ladder: each roll with its schedule date and the date it becomes after the roll rule adjusts it on the calendar. The person can change the axis, the roll count, the roll rule and the start of the ladder to see the expansion.
- Read the resolved dates. The person opens The resolved dates and sets the tenor, the convention, the calendar, the roll rule and the horizon. The system walks the resolution end to end and shows every turn: the horizon, the anchor, the offset, each schedule step, the roll and the final date. A step that cannot resolve is refused with the step that refused it and the dates it counted.
- Review. The person opens Review. The system repeats the whole resolution in one panel, with the tenor, the convention, the anchor date, the resolved date, the calendar, the roll rule and the change reason, and states whether the record resolves. Nothing is written until the person confirms.
- Write the tenor and the convention. The person confirms. The system writes the tenor row and the convention row, each with a new version, and shows the new version and the resolved date. See What is missing for the missing membership write.
- Check the history. The person opens a row's history. The system shows every version with the field-level difference from the version before, and the person can revert by writing the old values as a new version. History is never rewritten.
- Leave. The person goes back. Every picker and ladder that reads a tenor now resolves it under the new rules.
Two states sit off the rail. The refusal shows a tenor that cannot resolve, with the step that refused it, the dates it counted and the row that owns the fault; the record does not move. History is the state step 8 opens.
No step waits on the server after its reply, and the journey keeps no state.
4. Four-eyes controls
A change that touches data other functions rely on needs a second person, and the person who asks cannot decide. Book types and the controls on each records the rule for each act of this journey:
- A new tenor or a change to a tenor, and a change to a convention or to a convention's set: Market Risk decides, because these decide the date a trade, a curve or a report reads.
Review marks each change as needing a named function or no approval. When any
change is gated, the confirm becomes Raise request: the system raises one
approval request (inbox.v1.ops.raise_approval), shows it waiting, and leaves the
record as it was. A decider from the named function opens the request with
inbox.v1.ops.get_approval, reads what would change and the check, and approves or
declines with a comment (inbox.v1.ops.decide_approval). The request applies only when
every named function has approved, and one decline ends it. The prototype draws the
waiting, decide and declined states (?state=waiting, ?state=decide, ?state=declined); the check it draws is that the tenor resolves on the chosen calendar, which ?check=resolve makes fail.
The approval operations exist. No request kind is seeded for these acts, a request holds no pending change, and nothing applies the change when the last decider approves, so a screen cannot yet raise or apply a gated change. The deferred four-eyes authorisation framework is that missing piece. The deciders are Operations, Finance and Market Risk, and the Acme dataset holds no Finance account; the task Add the missing book-control actors to Acme adds it.
5. Screens and wireframes
One screen with the step rail on the left and one step on the right. The rail carries seven steps in order: see the tenors, open the tenor, set its resolution, walk the schedule, read the resolved dates, review, and write. History and The refusal sit beside the rail. The person moves between steps without leaving the screen.
Figure 1: Tenors: the step rail, the catalog with the resolution column, the code-and-resolution pair, the resolved-dates walk, the refusal and the version diff.
6. Prototype
A static page at doc/prototypes/tenors/define-how-tenors-resolve/index.html: mock
data only, no backend, no build step and no session. It argues that a tenor is
a code a person reads and a resolution the system computes, so the journey shows
both at once and carries the resolution down to the date the tenor produces on a
named calendar.
- States
list,tenor,convention,schedule,resolve,review,outcome,refused(the aliasfailureopens the same screen) andhistory- Source
doc/prototypes/tenors/define-how-tenors-resolve/index.html- Open
/OreStudio/doc/prototypes/tenors/define-how-tenors-resolve/once the site is built, or the file directly
7. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
tenor |
The catalog list and the row form: code, display name, description, sort order, kind, unit, multiplier, and the version columns in history | ores.refdata.tenor |
tenor_kind |
The kind filter on the list and the kind picker on the row | ores.refdata.tenor_kind |
tenor_unit |
The unit picker on the row | ores.refdata.tenor_unit |
tenor_anchor |
The convention's `measured_from` picker and the per-row anchor override | ores.refdata.tenor_anchor |
tenor_resolution_algorithm |
The convention's algorithm picker | ores.refdata.tenor_resolution_algorithm |
tenor_schedule |
The schedule axis, its source and the dates it produces | ores.refdata.tenor_schedule |
tenor_convention |
The convention code, description, default anchor and resolution algorithm | ores.refdata.tenor_convention |
tenor_convention_resolution |
Set membership, the anchor override, and for a SPECIAL tenor the offset unit and multiplier; for a SCHEDULE_STEP row the schedule code and step count | ores.refdata.tenor_convention_resolution |
business_day_convention_type |
The roll rule the resolved date is adjusted by | ores.refdata.business_day_convention_type |
calendar |
The named calendar a resolved date rolls on, and the events an event-lookup schedule reads | ores.refdata.calendar |
The catalog, the convention and the schedule are versioned and register a history provider. The resolution junction keeps closed versions in its table, but its protocol is read-only, so the screen shows membership and cannot change it.
8. Operations and messages
| Step | Operation | Subject | Status |
|---|---|---|---|
| See the tenors | List the catalog | refdata.v1.tenors.list |
exists |
| See the tenors | Offer the kind filter | refdata.v1.tenor_kinds.list |
exists |
| See the tenors | Resolve each row for the resolution column | refdata.v1.ops.resolve_tenor |
exists |
| Open the tenor | Read the row | refdata.v1.tenors.get |
exists |
| Open the tenor | Offer the change reasons | dq.v1.change_reasons.list |
exists |
| Open the tenor | Write the row | refdata.v1.tenors.put |
exists |
| Its resolution | List the conventions | refdata.v1.tenor_conventions.list |
exists |
| Its resolution | Read the convention's tenor set | refdata.v1.tenor_convention_resolutions.list_by_convention_code |
exists |
| Its resolution | Fill the anchor picker | refdata.v1.tenor_anchors.list |
exists |
| Its resolution | Fill the algorithm picker | refdata.v1.tenor_resolution_algorithms.list |
exists |
| Its resolution | Fill the roll-rule picker | refdata.v1.business_day_convention_types.list |
exists |
| Its resolution | Write the convention | refdata.v1.tenor_conventions.put |
exists |
| Its resolution | Write the set membership, the anchor override or the offsets | refdata.v1.tenor_convention_resolutions.put, .delete |
exists |
| The schedule | List the schedule axes | refdata.v1.tenor_schedules.list |
exists |
| The schedule | List the events an event-lookup axis reads | refdata.v1.calendar_events.list |
partial |
| The resolved dates | Resolve the tenor to a date | refdata.v1.ops.resolve_tenor |
exists |
| The resolved dates | Offer the roll rules | refdata.v1.business_day_convention_types.list |
exists |
| The resolved dates | Read the calendar a date rolls on | refdata.v1.calendars.list |
exists |
| Review and write | Write the tenor | refdata.v1.tenors.put |
exists |
| Review and write | Write the convention | refdata.v1.tenor_conventions.put |
exists |
| Review and write | Write the membership row | refdata.v1.tenor_convention_resolutions.put |
exists |
| Check the history | Read every version with its diff | refdata.v1.history.get (entity_type ores.refdata.tenor) |
exists |
| Check the history | List the version rows | refdata.v1.tenors_versions.list |
exists |
| Check the history | Revert by writing the old values | refdata.v1.tenors.put |
exists |
| The refusal | Carry the refusal to the screen | refdata.v1.ops.resolve_tenor (the result on its response) |
exists |
No coverage row is missing any more. The three that were were one
operation, and it now exists:
refdata.v1.ops.resolve_tenor resolves a tenor under a convention and
replies with the anchor date, the date before the roll, the adjusted date,
the roll rule and the calendar it rolled on. The resolution column and the
resolved-dates step are the same operation drawn in two places, and the
refusal travels on the same reply: a resolution the server declines is a
result with a non-ok outcome on resolve_tenor_response, naming the step
that refused and the row at fault, rather than a thrown exception reaching
the screen as a 500.
The partial rows are gone. Every one of them was waiting on something this
journey's own work has since built: the resolution junction was read-only and
now takes put and .delete, the convention had no roll rule and now carries
one, and the tenor write was thought to carry no change reason and in fact
carries one already, in the change_intent every refdata write takes.
9. What is missing
Three items, and two of them are the same operation seen from two sides.
- No operation resolves a tenor over the wire. The resolution lives in
resolve_end_date, a domain function inores.refdata.api/domain/tenor_resolution.hpp. It takes the horizon, the spot date and the schedule dates, and it returns a date or throws. The list step's resolution column and the resolved-dates step both need an operation, and the prototype draws the reply one would return. The candidate is arefdata.v1.tenors.resolverequest whose reply carries the anchor date, the date before the roll, the adjusted date, the roll rule and the calendar. Until then the screen cannot show a resolved date the server confirmed. The convention now carries the roll rule this reply would report, and the resolution junction now takes writes, so the operation is the only piece left. - The refusal is a thrown exception with no message on the wire.
resolve_end_datethrowsstd::invalid_argumentorstd::logic_error, and no operation carries either to a screen. A screen must show the step that refused, the dates it counted and the row that owns the fault. The prototype draws that payload. The candidate is a refusal message, either a failure reply on the resolve operation or a typed error the handler maps. Until then a refused resolution is a 500-class failure with nothing a person can read. This belongs with item 1: the operation is what would carry the refusal. - A swap's rolls are not a refdata row, and are not meant to be.
tenor_schedulenames the axis and the dates it produces, and nothing stores a ladder. The rolls a swap needs are computed where the swap is built. The closed-form axis is evaluated code-side, and an event-lookup axis readscalendar_eventrows. The schedule step draws the expansion so a reviewer can see the axis working, and it is not a write target. That is recorded here as the answer rather than left open, because the journey's own text says so and a repository of swap ladders would be a second source of truth for a date the schedule already derives.
The two items this section used to carry are built. tenor_convention gained its
roll rule, referencing business_day_convention_types and defaulting to
ModifiedFollowing, so the choice the screen draws has a row behind it.
tenor_convention_resolutions is no longer read-only, so the anchor override, the
SPECIAL tenor's offset and a SCHEDULE_STEP row's schedule code and step count all
write. And one item is withdrawn rather than built: the tenor write was recorded
as carrying no change reason, and it carries one already, because every refdata
write takes an intent whose reason_code and commentary the service copies
onto the row.
10. Related journeys
- Define the conventions for an instrument — the convention a tenor resolves under is authored there, and this screen shows the tenor side of the same pair
- Maintain a classification list — the tenor kinds, units, anchors, algorithms and schedules are rows in that journey's lists
- Keep a holiday calendar right — the named calendar a resolved date rolls on, and the events an event-lookup schedule reads
- Set up a currency pair — the pair's spot days and calendars decide the SPOT anchor a tenor resolves from
- Manage a curve's blueprint — a curve's pillars are tenors, and each pillar resolves to a date under this screen's rules
11. Related knowledge
- Tenor — the label, its conventions by curve type, and how each convention resolves it
- Term Structures and Tenors — the ladder a resolved tenor feeds
- Date Rolling and Business-Day Calendars — the roll rules and the calendar a resolved date lands on
- IMM Dates — the quarterly roll the
ROLL_QUARTERaxis follows - FOMC Dates — the meetings the
FOMC_MEETINGaxis reads - Reference Data — versioning, provenance and change reasons
- UX Language
12. See also
- Tenors journeys — the group this journey belongs to
- User Journeys — the index the journey joins
- The journey runtime — the step, the rail, and the rule that a final step cannot be returned past