Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update main vote analyzer to also handle special vote titles #1060

Merged
merged 2 commits into from
Nov 27, 2024
Merged
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
53 changes: 39 additions & 14 deletions backend/howtheyvote/analysis/votes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ def _make_group_key(self, date: datetime.date, vote: Vote) -> str | None:


class MainVoteAnalyzer:
"""This analyzer checks the vote description for common keywords that indicate
that the vote is a main vote in its vote group. Only main votes are displayed
in index pages and are searchable."""
"""This analyzer checks the vote description and title for common keywords
that indicate that the vote is a main vote in its vote group. Only main
votes are displayed in index pages and are searchable."""

MAIN_DESCRIPTIONS = set(
[
Expand Down Expand Up @@ -94,27 +94,52 @@ class MainVoteAnalyzer:
]
)

def __init__(self, vote_id: int, description: str | None):
MAIN_TITLES = set(
[
"election de la commission",
"election of the commission",
]
)

def __init__(self, vote_id: int, description: str | None, title: str | None):
self.vote_id = vote_id
self.description = description
self.title = title

def run(self) -> Fragment | None:
if self._description_is_main() or self._title_is_main():
return Fragment(
model="Vote",
source_id=self.vote_id,
source_name=type(self).__name__,
group_key=self.vote_id,
data={"is_main": True},
)

return None

def _description_is_main(self) -> bool:
if not self.description:
return None
return False

description = unidecode(self.description).lower()
parts = description.split(" - ")

if all([part not in self.MAIN_DESCRIPTIONS for part in parts]):
return None
if any([part in self.MAIN_DESCRIPTIONS for part in parts]):
return True

return Fragment(
model="Vote",
source_id=self.vote_id,
source_name=type(self).__name__,
group_key=self.vote_id,
data={"is_main": True},
)
return False

def _title_is_main(self) -> bool:
if not self.title:
return False

title = unidecode(self.title).lower()

if title in self.MAIN_TITLES:
return True

return False


class FeaturedVotesAnalyzer:
Expand Down
4 changes: 3 additions & 1 deletion backend/howtheyvote/pipelines/rcv_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ def _analyze_main_votes(self) -> None:
writer = BulkWriter()

for vote in self._votes():
analyzer = MainVoteAnalyzer(vote.id, vote.description)
analyzer = MainVoteAnalyzer(
vote_id=vote.id, description=vote.description, title=vote.title
)
writer.add(analyzer.run())

writer.flush()
Expand Down
Empty file added backend/tests/__init__.py
Empty file.
64 changes: 64 additions & 0 deletions backend/tests/analysis/test_votes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from howtheyvote.analysis.votes import MainVoteAnalyzer
from howtheyvote.models.common import Fragment

from ..helpers import record_to_dict


def test_main_vote_analyzer_description():
analyzer = MainVoteAnalyzer(
vote_id=1,
description="Am 123",
title="Lorem Ipsum",
)
assert analyzer.run() is None

analyzer = MainVoteAnalyzer(
vote_id=2,
description="Proposition de résolution",
title="Lorem ipsum",
)
expected = Fragment(
model="Vote",
source_id=2,
source_name="MainVoteAnalyzer",
group_key=2,
data={"is_main": True},
)
assert record_to_dict(analyzer.run()) == record_to_dict(expected)

analyzer = MainVoteAnalyzer(
vote_id=3,
description="Accord provisoire - Am 123",
title="Lorem ipsum",
)
expected = Fragment(
model="Vote",
source_id=3,
source_name="MainVoteAnalyzer",
group_key=3,
data={"is_main": True},
)
assert record_to_dict(analyzer.run()) == record_to_dict(expected)


def test_main_vote_analyzer_title():
analyzer = MainVoteAnalyzer(
vote_id=1,
description=None,
title="Ordre du jour de mardi",
)
assert analyzer.run() is None

expected = Fragment(
model="Vote",
source_id=2,
source_name="MainVoteAnalyzer",
group_key=2,
data={"is_main": True},
)
analyzer = MainVoteAnalyzer(
vote_id=2,
description=None,
title="Élection de la Commission",
)
assert record_to_dict(analyzer.run()) == record_to_dict(expected)
7 changes: 7 additions & 0 deletions backend/tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import Any

from sqlalchemy.orm import DeclarativeBase


def record_to_dict(record: DeclarativeBase) -> dict[str, Any]:
return {c.name: getattr(record, c.name) for c in record.__table__.columns}
7 changes: 0 additions & 7 deletions backend/tests/scrapers/helpers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
from pathlib import Path
from typing import Any

from sqlalchemy.orm import DeclarativeBase

FIXTURES_BASE = Path(__file__).resolve().parent / "data"


def load_fixture(path: str) -> str:
return FIXTURES_BASE.joinpath(path).read_text()


def record_to_dict(record: DeclarativeBase) -> dict[str, Any]:
return {c.name: getattr(record, c.name) for c in record.__table__.columns}
3 changes: 2 additions & 1 deletion backend/tests/scrapers/test_votes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
RCVListScraper,
)

from .helpers import load_fixture, record_to_dict
from ..helpers import record_to_dict
from .helpers import load_fixture


def test_rcv_list_scraper(responses):
Expand Down