| 1 | """Determine copyright status of works given relevant information such as |
|---|
| 2 | creator death date. |
|---|
| 3 | |
|---|
| 4 | Remarks |
|---|
| 5 | ======= |
|---|
| 6 | |
|---|
| 7 | Often cannot calculate PD with certainty (at least in an automated process). Different kinds of uncertainty: |
|---|
| 8 | |
|---|
| 9 | * Do not know persons associated with work (with certainty or at all) |
|---|
| 10 | * Do not know their death dates (may or may not know birth dates) |
|---|
| 11 | * Do not know when work was published |
|---|
| 12 | * etc |
|---|
| 13 | |
|---|
| 14 | And this is even assuming we have identified the work (and/or whether it is a |
|---|
| 15 | translation, critical edition etc etc). |
|---|
| 16 | |
|---|
| 17 | This uncertainty must therefore be presented. Core features |
|---|
| 18 | |
|---|
| 19 | * PD/Copyright status |
|---|
| 20 | * Level of uncertainty ... |
|---|
| 21 | * Comments |
|---|
| 22 | * Compressed comments/Standard flags |
|---|
| 23 | |
|---|
| 24 | Suggestions for flags: |
|---|
| 25 | * normalized title |
|---|
| 26 | * notes |
|---|
| 27 | * translation |
|---|
| 28 | |
|---|
| 29 | def determine_status(work, jurisdiction, when): |
|---|
| 30 | # now dispatch on jurisdiction (+ work type?) |
|---|
| 31 | |
|---|
| 32 | class CopyrightStatus: |
|---|
| 33 | # Flags etc |
|---|
| 34 | |
|---|
| 35 | class CalculatorBase: |
|---|
| 36 | def get_work_status(self, work): |
|---|
| 37 | |
|---|
| 38 | class CalculatorUK(CalculatorBase): |
|---|
| 39 | pass |
|---|
| 40 | """ |
|---|
| 41 | import logging |
|---|
| 42 | import datetime |
|---|
| 43 | |
|---|
| 44 | import swiss |
|---|
| 45 | try: |
|---|
| 46 | import json |
|---|
| 47 | except ImportError: |
|---|
| 48 | import simplejson as json |
|---|
| 49 | |
|---|
| 50 | #from sqlalchemy import orm |
|---|
| 51 | import pdw |
|---|
| 52 | |
|---|
| 53 | |
|---|
| 54 | logger = logging.getLogger('pdw.pd') |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | OLDEST_PERSON = 100 |
|---|
| 58 | |
|---|
| 59 | now = datetime.datetime.now() |
|---|
| 60 | def float_date(year, month=0, day=0): |
|---|
| 61 | return swiss.date.FlexiDate(year, month, day).as_float() |
|---|
| 62 | |
|---|
| 63 | def determine_status(work, jurisdiction): |
|---|
| 64 | # now dispatch on jurisdiction (+ work type?) |
|---|
| 65 | # note two letter country codes based on ISO 3166 |
|---|
| 66 | # |
|---|
| 67 | if jurisdiction == 'us': |
|---|
| 68 | pd_calculator = CalculatorUnitedStates |
|---|
| 69 | elif jurisdiction == 'ca': |
|---|
| 70 | pd_calculator = CalculatorCanada |
|---|
| 71 | elif jurisdiction in ('uk', 'gb') : |
|---|
| 72 | pd_calculator = CalculatorUk |
|---|
| 73 | else: |
|---|
| 74 | logger.error('Jurisdiction "%s" not currently supported. Try us,ca,uk' % jurisdiction) |
|---|
| 75 | assert 0 |
|---|
| 76 | return pd_calculator().get_work_status(work) |
|---|
| 77 | |
|---|
| 78 | def determine_status_from_raw(json_data): |
|---|
| 79 | ''' |
|---|
| 80 | To create a Work object and analize its pd status from a |
|---|
| 81 | python or json dict |
|---|
| 82 | ''' |
|---|
| 83 | |
|---|
| 84 | params = json.loads(json_data) |
|---|
| 85 | jur = params['jurisdiction'] |
|---|
| 86 | |
|---|
| 87 | work = pdw.model.Work.from_dict(params['work']) |
|---|
| 88 | |
|---|
| 89 | calculation = determine_status(work, jur) |
|---|
| 90 | result = json.dumps({'pd_probability': calculation.pd_prob, |
|---|
| 91 | 'confidence': 1-calculation.uncertainty, |
|---|
| 92 | 'log': calculation.log, |
|---|
| 93 | 'input': params},indent=2) |
|---|
| 94 | return result |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | class CalcResult(object): |
|---|
| 98 | '''A CalcResult object is returned by the calculator |
|---|
| 99 | ''' |
|---|
| 100 | def __init__(self): |
|---|
| 101 | self.calc_finished = False |
|---|
| 102 | self.date_pd = None |
|---|
| 103 | self.pd_prob = 0.0 # P(is PD) |
|---|
| 104 | self.uncertainty = 0.0 |
|---|
| 105 | self.log = [] # strings |
|---|
| 106 | |
|---|
| 107 | def __str__(self): |
|---|
| 108 | return "date_pd=%s pd_prob=%s log=%s" % (self.date_pd, self.pd_prob, self.log) |
|---|
| 109 | |
|---|
| 110 | class CalculatorBase(object): |
|---|
| 111 | """A Public Domain Calculator |
|---|
| 112 | when=None means today's date |
|---|
| 113 | """ |
|---|
| 114 | def __init__(self, when): |
|---|
| 115 | self.author_list = None |
|---|
| 116 | if when: |
|---|
| 117 | self._now = when |
|---|
| 118 | else: |
|---|
| 119 | self._now = float_date(datetime.date.today().year) |
|---|
| 120 | |
|---|
| 121 | def get_work_status(self, work): |
|---|
| 122 | self.calc_result = CalcResult() |
|---|
| 123 | self.work = work |
|---|
| 124 | |
|---|
| 125 | def get_author_list(self, parcel): |
|---|
| 126 | if self.author_list == None: |
|---|
| 127 | self.author_list = [] |
|---|
| 128 | for person in self.work.persons: |
|---|
| 129 | self.author_list.append(person.name) |
|---|
| 130 | return self.author_list |
|---|
| 131 | |
|---|
| 132 | def calc_anon(self): |
|---|
| 133 | self.calc_result.is_anon = False |
|---|
| 134 | for person in self.work.persons: |
|---|
| 135 | if person.name.lower() in ('anon', 'anon.', 'anonymous') : |
|---|
| 136 | self.calc_result.is_anon = True |
|---|
| 137 | |
|---|
| 138 | def calc_death_dates(self): |
|---|
| 139 | death_dates = [] # list of: (name, float date) |
|---|
| 140 | names = [] |
|---|
| 141 | most_recent_death_date = 0.0 |
|---|
| 142 | for person in self.work.persons: |
|---|
| 143 | death_date = person.death_date_ordered |
|---|
| 144 | # if we have no deathdate but do have a birthdate, assume |
|---|
| 145 | # death OLDEST_PERSON years after birth |
|---|
| 146 | if not death_date and person.birth_date_ordered: |
|---|
| 147 | death_date = person.birth_date_ordered + OLDEST_PERSON |
|---|
| 148 | self.calc_result.log.append('Author "%s" death date not given - assuming died %s years after birth (%s + %s = %s)' % (person.name, OLDEST_PERSON, person.birth_date_ordered, OLDEST_PERSON, death_date)) |
|---|
| 149 | death_dates.append((person.name, death_date)) |
|---|
| 150 | if death_date and death_date > most_recent_death_date: |
|---|
| 151 | most_recent_death_date = death_date |
|---|
| 152 | an_author_lives = False |
|---|
| 153 | for name, death_date in death_dates: |
|---|
| 154 | if not death_date: |
|---|
| 155 | # no birthday |
|---|
| 156 | # alive: big assumption! |
|---|
| 157 | self.calc_result.log.append('Author "%s" death date not given - assuming alive (!)' % name) |
|---|
| 158 | an_author_lives = True |
|---|
| 159 | |
|---|
| 160 | self.calc_result.death_dates = death_dates |
|---|
| 161 | self.calc_result.most_recent_death_date = most_recent_death_date |
|---|
| 162 | self.calc_result.an_author_lives = an_author_lives |
|---|
| 163 | |
|---|
| 164 | from uk import * |
|---|
| 165 | from us import * |
|---|
| 166 | from ca import * |
|---|
| 167 | from fast import * |
|---|