Problem Detection and Prioritization

After the matching pipeline completes (Section 2), the system performs a comprehensive analysis to identify data quality issues in both datasets.

Problem Categories

The documentation separates problems into three conceptual categories, but the current implementation only emits stop-level Problem rows. Route entity and route membership problem pages describe planned route diagnostics.

Category Question User Action
Stop Problems "Is this stop at the correct location with correct attributes?" Move node, update tags, fix matching.
Route Entity Problems "Does this route exist and is it defined correctly?" Create or delete route relation; fix route tags.
Route Membership Problems "Is the list of stops for this route consistent?" Add/remove stops from relation; reorder stops; fix roles.

1. Stop Problems (Implemented)

Issues with individual stops (Distance, Attributes, Isolation, Duplicates). See 4.1 Stop Problems.

2. Route Entity Problems (Planned)

Issues with the Route object itself.

  • Not yet implemented: route-level Problem rows, metadata mismatch diagnostics, and unmatched route detection.
  • See 4.2 Route Entity Problems.

3. Route Membership Problems (Planned)

Issues with the stop-route relationship.


Architecture

Problem detection is built on the same domain models as the matching pipeline. The enriched MatchingOutput (containing MatchRecord, AtlasNode, and OsmNode entities plus duplicate and route-evidence lookups) flows directly into problem detection — no ORM or dictionary conversion is needed.

Polymorphic Predicates

Each problem predicate is a plain function with a polymorphic signature:

def predicate(ctx: ProblemContext, record: MatchRecord | AtlasNode | OsmNode) -> list[ProblemResult]

Predicates use isinstance checks to decide what to evaluate:

  • distance_problem, attributes_problem, and contradicts_route_matching_problem only act on MatchRecord (return [] for bare nodes)
  • unmatched_problem only acts on bare AtlasNode or OsmNode (returns [] for MatchRecord)
  • duplicates_problem acts on all three types

This allows the same STOP_PROBLEM_PIPELINE list to be used for both matched and unmatched records.

Two Invocation Paths

Record type Invocation Where
Matched (MatchRecord) match_record.evaluate_matched_problems(problem_ctx, STOP_PROBLEM_PIPELINE) importer.py — calls the method natively on the domain entity
Unmatched (AtlasNode / OsmNode) evaluate_unmatched_problems(STOP_PROBLEM_PIPELINE, problem_ctx, node) importer.py — uses the standalone runner

Both paths produce list[ProblemResult]. The current fast importer converts their problem_type and priority fields into plain problem-row payloads before the database transaction.

ProblemResult Value Object

All predicates return ProblemResult, a lightweight frozen dataclass decoupled from SQLAlchemy:

@dataclass(frozen=True)
class ProblemResult:
    problem_type: str        # 'distance', 'attributes', 'contradicts_route_matching', 'unmatched', 'duplicates'
    priority: int            # 1 = P1, 2 = P2, 3 = P3
    has_atlas_duplicate: bool = False
    has_osm_duplicate: bool = False

Priority Levels

All problems use a consistent three-level priority system:

Level Meaning
P1 Critical
P2 Significant
P3 Minor

Priority assignment is rule-based and considers factors like:


Code Reference

Component File Purpose
Result value object problem_detection/result.py ProblemResult — frozen dataclass
Shared context problem_detection/context.py ProblemContext.build() — KDTrees, UIC counts, duplicate maps
Pipeline runner problem_detection/pipeline.py evaluate_unmatched_problems(), STOP_PROBLEM_PIPELINE
Distance predicate stop_predicates/distance.py distance_problem()
Attributes predicate stop_predicates/attributes.py attributes_problem()
Route-contradiction predicate stop_predicates/contradicts_route_matching.py contradicts_route_matching_problem()
Unmatched predicate stop_predicates/unmatched.py unmatched_problem()
Duplicates predicate stop_predicates/duplicates.py duplicates_problem()
Domain models models.py MatchRecord.evaluate_matched_problems(), AtlasNode, OsmNode
Database import database/importer.py Precomputes problem results before the maintenance window and maps them to insert rows
API endpoints problems.py Problem listing, aggregation, and duplicates grouping
Data update in progress
Elapsed: -- ETA: -- Phase: idle