> ## Documentation Index
> Fetch the complete documentation index at: https://api.tipee.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Timelines

> Several kinds of data in tipee are stored as a timeline of periods rather than as independent records. Writing to a timeline reshapes its neighbours — this page explains how.

Some data in tipee is not a list of independent records. It is a **timeline**: an ordered set of
periods that never overlap, each covering a date range and carrying a set of values.

Activity rates, sector assignments and vacation entitlements all work this way, and future
entitlements will too. They share the same write semantics, which this page describes once. Each
entity's own page lists what is specific to it — above all, which fields decide whether two periods
count as identical.

> [!WARNING]
>
> **The consequence to internalise.** An endpoint such as `resources.assign-activity-rate` does not
> mean "create a record". It means **"make these values true over this range"**, and tipee reshapes
> the neighbouring periods so that the statement holds.
> Most of the time this is what you want, and you can let tipee do it. The two situations worth
> knowing about are further down: a call that **appears to do nothing**, and a call that **deletes an
> existing period**.

## Anatomy

A timeline belongs to one subject — usually a collaborator — and to one kind of data. A collaborator
has an activity rate timeline, a sector timeline, and so on; they are independent of each other.

Each **period** has a **date range**, which may be open-ended where the entity allows it, and a **set
of values** whose shape depends on the entity. Within one timeline, periods are sorted by start date
and never overlap.

## The two rules

**Rule 1 — the requested range always wins.** The date range you send is stored exactly as sent.
tipee never shifts your start date, never trims your end date, never fills a gap on your behalf.
Existing periods yield in whichever direction is needed.

**Rule 2 — identical neighbours merge.** If the period you assign is **adjacent to or overlapping**
an existing one, **and** the two are identical, they become a single period spanning both ranges.
Otherwise the existing period is truncated, split, or removed to make room.

Adjacent means the two ranges touch: one ends the day before the other starts. Overlapping means
they share at least one day. A gap of one day or more is never bridged.

Identical means that **every** field in the entity's identity set holds the same value. That set is
defined per entity and listed on the entity's own page.

> [!NOTE]
>
> **The comparison is on effective values, not on what your payload contained.** Sending a value
> explicitly, omitting it so a default applies, or leaving it empty so it is inherited are all
> equivalent — what matters is the value that ends up applying. So two payloads that look different
> can merge, and two payloads that look alike may not.

## What a write does

In every diagram, `Before` is the existing timeline, `Assign` is the period you send, and `Result` is
the resulting timeline.

Different letters mean different values; **the same letter twice means identical values** — that is
how the diagrams encode Rule 2. Columns are contiguous: there is no hidden gap between two adjacent
columns.

### Assigning next to an identical period extends it

This is the one that surprises integrators. You assigned a new period, the call succeeded, and the
number of periods did not change — because tipee merged yours into its neighbour. Nothing was lost:
the existing period now runs further.

```
         │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │
Before   │══════ A ═══════│                │
Assign   │                │══════ A ═══════│
Result   │════ A  (one single period) ═════│
```

Had the values differed, you would simply have two periods side by side.

### Assigning over a different period truncates it

The existing period is cut back to the day before your range starts.

```
         │ Jan 1 – Jan 14 │Jan 15 – Jan 31 │ Feb 1 – Feb 28 │
Before   │═══════════════ A ═══════════════│                │
Assign   │                │═══════════════ B ═══════════════│
Result   │══════ A ═══════│═══════════════ B ═══════════════│
```

### Assigning the exact same range replaces it

No error, no duplicate. **This is the normal way to modify an existing period.**

```
         │ Jan 1 – Jan 31 │
Before   │══════ A ═══════│
Assign   │══════ B ═══════│
Result   │══════ B ═══════│
```

### Assigning inside a period splits it in two

Three periods result, and the tail keeps the original values.

```
         │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │ Mar 1 – Mar 31 │
Before   │═══════════════════════ A ════════════════════════│
Assign   │                │══════ B ═══════│                │
Result   │══════ A ═══════│══════ B ═══════│══════ A ═══════│
```

### Assigning over a whole period deletes it

> [!WARNING]
>
> When your range covers an existing period entirely and the values differ, that period is **removed**.
> It leaves no trace and there is no warning. Check the extent of your range before assigning over a
> populated stretch of timeline.

```
         │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │ Mar 1 – Mar 31 │
Before   │                │══════ A ═══════│                │
Assign   │═══════════════════════ B ════════════════════════│
Result   │════════════════ B   (A is gone) ═════════════════│
```

