diff --git a/Osstest/HistoryReport.pm b/Osstest/HistoryReport.pm
index c5e7e226..a0565b6a 100644
--- a/Osstest/HistoryReport.pm
+++ b/Osstest/HistoryReport.pm
@@ -40,4 +40,119 @@ BEGIN {
 
 use POSIX;
 
+our @cache_row_key_cols;
+
+our %cache;
+
+our %q_count;
+our %q_misses;
+
+our $rows_previous = 0;
+our $rows_today = 0;
+our $rows_hit = 0;
+
+sub cache_set_key_cols { @cache_row_key_cols = @_; }
+
+sub cache_row_key ($) {
+    my ($jr) = @_;
+    return join $; , map { $jr->{$_} } @cache_row_key_cols;
+}
+
+sub cacheable_fn ($$$) {
+    my ($jr, $cachekey, $fn) = @_;
+    $cachekey = '%'.$cachekey;
+    my $cached = $jr->{$cachekey};
+    $q_count{$cachekey}++;
+    if (!$cached) {
+	$q_misses{$cachekey}++;
+	$cached = $fn->();
+	$jr->{$cachekey} = $cached;
+    }
+    return $cached;
+}
+
+sub cacheable_query ($$$) {
+    my ($q, $jr, $cachekey) = @_;
+    cacheable_fn($jr, $cachekey, sub {
+	foreach my $k (keys %{ $q->{ParamTypes} }) {
+	    $k =~ m/^:/ or die "$k ?";
+	    $q->bind_param($k, $jr->{$'} // die "$k ?");
+	}
+	$q->execute();
+	return $q->fetchrow_hashref();
+    });
+}
+
+sub cache_read_previous ($) {
+    my ($html_file) = @_;
+    if (!open H, $html_file) {
+        return if $!==ENOENT;
+        die "failed to open $html_file: $!";
+    }
+    %cache = ();
+    for (;;) {
+	$rows_previous++;
+        $_ = <H> // last;
+        next unless m{^\<\!-- osstest-report-reuseable (.*)--\>$};
+	my $jr = {};
+	my $ch = $jr;
+	foreach (split / /, $1) {
+	    if (m{^\w+$}) {
+		$ch = { };
+		$jr->{'%'.$&} = $ch;
+		next;
+	    }
+	    s{^(\w+)=}{} or die;
+	    my $k = $1;
+	    s{\%([0-9a-f]{2})}{ chr hex $1 }ge;
+	    $ch->{$k} = $_;
+	}
+	$cache{cache_row_key($jr)} = $jr;
+    }
+    close H;
+}
+
+sub cache_row_lookup_prep ($) {
+    my ($jrr) = @_;
+
+    $rows_today++;
+    my $cacherow = $cache{cache_row_key($$jrr)};
+    if ($cacherow) {
+	$$jrr = $cacherow;
+	$rows_hit++;
+    }
+}
+
+sub cache_write_entry ($$) {
+    my ($fh, $jr) = @_;
+    print $fh "<!-- osstest-report-reuseable";
+    my $whash = sub {
+	my ($h) = @_;
+	foreach my $k (sort keys %$h) {
+	    next if $k =~ m/^\%/;
+	    $_ = $h->{$k};
+	    s{[^-+=/~:;_.,\w]}{ sprintf "%%%02x", ord $& }ge;
+	    printf $fh " %s=%s", $k, $_;
+	}
+    };
+    $whash->($jr);
+    foreach my $hk (sort keys %$jr) {
+	next unless $hk =~ m/^\%/;
+	print $fh " $'";
+	$whash->($jr->{$hk});
+    }
+    print $fh " -->\n";
+}
+
+sub cache_report_stats ($) {
+    my ($what) = @_;
+    print ::DEBUG "CACHE $what read=$rows_previous hits $rows_hit/$rows_today";
+    for my $cachekey (sort keys %q_count) {
+	my $total = $q_count{$cachekey};
+	my $hits = $total - ($q_misses{$cachekey} // 0);
+	print ::DEBUG " $cachekey=$hits/$total";
+    }
+    print ::DEBUG "\n";
+}
+
 1;
diff --git a/sg-report-host-history b/sg-report-host-history
index 05a2dfe0..a195bb21 100755
--- a/sg-report-host-history
+++ b/sg-report-host-history
@@ -77,121 +77,6 @@ our $restrictflight_cond = restrictflight_cond();
 our $flightcond;
 our $minflight;
 
-our @cache_row_key_cols;
-
-our %cache;
-
-our %q_count;
-our %q_misses;
-
-our $rows_previous = 0;
-our $rows_today = 0;
-our $rows_hit = 0;
-
-sub cache_set_key_cols { @cache_row_key_cols = @_; }
-
-sub cache_row_key ($) {
-    my ($jr) = @_;
-    return join $; , map { $jr->{$_} } @cache_row_key_cols;
-}
-
-sub cacheable_fn ($$$) {
-    my ($jr, $cachekey, $fn) = @_;
-    $cachekey = '%'.$cachekey;
-    my $cached = $jr->{$cachekey};
-    $q_count{$cachekey}++;
-    if (!$cached) {
-	$q_misses{$cachekey}++;
-	$cached = $fn->();
-	$jr->{$cachekey} = $cached;
-    }
-    return $cached;
-}
-
-sub cacheable_query ($$$) {
-    my ($q, $jr, $cachekey) = @_;
-    cacheable_fn($jr, $cachekey, sub {
-	foreach my $k (keys %{ $q->{ParamTypes} }) {
-	    $k =~ m/^:/ or die "$k ?";
-	    $q->bind_param($k, $jr->{$'} // die "$k ?");
-	}
-	$q->execute();
-	return $q->fetchrow_hashref();
-    });
-}
-
-sub cache_read_previous ($) {
-    my ($html_file) = @_;
-    if (!open H, $html_file) {
-        return if $!==ENOENT;
-        die "failed to open $html_file: $!";
-    }
-    %cache = ();
-    for (;;) {
-	$rows_previous++;
-        $_ = <H> // last;
-        next unless m{^\<\!-- osstest-report-reuseable (.*)--\>$};
-	my $jr = {};
-	my $ch = $jr;
-	foreach (split / /, $1) {
-	    if (m{^\w+$}) {
-		$ch = { };
-		$jr->{'%'.$&} = $ch;
-		next;
-	    }
-	    s{^(\w+)=}{} or die;
-	    my $k = $1;
-	    s{\%([0-9a-f]{2})}{ chr hex $1 }ge;
-	    $ch->{$k} = $_;
-	}
-	$cache{cache_row_key($jr)} = $jr;
-    }
-    close H;
-}
-
-sub cache_row_lookup_prep ($) {
-    my ($jrr) = @_;
-
-    $rows_today++;
-    my $cacherow = $cache{cache_row_key($$jrr)};
-    if ($cacherow) {
-	$$jrr = $cacherow;
-	$rows_hit++;
-    }
-}
-
-sub cache_write_entry ($$) {
-    my ($fh, $jr) = @_;
-    print $fh "<!-- osstest-report-reuseable";
-    my $whash = sub {
-	my ($h) = @_;
-	foreach my $k (sort keys %$h) {
-	    next if $k =~ m/^\%/;
-	    $_ = $h->{$k};
-	    s{[^-+=/~:;_.,\w]}{ sprintf "%%%02x", ord $& }ge;
-	    printf $fh " %s=%s", $k, $_;
-	}
-    };
-    $whash->($jr);
-    foreach my $hk (sort keys %$jr) {
-	next unless $hk =~ m/^\%/;
-	print $fh " $'";
-	$whash->($jr->{$hk});
-    }
-    print $fh " -->\n";
-}
-
-sub cache_report_stats ($) {
-    my ($what) = @_;
-    print ::DEBUG "CACHE $what read=$rows_previous hits $rows_hit/$rows_today";
-    for my $cachekey (sort keys %q_count) {
-	my $total = $q_count{$cachekey};
-	my $hits = $total - ($q_misses{$cachekey} // 0);
-	print ::DEBUG " $cachekey=$hits/$total";
-    }
-    print ::DEBUG "\n";
-}
-
 cache_set_key_cols(qw(flight job status name));
 
 sub computeflightsrange () {
