92 lines
3.8 KiB
Python
92 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from civicswarm.api import create_app
|
|
from civicswarm.service import CivicSwarmService
|
|
|
|
|
|
def build_service(tmp_path: Path) -> CivicSwarmService:
|
|
return CivicSwarmService.from_path(tmp_path / "civicswarm.sqlite")
|
|
|
|
|
|
def seed_service(service: CivicSwarmService) -> int:
|
|
proposal = service.create_proposal(
|
|
city="Springfield",
|
|
title="School street safety",
|
|
body=(
|
|
"Add protected bike lanes near schools.\n"
|
|
"Install safer crossings for pedestrians.\n"
|
|
"Preserve bus access for students and elders."
|
|
),
|
|
geography="ward-3",
|
|
tags=["transport", "schools", "safety"],
|
|
)
|
|
service.register_resident("resident-a", geography="ward-3", interests=["transport", "safety"], lived_experience=["parent"], languages=["en"])
|
|
service.register_resident("resident-b", geography="ward-7", interests=["schools"], lived_experience=["bus rider"], languages=["es", "en"])
|
|
service.register_resident("resident-c", geography="ward-3", interests=["parks"], lived_experience=["elder"], languages=["en"])
|
|
service.add_comment(proposal["id"], "I support safer crossings for children and elders.", resident_key="resident-a", geography="ward-3")
|
|
service.add_comment(proposal["id"], "We need bus access and safer sidewalks.", resident_key="resident-b", geography="ward-7")
|
|
service.add_comment(proposal["id"], "I worry about delayed bus service.", resident_key="resident-c", geography="ward-3")
|
|
service.submit_preference(proposal["id"], "resident-a", 0.9)
|
|
service.submit_preference(proposal["id"], "resident-b", 0.4)
|
|
service.submit_preference(proposal["id"], "resident-c", 0.1)
|
|
return proposal["id"]
|
|
|
|
|
|
def test_service_routes_comments_and_exports_brief(tmp_path: Path):
|
|
service = build_service(tmp_path)
|
|
proposal_id = seed_service(service)
|
|
|
|
summary = service.summarize_comments(proposal_id)
|
|
assert summary
|
|
assert "en" in summary
|
|
|
|
routed = service.route_proposal(proposal_id, top_n=2)
|
|
assert routed["fragments"]
|
|
assert routed["fragments"][0]["matches"]
|
|
|
|
dashboard = service.build_dashboard(proposal_id)
|
|
assert dashboard["preference_aggregate"]["count"] == 3
|
|
assert dashboard["consensus_pockets"]
|
|
|
|
brief = service.export_brief(proposal_id)
|
|
assert brief["proposal"]["title"] == "School street safety"
|
|
assert brief["provenance"]
|
|
assert brief["ledger"]
|
|
|
|
|
|
def test_dashboard_surface_exposes_api(tmp_path: Path):
|
|
app = create_app(tmp_path / "api.sqlite")
|
|
client = __import__("fastapi.testclient", fromlist=["TestClient"]).TestClient(app)
|
|
|
|
proposal = client.post(
|
|
"/proposals",
|
|
json={
|
|
"city": "Springfield",
|
|
"title": "Library weekend hours",
|
|
"body": "Keep the library open later on weekends for students and families.",
|
|
"geography": "ward-1",
|
|
"tags": ["libraries", "education"],
|
|
},
|
|
).json()
|
|
|
|
client.post("/residents", json={"resident_key": "resident-x", "geography": "ward-1", "interests": ["libraries"], "lived_experience": ["student"], "languages": ["en"]})
|
|
client.post(f"/proposals/{proposal['id']}/comments", json={"text": "I support later hours.", "resident_key": "resident-x", "channel": "sms"})
|
|
brief = client.get(f"/proposals/{proposal['id']}/brief").json()
|
|
|
|
assert brief["proposal"]["title"] == "Library weekend hours"
|
|
assert brief["summary"]
|
|
|
|
|
|
def test_private_preference_aggregation_supports_noise(tmp_path: Path):
|
|
service = build_service(tmp_path)
|
|
proposal_id = seed_service(service)
|
|
|
|
clean = service.aggregate_preferences(proposal_id)
|
|
noisy = service.aggregate_preferences(proposal_id, epsilon=0.75, seed=42)
|
|
|
|
assert clean["count"] == 3
|
|
assert noisy["count"] >= 1
|
|
assert noisy["mean_score"] != clean["mean_score"] or noisy["count"] != clean["count"]
|