diff --git a/pokr/controllers/person.py b/pokr/controllers/person.py index 48ef0c6..9abafce 100644 --- a/pokr/controllers/person.py +++ b/pokr/controllers/person.py @@ -1,11 +1,15 @@ # -*- coding: utf-8 -*- +import math +import numpy from collections import Counter, defaultdict, OrderedDict from .base import Controller +from pokr.cache import cache from pokr.database import db_session from pokr.models import Bill, Candidacy, cosponsorship, Meeting, Party, Person, Pledge, Statement - +from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.sql.expression import select class PersonController(Controller): model = 'person' @@ -100,3 +104,86 @@ def sorted_statements(cls, person): .filter(Statement.person_id==person.id)\ .order_by(Meeting.date.desc().nullslast(), Statement.sequence) + @classmethod + def get_similar_assembly_members(cls, person, assembly_id): + ideology_tuples = calculate_ideology_tuple(assembly_id) + # find the person's ideology level + self_ideology_tuple = None + for ideology_tuple in ideology_tuples: + if ideology_tuple[0] == person.id: + self_ideology_tuple = ideology_tuple + break + + #remove self from the tuples + ideology_tuples.remove(self_ideology_tuple) + self_ideology = self_ideology_tuple[1] + + # find the most similar 5 persons + diff_ideology_tuples = map(lambda tuple: (tuple[0], math.fabs(tuple[1] - self_ideology)), ideology_tuples) + diff_ideology_tuples.sort(cmp=lambda x,y: cmp(x[1], y[1])) + similar_people_ids = map(lambda tuple: tuple[0], diff_ideology_tuples[:5]) + + return Person.query\ + .filter(Person.id.in_(similar_people_ids))\ + .all() + + + +def rescale(u): + u = (u - min(u)) / (max(u) - min(u)) + return [float(v) for v in u] + +# Also draws heavily from govtrack.us +@cache.memoize(timeout=60*60*24) +def generate_cosponsorship_matrix(assembly_id): + try: + bills = Bill.query.filter(Bill.assembly_id==assembly_id).all() + rep_to_row = {} + cosponsorships = [] + + def rownum(id): + if not id in rep_to_row: + rep_to_row[id] = len(rep_to_row) + return rep_to_row[id] + + for bill in bills: + reps = bill.representative_people + if len(reps) == 0: + continue + + for rep in reps: + for cosponsor in bill.cosponsors: + rownum(cosponsor.id) + cosponsorships.append((rep.id, cosponsor.id)) + + P = numpy.identity(len(rep_to_row), numpy.float) + for sponsor, cosponsor in cosponsorships: + P[rep_to_row[sponsor], rep_to_row[cosponsor]] += 1.0 + + except NoResultFound, e: + print e + + return rep_to_row, P + +# Got a lot of inspiration from govtrack.us +@cache.memoize(timeout=60*60*24) +def calculate_ideology_tuple(assembly_id): + try: + rep_to_row, P = generate_cosponsorship_matrix(assembly_id) + u, s, vh = numpy.linalg.svd(P) + spectrum = vh[1,:] + spectrum = rescale(spectrum) + ids = [None for k in rep_to_row] + for k, v in rep_to_row.items(): + ids[v] = k + + ideology_tuples = [] + for index, person_id in enumerate(ids): + ideology_tuples.append((person_id, spectrum[index])) + + ideology_tuples.sort(cmp=lambda x,y: cmp(x[1], y[1])) + + except NoResultFound, e: + print e + + return ideology_tuples diff --git a/pokr/messages.pot b/pokr/messages.pot index 11c09ec..3209490 100644 --- a/pokr/messages.pot +++ b/pokr/messages.pot @@ -8,14 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-01 17:11+0900\n" +"POT-Creation-Date: 2015-04-08 08:14-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" +"Generated-By: Babel 1.3\n" #: pokr/models/meeting.py:49 #, python-format @@ -32,7 +32,7 @@ msgstr "" msgid "%(sitting_id)sth sitting" msgstr "" -#: pokr/templates/bill-layout.html:10 pokr/templates/bill.html:142 +#: pokr/templates/bill-layout.html:10 pokr/templates/bill.html:140 msgid "The summary of this bill has not been updated." msgstr "" @@ -74,36 +74,36 @@ msgstr "" msgid "see official page" msgstr "" -#: pokr/templates/bill.html:68 +#: pokr/templates/bill.html:67 msgid "suggest to assembly" msgstr "" -#: pokr/templates/bill.html:70 +#: pokr/templates/bill.html:68 msgid "see original pdf" msgstr "" -#: pokr/templates/bill.html:71 +#: pokr/templates/bill.html:69 msgid "see original text" msgstr "" -#: pokr/templates/bill.html:79 +#: pokr/templates/bill.html:77 msgid "sponsors" msgstr "" -#: pokr/templates/bill.html:82 pokr/templates/bill.html:105 +#: pokr/templates/bill.html:80 pokr/templates/bill.html:103 #: pokr/templates/macros.html:105 pokr/templates/person-legislations.html:64 msgid "representative sponsor" msgstr "" -#: pokr/templates/bill.html:93 pokr/templates/bill.html:119 +#: pokr/templates/bill.html:91 pokr/templates/bill.html:117 msgid "cosponsors" msgstr "" -#: pokr/templates/bill.html:134 +#: pokr/templates/bill.html:132 msgid "contents" msgstr "" -#: pokr/templates/bill.html:137 pokr/templates/party.html:43 +#: pokr/templates/bill.html:135 pokr/templates/party.html:43 msgid "summary" msgstr "" @@ -111,13 +111,13 @@ msgstr "" msgid "bills" msgstr "" -#: pokr/templates/bills.html:29 pokr/templates/parties.html:30 +#: pokr/templates/bills.html:30 pokr/templates/parties.html:30 #: pokr/templates/people.html:29 pokr/templates/region.html:54 #: pokr/templates/region.html:99 msgid "th" msgstr "" -#: pokr/templates/bills.html:57 pokr/templates/macro-statement.html:6 +#: pokr/templates/bills.html:62 pokr/templates/macro-statement.html:6 #: pokr/templates/macros.html:95 pokr/templates/mypage.html:88 #: pokr/templates/mypage.html:113 pokr/templates/person-legislations.html:29 #: pokr/templates/person-legislations.html:73 @@ -126,7 +126,7 @@ msgstr "" msgid "more" msgstr "" -#: pokr/templates/bills.html:58 pokr/templates/district-feeds.html:19 +#: pokr/templates/bills.html:63 pokr/templates/district-feeds.html:19 msgid "No summary" msgstr "" @@ -554,6 +554,7 @@ msgstr "" #: pokr/templates/person-legislations.html:13 #: pokr/templates/person-legislations.html:53 +#: pokr/templates/person-similar-people.html:18 msgid "th assembly" msgstr "" @@ -566,6 +567,14 @@ msgstr "" msgid "role" msgstr "" +#: pokr/templates/person-similar-people.html:4 +msgid "similar people" +msgstr "" + +#: pokr/templates/person-similar-people.html:8 +msgid "based on cosponsorships" +msgstr "" + #: pokr/templates/person-statements.html:6 msgid "Statements" msgstr "" @@ -620,11 +629,11 @@ msgstr "" msgid "Wikipedia" msgstr "" -#: pokr/templates/person.html:161 +#: pokr/templates/person.html:162 msgid "trends" msgstr "" -#: pokr/templates/person.html:163 +#: pokr/templates/person.html:164 #, python-format msgid "Results from Google Trends searched by query %(name)s" msgstr "" @@ -736,7 +745,7 @@ msgid "sources" msgstr "" #: pokr/templates/includes/header.html:15 -#: pokr/templates/includes/header.html:17 pokr/views/bill.py:19 +#: pokr/templates/includes/header.html:17 pokr/views/bill.py:26 msgid "bill" msgstr "" diff --git a/pokr/static/less/person.less b/pokr/static/less/person.less index 4770eee..6b18758 100644 --- a/pokr/static/less/person.less +++ b/pokr/static/less/person.less @@ -51,6 +51,24 @@ } } + #section-similar-people { + div.content { + div.group { + div { + display: inline-block; + + &.similarity { + width: 13%; + } + + &.people { + + } + } + } + } + } + #section-elections { tr:first-child th, tr:first-child td { diff --git a/pokr/templates/person-similar-people.html b/pokr/templates/person-similar-people.html new file mode 100644 index 0000000..f65ea00 --- /dev/null +++ b/pokr/templates/person-similar-people.html @@ -0,0 +1,32 @@ +{% import 'macros.html' as macro with context %} + +
+

