Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions specs/001-interactor-confidence-filter/contracts/ui-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# UI contract: Interactor confidence filtering and download

This feature exposes no new service API — it reads data already fetched. Its
contract is therefore the surface a reader and a test can address: the URL, the
control, and the file.

## 1. The URL

| Parameter | Type | Default | In the address when |
| ----------------- | ----------- | ------- | ------------------------- |
| `interactorScore` | number, 0–1 | `0.45` | the reader has changed it |

- Absent means 0.45, and the app **must not** rewrite the address to add it
(FR-007). This follows from declaring it as `urlParam<number>(0.45, 'number')`:
`currentQueryParams()` omits any value equal to its initial.
- A value that is malformed, negative or above 1 is replaced by 0.45 and must not
prevent the pathway or its interactors from opening (FR-008).
- Clearing the interactors removes it from the address (FR-013).

Shareability is the point: the same address opened elsewhere shows the same
interactors (SC-003).

## 2. The control

Rendered beneath the diagram, only while interactors are shown.

| Addressable by | Contract |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `cr-interactor-threshold` | present exactly when interactor nodes are on the graph; absent otherwise (FR-002) |
| `[data-threshold]` on that element | the threshold in force, so a test can read it without inspecting a slider's pixel position |
| its slider | `min=0`, `max=1`; changing it updates the URL and the diagram together (FR-003) |
| its empty state | when the threshold hides every interaction, says so — distinguishably from the entity having none (FR-012) |

**Tests assert on the interactors present on the diagram**, not on the slider's
position and not on the parameter. The control moving is not evidence that
anything was filtered.

## 3. The file

| Property | Value |
| -------- | -------------------------------------------------------------------------------------------- |
| Format | TSV, `text/tab-separated-values` |
| Name | `Interactors [<entity stId>] [<resource>].tsv` |
| Header | `geneName`, `identifier`, `speciesName`, `entitiesCount`, `evidenceCount`, `score` |
| Rows | exactly the interactions currently on the diagram — those at or above the threshold (FR-010) |

- Produced in the browser from data already held; no request is made.
- The object URL is revoked after use, which the existing participant export
omits to do.
- Because it is synchronous there is no progress and no failure state to report;
FR-011 reduces to "must not produce a silently empty or partial file", which is
asserted by comparing the row count against the interactors on the diagram.

## 4. What does not change

- No change to any ContentService request or response.
- No change to how interactors are requested, drawn or cleared.
- No new dbId anywhere: the export names its entity by stable id.
79 changes: 79 additions & 0 deletions specs/001-interactor-confidence-filter/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Data model: Interactor confidence filtering and download

No persisted storage and no new service payload. Everything here is state already
in the browser, plus one value in the URL.

## Interaction

What the interactor service already fetches and draws. Held on the cytoscape node
that owns it, as `occurrenceNode.data('interactors')`.

| Field | Type | Notes |
| -------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `score` | number | 0–1. Measured 0.482–0.98 across the 33 interactions of Q13158. The only field this feature reads to decide visibility. |
| `identifier` | string | With `databaseName`, forms the `DB:ID` shown in the table. |
| `geneName` | string[] | First entry, falling back to `variantIdentifier`, is what the table shows. |
| `speciesName` | string | Exported. |
| `entitiesCount`, `evidenceCount` | number | Exported. |

**Validation**: an interaction with no `score` was not seen in the measured data.
If one arrives it is treated as **below every threshold** — hidden rather than
shown — because a claim with no confidence behind it is the one a curator raising
the threshold is trying to remove. This rule is asserted in
`interactor-threshold.spec.ts` rather than left to chance.

## Confidence threshold

The lowest score a curator wants to see.

| Property | Value |
| ------------------------ | -------------------------------------------------------------------------------------------------- |
| Type | number, 0–1 |
| Default | **0.45** — `DEFAULT_SCORE` in `pwp-diagram`'s `InteractorsContent.java` |
| Scope | one per interaction resource |
| Lives in | the URL, as a `urlParam<number>` with initial value 0.45 |
| Absent from the URL when | equal to 0.45, because `currentQueryParams()` omits a value equal to its initial — which is FR-007 |

