luplo.core.impact

Impact analysis — traverse typed edges to find an item’s blast radius.

Given an item, walk outgoing links edges whose link_type is one of depends / blocks / supersedes / conflicts up to a fixed depth ceiling, and return the set of items reachable through those edges.

The ceiling (MAX_IMPACT_DEPTH) is a product-level design principle, enforced server-side: there is no config knob, no --deep override, no per-tenant exception. If a caller needs more than five hops, the model needs decomposing — not this limit raising.

Traversal is outgoing only (links.from_item_id = parent). Edge direction has an intended meaning for each type; the traversal layer does not second-guess it.

Cycles are handled inside the recursive CTE via a path array: an item that is already on the current walk is not traversed a second time.

Attributes

TRAVERSABLE_LINK_TYPES

Edge types that impact will walk. Other link types are ignored.

MAX_IMPACT_DEPTH

Hard ceiling on traversal depth. Not user-configurable.

MIN_IMPACT_DEPTH

Depth 0 would return only the root, so the smallest useful value is 1.

Classes

ImpactEdge

One hop in an impact traversal, from parent_id to child_id.

ImpactNode

An item reached by traversal, together with the edge that first reached it.

ImpactResult

Structured output of impact().

Functions

impact(→ ImpactResult)

Run an impact analysis from item_id.

Module Contents

Edge types that impact will walk. Other link types are ignored.

luplo.core.impact.MAX_IMPACT_DEPTH: int = 5

Hard ceiling on traversal depth. Not user-configurable.

luplo.core.impact.MIN_IMPACT_DEPTH: int = 1

Depth 0 would return only the root, so the smallest useful value is 1.

class luplo.core.impact.ImpactEdge

One hop in an impact traversal, from parent_id to child_id.

parent_id: str
child_id: str
depth: int
class luplo.core.impact.ImpactNode

An item reached by traversal, together with the edge that first reached it.

depth is the shortest-path depth from the root (1 means the item is a direct neighbour of the root).

item: luplo.core.models.Item
depth: int
via: ImpactEdge
class luplo.core.impact.ImpactResult

Structured output of impact().

nodes is deduplicated: every item appears once, at its shortest-path depth. Ordering is (depth ASC, title ASC, link_type ASC) — stable across runs so diffs are readable.

root: luplo.core.models.Item
nodes: list[ImpactNode]
depth_requested: int
async luplo.core.impact.impact(conn: psycopg.AsyncConnection[Any], item_id: str, project_id: str, *, depth: int = MAX_IMPACT_DEPTH) ImpactResult

Run an impact analysis from item_id.

Parameters:
  • conn – Async psycopg connection.

  • item_id – Root item (full ID or hex prefix — resolved via luplo.core.id_resolve.resolve_uuid_prefix()).

  • project_id – Project scope. Traversal never crosses projects; any edge pointing at an item outside this project is dropped.

  • depth – Maximum hops to traverse. Clamped to [MIN_IMPACT_DEPTH, MAX_IMPACT_DEPTH] — out-of-range values raise ValidationError.

Returns:

An ImpactResult carrying the root item and the list of reachable items ordered by (depth, title, link_type).

Raises:
  • ValidationError – If depth is outside the allowed range.

  • NotFoundError – If the root item does not exist in this project or is soft-deleted.