def add_acquisition(self, data, buyers, sellers, houses, non_auctions, buy_sell_modifiers, transaction, transaction_types): '''Add modeling of an acquisition as a transfer of title from the seller to the buyer''' hmo = get_crom_object(data) parent = data['parent_data'] # transaction = parent['transaction'] prices = parent.get('price') ask_price = parent.get('ask_price') auction_data = parent['auction_of_lot'] lot_object_key = object_key(auction_data) cno, lno, date = lot_object_key sale_type = non_auctions.get(cno, 'Auction') data['buyer'] = buyers data['seller'] = sellers acq_label = None try: object_label = f'“{hmo._label}”' acq_label = f'Acquisition of {cno} {lno} ({date}): {object_label}' except AttributeError: object_label = '(object)' acq_label = f'Acquisition of {cno} {lno} ({date})' # if not prices: # print(f'*** No price data found for {transaction} transaction') tx_data = parent.get('_prov_entry_data') current_tx = get_crom_object(tx_data) # The payment URIs are just the provenance entry URI with a suffix. In any case # where the provenance entry is merged, the payment should be merged as well. sell_payment_id = current_tx.id + '-Pay-to-Seller' buy_payment_id = current_tx.id + '-Pay-from-Buyer' # The acquisition URI is just the provenance entry URI with a suffix. In any case # where the provenance entry is merged, the acquisition should be merged as well. acq_id = hmo.id + '-Acq' acq = model.Acquisition(ident=acq_id, label=acq_label) acq.transferred_title_of = hmo self.attach_source_catalog(data, acq, buyers + sellers) multi = tx_data.get('multi_lot_tx') paym_label = f'multiple lots {multi}' if multi else object_label # paym = model.Payment(ident=payment_id, label=f'Payment for {paym_label}') payments = { 'buy': model.Payment(ident=buy_payment_id, label=f'Payment from buyer for {paym_label}'), 'sell': model.Payment(ident=sell_payment_id, label=f'Payment to seller for {paym_label}'), } for house_data in houses: house = get_crom_object(house_data) payments['buy'].paid_to = house payments['sell'].paid_from = house payments_used = set() THROUGH = CaseFoldingSet(buy_sell_modifiers['through']) FOR = CaseFoldingSet(buy_sell_modifiers['for']) single_seller = (len(sellers) == 1) single_buyer = (len(buyers) == 1) pi = self.helper.person_identity def is_or_anon(data: dict): mods = self.modifiers(data, 'auth_mod_a') return 'or anonymous' in mods or_anon_records = [is_or_anon(a) for a in sellers] uncertain_attribution = any(or_anon_records) for seq_no, seller_data in enumerate(sellers): seller = get_crom_object(seller_data) mod = self.modifiers(seller_data, 'auth_mod_a') attrib_assignment_classes = [model.AttributeAssignment] if uncertain_attribution: attrib_assignment_classes.append(vocab.PossibleAssignment) if THROUGH.intersects(mod): acq.carried_out_by = seller payments['sell'].carried_out_by = seller payments_used.add('sell') elif FOR.intersects(mod): acq.transferred_title_from = seller payments['sell'].paid_to = seller payments_used.add('sell') elif uncertain_attribution: # this is true if ANY of the sellers have an 'or anonymous' modifier # The assignment URIs are just the acquisition URI with a suffix. # In any case where the acquisition is merged, the assignments should be # merged as well. acq_assignment_uri = acq.id + f'-seller-assignment-{seq_no}' paym_assignment_uri = payments[ 'sell'].id + f'-seller-assignment-{seq_no}' acq_assignment_label = f'Uncertain seller as previous title holder in acquisition' acq_assignment = vocab.PossibleAssignment( ident=acq_assignment_uri, label=acq_assignment_label) acq_assignment.referred_to_by = vocab.Note( ident='', content=acq_assignment_label) acq_assignment.assigned_property = 'transferred_title_from' acq_assignment.assigned = seller acq.attributed_by = acq_assignment paym_assignment_label = f'Uncertain seller as recipient of payment' paym_assignment = vocab.PossibleAssignment( ident=paym_assignment_uri, label=paym_assignment_label) paym_assignment.referred_to_by = vocab.Note( ident='', content=paym_assignment_label) paym_assignment.assigned_property = 'paid_to' paym_assignment.assigned = seller payments['sell'].attributed_by = paym_assignment payments_used.add('sell') else: # covers non-modified # acq.carried_out_by = seller acq.transferred_title_from = seller # payments['sell'].carried_out_by = seller payments['sell'].paid_to = seller payments_used.add('sell') for buyer_data in buyers: buyer = get_crom_object(buyer_data) mod = self.modifiers(buyer_data, 'auth_mod_a') if 'or' in mod: # or/or others/or another mod_non_auth = buyer_data.get('auth_mod') if mod_non_auth: acq.referred_to_by = vocab.Note(ident='', label=f'Buyer modifier', content=mod_non_auth) warnings.warn(f'Handle buyer modifier: {mod}' ) # TODO: some way to model this uncertainty? if THROUGH.intersects(mod): acq.carried_out_by = buyer payments['buy'].carried_out_by = buyer payments_used.add('buy') elif FOR.intersects(mod): acq.transferred_title_to = buyer payments['buy'].paid_from = buyer payments_used.add('buy') else: # covers FOR modifiers and non-modified # acq.carried_out_by = buyer acq.transferred_title_to = buyer payments['buy'].paid_from = buyer # payments['buy'].carried_out_by = buyer payments_used.add('buy') if prices: amnt = get_crom_object(prices[0]) for paym in payments.values(): self.set_possible_attribute(paym, 'paid_amount', prices[0]) for price in prices[1:]: amnt = get_crom_object(price) content = self._price_note(price) if content: paym.referred_to_by = vocab.PriceStatement( ident='', content=content) elif ask_price: # for non-auction sales, the ask price is the amount paid for the acquisition for paym in payments.values(): self.set_possible_attribute(paym, 'paid_amount', ask_price) ts = tx_data.get('_date') if ts: acq.timespan = ts current_tx.part = acq for pay_key in payments_used: paym = payments[pay_key] current_tx.part = paym # current_tx.part = paym data['_prov_entries'] += [add_crom_data(data={}, what=current_tx)] # lot_uid, lot_uri = helper.shared_lot_number_ids(cno, lno) # TODO: `annotation` here is from add_physical_catalog_objects # paym.referred_to_by = annotation data['_acquisition'] = add_crom_data(data={'uri': acq_id}, what=acq) final_owner_data = data.get('_final_org', []) if final_owner_data: data['_organizations'].append(final_owner_data) final_owner = get_crom_object(final_owner_data) tx_label_args = tuple([ self.helper, sale_type, 'Sold', 'leading to the currently known location of' ] + list(lot_object_key)) tx, acq = self.final_owner_prov_entry(tx_label_args, final_owner, current_tx, hmo, ts) note = final_owner_data.get('note') if note: acq.referred_to_by = vocab.Note(ident='', content=note) data['_prov_entries'].append(add_crom_data(data={}, what=tx)) self.add_prev_post_owners(data, hmo, tx_data, sale_type, lot_object_key, ts) yield data, current_tx
def related_procurement(self, hmo, tx_label_args, current_tx=None, current_ts=None, buyer=None, seller=None, previous=False, ident=None, make_label=None): ''' Returns a new `vocab.ProvenanceEntry` object (and related acquisition) that is temporally related to the supplied procurement and associated data. The new procurement is for the given object, and has the given buyer and seller (both optional). If the `previous` flag is `True`, the new procurement is occurs before `current_tx`, and if the timespan `current_ts` is given, has temporal data to that effect. If `previous` is `False`, this relationship is reversed. The `make_label` argument, if supplied, is used as a function to generate the label for the provenance entry. Its arguments (generated in `handle_prev_post_owner`) are: * helper: the helper object for the current pipeline * sale_type: the sale type passed to `handle_prev_post_owner` (e.g. "Auction") * transaction: the transaction type being handled (e.g. "Sold") * rel: a string describing the relationship between this provenance entry and the object (e.g. "leading to the previous ownership of") * N trailing arguments used that are the contents of the `lot_object_key` tuple passed to `handle_prev_post_owner` ''' def _make_label_default(helper, sale_type, transaction, rel, *args): strs = [str(x) for x in args] return ', '.join(strs) if make_label is None: make_label = _make_label_default tx = vocab.ProvenanceEntry(ident=ident) tx_label = make_label(*tx_label_args) tx._label = tx_label tx.identified_by = model.Name(ident='', content=tx_label) if current_tx: if previous: tx.ends_before_the_start_of = current_tx else: tx.starts_after_the_end_of = current_tx modifier_label = 'Previous' if previous else 'Subsequent' try: pacq = model.Acquisition( ident='', label=f'{modifier_label} Acquisition of: “{hmo._label}”') pxfer = model.TransferOfCustody( ident='', label=f'{modifier_label} Transfer of Custody of: “{hmo._label}”' ) except AttributeError: pacq = model.Acquisition(ident='', label=f'{modifier_label} Acquisition') pxfer = model.TransferOfCustody( ident='', label=f'{modifier_label} Transfer of Custody') pacq.transferred_title_of = hmo pxfer.transferred_custody_of = hmo if buyer: pacq.transferred_title_to = buyer pxfer.transferred_custody_to = buyer if seller: pacq.transferred_title_from = seller pxfer.transferred_custody_from = seller tx.part = pacq tx.part = pxfer if current_ts: if previous: pacq.timespan = timespan_before(current_ts) else: pacq.timespan = timespan_after(current_ts) return tx, pacq
def add_acquisition(self, data, buyers, sellers, non_auctions, buy_sell_modifiers, transaction, transaction_types): '''Add modeling of an acquisition as a transfer of title from the seller to the buyer''' hmo = get_crom_object(data) parent = data['parent_data'] # transaction = parent['transaction'] prices = parent.get('price') auction_data = parent['auction_of_lot'] lot_object_key = object_key(auction_data) cno, lno, date = lot_object_key sale_type = non_auctions.get(cno, 'Auction') data['buyer'] = buyers data['seller'] = sellers acq_label = None try: object_label = f'“{hmo._label}”' acq_label = f'Acquisition of {cno} {lno} ({date}): {object_label}' except AttributeError: object_label = '(object)' acq_label = f'Acquisition of {cno} {lno} ({date})' # if not prices: # print(f'*** No price data found for {transaction} transaction') tx_data = parent['_prov_entry_data'] current_tx = get_crom_object(tx_data) payment_id = current_tx.id + '-Pay' acq_id = hmo.id + '-Acq' acq = model.Acquisition(ident=acq_id, label=acq_label) acq.transferred_title_of = hmo self.attach_source_catalog(data, acq, buyers + sellers) multi = tx_data.get('multi_lot_tx') paym_label = f'multiple lots {multi}' if multi else object_label paym = model.Payment(ident=payment_id, label=f'Payment for {paym_label}') THROUGH = set(buy_sell_modifiers['through']) FOR = set(buy_sell_modifiers['for']) single_seller = (len(sellers) == 1) single_buyer = (len(buyers) == 1) for seq_no, seller_data in enumerate(sellers): seller = get_crom_object(seller_data) mod = seller_data.get('auth_mod_a', '') if mod == 'or': mod_non_auth = seller_data.get('auth_mod') if mod_non_auth: acq.referred_to_by = vocab.Note(ident='', label=f'Seller modifier', content=mod_non_auth) warnings.warn('Handle OR buyer modifier' ) # TODO: some way to model this uncertainty? if mod in THROUGH: acq.carried_out_by = seller paym.carried_out_by = seller elif mod in FOR: acq.transferred_title_from = seller paym.paid_to = seller elif mod == 'or anonymous': acq_assignment = vocab.PossibleAssignment( ident=acq.id + f'-seller-assignment-{seq_no}', label= f'Uncertain seller as previous title holder in acquisition' ) acq_assignment.assigned_property = 'transferred_title_from' acq_assignment.assigned = seller acq.attributed_by = acq_assignment paym_assignment = vocab.PossibleAssignment( ident=paym.id + f'-seller-assignment-{seq_no}', label=f'Uncertain seller as recipient of payment') paym_assignment.assigned_property = 'paid_to' paym_assignment.assigned = seller paym.attributed_by = paym_assignment else: # covers non-modified acq.carried_out_by = seller acq.transferred_title_from = seller paym.carried_out_by = seller paym.paid_to = seller for buyer_data in buyers: buyer = get_crom_object(buyer_data) mod = buyer_data.get('auth_mod_a', '') if mod == 'or': # or/or others/or another mod_non_auth = buyer_data.get('auth_mod') if mod_non_auth: acq.referred_to_by = vocab.Note(ident='', label=f'Buyer modifier', content=mod_non_auth) warnings.warn(f'Handle buyer modifier: {mod}' ) # TODO: some way to model this uncertainty? if mod in THROUGH: acq.carried_out_by = buyer paym.carried_out_by = buyer else: # covers FOR modifiers and non-modified acq.transferred_title_to = buyer paym.paid_from = buyer if prices: amnt = get_crom_object(prices[0]) paym.paid_amount = amnt for price in prices[1:]: amnt = get_crom_object(price) content = self._price_note(price) if content: paym.referred_to_by = vocab.PriceStatement(ident='', content=content) ts = tx_data.get('_date') if ts: acq.timespan = ts current_tx.part = acq current_tx.part = paym data['_prov_entries'] += [add_crom_data(data={}, what=current_tx)] # lot_uid, lot_uri = helper.shared_lot_number_ids(cno, lno) # TODO: `annotation` here is from add_physical_catalog_objects # paym.referred_to_by = annotation data['_acquisition'] = add_crom_data(data={'uri': acq_id}, what=acq) final_owner_data = data.get('_final_org', []) if final_owner_data: data['_organizations'].append(final_owner_data) final_owner = get_crom_object(final_owner_data) tx_label_args = tuple([ self.helper, sale_type, 'Sold', 'leading to the currently known location of' ] + list(lot_object_key)) tx, acq = self.final_owner_procurement(tx_label_args, final_owner, current_tx, hmo, ts) note = final_owner_data.get('note') if note: acq.referred_to_by = vocab.Note(ident='', content=note) data['_prov_entries'].append(add_crom_data(data={}, what=tx)) post_own = data.get('post_owner', []) prev_own = data.get('prev_owner', []) prev_post_owner_records = [(post_own, False), (prev_own, True)] for owner_data, rev in prev_post_owner_records: if rev: rev_name = 'prev-owner' else: rev_name = 'post-owner' ignore_fields = {'own_so', 'own_auth_l', 'own_auth_d'} for seq_no, owner_record in enumerate(owner_data): record_id = f'{rev_name}-{seq_no+1}' if not any([ bool(owner_record.get(k)) for k in owner_record.keys() if k not in ignore_fields ]): # some records seem to have metadata (source information, location, or notes) # but no other fields set these should not constitute actual records of a prev/post owner. continue self.handle_prev_post_owner(data, hmo, tx_data, sale_type, lot_object_key, owner_record, record_id, rev, ts, make_label=prov_entry_label) yield data, current_tx