Example #1
0
    def __call__(self, data: dict, non_auctions, event_properties,
                 buy_sell_modifiers, transaction_types):
        '''Determine if this record has an acquisition or bidding, and add appropriate modeling'''
        parent = data['parent_data']

        auction_houses_data = event_properties['auction_houses']
        event_experts = event_properties['experts']
        event_commissaires = event_properties['commissaire']

        sales_record = get_crom_object(data['_record'])
        transaction = parent['transaction']
        transaction = transaction.replace('[?]', '').rstrip()
        auction_data = parent['auction_of_lot']
        cno, lno, date = object_key(auction_data)
        shared_lot_number = self.helper.shared_lot_number_from_lno(lno)
        buyers = [
            self.add_person(self.helper.copy_source_information(p, parent),
                            record=sales_record,
                            relative_id=f'buyer_{i+1}',
                            catalog_number=cno)
            for i, p in enumerate(parent['buyer'])
        ]

        data.setdefault('_prov_entries', [])
        data.setdefault('_other_owners', [])

        sellers = [
            self.add_person(self.helper.copy_source_information(p, parent),
                            record=sales_record,
                            relative_id=f'seller_{i+1}',
                            catalog_number=cno)
            for i, p in enumerate(parent['seller'])
        ]
        all_mods = {
            m.lower().strip()
            for a in sellers for m in a.get('auth_mod_a', '').split(';')
        } - {''}
        seller_group = (all_mods == {
            'or'
        })  # the seller is *one* of the named people, model as a group

        orig_sellers = sellers
        if seller_group:
            tx_data = parent.get('_prov_entry_data')
            if tx_data:  # if there is a prov entry (e.g. was not withdrawn)
                current_tx = get_crom_object(tx_data)
                # The seller group URI is just the provenance entry URI with a suffix.
                # In any case where the provenance entry is merged, the seller group
                # should be merged as well.
                group_uri = current_tx.id + '-SellerGroup'
                group_data = {
                    'uri': group_uri,
                }
            else:
                pi_record_no = data['pi_record_no']
                group_uri_key = ('GROUP', 'PI', pi_record_no, 'SellerGroup')
                group_uri = self.helper.make_proj_uri(*group_uri_key)
                group_data = {
                    'uri_keys': group_uri_key,
                    'uri': group_uri,
                }
            g_label = f'Group containing the seller of {object_key_string(cno, lno, date)}'
            g = vocab.UncertainMemberClosedGroup(ident=group_uri,
                                                 label=g_label)
            for seller_data in sellers:
                seller = get_crom_object(seller_data)
                seller.member_of = g
                data['_other_owners'].append(seller_data)
            sellers = [add_crom_data({}, g)]

        SOLD = transaction_types['sold']
        UNSOLD = transaction_types['unsold']
        UNKNOWN = transaction_types['unknown']

        sale_type = non_auctions.get(cno, 'Auction')
        data.setdefault('_owner_locations', [])
        if transaction in SOLD:
            houses = [
                self.helper.add_auction_house_data(h)
                for h in auction_houses_data.get(cno, [])
            ]
            for data, current_tx in self.add_acquisition(
                    data, buyers, sellers, houses, non_auctions,
                    buy_sell_modifiers, transaction, transaction_types):
                acq = get_crom_object(data['_acquisition'])
                self.add_mod_notes(acq, all_mods, label=f'Seller modifier')
                experts = event_experts.get(cno, [])
                commissaires = event_commissaires.get(cno, [])
                custody_recievers = houses + [
                    add_crom_data(data={}, what=r)
                    for r in experts + commissaires
                ]

                if sale_type in ('Auction', 'Collection Catalog'):
                    # 'Collection Catalog' is treated just like an Auction
                    self.add_transfer_of_custody(
                        data,
                        current_tx,
                        xfer_to=custody_recievers,
                        xfer_from=sellers,
                        buy_sell_modifiers=buy_sell_modifiers,
                        sequence=1,
                        purpose='selling')
                    self.add_transfer_of_custody(
                        data,
                        current_tx,
                        xfer_to=buyers,
                        xfer_from=custody_recievers,
                        buy_sell_modifiers=buy_sell_modifiers,
                        sequence=2,
                        purpose='completing sale')
                elif sale_type in ('Private Contract Sale', 'Stock List'):
                    # 'Stock List' is treated just like a Private Contract Sale, except for the catalogs
                    metadata = {
                        'pi_record_no': parent['pi_record_no'],
                        'catalog_number': cno
                    }
                    for i, h in enumerate(custody_recievers):
                        house = get_crom_object(h)
                        if hasattr(house, 'label'):
                            house._label = f'{house._label}, private sale organizer for {cno} {shared_lot_number} ({date})'
                        else:
                            house._label = f'Private sale organizer for {cno} {shared_lot_number} ({date})'
                        data['_organizations'].append(h)

                    self.add_transfer_of_custody(
                        data,
                        current_tx,
                        xfer_to=custody_recievers,
                        xfer_from=sellers,
                        buy_sell_modifiers=buy_sell_modifiers,
                        sequence=1,
                        purpose='selling')
                    self.add_transfer_of_custody(
                        data,
                        current_tx,
                        xfer_to=buyers,
                        xfer_from=custody_recievers,
                        buy_sell_modifiers=buy_sell_modifiers,
                        sequence=2,
                        purpose='completing sale')

                    prev_procurements = self.add_private_sellers(
                        data, sellers, sale_type, transaction,
                        transaction_types)
                else:
                    warnings.warn(
                        f'*** not modeling transfer of custody for auction type {transaction}'
                    )
                yield data
        elif transaction in UNSOLD:
            houses = [
                self.helper.add_auction_house_data(h)
                for h in auction_houses_data.get(cno, [])
            ]
            experts = event_experts.get(cno, [])
            commissaires = event_commissaires.get(cno, [])
            custody_recievers = houses + [
                add_crom_data(data={}, what=r) for r in experts + commissaires
            ]
            bid_count = 0
            for data in self.add_bidding(data, buyers, sellers,
                                         buy_sell_modifiers, sale_type,
                                         transaction, transaction_types,
                                         custody_recievers):
                bid_count += 1
                act = get_crom_object(data.get('_bidding'))
                self.add_mod_notes(act, all_mods, label=f'Seller modifier')
                yield data
            if not bid_count:
                # there was no bidding, but we still want to model the seller(s) as
                # having previously acquired the object.
                prev_procurements = self.add_non_sale_sellers(
                    data, sellers, sale_type, transaction, transaction_types)
                yield data
        elif transaction in UNKNOWN:
            if sale_type == 'Lottery':
                yield data
            else:
                houses = [
                    self.helper.add_auction_house_data(h)
                    for h in auction_houses_data.get(cno, [])
                ]
                for data in self.add_bidding(data, buyers, sellers,
                                             buy_sell_modifiers, sale_type,
                                             transaction, transaction_types,
                                             houses):
                    act = get_crom_object(data.get('_bidding'))
                    self.add_mod_notes(act, all_mods, label=f'Seller modifier')
                    yield data
        else:
            prev_procurements = self.add_non_sale_sellers(
                data, sellers, sale_type, transaction, transaction_types)
            lot = get_crom_object(parent['_event_causing_prov_entry'])
            for tx_data in prev_procurements:
                tx = get_crom_object(tx_data)
                lot.starts_after_the_end_of = tx
            warnings.warn(
                f'Cannot create acquisition data for unrecognized transaction type: {transaction!r}'
            )
            yield data