**Clamping**: a value outside 0–1, or one that does not parse, falls back to 0.45
rather than blocking the pathway from opening (FR-008). The clamp is a pure
function so it can be tested against the hand-edited addresses FR-008 describes.

**Per-resource memory**: a `Map<string, number>` on `InteractorService`, keyed by
resource name, mirroring `interactorsThreshold` in the old browser. Switching
resource writes that resource's remembered value into the single URL param.
Session-only; the old browser also forgets on reload.

## Interaction resource

Already modelled: `InteractorService.currentResource = signal<ResourceAndType>`,
where `ResourceType` is STATIC, PSICQUIC or CUSTOM. This feature adds no fields —
it uses the resource's **name** as the key for the threshold map.

## Shown interactor set

Derived, not stored: the interactions of the entities whose interactors were
requested, with `score >= threshold`. It is what the diagram draws and exactly
what the export writes (FR-010).

**State transitions** that must hold:

| From | Event | To |
| -------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| no interactors shown | reader opens interactors for an entity | shown at the threshold in force; control appears |
| shown | threshold raised above some scores | those interactions leave the diagram; the rest stay |
| shown | threshold lowered to 0 | every interaction the resource returned is on the diagram |
| shown | threshold above every score | nothing drawn, and the control says the threshold is hiding them — distinct from having none (FR-012) |
| shown | resource switched | that resource's remembered threshold applies, not the previous one (FR-004a) |
| shown | interactors cleared | control and export go; the param leaves the URL (FR-013) |

## Export row

One line of TSV per interaction currently shown.

Columns, in the order the interactors table already presents them, so the file
matches what the curator was looking at: `geneName`, `identifier`,
`speciesName`, `entitiesCount`, `evidenceCount`, `score`.

Filename follows the existing participant export
(`Participating Molecules [R-HSA-109606].tsv`), naming the entity and the
resource: `Interactors [<entity stId>] [<resource>].tsv`.
124 changes: 124 additions & 0 deletions specs/001-interactor-confidence-filter/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Implementation Plan: Interactor confidence filtering and download

**Branch**: `001-interactor-confidence-filter` | **Date**: 2026-09-14 | **Spec**: [spec.md](./spec.md)

**Input**: Feature specification from `specs/001-interactor-confidence-filter/spec.md`

## Summary

Give the interactor overlay a confidence threshold and an export, which are the
two rows of the curator release checklist (`RELEASE-TESTING.md:117-118`) that
cannot be signed off because the behaviour does not exist here.

Both are additions to machinery that already exists. The scores are already
fetched and already displayed in the interactors table; the interactions are
already held on the cytoscape node that owns them
(`occurrenceNode.data('interactors')`), so filtering and export are both
in-memory. Nothing new is fetched, and no service contract changes.

The threshold goes in the URL, because `UrlStateService`'s reader resets any
param the URL does not mention — a signal alone does not survive the turn, let
alone a reload. Declaring it with an initial value of `0.45` gets FR-007 free:
`currentQueryParams()` omits a value equal to its initial, so a threshold nobody
changed is absent from the address rather than written into it.

## Technical Context

**Language/Version**: TypeScript 5.x, Angular 21 (zoneless, signals, `strictTemplates`)

**Primary Dependencies**: cytoscape (the diagram), Angular Material (the control), existing `InteractorService` and `UrlStateService`

**Storage**: none. The threshold lives in the URL; per-resource memory is a `Map` in the service for the session only, matching the old browser, which also forgets on reload.

**Testing**: vitest for units, Playwright for e2e (`npm run e2e`, project `code`)

**Target Platform**: the browser, same as the rest of the pathway browser

**Project Type**: feature inside an existing Angular workspace — no new project

**Performance Goals**: dragging the control must not make the diagram
unresponsive (SC-005). Filtering is a class or style toggle over elements already
on the graph, so the cost is cytoscape's restyle, not a re-layout or a fetch.

