diff --git a/src/repo_registry/candidate_graph/generator.py b/src/repo_registry/candidate_graph/generator.py index c1b567e..fc1fb57 100644 --- a/src/repo_registry/candidate_graph/generator.py +++ b/src/repo_registry/candidate_graph/generator.py @@ -68,7 +68,14 @@ class CandidateGraphGenerator: ability = CandidateAbilityDraft( name=f"Review {repository.name} Repository Usefulness", description=self._ability_description(chunks), - confidence=0.55 if docs else 0.35, + confidence=self._ability_confidence( + docs=docs, + interfaces=interfaces, + tests=tests, + examples=examples, + frameworks=frameworks, + languages=languages, + ), source_refs=self._source_refs(ability_sources), capabilities=[], ) @@ -88,7 +95,12 @@ class CandidateGraphGenerator: ), inputs=[], outputs=["repository structure summary"], - confidence=0.6, + confidence=self._structure_confidence( + manifests=manifests, + frameworks=frameworks, + languages=languages, + docs=docs, + ), source_refs=self._source_refs(manifests + frameworks + languages), evidence=self._evidence(tests, examples, docs), ) @@ -127,7 +139,12 @@ class CandidateGraphGenerator: description=self._interface_description(chunks), inputs=[], outputs=["callable interface"], - confidence=0.65, + confidence=self._interface_confidence( + interfaces=interfaces, + tests=tests, + examples=examples, + docs=docs, + ), source_refs=self._source_refs(interfaces), features=features, evidence=self._evidence(tests, examples, docs), @@ -177,6 +194,73 @@ class CandidateGraphGenerator: return "API" return "interface" + def _ability_confidence( + self, + *, + docs: list[ObservedFact], + interfaces: list[ObservedFact], + tests: list[ObservedFact], + examples: list[ObservedFact], + frameworks: list[ObservedFact], + languages: list[ObservedFact], + ) -> float: + return self._confidence( + 0.25, + [ + (0.20, bool(docs)), + (0.15, bool(interfaces)), + (0.15, bool(tests)), + (0.10, bool(examples)), + (0.10, bool(frameworks)), + (0.05, bool(languages)), + ], + ) + + def _interface_confidence( + self, + *, + interfaces: list[ObservedFact], + tests: list[ObservedFact], + examples: list[ObservedFact], + docs: list[ObservedFact], + ) -> float: + return self._confidence( + 0.30, + [ + (0.20, bool(interfaces)), + (0.15, bool(tests)), + (0.10, bool(examples)), + (0.10, bool(docs)), + (0.05, len(interfaces) > 1), + ], + ) + + def _structure_confidence( + self, + *, + manifests: list[ObservedFact], + frameworks: list[ObservedFact], + languages: list[ObservedFact], + docs: list[ObservedFact], + ) -> float: + return self._confidence( + 0.25, + [ + (0.20, bool(manifests)), + (0.15, bool(frameworks)), + (0.10, bool(languages)), + (0.05, bool(docs)), + ], + ) + + def _confidence( + self, + base: float, + factors: list[tuple[float, bool]], + ) -> float: + score = base + sum(weight for weight, applies in factors if applies) + return min(1.0, round(score, 2)) + def _ability_description(self, chunks: list[ContentChunk]) -> str: doc_summary = self._document_summary(chunks) if doc_summary: diff --git a/tests/test_candidate_graph.py b/tests/test_candidate_graph.py index d3ea051..6287c43 100644 --- a/tests/test_candidate_graph.py +++ b/tests/test_candidate_graph.py @@ -54,6 +54,7 @@ def test_candidate_generator_builds_review_seed_from_observed_facts(): assert ability.source_refs[0].path == "README.md" interface_capability = ability.capabilities[0] assert interface_capability.name == "Expose Repository Interface" + assert interface_capability.confidence == 0.75 assert interface_capability.features[0].type == "API" assert interface_capability.features[0].location == "app.py" assert interface_capability.evidence[0].strength == "strong" @@ -94,3 +95,48 @@ def test_candidate_generator_enriches_descriptions_from_content_chunks(): assert "MailRouter. Routes incoming customer email" in graph[0].description assert '@app.post("/classify")' in graph[0].capabilities[0].description + + +def test_candidate_confidence_scoring_stays_conservative_for_weak_facts(): + repository = Repository( + id=1, + name="WeakDocs", + url="/tmp/weak-docs", + description=None, + branch="main", + status="analyzed", + ) + + graph = CandidateGraphGenerator().generate( + repository, + [fact(1, "documentation", "README", "README.md")], + ) + + assert graph[0].confidence == 0.45 + assert graph[0].capabilities == [] + + +def test_candidate_confidence_scoring_increases_with_supporting_facts(): + repository = Repository( + id=1, + name="Supported", + url="/tmp/supported", + description=None, + branch="main", + status="analyzed", + ) + facts = [ + fact(1, "documentation", "README", "README.md"), + fact(2, "interface", "python route decorator", "app.py", '@app.get("/health")'), + fact(3, "test", "test_app.py", "tests/test_app.py"), + fact(4, "example", "client.py", "examples/client.py"), + fact(5, "framework", "FastAPI", "requirements.txt"), + fact(6, "language", "Python"), + fact(7, "manifest", "requirements.txt", "requirements.txt"), + ] + + graph = CandidateGraphGenerator().generate(repository, facts) + + assert graph[0].confidence == 1.0 + assert graph[0].capabilities[0].confidence == 0.85 + assert graph[0].capabilities[1].confidence == 0.75