Example #2
0
    def model_object_artists(self,
                             data,
                             people,
                             hmo,
                             prod_event,
                             attribution_modifiers,
                             attribution_group_types,
                             attribution_group_names,
                             all_uncertain=False):
        FORMERLY_ATTRIBUTED_TO = attribution_modifiers[
            'formerly attributed to']
        POSSIBLY = attribution_modifiers['possibly by']
        UNCERTAIN = attribution_modifiers['uncertain']
        ATTRIBUTED_TO = attribution_modifiers['attributed to']

        event_uri = prod_event.id
        sales_record = get_crom_object(data['_record'])
        artists = [p for p in people if not self.is_or_anon(p)]
        or_anon_records = any([self.is_or_anon(a) for a in people])
        if or_anon_records:
            all_uncertain = True

        try:
            hmo_label = f'{hmo._label}'
        except AttributeError:
            hmo_label = 'object'

        # 5. Determine if the artist records represent a disjunction (similar to 2 above):
        artist_all_mods = {
            m.lower().strip()
            for a in artists for m in a.get('attrib_mod_auth', '').split(';')
        } - {''}
        all_or_modifiers = ['or' in a['modifiers'] for a in artists]
        artist_group_flag = (not or_anon_records) and len(
            all_or_modifiers) and all(all_or_modifiers)
        artist_group = None
        if artist_group_flag:
            # The artist group URI is just the production event URI with a suffix. When URIs are
            # reconciled during prev/post sale rewriting, this will allow us to also reconcile
            # the URIs for the artist groups (of which there should only be one per production/object)
            group_uri = prod_event.id + '-ArtistGroup'
            g_label = f'Group containing the artist of {hmo_label}'
            artist_group = vocab.UncertainMemberClosedGroup(ident=group_uri,
                                                            label=g_label)
            artist_group.identified_by = model.Name(ident='', content=g_label)
            pi_record_no = data['pi_record_no']
            group_uri_key = ('GROUP', 'PI', pi_record_no, 'ArtistGroup')
            group_data = {
                'uri': group_uri,
                'uri_keys': group_uri_key,
                'role_label': 'uncertain artist'
            }
            add_crom_data(data=group_data, what=artist_group)
            data['_organizations'].append(group_data)

            # 6. Model all the artist records as sub-production events:
            prod_event.carried_out_by = artist_group
            for seq_no, a_data in enumerate(artists):
                mods = a_data['modifiers']
                attribute_assignment_id = self.helper.prepend_uri_key(
                    prod_event.id, f'ASSIGNMENT,Artist-{seq_no}')
                a_data = self.model_person_or_group(data,
                                                    a_data,
                                                    attribution_group_types,
                                                    attribution_group_names,
                                                    seq_no=seq_no,
                                                    role='Artist',
                                                    sales_record=sales_record)
                artist_label = a_data.get(
                    'label')  # TODO: this may not be right for groups
                person = get_crom_object(a_data)
                if ATTRIBUTED_TO.intersects(mods):
                    attrib_assignment_classes = [model.AttributeAssignment]
                    attrib_assignment_classes.append(vocab.PossibleAssignment)
                    assignment = vocab.make_multitype_obj(
                        *attrib_assignment_classes,
                        ident=attribute_assignment_id,
                        label=f'Possibly attributed to {artist_label}')
                    assignment._label = f'Possibly by {artist_label}'
                    person.attributed_by = assignment
                    assignment.assigned_property = 'member_of'
                    assignment.assigned = person
                else:
                    person.member_of = artist_group
        else:
            for seq_no, a_data in enumerate(artists):
                uncertain = all_uncertain
                attribute_assignment_id = self.helper.prepend_uri_key(
                    prod_event.id, f'ASSIGNMENT,Artist-{seq_no}')
                a_data = self.model_person_or_group(data,
                                                    a_data,
                                                    attribution_group_types,
                                                    attribution_group_names,
                                                    seq_no=seq_no,
                                                    role='Artist',
                                                    sales_record=sales_record)
                artist_label = a_data.get(
                    'label')  # TODO: this may not be right for groups
                person = get_crom_object(a_data)
                mods = a_data['modifiers']
                attrib_assignment_classes = [model.AttributeAssignment]
                subprod_path = self.helper.make_uri_path(*a_data["uri_keys"])
                subevent_id = event_uri + f'-{subprod_path}'
                if UNCERTAIN.intersects(mods):
                    if POSSIBLY.intersects(mods):
                        attrib_assignment_classes.append(
                            vocab.PossibleAssignment)
                        assignment = vocab.make_multitype_obj(
                            *attrib_assignment_classes,
                            ident=attribute_assignment_id,
                            label=f'Possibly attributed to {artist_label}')
                        assignment._label = f'Possibly by {artist_label}'
                    else:
                        attrib_assignment_classes.append(
                            vocab.ProbableAssignment)
                        assignment = vocab.make_multitype_obj(
                            *attrib_assignment_classes,
                            ident=attribute_assignment_id,
                            label=f'Probably attributed to {artist_label}')
                        assignment._label = f'Probably by {artist_label}'

                    # TODO: this assigns an uncertain carried_out_by property directly to the top-level production;
                    #       should it instead be an uncertain sub-production part?
                    prod_event.attributed_by = assignment
                    assignment.assigned_property = 'carried_out_by'
                    assignment.assigned = person
                elif FORMERLY_ATTRIBUTED_TO.intersects(mods):
                    attrib_assignment_classes = [vocab.ObsoleteAssignment]
                    if uncertain:
                        attrib_assignment_classes.append(
                            vocab.PossibleAssignment)
                    assignment = vocab.make_multitype_obj(
                        *attrib_assignment_classes,
                        ident=attribute_assignment_id,
                        label=f'Formerly attributed to {artist_label}')
                    prod_event.attributed_by = assignment
                    assignment.assigned_property = 'carried_out_by'
                    assignment.assigned = person
                else:
                    if uncertain or ATTRIBUTED_TO.intersects(mods):
                        attrib_assignment_classes.append(
                            vocab.PossibleAssignment)
                        assignment = vocab.make_multitype_obj(
                            *attrib_assignment_classes,
                            ident=attribute_assignment_id,
                            label=f'Possibly attributed to {artist_label}')
                        prod_event.attributed_by = assignment
                        assignment.assigned_property = 'carried_out_by'
                        assignment.assigned = person
                    else:
                        subevent = model.Production(
                            ident=subevent_id,
                            label=f'Production sub-event for {artist_label}')
                        subevent.carried_out_by = person
                        prod_event.part = subevent
