hold targeter reify experiment
authorBill Erickson <berickxx@gmail.com>
Tue, 14 Jun 2016 19:27:40 +0000 (15:27 -0400)
committerBill Erickson <berickxx@gmail.com>
Tue, 14 Jun 2016 19:27:40 +0000 (15:27 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold_targeter.sql
Open-ILS/src/support-scripts/test-scripts/hold_targeter.pl

index d17f44b..b67dce0 100644 (file)
@@ -9,9 +9,6 @@ use OpenSRF::Utils qw/:datetime/;
 use OpenILS::Application::AppUtils;
 use OpenILS::Utils::CStoreEditor qw/:funcs/;
 
-# WIP notes:
-# avoid 'duplicate key value violates unique constraint "copy_once_per_hold"'
-
 our $U = "OpenILS::Application::AppUtils";
 our $dt_parser = DateTime::Format::ISO8601->new;
 
@@ -25,25 +22,125 @@ sub new {
     return bless($self, $class);
 }
 
+# Target and retarget holds.
+# By default, targets all holds that need targeting. 
+#
+# Optional parameters:
+#
+# hold => <id>
+#  -- ID of a specific hold to target.  
+#
+# retarget_interval => <interval string>
+#   -- If set, this overrides the value found in the 
+#      'circ.holds.retarget_interval' global flag.
+#
+# newest_first => 1
+#   -- If set, holds will be targeted in reverse order of create_time.
+#      This is useful for targeting / re-targeting newer holds first.
+#
+# target_all => 1 
+#  -- Forces targeting / re-targeting of all active holds.
+#     This is primarily usefulf or testing.  USE WITH CAUTION.
+#
+# Returns an array of hold targeter response objects, one response per hold.
+sub target {
+    my ($self, %args) = @_;
+
+    foreach (qw/hold retarget_interval newest_first target_all/) {
+        $self->{$_} = $args{$_} if exists $args{$_};
+    }
+
+    $self->init;
+
+    my @responses;
+    for my $hold_id ($self->find_holds_to_target) {
+        my $single = OpenILS::Utils::HoldTargeter::Single->new(parent => $self);
+        $single->target($hold_id);
+        push(@responses, $single->result);
+    }
+    return \@responses;
+}
+
+sub find_holds_to_target {
+    my $self = shift;
+
+    return ($self->{hold}) if $self->{hold};
+    
+    my $query = {
+        select => {ahr => ['id']},
+        from => 'ahr',
+        where => {
+            capture_time => undef,
+            fulfillment_time => undef,
+            cancel_time => undef,
+            frozen => 'f'
+        },
+        order_by => [
+            {class => 'ahr', field => 'selection_depth', direction => 'DESC'},
+            {class => 'ahr', field => 'request_time'},
+            {class => 'ahr', field => 'prev_check_time'}
+        ]
+    };
+
+    if (!$self->{target_all}) {
+        # Unless we're retargeting all holds, limit to holds that have no
+        # prev_check_time or those whose prev_check_time occurred 
+        # before the retarget interval.
+
+        my $date = DateTime->now->subtract(
+            seconds => $self->{retarget_interval});
+
+        $query->{where}->{'-or'} = [
+            {prev_check_time => undef},
+            {prev_check_time => {'<=' => $date->strftime('%F %T%z')}}
+        ];
+    }
+
+    # Newest-first sorting cares only about hold create_time.
+    $query->{order_by} = 
+        [{class => 'ahr', field => 'request_time', direction => 'DESC'}]
+        if $self->{newest_first};
+
+    my $holds = $self->editor->json_query($query);
+
+    return map {$_->{id}} @$holds;
+}
+
 sub editor {
     my $self = shift;
     return $self->{editor};
 }
 
+# Load startup data required by all targeter actions.
 sub init {
     my $self = shift;
+    my $e = $self->editor;
 
     my $closed_orgs_query = {
         close_start => {'<=', 'now'},
         close_end => {'>=', 'now'}
     };
 
-    if ($self->{retarget_interval}) {
+    if (!$self->{target_all}) {
 
-        # Convert the interval to seconds for current and future use.
-        $self->{retarget_interval} = 
-            interval_to_seconds($self->{retarget_interval});
+        # See if the caller provided an interval
+        my $interval = $self->{retarget_interval};
 
+        if (!$interval) {
+            # See if we have a global flag value for the interval
+
+            $interval = $e->search_config_global_flag({
+                name => 'circ.holds.retarget_interval',
+                enabled => 't'
+            })->[0];
+    
+            # If no flag is present, default to a 24-hour retarget interval.
+            $interval = $interval ? $interval->value : '24h';
+        }
+
+        # Convert the interval to seconds for current and future use.
+        $self->{retarget_interval} = interval_to_seconds($interval);
+       
         # An org unit is considered closed for retargeting purposes
         # if it's closed both now and at the next re-target date.
 
@@ -75,66 +172,13 @@ sub get_ou_setting {
 
     $c->{$org_id} = {} unless $c->{$org_id};
 
-    if (not exists $c->{$org_id}->{$setting}) {
-        my $r = $U->ou_ancestor_setting($org_id, $setting, $self->{editor});
-        $c->{$org_id}->{$setting} = $r ? $r->{value} : undef;
-    }
+    $c->{$org_id}->{$setting} = 
+        $U->ou_ancestor_setting_value($org_id, $setting, $self->{editor})
+        unless exists $c->{$org_id}->{$setting};
 
     return $c->{$org_id}->{$setting};
 }
 
-sub target_hold {
-    my ($self, $hold_id) = @_;
-    my $targeter = OpenILS::Utils::HoldTargeter::Single->new(parent => $self);
-    $targeter->target($hold_id);
-    return $targeter->result;
-}
-
-# Targets all holds whose prev_check_time is older than the provide interval.
-# Also targets all holds that have never been targeted.
-sub target_all {
-    my $self = shift;
-    my @responses;
-    for my $hold_id ($self->find_holds_to_target) {
-        my $resp = $self->target_hold($hold_id);
-        push(@responses, $resp);
-    }
-    return \@responses;
-}
-
-sub find_holds_to_target {
-    my $self = shift;
-
-    my $date = DateTime->now->subtract(seconds => $self->{retarget_interval});
-
-    my $rtime_sort = $self->{newest_first} ? 'DESC' : 'ASC';
-
-    my $query = {
-        select => {ahr => ['id']},
-        from => 'ahr',
-        where => {
-            capture_time => undef,
-            fulfillment_time => undef,
-            frozen => 'f',
-            cancel_time => undef,
-            '-or' => [
-                {prev_check_time => undef},
-                {prev_check_time => {'<=' => $date->strftime('%F %T%z')}}
-            ]
-        },
-        order_by => [
-            {class => 'ahr', field => 'selection_depth', direction => 'DESC'},
-            {class => 'ahr', field => 'request_time', direction => $rtime_sort},
-            {class => 'ahr', field => 'prev_check_time'}
-        ]
-    };
-
-    my $holds = $self->editor->json_query($query);
-
-    return map {$_->{id}} @$holds;
-}
-
-
 # -----------------------------------------------------------------------
 # Knows how to target a single hold.
 # -----------------------------------------------------------------------
@@ -218,17 +262,16 @@ sub result {
     };
 }
 
-# List of potential copies in the form of slim hashes.
-# This is a working list of copies that evolves as copies
-# are filtered for various reasons or deemed non-targetable.
+# List of potential copies in the form of slim hashes.  This list
+# evolves as copies are filtered as they are deemed non-targetable.
 sub copies {
     my ($self, $copies) = @_;
     $self->{copies} = $copies if $copies;
     return $self->{copies};
 }
 
-# Final set of targetable ("good") copies that may be eligible for 
-# recall processing.
+# Final set of potential copies, including those that may not be 
+# currently targetable, that may be eligible for recall processing.
 sub recall_copies {
     my ($self, $recall_copies) = @_;
     $self->{recall_copies} = $recall_copies if $recall_copies;
index 914ad40..ba584f0 100644 (file)
@@ -1,5 +1,3 @@
-
-
 BEGIN;
 
 CREATE OR REPLACE FUNCTION
@@ -9,5 +7,19 @@ CREATE OR REPLACE FUNCTION
     INSERT INTO action.hold_copy_map (hold, target_copy) SELECT $1, UNNEST($2);
 $$ LANGUAGE SQL;
 
+-- DATA
+
+INSERT INTO config.global_flag (name, label, value, enabled) VALUES (
+    'circ.holds.retarget_interval',
+    oils_i18n_gettext(
+        'circ.holds.retarget_interval',
+        'Holds Retarget Interval', 
+        'cgf',
+        'label'
+    ),
+    '24h',
+    TRUE
+);
+
 COMMIT;
 
index 75a7fca..e4b8dab 100755 (executable)
@@ -13,13 +13,14 @@ use Data::Dumper;
 my $config = shift; # path to opensrf_core.xml
 osrf_connect($config); # connect to jabber
 
-my $targeter = OpenILS::Utils::HoldTargeter->new(
-    retarget_interval => '12h',
-    newest_first => 1
+my $targeter = OpenILS::Utils::HoldTargeter->new;
+
+my $responses = $targeter->target(
+    #hold => 80,
+    #retarget_interval => '12h',
+    #newest_first => 1,
+    target_all => 1
 );
 
-$targeter->init;
-my $responses = $targeter->target_all;
-#my $responses = $targeter->target_hold(80);
 print Dumper($responses);
 print "Processed " . scalar(@$responses) . " holds\n";