From 077cdcfeb2e29299a64b26789865d6c538bd0bdd Mon Sep 17 00:00:00 2001
From: Edward Betts <edward@4angle.com>
Date: Tue, 8 Oct 2019 11:11:15 +0100
Subject: [PATCH] Show people with the birth/death on item page

---
 app.py               | 19 ++++++--------
 depicts/human.py     | 59 ++++++++++++++++++++++++++++++++++++++++++++
 depicts/mediawiki.py | 18 ++++++++++++++
 depicts/model.py     | 11 +++++++++
 static/js/item.js    |  6 +++++
 templates/item.html  | 11 +++++++++
 6 files changed, 112 insertions(+), 12 deletions(-)
 create mode 100644 depicts/human.py

diff --git a/app.py b/app.py
index cb15b28..299beec 100755
--- a/app.py
+++ b/app.py
@@ -3,7 +3,7 @@
 from flask import Flask, render_template, url_for, redirect, request, g, jsonify, session
 from depicts import (utils, wdqs, commons, mediawiki, painting, saam, database,
                      dia, rijksmuseum, npg, museodelprado, barnesfoundation,
-                     wd_catalog, relaxed_ssl)
+                     wd_catalog, relaxed_ssl, human)
 from depicts.pager import Pagination, init_pager
 from depicts.model import (DepictsItem, DepictsItemAltLabel, Edit, PaintingItem,
                            Language)
@@ -203,7 +203,7 @@ def save(item_id):
     painting_item = PaintingItem.query.get(item_id)
     if painting_item is None:
         painting_entity = mediawiki.get_entity_with_cache(f'Q{item_id}')
-        label = get_entity_label(painting_entity)
+        label = mediawiki.get_entity_label(painting_entity)
         painting_item = PaintingItem(item_id=item_id, label=label, entity=painting_entity)
         database.session.add(painting_item)
         database.session.commit()
@@ -546,6 +546,8 @@ def item_page(item_id):
         label = None
     other = get_other(item.entity)
 
+    people = human.from_name(label) if label else None
+
     if 'P276' in entity['claims']:
         location = first_datavalue(entity, 'P276')['id']
         institution = other[location]
@@ -632,18 +634,11 @@ def item_page(item_id):
                            show_translation_links=show_translation_links,
                            existing_depicts=existing_depicts,
                            image=image,
+                           people=people,
                            other=other,
                            # hits=hits,
                            title=item.display_title)
 
-def get_entity_label(entity):
-    if 'en' in entity['labels']:
-        return entity['labels']['en']['value']
-
-    label_values = {l['value'] for l in entity['labels'].values()}
-    if len(label_values) == 1:
-        return list(label_values)[0]
-
 def get_languages(codes):
     return Language.query.filter(Language.wikimedia_language_code.in_(codes))
 
@@ -687,7 +682,7 @@ def get_labels(keys, name=None):
         json.dump({'keys': keys, 'labels': labels},
                   open(filename, 'w'), indent=2)
 
-    return {entity['id']: get_entity_label(entity) for entity in labels}
+    return {entity['id']: mediawiki.get_entity_label(entity) for entity in labels}
 
 def get_other(entity):
     other_items = set()
@@ -744,7 +739,7 @@ def next_page(item_id):
     image_filename = first_datavalue(entity, 'P18')
     image = image_with_cache(qid, image_filename, width)
 
-    label = get_entity_label(entity)
+    label = mediawiki.get_entity_label(entity)
     other = get_other(entity)
 
     other_list = []
diff --git a/depicts/human.py b/depicts/human.py
new file mode 100644
index 0000000..9409fca
--- /dev/null
+++ b/depicts/human.py
@@ -0,0 +1,59 @@
+from .model import HumanItem
+from . import mediawiki
+import re
+
+re_four_digits = re.compile(r'\b\d{4}\b')
+
+re_iso_date = re.compile(r'\b\d{4}-\d{2}-\d{2}\b')
+re_four_and_two = re.compile(r'\b(\d{2})(\d{2})[-–](\d{2})\b')
+re_catalog_number = re.compile(r'\b\d{4}[^\d]+\d+[^\d]+\d{4}\b')
+
+def query(yob, yod):
+    if yod < yob:
+        return []
+    return HumanItem.query.filter_by(yob=yob, yod=yod).all()
+
+def get_items_from_name(name):
+    found = []
+
+    m = re_four_and_two.search(name)
+    years = tuple(int(y) for y in re_four_digits.findall(name))
+
+    print(name)
+
+    yob1, yod1 = None, None
+    if m:
+        century = m.group(1)
+        yob1 = int(century + m.group(2))
+        yod1 = int(century + m.group(3))
+
+        found += query(yob1, yod1)
+
+    if len(years) == 2 and years != (yob1, yod1):
+        print(years)
+        found += query(*years)
+
+    return found
+
+def from_name(name):
+    candidates = get_items_from_name(name)
+    lookup = {item.qid: item for item in candidates}
+    qids = list(lookup.keys())
+
+    found = []
+    for entity in mediawiki.get_entities_with_cache(qids, props='labels|descriptions'):
+        qid = entity['id']
+        item = lookup[qid]
+        i = {
+            'qid': entity['id'],
+            'year_of_birth': item.year_of_birth,
+            'year_of_death': item.year_of_death,
+        }
+        label = mediawiki.get_entity_label(entity)
+        if label:
+            i['label'] = label
+        if 'en' in entity['descriptions']:
+            i['description'] = entity['descriptions']['en']['value']
+        found.append(i)
+    found.sort(key=lambda i: i['label'])
+    return found
diff --git a/depicts/mediawiki.py b/depicts/mediawiki.py
index a3ae224..2bd96f7 100644
--- a/depicts/mediawiki.py
+++ b/depicts/mediawiki.py
@@ -63,6 +63,16 @@ def get_entity_with_cache(qid, refresh=False):
 
     return entity
 