> **Overlapping ranges with identical values** merge into the union of the two ranges, exactly as
> adjacent ones do.
>
> ```
>          │ Jan 1 – Jan 14 │Jan 15 – Jan 31 │ Feb 1 – Feb 28 │
> Before   │═══════════════ A ═══════════════│                │
> Assign   │                │═══════════════ A ═══════════════│
> Result   │═════════════ A  (one single period) ═════════════│
> ```
>
> **Splitting works on open-ended periods too.** The tail stays open-ended.
>
> ```
>          │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │  Mar 1 – ...   │
> Before   │═══════════════ A   (no end date) ════════════════│
> Assign   │                │══════ B ═══════│                │
> Result   │══════ A ═══════│══════ B ═══════│══════ A ═══════│
> ```
>
> **The rules are symmetric.** A range starting before an existing period pushes that period's
> start forward.
>
> ```
>          │ Jan 1 – Jan 31 │ Feb 1 – Feb 10 │Feb 11 – Feb 28 │
> Before   │                │═══════════════ A ═══════════════│
> Assign   │═══════════════ B ═══════════════│                │
> Result   │═══════════════ B ═══════════════│══════ A ═══════│
> ```
>
> **A range spanning several periods** makes each neighbour yield on its own side.
>
> ```
>          │ Jan 1 – Jan 14 │Jan 15 – Jan 31 │ Feb 1 – Feb 15 │Feb 16 – Feb 28 │
> Before   │═══════════════ A ═══════════════│═══════════════ C ═══════════════│
> Assign   │                │═══════════════ B ═══════════════│                │
> Result   │══════ A ═══════│═══════════════ B ═══════════════│══════ C ═══════│
> ```
>
> **A merge can happen on both sides at once.** Overwriting a middle period so that it becomes
> identical to its neighbours collapses everything into one.
>
> ```
>          │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │ Mar 1 – Mar 31 │
> Before   │══════ A ═══════│══════ C ═══════│══════ A ═══════│
> Assign   │                │══════ A ═══════│                │
> Result   │═════════════ A  (one single period) ═════════════│
> ```

## Calls that appear to do nothing

A merge can absorb your request entirely. The call succeeds, the timeline is unchanged, and nothing
is wrong.

**Assigning an identical open-ended period after an existing open-ended one** changes nothing: the
union of the two ranges is the existing range.

```
         │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │  Mar 1 – ...   │
Before   │═══════════════ A   (no end date) ════════════════│
Assign   │                │═══════ A   (no end date) ═══════│
Result   │═════════ A   (no end date)  — unchanged ═════════│
```

> [!WARNING]
>
> **Assigning an identical bounded period inside an open-ended one silently discards your end date.**
> You asked for values ending on Feb 28, the call succeeded, and the period still runs forever —
> because the union of `[Jan 1 → ∞]` and `[Feb 1 → Feb 28]` is still `[Jan 1 → ∞]`.
> This one does not generate a support call. It generates wrong data months later.
>
> **What to do:** an assign can never shorten a period. To end one, use the entity's unassign
> endpoint.

```
         │ Jan 1 – Jan 31 │ Feb 1 – Feb 28 │  Mar 1 – ...   │
Before   │═══════════════ A   (no end date) ════════════════│
Assign   │                │══════ A ═══════│                │
Result   │═════════ A   (no end date)  — unchanged ═════════│
```

The same holds for an identical period assigned inside an existing bounded one. In all three cases,
changing any single identity field gives you the split you expected instead.

## Removing instead of assigning

Where an entity exposes an unassign endpoint, it clears the timeline over the intersection of your
range with the existing periods, and succeeds silently when there was nothing to remove.

It is the only way to **end** a period on a given date.

```
         │ Jan 1 – Jan 31 │  Feb 1 – ...   │
Before   │═══════ A   (no end date) ═══════│
Unassign │                │════ remove ════│
Result   │══════ A ═══════│   · empty ·    │
```

It can also punch a hole in the middle of a period, splitting it in two.

## Recipes

> Assign the new values over an open-ended range beginning on that date. The existing period is
> truncated the day before, and you end up with two periods.
> If the new values happen to be identical to the current ones, nothing happens — which is the
> correct outcome, since the timeline already said what you wanted it to say.

> Re-assign the **exact same date range** with the new values. The period is replaced in place.
>
> **What to do afterwards:** read the timeline back. If the corrected values now match a
> neighbour, the two will have merged, and your period no longer exists as a separate entry.

> An assign cannot shorten a period from the right. Use the entity's unassign endpoint over the
> range you want cleared, starting the day after the intended end.

> There is no merge indicator in a response, and periods are not always addressable individually.
> Reading the timeline before and after the write, and diffing the two, is the reliable way to know
> whether your call created, merged, truncated, split or did nothing.

## What varies between entities

The rules above hold for every timeline. What each entity defines for itself, on its own page:

> **The identity fields**
>
> Which fields must match for two periods to be considered identical — the input to Rule 2.

> **The endpoints**
>
> The assign and unassign operation names, their payloads, and what they return.

> **Range constraints**
>
> Whether open-ended ranges are accepted, and whether the timeline may contain gaps at all.

> **Defaults and inheritance**
>
> Which values can be omitted, and what they resolve to — which in turn decides what counts as
> identical.

Read this page first, then the entity's page for its specifics.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.