{{ gettext('similar people') }}

+ + + + + + + + + {% for candidacy in person.candidacies %} + {% if candidacy.is_elected %} + {% set similar_assembly_members = PersonController.get_similar_assembly_members(person, candidacy.assembly_id) %} + + + + + {% endif %} + {% endfor %} + +
{{ gettext('based on cosponsorships') }}
+ {{ candidacy.assembly_id }}{{ gettext('th assembly') }} + + {% for similar_assembly_member in similar_assembly_members %} + + {{ similar_assembly_member.name }} + + {% endfor %} +
+
\ No newline at end of file diff --git a/pokr/templates/person.html b/pokr/templates/person.html index 9bc1333..a67dac8 100644 --- a/pokr/templates/person.html +++ b/pokr/templates/person.html @@ -152,6 +152,7 @@

{% endif %} + {% if has_bills %}{% include 'person-similar-people.html' with context %}{% endif %} {% if has_bills %}{% include 'person-legislations.html' with context %}{% endif %} {% include 'person-statements.html' with context %} {% include 'person-elections.html' with context %} diff --git a/pokr/translations/ko/LC_MESSAGES/messages.mo b/pokr/translations/ko/LC_MESSAGES/messages.mo index 04e73aa..ffc6ba6 100644 Binary files a/pokr/translations/ko/LC_MESSAGES/messages.mo and b/pokr/translations/ko/LC_MESSAGES/messages.mo differ diff --git a/pokr/translations/ko/LC_MESSAGES/messages.po b/pokr/translations/ko/LC_MESSAGES/messages.po index 96784ad..05e4920 100644 --- a/pokr/translations/ko/LC_MESSAGES/messages.po +++ b/pokr/translations/ko/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-01 17:11+0900\n" +"POT-Creation-Date: 2015-04-08 08:14-0700\n" "PO-Revision-Date: 2012-08-06 12:48+0900\n" "Last-Translator: Cheol KANG \n" "Language-Team: ko \n" @@ -15,7 +15,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" +"Generated-By: Babel 1.3\n" #: pokr/models/meeting.py:49 #, python-format @@ -32,7 +32,7 @@ msgstr "%(session_id)s회" msgid "%(sitting_id)sth sitting" msgstr "%(sitting_id)s차" -#: pokr/templates/bill-layout.html:10 pokr/templates/bill.html:142 +#: pokr/templates/bill-layout.html:10 pokr/templates/bill.html:140 msgid "The summary of this bill has not been updated." msgstr "이 의안의 요약이 아직 업로드되지 않았습니다." @@ -74,36 +74,36 @@ msgstr "외부링크" msgid "see official page" msgstr "국회에서 보기" -#: pokr/templates/bill.html:68 +#: pokr/templates/bill.html:67 msgid "suggest to assembly" msgstr "의견제출하기" -#: pokr/templates/bill.html:70 +#: pokr/templates/bill.html:68 msgid "see original pdf" msgstr "원문 PDF 보기" -#: pokr/templates/bill.html:71 +#: pokr/templates/bill.html:69 msgid "see original text" msgstr "원문 텍스트 보기" -#: pokr/templates/bill.html:79 +#: pokr/templates/bill.html:77 msgid "sponsors" msgstr "발의자" -#: pokr/templates/bill.html:82 pokr/templates/bill.html:105 +#: pokr/templates/bill.html:80 pokr/templates/bill.html:103 #: pokr/templates/macros.html:105 pokr/templates/person-legislations.html:64 msgid "representative sponsor" msgstr "대표발의자" -#: pokr/templates/bill.html:93 pokr/templates/bill.html:119 +#: pokr/templates/bill.html:91 pokr/templates/bill.html:117 msgid "cosponsors" msgstr "공동발의자" -#: pokr/templates/bill.html:134 +#: pokr/templates/bill.html:132 msgid "contents" msgstr "내용" -#: pokr/templates/bill.html:137 pokr/templates/party.html:43 +#: pokr/templates/bill.html:135 pokr/templates/party.html:43 msgid "summary" msgstr "요약" @@ -111,13 +111,13 @@ msgstr "요약" msgid "bills" msgstr "의안" -#: pokr/templates/bills.html:29 pokr/templates/parties.html:30 +#: pokr/templates/bills.html:30 pokr/templates/parties.html:30 #: pokr/templates/people.html:29 pokr/templates/region.html:54 #: pokr/templates/region.html:99 msgid "th" msgstr "대" -#: pokr/templates/bills.html:57 pokr/templates/macro-statement.html:6 +#: pokr/templates/bills.html:62 pokr/templates/macro-statement.html:6 #: pokr/templates/macros.html:95 pokr/templates/mypage.html:88 #: pokr/templates/mypage.html:113 pokr/templates/person-legislations.html:29 #: pokr/templates/person-legislations.html:73 @@ -126,7 +126,7 @@ msgstr "대" msgid "more" msgstr "더보기" -#: pokr/templates/bills.html:58 pokr/templates/district-feeds.html:19 +#: pokr/templates/bills.html:63 pokr/templates/district-feeds.html:19 msgid "No summary" msgstr "요약 준비 중" @@ -511,7 +511,7 @@ msgstr "이름순 정렬" #: pokr/templates/people.html:46 msgid "Sort by cosponsorship" -msgstr "의안활동순 정렬" +msgstr "공동발의안수순 정렬" #: pokr/templates/person-elections.html:4 msgid "By-election results are coming soon!" @@ -554,6 +554,7 @@ msgstr "입법활동" #: pokr/templates/person-legislations.html:13 #: pokr/templates/person-legislations.html:53 +#: pokr/templates/person-similar-people.html:18 msgid "th assembly" msgstr "대 국회" @@ -566,6 +567,14 @@ msgstr "제목" msgid "role" msgstr "역할" +#: pokr/templates/person-similar-people.html:4 +msgid "similar people" +msgstr "관련 인물" + +#: pokr/templates/person-similar-people.html:8 +msgid "based on cosponsorships" +msgstr "공동발의안 기준" + #: pokr/templates/person-statements.html:6 msgid "Statements" msgstr "발언" @@ -620,11 +629,11 @@ msgstr "키워드" msgid "Wikipedia" msgstr "위키피디아" -#: pokr/templates/person.html:161 +#: pokr/templates/person.html:162 msgid "trends" msgstr "검색추이" -#: pokr/templates/person.html:163 +#: pokr/templates/person.html:164 #, python-format msgid "Results from Google Trends searched by query %(name)s" msgstr "'%(name)s를 구글 Trends에서 검색한 결과입니다." @@ -736,7 +745,7 @@ msgid "sources" msgstr "소스" #: pokr/templates/includes/header.html:15 -#: pokr/templates/includes/header.html:17 pokr/views/bill.py:19 +#: pokr/templates/includes/header.html:17 pokr/views/bill.py:26 msgid "bill" msgstr "의안"