generated from coulomb/repo-seed
confidence labels
This commit is contained in:
@@ -82,6 +82,7 @@ curl http://127.0.0.1:8000/repos/1/analysis-runs/1/candidate-graph
|
||||
```
|
||||
|
||||
Candidate entries are source-linked review seeds. They are not canonical registry truth until a review workflow approves them.
|
||||
Candidate, approved, and search responses include numeric confidence values plus `low`, `medium`, or `high` confidence labels for quick triage.
|
||||
|
||||
Approve a candidate graph into the canonical registry:
|
||||
|
||||
|
||||
@@ -4,6 +4,14 @@ from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
def confidence_label(confidence: float) -> str:
|
||||
if confidence >= 0.8:
|
||||
return "high"
|
||||
if confidence >= 0.5:
|
||||
return "medium"
|
||||
return "low"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Repository:
|
||||
id: int
|
||||
@@ -107,6 +115,7 @@ class CandidateFeature:
|
||||
confidence: float
|
||||
status: str
|
||||
source_refs: list[SourceReference]
|
||||
confidence_label: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -119,6 +128,7 @@ class CandidateCapability:
|
||||
confidence: float
|
||||
status: str
|
||||
source_refs: list[SourceReference]
|
||||
confidence_label: str = ""
|
||||
features: list[CandidateFeature] = field(default_factory=list)
|
||||
evidence: list[CandidateEvidence] = field(default_factory=list)
|
||||
|
||||
@@ -131,6 +141,7 @@ class CandidateAbility:
|
||||
confidence: float
|
||||
status: str
|
||||
source_refs: list[SourceReference]
|
||||
confidence_label: str = ""
|
||||
capabilities: list[CandidateCapability] = field(default_factory=list)
|
||||
|
||||
|
||||
@@ -157,6 +168,7 @@ class Feature:
|
||||
type: str
|
||||
location: str
|
||||
confidence: float
|
||||
confidence_label: str = ""
|
||||
source_refs: list[SourceReference] = field(default_factory=list)
|
||||
|
||||
|
||||
@@ -168,6 +180,7 @@ class Capability:
|
||||
inputs: list[str]
|
||||
outputs: list[str]
|
||||
confidence: float
|
||||
confidence_label: str = ""
|
||||
features: list[Feature] = field(default_factory=list)
|
||||
evidence: list[Evidence] = field(default_factory=list)
|
||||
|
||||
@@ -178,6 +191,7 @@ class Ability:
|
||||
name: str
|
||||
description: str
|
||||
confidence: float
|
||||
confidence_label: str = ""
|
||||
capabilities: list[Capability] = field(default_factory=list)
|
||||
|
||||
|
||||
@@ -194,6 +208,7 @@ class SearchResult:
|
||||
match_type: str
|
||||
match_name: str
|
||||
confidence: float
|
||||
confidence_label: str = ""
|
||||
match_description: str = ""
|
||||
matched_field: str = ""
|
||||
ability_id: int | None = None
|
||||
@@ -212,6 +227,7 @@ class AbilitySummary:
|
||||
name: str
|
||||
description: str
|
||||
confidence: float
|
||||
confidence_label: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -224,3 +240,4 @@ class CapabilitySummary:
|
||||
name: str
|
||||
description: str
|
||||
confidence: float
|
||||
confidence_label: str = ""
|
||||
|
||||
@@ -25,6 +25,7 @@ from repo_registry.core.models import (
|
||||
ReviewDecision,
|
||||
SearchResult,
|
||||
SourceReference,
|
||||
confidence_label,
|
||||
)
|
||||
from repo_registry.content_indexing.extractor import ContentChunkCandidate
|
||||
from repo_registry.candidate_graph.generator import CandidateAbilityDraft
|
||||
@@ -401,6 +402,7 @@ class RegistryStore:
|
||||
confidence=row["confidence"],
|
||||
status=row["status"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -429,6 +431,7 @@ class RegistryStore:
|
||||
confidence=row["confidence"],
|
||||
status=row["status"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
features=features_by_capability.get(row["id"], []),
|
||||
evidence=evidence_by_capability.get(row["id"], []),
|
||||
)
|
||||
@@ -442,6 +445,7 @@ class RegistryStore:
|
||||
confidence=row["confidence"],
|
||||
status=row["status"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
capabilities=capabilities_by_ability.get(row["id"], []),
|
||||
)
|
||||
for row in ability_rows
|
||||
@@ -1119,6 +1123,7 @@ class RegistryStore:
|
||||
name=row["name"],
|
||||
description=row["description"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
@@ -1146,6 +1151,7 @@ class RegistryStore:
|
||||
name=row["name"],
|
||||
description=row["description"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
@@ -1555,6 +1561,7 @@ class RegistryStore:
|
||||
type=row["type"],
|
||||
location=row["location"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
)
|
||||
)
|
||||
@@ -1581,6 +1588,7 @@ class RegistryStore:
|
||||
inputs=json.loads(row["inputs"]),
|
||||
outputs=json.loads(row["outputs"]),
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
features=features_by_capability.get(row["id"], []),
|
||||
evidence=evidence_by_capability.get(row["id"], []),
|
||||
)
|
||||
@@ -1592,6 +1600,7 @@ class RegistryStore:
|
||||
name=row["name"],
|
||||
description=row["description"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
capabilities=capabilities_by_ability.get(row["id"], []),
|
||||
)
|
||||
for row in ability_rows
|
||||
@@ -1706,9 +1715,10 @@ class RegistryStore:
|
||||
repository_name=row["repository_name"],
|
||||
match_type="repository",
|
||||
match_name=row["repository_name"],
|
||||
confidence=1.0,
|
||||
confidence_label=confidence_label(1.0),
|
||||
match_description=row["description"] or "",
|
||||
matched_field=matched_field,
|
||||
confidence=1.0,
|
||||
)
|
||||
)
|
||||
for row in ability_rows:
|
||||
@@ -1721,9 +1731,10 @@ class RegistryStore:
|
||||
repository_name=row["repository_name"],
|
||||
match_type="ability",
|
||||
match_name=row["ability_name"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
match_description=row["ability_description"],
|
||||
matched_field=matched_field,
|
||||
confidence=row["confidence"],
|
||||
ability_id=row["ability_id"],
|
||||
ability_name=row["ability_name"],
|
||||
)
|
||||
@@ -1740,9 +1751,10 @@ class RegistryStore:
|
||||
repository_name=row["repository_name"],
|
||||
match_type="capability",
|
||||
match_name=row["capability_name"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
match_description=row["capability_description"],
|
||||
matched_field=matched_field,
|
||||
confidence=row["confidence"],
|
||||
ability_id=row["ability_id"],
|
||||
ability_name=row["ability_name"],
|
||||
capability_id=row["capability_id"],
|
||||
@@ -1764,9 +1776,10 @@ class RegistryStore:
|
||||
repository_name=row["repository_name"],
|
||||
match_type="feature",
|
||||
match_name=row["feature_name"],
|
||||
confidence=row["confidence"],
|
||||
confidence_label=confidence_label(row["confidence"]),
|
||||
match_description=row["feature_type"],
|
||||
matched_field=matched_field,
|
||||
confidence=row["confidence"],
|
||||
ability_id=row["ability_id"],
|
||||
ability_name=row["ability_name"],
|
||||
capability_id=row["capability_id"],
|
||||
@@ -1789,9 +1802,12 @@ class RegistryStore:
|
||||
repository_name=row["repository_name"],
|
||||
match_type="evidence",
|
||||
match_name=row["reference"],
|
||||
confidence=self._evidence_confidence(row["strength"]),
|
||||
confidence_label=confidence_label(
|
||||
self._evidence_confidence(row["strength"])
|
||||
),
|
||||
match_description=row["evidence_type"],
|
||||
matched_field=matched_field,
|
||||
confidence=self._evidence_confidence(row["strength"]),
|
||||
ability_id=row["ability_id"],
|
||||
ability_name=row["ability_name"],
|
||||
capability_id=row["capability_id"],
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from fastapi import Depends, FastAPI, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
@@ -324,7 +324,26 @@ class CandidateEvidenceMerge(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
app = FastAPI(title="Repository Ability Registry", version="0.1.0")
|
||||
API_DESCRIPTION = (
|
||||
"Register repositories, analyze their observable implementation facts, "
|
||||
"curate reviewable ability graphs, and search approved repository abilities."
|
||||
)
|
||||
|
||||
OPENAPI_TAGS = [
|
||||
{"name": "health", "description": "Service health checks."},
|
||||
{"name": "repositories", "description": "Repository registration and metadata."},
|
||||
{"name": "analysis", "description": "Repository scans and extracted review inputs."},
|
||||
{"name": "review", "description": "Candidate graph approval and correction workflow."},
|
||||
{"name": "registry", "description": "Approved ability maps and manual registry CRUD."},
|
||||
{"name": "search", "description": "Agent-facing discovery endpoints."},
|
||||
]
|
||||
|
||||
app = FastAPI(
|
||||
title="Repository Ability Registry",
|
||||
version="0.1.0",
|
||||
description=API_DESCRIPTION,
|
||||
openapi_tags=OPENAPI_TAGS,
|
||||
)
|
||||
|
||||
|
||||
from repo_registry.web_ui.views import router as ui_router
|
||||
@@ -332,12 +351,12 @@ from repo_registry.web_ui.views import router as ui_router
|
||||
app.include_router(ui_router)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
@app.get("/health", tags=["health"])
|
||||
def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/repos", status_code=201)
|
||||
@app.post("/repos", status_code=201, tags=["repositories"])
|
||||
def create_repository(
|
||||
payload: RepositoryCreate,
|
||||
service: RegistryService = Depends(get_service),
|
||||
@@ -349,14 +368,14 @@ def create_repository(
|
||||
return asdict(repository)
|
||||
|
||||
|
||||
@app.get("/repos")
|
||||
@app.get("/repos", tags=["repositories"])
|
||||
def list_repositories(
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> list[dict[str, object]]:
|
||||
return [asdict(repository) for repository in service.list_repositories()]
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}")
|
||||
@app.get("/repos/{repository_id}", tags=["repositories"])
|
||||
def get_repository(
|
||||
repository_id: int,
|
||||
service: RegistryService = Depends(get_service),
|
||||
@@ -367,7 +386,7 @@ def get_repository(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.patch("/repos/{repository_id}")
|
||||
@app.patch("/repos/{repository_id}", tags=["repositories"])
|
||||
def update_repository(
|
||||
repository_id: int,
|
||||
payload: RepositoryUpdate,
|
||||
@@ -384,7 +403,7 @@ def update_repository(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.delete("/repos/{repository_id}", status_code=204)
|
||||
@app.delete("/repos/{repository_id}", status_code=204, tags=["repositories"])
|
||||
def delete_repository(
|
||||
repository_id: int,
|
||||
service: RegistryService = Depends(get_service),
|
||||
@@ -395,7 +414,7 @@ def delete_repository(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/analysis-runs", status_code=201)
|
||||
@app.post("/repos/{repository_id}/analysis-runs", status_code=201, tags=["analysis"])
|
||||
def create_analysis_run(
|
||||
repository_id: int,
|
||||
payload: AnalysisRunCreate,
|
||||
@@ -411,7 +430,7 @@ def create_analysis_run(
|
||||
return asdict(summary)
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/analysis-runs")
|
||||
@app.get("/repos/{repository_id}/analysis-runs", tags=["analysis"])
|
||||
def list_analysis_runs(
|
||||
repository_id: int,
|
||||
service: RegistryService = Depends(get_service),
|
||||
@@ -422,7 +441,7 @@ def list_analysis_runs(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}")
|
||||
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}", tags=["analysis"])
|
||||
def get_analysis_run(
|
||||
repository_id: int,
|
||||
analysis_run_id: int,
|
||||
@@ -434,7 +453,7 @@ def get_analysis_run(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/review-decisions")
|
||||
@app.get("/repos/{repository_id}/review-decisions", tags=["review"])
|
||||
def list_repository_review_decisions(
|
||||
repository_id: int,
|
||||
service: RegistryService = Depends(get_service),
|
||||
@@ -448,7 +467,10 @@ def list_repository_review_decisions(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/review-decisions")
|
||||
@app.get(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/review-decisions",
|
||||
tags=["review"],
|
||||
)
|
||||
def list_analysis_run_review_decisions(
|
||||
repository_id: int,
|
||||
analysis_run_id: int,
|
||||
@@ -466,7 +488,7 @@ def list_analysis_run_review_decisions(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/observed-facts")
|
||||
@app.get("/repos/{repository_id}/observed-facts", tags=["analysis"])
|
||||
def list_observed_facts(
|
||||
repository_id: int,
|
||||
analysis_run_id: int | None = None,
|
||||
@@ -481,7 +503,7 @@ def list_observed_facts(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/content-chunks")
|
||||
@app.get("/repos/{repository_id}/content-chunks", tags=["analysis"])
|
||||
def list_content_chunks(
|
||||
repository_id: int,
|
||||
analysis_run_id: int | None = None,
|
||||
@@ -496,7 +518,10 @@ def list_content_chunks(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/content-chunks")
|
||||
@app.get(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/content-chunks",
|
||||
tags=["analysis"],
|
||||
)
|
||||
def list_analysis_run_content_chunks(
|
||||
repository_id: int,
|
||||
analysis_run_id: int,
|
||||
@@ -511,7 +536,10 @@ def list_analysis_run_content_chunks(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph")
|
||||
@app.get(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph",
|
||||
tags=["review"],
|
||||
)
|
||||
def get_candidate_graph(
|
||||
repository_id: int,
|
||||
analysis_run_id: int,
|
||||
@@ -523,7 +551,10 @@ def get_candidate_graph(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve")
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve",
|
||||
tags=["review"],
|
||||
)
|
||||
def approve_candidate_graph(
|
||||
repository_id: int,
|
||||
analysis_run_id: int,
|
||||
@@ -544,7 +575,8 @@ def approve_candidate_graph(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-abilities/{candidate_ability_id}/reject"
|
||||
"/candidate-abilities/{candidate_ability_id}/reject",
|
||||
tags=["review"],
|
||||
)
|
||||
def reject_candidate_ability(
|
||||
repository_id: int,
|
||||
@@ -568,7 +600,8 @@ def reject_candidate_ability(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-capabilities/{candidate_capability_id}/reject"
|
||||
"/candidate-capabilities/{candidate_capability_id}/reject",
|
||||
tags=["review"],
|
||||
)
|
||||
def reject_candidate_capability(
|
||||
repository_id: int,
|
||||
@@ -592,7 +625,8 @@ def reject_candidate_capability(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-features/{candidate_feature_id}/reject"
|
||||
"/candidate-features/{candidate_feature_id}/reject",
|
||||
tags=["review"],
|
||||
)
|
||||
def reject_candidate_feature(
|
||||
repository_id: int,
|
||||
@@ -616,7 +650,8 @@ def reject_candidate_feature(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-evidence/{candidate_evidence_id}/reject"
|
||||
"/candidate-evidence/{candidate_evidence_id}/reject",
|
||||
tags=["review"],
|
||||
)
|
||||
def reject_candidate_evidence(
|
||||
repository_id: int,
|
||||
@@ -640,7 +675,8 @@ def reject_candidate_evidence(
|
||||
|
||||
@app.patch(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-abilities/{candidate_ability_id}"
|
||||
"/candidate-abilities/{candidate_ability_id}",
|
||||
tags=["review"],
|
||||
)
|
||||
def edit_candidate_ability(
|
||||
repository_id: int,
|
||||
@@ -664,7 +700,8 @@ def edit_candidate_ability(
|
||||
|
||||
@app.patch(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-capabilities/{candidate_capability_id}"
|
||||
"/candidate-capabilities/{candidate_capability_id}",
|
||||
tags=["review"],
|
||||
)
|
||||
def edit_candidate_capability(
|
||||
repository_id: int,
|
||||
@@ -688,7 +725,8 @@ def edit_candidate_capability(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-capabilities/{candidate_capability_id}/relink"
|
||||
"/candidate-capabilities/{candidate_capability_id}/relink",
|
||||
tags=["review"],
|
||||
)
|
||||
def relink_candidate_capability(
|
||||
repository_id: int,
|
||||
@@ -712,7 +750,8 @@ def relink_candidate_capability(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-features/{candidate_feature_id}/relink"
|
||||
"/candidate-features/{candidate_feature_id}/relink",
|
||||
tags=["review"],
|
||||
)
|
||||
def relink_candidate_feature(
|
||||
repository_id: int,
|
||||
@@ -736,7 +775,8 @@ def relink_candidate_feature(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-evidence/{candidate_evidence_id}/relink"
|
||||
"/candidate-evidence/{candidate_evidence_id}/relink",
|
||||
tags=["review"],
|
||||
)
|
||||
def relink_candidate_evidence(
|
||||
repository_id: int,
|
||||
@@ -760,7 +800,8 @@ def relink_candidate_evidence(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-abilities/{source_ability_id}/merge"
|
||||
"/candidate-abilities/{source_ability_id}/merge",
|
||||
tags=["review"],
|
||||
)
|
||||
def merge_candidate_ability(
|
||||
repository_id: int,
|
||||
@@ -784,7 +825,8 @@ def merge_candidate_ability(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-capabilities/{source_capability_id}/merge"
|
||||
"/candidate-capabilities/{source_capability_id}/merge",
|
||||
tags=["review"],
|
||||
)
|
||||
def merge_candidate_capability(
|
||||
repository_id: int,
|
||||
@@ -808,7 +850,8 @@ def merge_candidate_capability(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-features/{source_feature_id}/merge"
|
||||
"/candidate-features/{source_feature_id}/merge",
|
||||
tags=["review"],
|
||||
)
|
||||
def merge_candidate_feature(
|
||||
repository_id: int,
|
||||
@@ -832,7 +875,8 @@ def merge_candidate_feature(
|
||||
|
||||
@app.post(
|
||||
"/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-evidence/{source_evidence_id}/merge"
|
||||
"/candidate-evidence/{source_evidence_id}/merge",
|
||||
tags=["review"],
|
||||
)
|
||||
def merge_candidate_evidence(
|
||||
repository_id: int,
|
||||
@@ -854,7 +898,7 @@ def merge_candidate_evidence(
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/abilities", status_code=201)
|
||||
@app.post("/repos/{repository_id}/abilities", status_code=201, tags=["registry"])
|
||||
def create_ability(
|
||||
repository_id: int,
|
||||
payload: AbilityCreate,
|
||||
@@ -867,7 +911,7 @@ def create_ability(
|
||||
return {"id": ability_id}
|
||||
|
||||
|
||||
@app.patch("/repos/{repository_id}/abilities/{ability_id}")
|
||||
@app.patch("/repos/{repository_id}/abilities/{ability_id}", tags=["registry"])
|
||||
def update_ability(
|
||||
repository_id: int,
|
||||
ability_id: int,
|
||||
@@ -886,7 +930,7 @@ def update_ability(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.delete("/repos/{repository_id}/abilities/{ability_id}")
|
||||
@app.delete("/repos/{repository_id}/abilities/{ability_id}", tags=["registry"])
|
||||
def delete_ability(
|
||||
repository_id: int,
|
||||
ability_id: int,
|
||||
@@ -898,7 +942,7 @@ def delete_ability(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/capabilities", status_code=201)
|
||||
@app.post("/repos/{repository_id}/capabilities", status_code=201, tags=["registry"])
|
||||
def create_capability(
|
||||
repository_id: int,
|
||||
payload: CapabilityCreate,
|
||||
@@ -911,7 +955,7 @@ def create_capability(
|
||||
return {"id": capability_id}
|
||||
|
||||
|
||||
@app.patch("/repos/{repository_id}/capabilities/{capability_id}")
|
||||
@app.patch("/repos/{repository_id}/capabilities/{capability_id}", tags=["registry"])
|
||||
def update_capability(
|
||||
repository_id: int,
|
||||
capability_id: int,
|
||||
@@ -930,7 +974,7 @@ def update_capability(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.delete("/repos/{repository_id}/capabilities/{capability_id}")
|
||||
@app.delete("/repos/{repository_id}/capabilities/{capability_id}", tags=["registry"])
|
||||
def delete_capability(
|
||||
repository_id: int,
|
||||
capability_id: int,
|
||||
@@ -942,7 +986,7 @@ def delete_capability(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/features", status_code=201)
|
||||
@app.post("/repos/{repository_id}/features", status_code=201, tags=["registry"])
|
||||
def create_feature(
|
||||
repository_id: int,
|
||||
payload: FeatureCreate,
|
||||
@@ -955,7 +999,7 @@ def create_feature(
|
||||
return {"id": feature_id}
|
||||
|
||||
|
||||
@app.patch("/repos/{repository_id}/features/{feature_id}")
|
||||
@app.patch("/repos/{repository_id}/features/{feature_id}", tags=["registry"])
|
||||
def update_feature(
|
||||
repository_id: int,
|
||||
feature_id: int,
|
||||
@@ -974,7 +1018,7 @@ def update_feature(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.delete("/repos/{repository_id}/features/{feature_id}")
|
||||
@app.delete("/repos/{repository_id}/features/{feature_id}", tags=["registry"])
|
||||
def delete_feature(
|
||||
repository_id: int,
|
||||
feature_id: int,
|
||||
@@ -986,7 +1030,7 @@ def delete_feature(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/evidence", status_code=201)
|
||||
@app.post("/repos/{repository_id}/evidence", status_code=201, tags=["registry"])
|
||||
def create_evidence(
|
||||
repository_id: int,
|
||||
payload: EvidenceCreate,
|
||||
@@ -999,7 +1043,7 @@ def create_evidence(
|
||||
return {"id": evidence_id}
|
||||
|
||||
|
||||
@app.patch("/repos/{repository_id}/evidence/{evidence_id}")
|
||||
@app.patch("/repos/{repository_id}/evidence/{evidence_id}", tags=["registry"])
|
||||
def update_evidence(
|
||||
repository_id: int,
|
||||
evidence_id: int,
|
||||
@@ -1018,7 +1062,7 @@ def update_evidence(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.delete("/repos/{repository_id}/evidence/{evidence_id}")
|
||||
@app.delete("/repos/{repository_id}/evidence/{evidence_id}", tags=["registry"])
|
||||
def delete_evidence(
|
||||
repository_id: int,
|
||||
evidence_id: int,
|
||||
@@ -1030,7 +1074,7 @@ def delete_evidence(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/repos/{repository_id}/ability-map")
|
||||
@app.get("/repos/{repository_id}/ability-map", tags=["registry"])
|
||||
def get_ability_map(
|
||||
repository_id: int,
|
||||
service: RegistryService = Depends(get_service),
|
||||
@@ -1041,14 +1085,33 @@ def get_ability_map(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.get("/search")
|
||||
@app.get("/search", tags=["search"])
|
||||
def search(
|
||||
q: str,
|
||||
status: str | None = None,
|
||||
language: str | None = None,
|
||||
framework: str | None = None,
|
||||
ability: str | None = None,
|
||||
capability: str | None = None,
|
||||
q: str = Query(
|
||||
...,
|
||||
description="Natural-language or keyword query over approved registry entries.",
|
||||
examples=["classify email", "FastAPI health endpoint"],
|
||||
),
|
||||
status: str | None = Query(
|
||||
default=None,
|
||||
description="Filter repositories by registry status, such as indexed.",
|
||||
),
|
||||
language: str | None = Query(
|
||||
default=None,
|
||||
description="Filter by observed programming language.",
|
||||
),
|
||||
framework: str | None = Query(
|
||||
default=None,
|
||||
description="Filter by observed framework hint.",
|
||||
),
|
||||
ability: str | None = Query(
|
||||
default=None,
|
||||
description="Filter to results under an approved ability name.",
|
||||
),
|
||||
capability: str | None = Query(
|
||||
default=None,
|
||||
description="Filter to results under an approved capability name.",
|
||||
),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> list[dict[str, object]]:
|
||||
return [
|
||||
@@ -1064,14 +1127,14 @@ def search(
|
||||
]
|
||||
|
||||
|
||||
@app.get("/abilities")
|
||||
@app.get("/abilities", tags=["search"])
|
||||
def list_abilities(
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> list[dict[str, object]]:
|
||||
return [asdict(ability) for ability in service.list_abilities()]
|
||||
|
||||
|
||||
@app.get("/capabilities")
|
||||
@app.get("/capabilities", tags=["search"])
|
||||
def list_capabilities(
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> list[dict[str, object]]:
|
||||
|
||||
@@ -206,7 +206,7 @@ def search_page(
|
||||
{render_search_context(asdict(result))}
|
||||
</td>
|
||||
<td>{escape(result.matched_field)}</td>
|
||||
<td>{result.confidence:.2f}</td>
|
||||
<td>{result.confidence:.2f} <span class="pill">{escape(result.confidence_label)}</span></td>
|
||||
</tr>
|
||||
"""
|
||||
for result in results
|
||||
@@ -1018,7 +1018,7 @@ def render_candidate_graph(graph: dict, repository_id: int, analysis_run_id: int
|
||||
<strong>{escape(ability['name'])}</strong>
|
||||
<span class="pill">ID {ability['id']}</span>
|
||||
<span class="pill">{escape(ability['status'])}</span>
|
||||
<span class="pill">{ability['confidence']:.2f}</span>
|
||||
<span class="pill">{ability['confidence']:.2f} {escape(ability['confidence_label'])}</span>
|
||||
{render_candidate_ability_actions(ability, repository_id, analysis_run_id)}
|
||||
<p class="muted">{escape(ability['description'])}</p>
|
||||
{render_candidate_edit_form('candidate-abilities', ability, repository_id, analysis_run_id)}
|
||||
@@ -1150,7 +1150,7 @@ def render_candidate_capability(
|
||||
<strong>{escape(capability['name'])}</strong>
|
||||
<span class="pill">ID {capability['id']}</span>
|
||||
<span class="pill">{escape(capability['status'])}</span>
|
||||
<span class="pill">{capability['confidence']:.2f}</span>
|
||||
<span class="pill">{capability['confidence']:.2f} {escape(capability['confidence_label'])}</span>
|
||||
{render_candidate_reject_form('candidate-capabilities', capability, repository_id, analysis_run_id)}
|
||||
<p class="muted">{escape(capability['description'])}</p>
|
||||
{render_candidate_edit_form('candidate-capabilities', capability, repository_id, analysis_run_id)}
|
||||
@@ -1287,6 +1287,7 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
|
||||
<li id="capability-{capability['id']}">
|
||||
<strong>{escape(capability['name'])}</strong>
|
||||
<span class="pill">ID {capability['id']}</span>
|
||||
<span class="pill">{capability['confidence']:.2f} {escape(capability['confidence_label'])}</span>
|
||||
<p class="muted">{escape(capability['description'])}</p>
|
||||
{render_approved_capability_forms(capability, repository_id)}
|
||||
<ul>{features}{evidence}</ul>
|
||||
@@ -1298,6 +1299,7 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
|
||||
<li id="ability-{ability['id']}">
|
||||
<strong>{escape(ability['name'])}</strong>
|
||||
<span class="pill">ID {ability['id']}</span>
|
||||
<span class="pill">{ability['confidence']:.2f} {escape(ability['confidence_label'])}</span>
|
||||
<p class="muted">{escape(ability['description'])}</p>
|
||||
{render_approved_ability_forms(ability, repository_id)}
|
||||
<ul>{''.join(capabilities)}</ul>
|
||||
@@ -1348,6 +1350,7 @@ def render_approved_feature(feature: dict, repository_id: int) -> str:
|
||||
<li>
|
||||
{escape(feature["name"])}
|
||||
<span class="pill">{escape(feature["type"])}</span>
|
||||
<span class="pill">{feature["confidence"]:.2f} {escape(feature["confidence_label"])}</span>
|
||||
<span class="source">{escape(feature["location"])}</span>
|
||||
{render_sources(feature.get("source_refs", []))}
|
||||
<form class="stack" method="post" action="/ui/repos/{repository_id}/features/{feature['id']}/edit">
|
||||
|
||||
@@ -4,6 +4,28 @@ from repo_registry.web_api import app as app_module
|
||||
from repo_registry.web_api.app import Settings, app, get_service, get_settings
|
||||
|
||||
|
||||
def test_openapi_groups_agent_facing_endpoints():
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/openapi.json")
|
||||
|
||||
assert response.status_code == 200
|
||||
schema = response.json()
|
||||
assert {tag["name"] for tag in schema["tags"]} >= {
|
||||
"repositories",
|
||||
"analysis",
|
||||
"review",
|
||||
"registry",
|
||||
"search",
|
||||
}
|
||||
search_operation = schema["paths"]["/search"]["get"]
|
||||
assert search_operation["tags"] == ["search"]
|
||||
assert {
|
||||
parameter["name"]: parameter["description"]
|
||||
for parameter in search_operation["parameters"]
|
||||
}["q"].startswith("Natural-language")
|
||||
|
||||
|
||||
def test_api_manual_registry_loop(tmp_path):
|
||||
def override_settings():
|
||||
return Settings(
|
||||
@@ -323,6 +345,11 @@ def test_api_analysis_run_loop(tmp_path):
|
||||
ability_map = approve_response.json()
|
||||
assert ability_map["repository"]["status"] == "indexed"
|
||||
assert ability_map["abilities"][0]["name"] == "Frontend Delivery"
|
||||
assert ability_map["abilities"][0]["confidence_label"] in {
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
}
|
||||
assert ability_map["abilities"][0]["capabilities"][0]["name"] == (
|
||||
"Describe Frontend Stack"
|
||||
)
|
||||
@@ -331,6 +358,7 @@ def test_api_analysis_run_loop(tmp_path):
|
||||
assert search_response.status_code == 200
|
||||
assert search_response.json()
|
||||
assert "matched_field" in search_response.json()[0]
|
||||
assert "confidence_label" in search_response.json()[0]
|
||||
|
||||
filtered_search_response = client.get(
|
||||
"/search",
|
||||
@@ -348,6 +376,11 @@ def test_api_analysis_run_loop(tmp_path):
|
||||
assert abilities_response.status_code == 200
|
||||
assert abilities_response.json()[0]["name"] == "Frontend Delivery"
|
||||
assert abilities_response.json()[0]["repository_name"] == "Frontend"
|
||||
assert abilities_response.json()[0]["confidence_label"] in {
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
}
|
||||
|
||||
capabilities_response = client.get("/capabilities")
|
||||
assert capabilities_response.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user