# S02 Research — Related Techniques Cross-Linking

## Summary

The data model, API plumbing, and frontend rendering for related technique links all exist but are **empty** — the pipeline never populates the `related_technique_links` table. This slice needs to compute relatedness dynamically at query time rather than pre-populating a join table.

The technique page already renders `related_links` when non-empty (simple list with relationship label). The slice upgrades this to a visually richer card-based display showing 3-4 related techniques with context (creator name, topic category, relationship reason).

**Approach:** Dynamic computation in the `GET /techniques/{slug}` endpoint, replacing the current join-table-based approach. No new tables, no pipeline changes, no background jobs.

## Recommendation

Compute related techniques dynamically in the technique detail endpoint using a scored query: same-creator same-category pages rank highest, same-category different-creator next, then shared `topic_tags` overlap. Limit to 4 results. Return enriched `RelatedLinkItem` with `creator_name`, `topic_category`, and a human-readable `reason` string. Upgrade the frontend section from a plain link list to cards matching the existing design system.

## Implementation Landscape

### Backend — What Exists

| File | Role | Relevant Code |
|------|------|---------------|
| `backend/models.py:69-74` | `RelationshipType` enum | `same_technique_other_creator`, `same_creator_adjacent`, `general_cross_reference` |
| `backend/models.py:249-274` | `RelatedTechniqueLink` model | Fully defined, bidirectional FK to `TechniquePage` |
| `backend/models.py:232-247` | `TechniquePage` relationships | `outgoing_links`, `incoming_links` mapped |
| `backend/routers/techniques.py:109-181` | `get_technique()` | Eager-loads both link directions, builds `related_links` list |
| `backend/schemas.py:243-249` | `RelatedLinkItem` | `target_title`, `target_slug`, `relationship` |

### Backend — What's Needed

1. **New query in `get_technique()`**: After loading the technique page, run a second query to find related pages. Scoring logic:
   - Same `creator_id` AND same `topic_category`: weight 3 (same creator, adjacent technique)
   - Same `topic_category`, different creator: weight 2 (same topic area)
   - Overlapping `topic_tags` (using PostgreSQL `&&` array overlap operator): weight 1 per shared tag
   - Exclude the current page itself
   - ORDER BY score DESC, LIMIT 4

2. **Enrich `RelatedLinkItem`**: Add `creator_name: str = ""`, `topic_category: str = ""`, `reason: str = ""` fields. The `reason` field provides display text like "Same creator" or "Also about compression".

3. **The existing `related_technique_links` table and its eager-loading can stay** — if manually curated links exist in the future, they should take priority. The dynamic query fills in when the table is empty.

### Frontend — What Exists

| File | Role | Relevant Code |
|------|------|---------------|
| `frontend/src/pages/TechniquePage.tsx:494-511` | Related section | Renders `related_links` as `<ul>` with link + relationship label |
| `frontend/src/App.css:1774-1808` | Styles | Basic list styling with muted relationship text |
| `frontend/src/api/public-client.ts:48-52` | `RelatedLinkItem` type | `target_title`, `target_slug`, `relationship` |

### Frontend — What's Needed

1. **Update `RelatedLinkItem` interface** in `public-client.ts`: add `creator_name`, `topic_category`, `reason` fields.

2. **Replace plain list with card layout** in `TechniquePage.tsx`: Each related technique becomes a small card showing title (linked), creator name, topic category badge, and reason text. Use existing CSS variables and badge patterns from the codebase.

3. **Update CSS** in `App.css`: Replace the `.technique-related__list` vertical list with a responsive grid of cards (2 columns on desktop, 1 on mobile). Follow existing card patterns (e.g., technique cards on sub-topic pages from S01).

### Key Constraints

- **PostgreSQL array overlap**: The `topic_tags` column is `ARRAY(String)`. Use `TechniquePage.topic_tags.overlap(current_tags)` in SQLAlchemy for the `&&` operator. This is standard PostgreSQL — no extensions needed.
- **Performance**: The dynamic query hits `technique_pages` table with filters on `creator_id`, `topic_category`, and array overlap. For datasets under 10K rows, this is fast without additional indexes. A GIN index on `topic_tags` would help at scale but isn't needed now.
- **The `related_technique_links` table stays empty** — no migration needed. The dynamic query supplements or replaces it at query time.
- **Existing tests**: `backend/tests/test_public_api.py` has technique detail tests. The related links section currently returns `[]`. Tests need updating to verify dynamic results.

### Natural Seams (Task Decomposition)

1. **Backend: dynamic related-techniques query** — modify `get_technique()` in `routers/techniques.py`, enrich `RelatedLinkItem` schema in `schemas.py`. Verify with a test that creates 3+ technique pages sharing tags/category and confirms the endpoint returns related links.

2. **Frontend: card-based related section** — update `RelatedLinkItem` type, replace the list rendering in `TechniquePage.tsx` with cards, add/update CSS. Verify with `npm run build` (zero TS errors) and visual check.

These two tasks are independent (frontend works with the existing empty array until backend is deployed, and the backend change is backward-compatible since it only adds fields).

### What to Build First

The backend query is the riskier piece — it involves SQLAlchemy array operators and scoring logic. Build and test that first. The frontend is straightforward CSS/JSX using established patterns.

### Verification

- **Backend**: Integration test — create 4+ technique pages with overlapping tags/categories, call `GET /techniques/{slug}`, assert `related_links` contains expected entries with correct `reason` strings and scores ≤ 4 results.
- **Frontend**: `npm run build` passes with zero errors. Visual verification that the related section renders cards (not a plain list) with creator name and category badge.
- **End-to-end**: On the live system, navigate to any technique page and confirm 3-4 related techniques appear at the bottom.
