50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
|
|
"""Data model for routing catalog entries.
|
||
|
|
|
||
|
|
A `RouteEntry` is a pointer: it names the owner and the authoritative doc for a
|
||
|
|
credential need. Only the SSH lane (`warden_executes: true`) may carry an authored
|
||
|
|
`steps` block and a `cert_command` pattern — every other entry is identifiers and
|
||
|
|
pointers only (the no-double-source rule, enforced in `catalog.py`).
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from typing import List, Optional
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class RouteEntry:
|
||
|
|
id: str
|
||
|
|
title: str
|
||
|
|
need_keywords: List[str]
|
||
|
|
owner_repo: str
|
||
|
|
subsystem: str
|
||
|
|
warden_executes: bool
|
||
|
|
wiki_ref: str
|
||
|
|
canon_ref: str
|
||
|
|
reviewed: str
|
||
|
|
status: str # "active" | "draft"
|
||
|
|
# SSH lane only — None/empty for routed (non-executed) needs.
|
||
|
|
steps: List[str] = field(default_factory=list)
|
||
|
|
cert_command: Optional[str] = None
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_active(self) -> bool:
|
||
|
|
return self.status == "active"
|
||
|
|
|
||
|
|
def match_score(self, tokens: List[str]) -> int:
|
||
|
|
"""Keyword-overlap score against need_keywords, title, and id.
|
||
|
|
|
||
|
|
Pure ranking helper — no I/O, no external calls.
|
||
|
|
"""
|
||
|
|
haystack = set(k.lower() for k in self.need_keywords)
|
||
|
|
haystack.update(self.id.lower().replace("-", " ").split())
|
||
|
|
haystack.update(self.title.lower().replace("-", " ").split())
|
||
|
|
score = 0
|
||
|
|
for tok in tokens:
|
||
|
|
t = tok.lower()
|
||
|
|
if t in haystack:
|
||
|
|
score += 2
|
||
|
|
elif any(t in h or h in t for h in haystack):
|
||
|
|
score += 1
|
||
|
|
return score
|