Example #3
0
    def model_people_as_possible_group(self,
                                       people,
                                       tx_data,
                                       data,
                                       object_key,
                                       label,
                                       store_key='_other_owners',
                                       mod_key='auth_mod_a'):
        '''
		Takes an array of crom-object-containing dicts containing Person objects (people)
		and returns an array of people that should be used in its place according to
		the modifier values in each dict's mod_key field. Also returns the set of
		modifier values.
		
		If all modifiers are 'or', then the set of people are set as members of a
		new Group, and that group is returned as the single stand-in for the set of
		people.
		
		tx_data is the crom-containing dict for a related prov entry.
		
		If a group is created, the people records will be stored in data[store_key].
		
		object_key and label will be used in constructing a descriptive string
		for the group: 'Group containing the {label.lower()} of {object_key}'.
		For example, label='buyer' and object_key='B-340 0291 (1820-07-19)'.
		'''
        all_mods = {
            m.lower().strip()
            for a in people for m in a.get(mod_key, '').split(';')
        } - {''}
        group = (all_mods == {'or'}
                 )  # the person is *one* of the named people, model as a group
        if group:
            names = []
            for person_data in people:
                if len(person_data['identifiers']):
                    names.append(person_data['identifiers'][0].content)
                else:
                    names.append(person_data['label'])
            group_name = ' OR '.join(names)
            if tx_data:  # if there is a prov entry (e.g. was not withdrawn)
                current_tx = get_crom_object(tx_data)
                # The person group URI is just the provenance entry URI with a suffix.
                # In any case where the provenance entry is merged, the person group
                # should be merged as well.
                group_uri = current_tx.id + f'-{label}Group'
                group_data = {
                    'uri': group_uri,
                }
            else:
                pi_record_no = data['pi_record_no']
                group_uri_key = ('GROUP', 'PI', pi_record_no, f'{label}Group')
                group_uri = self.helper.make_proj_uri(*group_uri_key)
                group_data = {
                    'uri_keys': group_uri_key,
                    'uri': group_uri,
                }
            g_label = f'Group containing the {label.lower()} of {object_key}'
            g = vocab.UncertainMemberClosedGroup(ident=group_uri,
                                                 label=g_label)
            g.identified_by = model.Name(ident='', content=group_name)
            for person_data in people:
                person = get_crom_object(person_data)
                person.member_of = g
                data['_other_owners'].append(person_data)
            people = [add_crom_data(group_data, g)]
        return people, all_mods