**Constraints**: the six quality gates, with two ratcheted baselines — `check:lint` 653 and `check:dead` 146 — which a new component must not raise.

**Scale/Scope**: one new component, one new URL param, one export function, plus
changes to the interactor service. Measured worst case in the data: 33
interactions on a single entity; a diagram may show several entities' at once.

## Constitution Check

| Principle | How this plan satisfies it |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **I. Verify the instrument** | Every assertion counts interactors **on the diagram**, never the control's position or the param's value. "Interactors are shown" is read from the graph holding interactor nodes, not from `currentResource()` being set, because the graph is what the reader sees. |
| **II. Measure in the running app** | The threshold is URL state, and URL state has twice behaved in ways invisible from the source (#185, #191). Every acceptance scenario gets an e2e case; unit tests cover only the pure parts (the filter predicate, the TSV shape). |
| **III. Prove a test fails first** | The two checklist rows are currently **missing**, so each new e2e case must be shown red against `main` before the feature exists — that is the cheapest possible "prove it fails", and it is recorded per task. |
| **IV. Never leave a reader on an unstable id** | The export names its entity by stable id; the threshold param carries a number, no ids. Nothing here introduces a dbId. |
| **V. Comments carry the failure, with measured figures** | The only figures that appear in code comments are 0.45 (from `InteractorsContent.java`) and the 0.482–0.98 range (measured for Q13158). Both are cited where used. |

**Gate result: pass.** No violations to justify; Complexity Tracking below is empty.

One deviation from the input brief, recorded rather than silently taken:
`FileDownloadService` / `ManagedDownloadDirective` are **not** reused. They exist
for server downloads — progress, cancellation, a 180s ceiling, a failure reason
from a response. The export is synchronous and in-memory, so those states cannot
occur, and wiring them in would add a spinner that never spins. See research.md
§3, which also records the consequence for FR-011.

## Project Structure

### Documentation (this feature)

```
specs/001-interactor-confidence-filter/
├── spec.md
├── plan.md # this file
├── research.md # Phase 0
├── data-model.md # Phase 1
├── quickstart.md # Phase 1
├── contracts/
│ └── ui-contract.md # Phase 1 — the UI surface, there being no new service API
└── checklists/
└── requirements.md
```

### Source code (repository root)

```
projects/pathway-browser/src/app/
├── interactors/
│ ├── interactor-threshold/ # NEW: the control
│ │ ├── interactor-threshold.component.ts
│ │ ├── interactor-threshold.component.html
│ │ └── interactor-threshold.component.scss
│ ├── interactor-export.ts # NEW: pure — rows to TSV
│ ├── interactor-export.spec.ts # NEW
│ ├── interactor-threshold.ts # NEW: pure — the filter predicate + clamping
│ ├── interactor-threshold.spec.ts # NEW
│ └── services/interactor.service.ts # CHANGED: per-resource thresholds, apply the filter
├── services/url-state.service.ts # CHANGED: one new param
└── viewport/
├── viewport.component.html # CHANGED: render the control under the diagram
└── viewport.component.ts # CHANGED: whether to show it

e2e/
└── interactor-threshold.spec.ts # NEW

RELEASE-TESTING.md # CHANGED: :117 and :118 missing -> auto
CURATOR-REPORT.md # CHANGED: drop the two matching gaps
```

**Structure Decision**: the feature lives beside the interactor code it extends,
under `projects/pathway-browser/src/app/interactors/`. The two pure modules are
separate files so they can be unit-tested without a browser — the pattern
`url-state.service.spec.ts` uses for `FRAGMENT_PATTERN` and `isContentRoute`.

## Complexity Tracking

No constitution violations, so nothing to justify here.

## Phase status

- [x] Phase 0 — research.md: the five decisions, all evidenced
- [x] Phase 1 — data-model.md, contracts/ui-contract.md, quickstart.md
- [ ] Phase 2 — tasks.md, by `/speckit-tasks`
Loading