+def get_entities_with_cache(ids, **params):
+    filename = f'cache/entities_{"_".join(ids)}.json'
+    if os.path.exists(filename):
+        entity = json.load(open(filename))
+    else:
+        entity = get_entities(ids, **params)
+        json.dump(entity, open(filename, 'w'), indent=2)
+
+    return entity
+
 def mediawiki_query(titles, params, site):
     if not titles:
         return []
@@ -132,3 +142,11 @@ def get_categories(titles, site):
             continue
         title_and_cats.append((i['title'], cats))
     return title_and_cats
+
+def get_entity_label(entity):
+    if 'en' in entity['labels']:
+        return entity['labels']['en']['value']
+
+    label_values = {l['value'] for l in entity['labels'].values()}
+    if len(label_values) == 1:
+        return list(label_values)[0]
diff --git a/depicts/model.py b/depicts/model.py
index a86cce7..6d7a51b 100644
--- a/depicts/model.py
+++ b/depicts/model.py
@@ -43,6 +43,17 @@ class PaintingItem(Base):
     entity = Column(postgresql.JSON)
     qid = column_property('Q' + cast(item_id, String))
 
+class HumanItem(Base):
+    __tablename__ = 'human'
+    item_id = Column(Integer, primary_key=True, autoincrement=False)
+    year_of_birth = Column(Integer, nullable=False)
+    year_of_death = Column(Integer, nullable=False)
+    age_at_death = column_property(year_of_death - year_of_birth)
+    qid = column_property('Q' + cast(item_id, String))
+
+    yob = synonym('year_of_birth')
+    yod = synonym('year_of_death')
+
 class Language(Base):
     __tablename__ = 'language'
     item_id = Column(Integer, primary_key=True, autoincrement=False)
diff --git a/static/js/item.js b/static/js/item.js
index 497eed5..b0ce06e 100644
--- a/static/js/item.js
+++ b/static/js/item.js
@@ -8,12 +8,18 @@ var app = new Vue({
         searchTerms: '',
         hits: [],
         new_depicts: [],
+        people: people,
         existing_depicts: existing_depicts,
     },
     methods: {
         remove(index) {
             this.$delete(this.new_depicts, index);
         },
+        add_person(person) {
+            var hit = person;
+            hit['count'] = 0;
+            this.new_depicts.push(hit);
+        },
         add_depicts(hit) {
             this.new_depicts.push(hit);
             this.hits = [];
diff --git a/templates/item.html b/templates/item.html
index 1633169..dfaad7d 100644
--- a/templates/item.html
+++ b/templates/item.html
@@ -121,6 +121,16 @@ span.description { color: rgb(96, 96, 96); }
 
         <h3>what can you see in this painting?</h3>
 
+        <div v-if="people.length">
+          <div>These people were born and died in the same years as appears in the title of the painting.</div>
+          <div v-for="person in people">
+            <a href="#" @click.prevent="add_person(person)">{{ person.label || '[name missing]' }}</a>,
+            {{ person.year_of_birth }}-{{ person.year_of_death}} ({{ person.qid }})
+            <span v-if="person.description" class="description">{{ person.description }}</span>
+            <a :href="'https://www.wikidata.org/wiki/' + person.qid">[wikidata]</a>
+          </div>
+        </div>
+
         <div v-if="new_depicts.length">
           <div>{{ new_depicts.length }} new items to add to painting depicts statement</div>
         </div>
@@ -178,6 +188,7 @@ span.description { color: rgb(96, 96, 96); }
 <script>
   var lookup_url = {{ url_for('depicts_lookup') | tojson }};
   var existing_depicts = {{ existing_depicts | tojson }};
+  var people = {{ people | tojson }};
 </script>
 <script src="{{ url_for('static', filename='vue/vue.js') }}"></script>
 <script src="{{ url_for('static', filename='js/item.js') }}"></script>