Example #4
0
    def model_object_influence(self,
                               data,
                               people,
                               hmo,
                               prod_event,
                               attribution_modifiers,
                               attribution_group_types,
                               attribution_group_names,
                               all_uncertain=False):
        STYLE_OF = attribution_modifiers['style of']
        COPY_AFTER = attribution_modifiers['copy after']
        NON_ARTIST_MODS = COPY_AFTER | STYLE_OF
        GROUP_TYPES = set(attribution_group_types.values())
        GROUP_MODS = {
            k
            for k, v in attribution_group_types.items() if v in GROUP_TYPES
        }

        non_artist_assertions = people
        sales_record = get_crom_object(data['_record'])

        try:
            hmo_label = f'{hmo._label}'
        except AttributeError:
            hmo_label = 'object'

        # 2. Determine if the non-artist records represent a disjunction. If all such records have an "or" modifier, we will represent all the people as a Group, and classify it to indicate that one and only one of the named people was the actor. If there is at least one 'or' modifier, but not all of the records have 'or', then we model each such record with uncertainty.
        non_artist_all_mods = {
            m.lower().strip()
            for a in non_artist_assertions
            for m in a.get('attrib_mod_auth', '').split(';')
        } - {''}
        non_artist_group_flag = len(non_artist_assertions) and all(
            ['or' in a['modifiers'] for a in non_artist_assertions])
        non_artist_group = None
        if non_artist_group_flag:
            non_artist_mod = list(
                NON_ARTIST_MODS.intersection(non_artist_all_mods))[0]
            # The artist group URI is just the production event URI with a suffix. When URIs are
            # reconciled during prev/post sale rewriting, this will allow us to also reconcile
            # the URIs for the artist groups (of which there should only be one per production/object)
            group_uri = prod_event.id + '-NonArtistGroup'
            g_label = f'Group containing the {non_artist_mod} of {hmo_label}'
            non_artist_group = vocab.UncertainMemberClosedGroup(
                ident=group_uri, label=g_label)
            non_artist_group.identified_by = model.Name(ident='',
                                                        content=g_label)
            group_data = {
                'uri': group_uri,
                'role_label': 'uncertain influencer'
            }
            make_la_org = pipeline.linkedart.MakeLinkedArtOrganization()
            group_data = make_la_org(group_data)
            data['_organizations'].append(group_data)

        # 3. Model all the non-artist records as an appropriate property/relationship of the object or production event:
        for seq_no, a_data in enumerate(non_artist_assertions):
            a_data = self.model_person_or_group(data,
                                                a_data,
                                                attribution_group_types,
                                                attribution_group_names,
                                                seq_no=seq_no,
                                                role='NonArtist',
                                                sales_record=sales_record)
            artist_label = a_data.get('label')
            person = get_crom_object(a_data)

            mods = a_data['modifiers']
            attrib_assignment_classes = [model.AttributeAssignment]
            uncertain = all_uncertain
            if uncertain or 'or' in mods:
                if non_artist_group_flag:
                    person.member_of = non_artist_group
                else:
                    uncertain = True
                    attrib_assignment_classes.append(vocab.PossibleAssignment)

            if STYLE_OF.intersects(mods):
                attribute_assignment_id = self.helper.prepend_uri_key(
                    prod_event.id, f'ASSIGNMENT,NonArtist-{seq_no}')
                assignment = vocab.make_multitype_obj(
                    *attrib_assignment_classes,
                    ident=attribute_assignment_id,
                    label=f'In the style of {artist_label}')
                prod_event.attributed_by = assignment
                assignment.assigned_property = 'influenced_by'
                assignment.property_classified_as = vocab.instances['style of']
                assignment.assigned = person
            elif COPY_AFTER.intersects(mods):
                cls = type(hmo)
                # The original object URI is just the object URI with a suffix. When URIs are
                # reconciled during prev/post sale rewriting, this will allow us to also reconcile
                # the URIs for the original object (of which there should be at most one per object)
                original_id = hmo.id + '-Original'
                original_label = f'Original of {hmo_label}'
                original_hmo = cls(ident=original_id, label=original_label)

                # original title
                original_hmo.identified_by = vocab.ConstructedTitle(
                    ident='', content=f'[Work] by {artist_label}')

                # Similarly for the production of the original object.
                original_event_id = original_hmo.id + '-Production'
                original_event = model.Production(
                    ident=original_event_id,
                    label=f'Production event for {original_label}')
                original_hmo.produced_by = original_event

                original_subevent_id = original_event_id + f'-{seq_no}'  # TODO: fix for the case of post-sales merging
                original_subevent = model.Production(
                    ident=original_subevent_id,
                    label=f'Production sub-event for {artist_label}')
                original_event.part = original_subevent
                original_subevent.carried_out_by = person

                if uncertain:
                    assignment = vocab.make_multitype_obj(
                        *attrib_assignment_classes,
                        ident=attribute_assignment_id,
                        label=f'Possibly influenced by {person._label}')
                    prod_event.attributed_by = assignment
                    assignment.assigned_property = 'influenced_by'
                    assignment.assigned = original_hmo
                else:
                    prod_event.influenced_by = original_hmo
                data['_original_objects'].append(
                    add_crom_data(data={'uri': original_id},
                                  what=original_hmo))
            else:
                warnings.warn(
                    f'Unrecognized non-artist attribution modifers: {mods}')