Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/app/services/ranking_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def _parse_llm_response(
except json.JSONDecodeError:
# Try to find JSON object in text
import re
match = re.search(r'\{[^{}]*\}', response_text, re.DOTALL)
match = re.search(r'\{[\s\S]*\}', response_text)
if match:
try:
data = json.loads(match.group())
Expand Down
31 changes: 30 additions & 1 deletion backend/tests/test_ranking_variance.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from app.models.idea import IdeaCandidate, DraftPlan, RiskItem, ExperimentSpec
from app.services.ranking_service import RankingService
from app.services.ranking_service import PAPER_TYPE_WEIGHTS, RankingService
from datetime import datetime


Expand Down Expand Up @@ -93,6 +93,34 @@ def _make_candidate(idx: int, session_id: str = "test_session") -> IdeaCandidate
)


def test_parse_embedded_nested_json_response():
"""LLM ranking JSON may be wrapped in explanatory prose."""
service = RankingService()
response = """
Here is the ranking result:
{
"novelty": {"score": 9.0, "rationale": "Fresh direction"},
"feasibility": {"score": 8.0, "rationale": "Buildable"},
"impact": {"score": 7.0, "rationale": "Useful"},
"clarity": {"score": 6.0, "rationale": "Clear enough"},
"risk": {"score": 5.0, "rationale": "Manageable"},
"alignment": {"score": 4.0, "rationale": "Partial fit"},
"referenceSupport": {"score": 3.0, "rationale": "Sparse refs"},
"experimentSpecificity": {"score": 2.0, "rationale": "Needs detail"},
"overallRationale": "Good candidate",
"confidence": 0.9
}
"""

result = service._parse_llm_response("cand_embedded", response, PAPER_TYPE_WEIGHTS["default"])

assert result.criteria["novelty"].value == 9.0
assert result.criteria["experimentSpecificity"].value == 2.0
assert result.overallRationale == "Good candidate"
assert result.confidence == 0.9
print("PASS: test_parse_embedded_nested_json_response")


def test_heuristic_scores_are_not_all_equal():
"""Scores must vary across 6 distinct candidates (heuristic fallback)."""
service = RankingService()
Expand Down Expand Up @@ -197,6 +225,7 @@ def test_score_breakdown_structure():


if __name__ == "__main__":
test_parse_embedded_nested_json_response()
test_overall_score_uses_all_8_criteria()
test_score_breakdown_structure()
test_heuristic_scores_are_not_all_equal()
Expand Down