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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.

wireframe_define_how_tenors_resolve.png

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 alias failure opens the same screen) and history
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.

  1. No operation resolves a tenor over the wire. The resolution lives in resolve_end_date, a domain function in ores.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 a refdata.v1.tenors.resolve request 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.
  2. The refusal is a thrown exception with no message on the wire. resolve_end_date throws std::invalid_argument or std::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.
  3. A swap's rolls are not a refdata row, and are not meant to be. tenor_schedule names 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 reads calendar_event rows. 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

11. Related knowledge

12. See also

Emacs 29.3 (Org mode 9.6.15)