LP#1549505: Teach QP and its caller stack how to use badges
authorMike Rylander <mrylander@gmail.com>
Fri, 22 Jan 2016 22:13:25 +0000 (17:13 -0500)
committerKathy Lussier <klussier@masslnc.org>
Fri, 29 Jul 2016 20:56:10 +0000 (16:56 -0400)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/metabib.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
Open-ILS/src/sql/Pg/upgrade/YYYY.function.qp_search.sql [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/record/summary.tt2
Open-ILS/src/templates/opac/parts/result/table.tt2

index a337619..5c07790 100644 (file)
@@ -1364,9 +1364,9 @@ sub staged_search {
 
             # Create backwards-compatible result structures
             if($IAmMetabib) {
-                $results = [map {[$_->{id}, $_->{rel}, $_->{record}]} @$results];
+                $results = [map {[$_->{id}, $_->{badges}, $_->{popularity}, $_->{rel}, $_->{record}]} @$results];
             } else {
-                $results = [map {[$_->{id}]} @$results];
+                $results = [map {[$_->{id}, $_->{badges}, $_->{popularity}]} @$results];
             }
 
             push @$new_ids, grep {defined($_)} map {$_->[0]} @$results;
index ea55769..f06d097 100644 (file)
@@ -651,6 +651,8 @@ __PACKAGE__->add_search_filter( 'statuses' );
 __PACKAGE__->add_search_filter( 'locations' );
 __PACKAGE__->add_search_filter( 'location_groups' );
 __PACKAGE__->add_search_filter( 'bib_source' );
+__PACKAGE__->add_search_filter( 'badge_orgs' );
+__PACKAGE__->add_search_filter( 'badges' );
 __PACKAGE__->add_search_filter( 'site' );
 __PACKAGE__->add_search_filter( 'pref_ou' );
 __PACKAGE__->add_search_filter( 'lasso' );
@@ -808,7 +810,6 @@ sub toSQL {
         $rel = "($rel * COALESCE( NULLIF( FIRST(mrv.vlist \@> ARRAY[lang_with.id]), FALSE )::INT * $plw, 1))";
         $$flat_plan{uses_mrv} = 1;
     }
-    $rel = "1.0/($rel)::NUMERIC";
 
     my $mrv_join = '';
     if ($$flat_plan{uses_mrv}) {
@@ -831,23 +832,66 @@ sub toSQL {
         $bre_join = 'INNER JOIN biblio.record_entry bre ON m.source = bre.id';
     }
     
-    my $rank = $rel;
-
     my $desc = 'ASC';
     $desc = 'DESC' if ($self->find_modifier('descending'));
 
     my $nullpos = 'NULLS LAST';
     $nullpos = 'NULLS FIRST' if ($self->find_modifier('nullsfirst'));
 
+    # Do we have a badges() filter?
+    my $badges = '';
+    my ($badge_filter) = $self->find_filter('badges');
+    if ($badge_filter && @{$badge_filter->args}) {
+        $badges = join (',', grep /^\d+$/, @{$badge_filter->args});
+    }
+
+    # Do we have a badge_orgs() filter? (used for calculating popularity)
+    my $borgs = '';
+    my ($bo_filter) = $self->find_filter('badge_orgs');
+    if ($bo_filter && @{$bo_filter->args}) {
+        $borgs = join (',', grep /^\d+$/, @{$bo_filter->args});
+    }
+
+    # Build the badge-ish WITH query
+    my $pop_with = <<'    WITH';
+        pop_with AS (
+            SELECT  record,
+                    ARRAY_AGG(badge) AS badges,
+                    SUM(s.score::NUMERIC*b.weight::NUMERIC)/SUM(b.weight::NUMERIC) AS total_score
+              FROM  rating.record_badge_score s
+                    JOIN rating.badge b ON (
+                        b.id = s.badge
+    WITH
+
+    $pop_with .= " AND b.id = ANY ('{$badges}')" if ($badges);
+    $pop_with .= " AND b.scope = ANY ('{$borgs}')" if ($borgs);
+    $pop_with .= ') GROUP BY 1)'; 
+
+    my $pop_join = $badges ? # inner join if we are restricting via badges()
+        'INNER JOIN pop_with ON ( m.source = pop_with.record )' : 
+        'LEFT JOIN pop_with ON ( m.source = pop_with.record )';
+
+    $$flat_plan{with} .= ',' if $$flat_plan{with};
+    $$flat_plan{with} .= $pop_with;
+
+
+    my $rank;
+    my $pop_extra_sort = '';
     if (grep {$_ eq $sort_filter} @{$self->QueryParser->dynamic_sorters}) {
         $rank = "FIRST((SELECT value FROM metabib.record_sorter rbr WHERE rbr.source = m.source and attr = '$sort_filter'))"
     } elsif ($sort_filter eq 'create_date') {
         $rank = "FIRST((SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source))";
     } elsif ($sort_filter eq 'edit_date') {
         $rank = "FIRST((SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source))";
+    } elsif ($sort_filter eq 'poprel') {
+        $rank = '1.0/((' . $rel . ') * (1.0 + AVG(COALESCE(pop_with.total_score::NUMERIC,0.0)) / 5.0))::NUMERIC';
+    } elsif ($sort_filter =~ /^pop/) {
+        $rank = '1.0/(AVG(COALESCE(pop_with.total_score::NUMERIC,0.0)) + 5.0)::NUMERIC';
+        my $pop_desc = $desc eq 'ASC' ? 'DESC' : 'ASC';
+        $pop_extra_sort = "3 $pop_desc $nullpos,";
     } else {
         # default to rel ranking
-        $rank = $rel;
+        $rank = "1.0/($rel)::NUMERIC";
     }
 
     my $key = 'm.source';
@@ -877,18 +921,21 @@ SELECT  $key AS id,
         $agg_records,
         $rel AS rel,
         $rank AS rank, 
-        FIRST(pubdate_t.value) AS tie_break
+        FIRST(pubdate_t.value) AS tie_break,
+        STRING_AGG(ARRAY_TO_STRING(pop_with.badges,','),',') AS badges,
+        AVG(COALESCE(pop_with.total_score::NUMERIC,0.0))::NUMERIC(2,1) AS popularity
   FROM  metabib.metarecord_source_map m
         $$flat_plan{from}
-        $pubdate_join
         $mra_join
         $mrv_join
         $bre_join
+        $pop_join
+        $pubdate_join
         $lang_join
   WHERE 1=1
         $flat_where
   GROUP BY 1
-  ORDER BY 4 $desc $nullpos, 5 DESC $nullpos, 3 DESC
+  ORDER BY 4 $desc $nullpos, $pop_extra_sort 5 DESC $nullpos, 3 DESC
   LIMIT $core_limit
 SQL
 
index fad6ef3..aebd348 100644 (file)
@@ -3067,6 +3067,8 @@ sub query_parser_fts {
     delete $$summary_row{id};
     delete $$summary_row{rel};
     delete $$summary_row{record};
+    delete $$summary_row{badges};
+    delete $$summary_row{popularity};
 
     if (defined($simple_plan)) {
         $$summary_row{complex_query} = $simple_plan ? 0 : 1;
@@ -3098,6 +3100,8 @@ __PACKAGE__->register_method(
     cachable    => 1,
 );
 
+my $top_org;
+
 sub query_parser_fts_wrapper {
     my $self = shift;
     my $client = shift;
@@ -3114,6 +3118,8 @@ sub query_parser_fts_wrapper {
         die "No search arguments were passed to ".$self->api_name;
     }
 
+    $top_org ||= actor::org_unit->search( { parent_ou => undef } )->next;
+
     $log->debug("Constructing QueryParser query from staged search hash ...", DEBUG);
     my $base_query = '';
     for my $sclass ( keys %{$args{searches}} ) {
@@ -3144,7 +3150,59 @@ sub query_parser_fts_wrapper {
         if ($args{preferred_language_weight} and !$base_plan->parse_tree->find_filter('preferred_language_weight') and !$base_plan->parse_tree->find_filter('preferred_language_multiplier'));
 
 
+    my $borgs = undef;
+    if (!$base_plan->parse_tree->find_filter('badge_orgs')) {
+        # supply a suitable badge_orgs filter unless user has
+        # explicitly supplied one
+        my $site = undef;
+
+        my @lg_id_list = @{$args{location_groups}} if (ref $args{location_groups});
+
+        my ($lg_filter) = $base_plan->parse_tree->find_filter('location_groups');
+        @lg_id_list = @{$lg_filter->args} if ($lg_filter && @{$lg_filter->args});
+
+        if (@lg_id_list) {
+            my @borg_list;
+            for my $lg ( grep { /^\d+$/ } @lg_id_list ) {
+                my $lg_obj = asset::copy_location_group->retrieve($lg);
+                next unless $lg_obj;
+    
+                push(@borg_list, ''.$lg_obj->owner);
+            }
+            $borgs = join(',', @borg_list) if @borg_list;
+        }
+    
+        if (!$borgs) {
+            my ($site_filter) = $base_plan->parse_tree->find_filter('site');
+            if ($site_filter && @{$site_filter->args}) {
+                $site = $top_org if ($site_filter->args->[0] eq '-');
+                $site = $top_org if ($site_filter->args->[0] eq $top_org->shortname);
+                $site = actor::org_unit->search( { shortname => $site_filter->args->[0] })->next unless ($site);
+            } elsif ($args{org_unit}) {
+                $site = $top_org if ($args{org_unit} eq '-');
+                $site = $top_org if ($args{org_unit} eq $top_org->shortname);
+                $site = actor::org_unit->search( { shortname => $args{org_unit} })->next unless ($site);
+            } else {
+                $site = $top_org;
+            }
+
+            if ($site) {
+                $borgs = OpenSRF::AppSession->create( 'open-ils.cstore' )->request(
+                    'open-ils.cstore.json_query.atomic',
+                    { from => [ 'actor.org_unit_ancestors', $site->id ] }
+                )->gather(1);
+
+                if (ref $borgs && @$borgs) {
+                    $borgs = join(',', map { $_->{'id'} } @$borgs);
+                } else {
+                    $borgs = undef;
+                }
+            }
+        }
+    }
+
     $query = "estimation_strategy($args{estimation_strategy}) $query" if ($args{estimation_strategy});
+    $query = "badge_orgs($borgs) $query" if ($borgs);
     $query = "site($args{org_unit}) $query" if ($args{org_unit});
     $query = "depth($args{depth}) $query" if (defined($args{depth}));
     $query = "sort($args{sort}) $query" if ($args{sort});
@@ -3173,7 +3231,7 @@ sub query_parser_fts_wrapper {
         $args{item_form} = [ split '', $f ];
     }
 
-    for my $filter ( qw/locations location_groups statuses between audience language lit_form item_form item_type bib_level vr_format/ ) {
+    for my $filter ( qw/locations location_groups statuses between audience language lit_form item_form item_type bib_level vr_format badges/ ) {
         if (my $s = $args{$filter}) {
             $s = [$s] if (!ref($s));
 
index 5f3d51c..0253478 100644 (file)
@@ -66,6 +66,21 @@ sub load_record {
         $self->mk_copy_query($rec_id, $org, $copy_depth, $copy_limit, $copy_offset, $pref_ou)
     );
 
+    if ($self->cgi->param('badges')) {
+        my $badges = $self->cgi->param('badges');
+        $badges = $badges ? [split(',', $badges)] : [];
+        $badges = [grep { /^\d+$/ }, @$badges];
+        if (@$badges) {
+            $self->ctx->{badge_scores} = $cstore->request(
+                'open-ils.cstore.direct.rating.record_badge_score.search.atomic',
+                { record => $rec_id, badge => $badges },
+                { flesh => 1, flesh_fields => { rrbs => ['badge'] } }
+            )->gather(1);
+        }
+    } else {
+        $self->ctx->{badge_scores} = [];
+    }
+
     # find foreign copy data
     my $peer_rec = $U->simplereq(
         'open-ils.search',
index 51bdf23..7ee9cfa 100644 (file)
@@ -522,13 +522,15 @@ sub load_rresults {
         }
     }
 
-    if ($tag_circs) {
-        for my $rec (@{$ctx->{records}}) {
-            my ($res_rec) = grep { $_->[0] == $rec->{$id_key} } @{$results->{ids}};
-            # index 1 in the per-record result array is a boolean which
+    for my $rec (@{$ctx->{records}}) {
+        my ($res_rec) = grep { $_->[0] == $rec->{$id_key} } @{$results->{ids}};
+        $rec->{badges} = [split(',', $res_rec->[1])] if $res_rec->[1];
+        $rec->{popularity} = $res_rec->[2];
+        if ($tag_circs) {
+            # index 3 (5 for MR) in the per-record result array is a boolean which
             # indicates whether the record in question is in the users
             # accessible circ history list
-            my $index = $is_meta ? 3 : 1;
+            my $index = $is_meta ? 5 : 3;
             $rec->{user_circulated} = 1 if $res_rec->[$index];
         }
     }
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.function.qp_search.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.function.qp_search.sql
new file mode 100644 (file)
index 0000000..9dbbfe5
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2016 Equinox Software, Inc.
+ * Mike Rylander <miker@esilibrary.com> 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+
+BEGIN;
+
+ALTER TYPE search.search_result ADD ATTRIBUTE badges TEXT, ADD ATTRIBUTE popularity NUMERIC;
+
+CREATE OR REPLACE FUNCTION search.query_parser_fts (
+
+    param_search_ou INT,
+    param_depth     INT,
+    param_query     TEXT,
+    param_statuses  INT[],
+    param_locations INT[],
+    param_offset    INT,
+    param_check     INT,
+    param_limit     INT,
+    metarecord      BOOL,
+    staff           BOOL,
+    deleted_search  BOOL,
+    param_pref_ou   INT DEFAULT NULL
+) RETURNS SETOF search.search_result AS $func$
+DECLARE
+
+    current_res         search.search_result%ROWTYPE;
+    search_org_list     INT[];
+    luri_org_list       INT[];
+    tmp_int_list        INT[];
+
+    check_limit         INT;
+    core_limit          INT;
+    core_offset         INT;
+    tmp_int             INT;
+
+    core_result         RECORD;
+    core_cursor         REFCURSOR;
+    core_rel_query      TEXT;
+
+    total_count         INT := 0;
+    check_count         INT := 0;
+    deleted_count       INT := 0;
+    visible_count       INT := 0;
+    excluded_count      INT := 0;
+
+    luri_as_copy        BOOL;
+BEGIN
+
+    check_limit := COALESCE( param_check, 1000 );
+    core_limit  := COALESCE( param_limit, 25000 );
+    core_offset := COALESCE( param_offset, 0 );
+
+    SELECT COALESCE( enabled, FALSE ) INTO luri_as_copy FROM config.global_flag WHERE name = 'opac.located_uri.act_as_copy';
+
+    -- core_skip_chk := COALESCE( param_skip_chk, 1 );
+
+    IF param_search_ou > 0 THEN
+        IF param_depth IS NOT NULL THEN
+            SELECT ARRAY_AGG(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
+        ELSE
+            SELECT ARRAY_AGG(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
+        END IF;
+
+        IF luri_as_copy THEN
+            SELECT ARRAY_AGG(distinct id) INTO luri_org_list FROM actor.org_unit_full_path( param_search_ou );
+        ELSE
+            SELECT ARRAY_AGG(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
+        END IF;
+
+    ELSIF param_search_ou < 0 THEN
+        SELECT ARRAY_AGG(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
+
+        FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
+
+            IF luri_as_copy THEN
+                SELECT ARRAY_AGG(distinct id) INTO tmp_int_list FROM actor.org_unit_full_path( tmp_int );
+            ELSE
+                SELECT ARRAY_AGG(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
+            END IF;
+
+            luri_org_list := luri_org_list || tmp_int_list;
+        END LOOP;
+
+        SELECT ARRAY_AGG(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
+
+    ELSIF param_search_ou = 0 THEN
+        -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
+    END IF;
+
+    IF param_pref_ou IS NOT NULL THEN
+            IF luri_as_copy THEN
+                SELECT ARRAY_AGG(distinct id) INTO tmp_int_list FROM actor.org_unit_full_path( param_pref_ou );
+            ELSE
+                SELECT ARRAY_AGG(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( param_pref_ou );
+            END IF;
+
+        luri_org_list := luri_org_list || tmp_int_list;
+    END IF;
+
+    OPEN core_cursor FOR EXECUTE param_query;
+
+    LOOP
+
+        FETCH core_cursor INTO core_result;
+        EXIT WHEN NOT FOUND;
+        EXIT WHEN total_count >= core_limit;
+
+        total_count := total_count + 1;
+
+        CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
+
+        check_count := check_count + 1;
+
+        IF NOT deleted_search THEN
+
+            PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
+            IF NOT FOUND THEN
+                -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
+                deleted_count := deleted_count + 1;
+                CONTINUE;
+            END IF;
+
+            PERFORM 1
+              FROM  biblio.record_entry b
+                    JOIN config.bib_source s ON (b.source = s.id)
+              WHERE s.transcendant
+                    AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
+
+            IF FOUND THEN
+                -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
+                visible_count := visible_count + 1;
+
+                current_res.id = core_result.id;
+                current_res.rel = core_result.rel;
+                current_res.badges = core_result.badges;
+                current_res.popularity = core_result.popularity;
+
+                tmp_int := 1;
+                IF metarecord THEN
+                    SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
+                END IF;
+
+                IF tmp_int = 1 THEN
+                    current_res.record = core_result.records[1];
+                ELSE
+                    current_res.record = NULL;
+                END IF;
+
+                RETURN NEXT current_res;
+
+                CONTINUE;
+            END IF;
+
+            PERFORM 1
+              FROM  asset.call_number cn
+                    JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
+                    JOIN asset.uri uri ON (map.uri = uri.id)
+              WHERE NOT cn.deleted
+                    AND cn.label = '##URI##'
+                    AND uri.active
+                    AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
+                    AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                    AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
+              LIMIT 1;
+
+            IF FOUND THEN
+                -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
+                visible_count := visible_count + 1;
+
+                current_res.id = core_result.id;
+                current_res.rel = core_result.rel;
+                current_res.badges = core_result.badges;
+                current_res.popularity = core_result.popularity;
+
+                tmp_int := 1;
+                IF metarecord THEN
+                    SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
+                END IF;
+
+                IF tmp_int = 1 THEN
+                    current_res.record = core_result.records[1];
+                ELSE
+                    current_res.record = NULL;
+                END IF;
+
+                RETURN NEXT current_res;
+
+                CONTINUE;
+            END IF;
+
+            IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
+
+                PERFORM 1
+                  FROM  asset.call_number cn
+                        JOIN asset.copy cp ON (cp.call_number = cn.id)
+                  WHERE NOT cn.deleted
+                        AND NOT cp.deleted
+                        AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
+                        AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                        AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+                    PERFORM 1
+                      FROM  biblio.peer_bib_copy_map pr
+                            JOIN asset.copy cp ON (cp.id = pr.target_copy)
+                      WHERE NOT cp.deleted
+                            AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
+                            AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                            AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                      LIMIT 1;
+
+                    IF NOT FOUND THEN
+                    -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
+                        excluded_count := excluded_count + 1;
+                        CONTINUE;
+                    END IF;
+                END IF;
+
+            END IF;
+
+            IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
+
+                PERFORM 1
+                  FROM  asset.call_number cn
+                        JOIN asset.copy cp ON (cp.call_number = cn.id)
+                  WHERE NOT cn.deleted
+                        AND NOT cp.deleted
+                        AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
+                        AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                        AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+                    PERFORM 1
+                      FROM  biblio.peer_bib_copy_map pr
+                            JOIN asset.copy cp ON (cp.id = pr.target_copy)
+                      WHERE NOT cp.deleted
+                            AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
+                            AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                            AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                      LIMIT 1;
+
+                    IF NOT FOUND THEN
+                        -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
+                        excluded_count := excluded_count + 1;
+                        CONTINUE;
+                    END IF;
+                END IF;
+
+            END IF;
+
+            IF staff IS NULL OR NOT staff THEN
+
+                PERFORM 1
+                  FROM  asset.opac_visible_copies
+                  WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                        AND record IN ( SELECT * FROM unnest( core_result.records ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+                    PERFORM 1
+                      FROM  biblio.peer_bib_copy_map pr
+                            JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
+                      WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                            AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                      LIMIT 1;
+
+                    IF NOT FOUND THEN
+
+                        -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
+                        excluded_count := excluded_count + 1;
+                        CONTINUE;
+                    END IF;
+                END IF;
+
+            ELSE
+
+                PERFORM 1
+                  FROM  asset.call_number cn
+                        JOIN asset.copy cp ON (cp.call_number = cn.id)
+                  WHERE NOT cn.deleted
+                        AND NOT cp.deleted
+                        AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                        AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                  LIMIT 1;
+
+                IF NOT FOUND THEN
+
+                    PERFORM 1
+                      FROM  biblio.peer_bib_copy_map pr
+                            JOIN asset.copy cp ON (cp.id = pr.target_copy)
+                      WHERE NOT cp.deleted
+                            AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
+                            AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
+                      LIMIT 1;
+
+                    IF NOT FOUND THEN
+
+                        PERFORM 1
+                          FROM  asset.call_number cn
+                                JOIN asset.copy cp ON (cp.call_number = cn.id)
+                          WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                                AND NOT cp.deleted
+                          LIMIT 1;
+
+                        IF NOT FOUND THEN
+                            -- Recheck Located URI visibility in the case of no "foreign" copies
+                            PERFORM 1
+                              FROM  asset.call_number cn
+                                    JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
+                                    JOIN asset.uri uri ON (map.uri = uri.id)
+                              WHERE NOT cn.deleted
+                                    AND cn.label = '##URI##'
+                                    AND uri.active
+                                    AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
+                                    AND cn.owning_lib NOT IN ( SELECT * FROM unnest( luri_org_list ) )
+                              LIMIT 1;
+
+                            IF FOUND THEN
+                                -- RAISE NOTICE ' % were excluded for foreign located URIs... ', core_result.records;
+                                excluded_count := excluded_count + 1;
+                                CONTINUE;
+                            END IF;
+                        ELSE
+                            -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
+                            excluded_count := excluded_count + 1;
+                            CONTINUE;
+                        END IF;
+                    END IF;
+
+                END IF;
+
+            END IF;
+
+        END IF;
+
+        visible_count := visible_count + 1;
+
+        current_res.id = core_result.id;
+        current_res.rel = core_result.rel;
+        current_res.badges = core_result.badges;
+        current_res.popularity = core_result.popularity;
+
+        tmp_int := 1;
+        IF metarecord THEN
+            SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
+        END IF;
+
+        IF tmp_int = 1 THEN
+            current_res.record = core_result.records[1];
+        ELSE
+            current_res.record = NULL;
+        END IF;
+
+        RETURN NEXT current_res;
+
+        IF visible_count % 1000 = 0 THEN
+            -- RAISE NOTICE ' % visible so far ... ', visible_count;
+        END IF;
+
+    END LOOP;
+
+    current_res.id = NULL;
+    current_res.rel = NULL;
+    current_res.record = NULL;
+    current_res.badges = NULL;
+    current_res.popularity = NULL;
+    current_res.total = total_count;
+    current_res.checked = check_count;
+    current_res.deleted = deleted_count;
+    current_res.visible = visible_count;
+    current_res.excluded = excluded_count;
+
+    CLOSE core_cursor;
+
+    RETURN NEXT current_res;
+
+END;
+$func$ LANGUAGE PLPGSQL;
+COMMIT;
+
index 76f45ff..b2bd5e5 100644 (file)
@@ -314,6 +314,16 @@ IF num_uris > 0;
         </span>
     </li>
     [%- END %]
+    [%- IF (ctx.badge_scores.size > 0) %]
+    <li id='rdetail_badges'>
+        <strong class='rdetail_label'>[% l("Badges:") %]</strong>
+        <ul>
+          [% FOR bscore IN ctx.badge_scores; %]
+            <li><strong>[% bscore.badge.name | html %]</strong>: [% bscore.score %] / 5.0</li>
+        [%- END -%]
+        </span>
+    </li>
+    [%- END %]
 </ul>
 
 [%- INCLUDE "opac/parts/record/contents.tt2" %]
index 960d8c6..02fe684 100644 (file)
                                 ELSE;
                                     # for MR, bre_id refers to the master and in
                                     # this case, only, record
-                                    record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id);
+                                    record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id, { badges => rec.badges.join(',') });
                                 END;
                                 hold_type = 'M';
                             ELSE;
-                                record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id);
+                                record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id, { badges => rec.badges.join(',') });
                                 hold_type = 'T';
                             END;
                     -%]
@@ -155,6 +155,11 @@ END;
                                                         END
                                                     -%]
                                                     </div>
+                                                    [% IF rec.popularity > 0.0 %]
+                                                      <div>
+                                                        <span><strong>[% l('Popularity:') %]</strong> [% rec.popularity %] / 5.0</span>
+                                                      </div>
+                                                    [% END %]
                                                     <table 
                                                        role="presentation"
                                                        title="[% l('Record Holdings Summary') %]"