chiark / gitweb /
Show total mass/volumes
[ypp-sc-tools.db-live.git] / yarrg / web / routetrade
index 4dbe36e95c533e95cfecce13497c3baba0df66b4..cb7550e5e127f1a8e7a1623d8861b80144507d2b 100644 (file)
  This Mason component is the core trade planner for a specific route.
 
 
-========== TODO ==========
-16:36 <ceb> alpha,byrne,papaya,turtle,jorvik,luthien is my example
-
-use POST for update.  Hrrm.
-
-LATER OR NOT AT ALL
-
-adjustable potential cost of losses (rather than fixed 1e-BIG per league)
-
-max volume/mass
-
-========== TODO ==========
-
 </%doc>
 <%args>
 $dbh
@@ -63,6 +50,7 @@ my $loss_per_league= 1e-7;
 
 my @flow_conds;
 my @query_params;
+my %dists;
 
 my $sd_condition= sub {
        my ($bs, $ix) = @_;
@@ -98,10 +86,12 @@ foreach my $src_i (0..$#islandids) {
 
                if ($specific && !$confusing &&
                    # With a circular route, do not carry goods round the loop
-                   !($src_i==0 && $dst_i==$#islandids &&
+                   !(($src_i==0 || $src_i==$#islandids) &&
+                     $dst_i==$#islandids &&
                      $src_isle == $islandids[$dst_i])) {
                        if ($islandpair{$src_isle,$dst_isle}) {
                                $confusing= 1;
+print "confusing $src_i $src_isle  $dst_i $dst_isle\n";
                        } else {
                                $islandpair{$src_isle,$dst_isle}=
                                        [ $src_i, $dst_i ];
@@ -172,6 +162,20 @@ my $sth= $dbh->prepare($stmt);
 $sth->execute(@query_params);
 my @flows;
 
+my $distquery= $dbh->prepare("
+               SELECT dist FROM dists WHERE aiid = ? AND biid = ?
+               ");
+my $distance= sub {
+       my ($from,$to)= @_;
+       my $d= $dists{$from}{$to};
+       return $d if defined $d;
+       $distquery->execute($from,$to);
+       $d = $distquery->fetchrow_array();
+       defined $d or die "$from $to ?";
+       $dists{$from}{$to}= $d;
+       return $d;
+};
+
 my @cols= ({ NoSort => 1 });
 
 my $addcols= sub {
@@ -201,9 +205,12 @@ $addcols->({ DoReverse => 1, SortColKey => 'MarginSortKey' },
        qw(     Margin
        ));
 $addcols->({ DoReverse => 1 },
-       qw(     unitprofit MaxQty
-               MaxCapital MaxProfit
+       qw(     unitprofit MaxQty MaxCapital MaxProfit dist
        ));
+foreach my $v (qw(MaxMass MaxVolume)) {
+   $addcols->({
+       DoReverse => 1, Total => 0, SortColKey => "${v}SortKey" }, $v);
+}
 
 </%perl>
 
@@ -252,6 +259,12 @@ $addcols->({ DoReverse => 1 },
 % }
 
 <%perl>
+
+if (!@flows) {
+       print 'No profitable trading opportunities were found.';
+       return;
+}
+
 foreach my $f (@flows) {
 
        $f->{MaxQty}= $f->{'org_qty_agg'} < $f->{'dst_qty_agg'}
@@ -259,6 +272,12 @@ foreach my $f (@flows) {
        $f->{MaxProfit}= $f->{MaxQty} * $f->{'unitprofit'};
        $f->{MaxCapital}= $f->{MaxQty} * $f->{'org_price'};
 
+       $f->{MaxMassSortKey}= $f->{MaxQty} * $f->{'unitmass'};
+       $f->{MaxVolumeSortKey}= $f->{MaxQty} * $f->{'unitvolume'};
+       foreach my $v (qw(Mass Volume)) {
+               $f->{"Max$v"}= sprintf "%.1f", $f->{"Max${v}SortKey"} * 1e-6;
+       }
+
        $f->{MarginSortKey}= sprintf "%d",
                $f->{'dst_price'} * 10000 / $f->{'org_price'};
        $f->{Margin}= sprintf "%3.1f%%",
@@ -268,6 +287,8 @@ foreach my $f (@flows) {
                $f->{'dst_price'} * (1.0 - $loss_per_league) ** $f->{'dist'}
                - $f->{'org_price'};
 
+       $dists{'org_id'}{'dst_id'}= $f->{'dist'};
+
        my @uid= $f->{commodid};
        foreach my $od (qw(org dst)) {
                push @uid,
@@ -353,7 +374,7 @@ Route contains archipelago(es), not just specific islands.
 Route is complex - it visits the same island several times
 and isn't a simple loop.
 % }
-Therefore, optimal trade pattern not calculated.
+Therefore, optimal voyage trade plan not calculated.
 
 % } else { # ========== OPTMISATION ==========
 <%perl>
@@ -423,7 +444,9 @@ if ($qa->{'debug'}) {
                qw(glpsol --cpxlp /dev/stdin -o /dev/stdout));
        print "<pre>\n" if $qa->{'debug'};
        my $found_section= 0;
+       my $glpsol_out= '';
        while (<$output>) {
+               $glpsol_out.= $_;
                print encode_entities($_) if $qa->{'debug'};
                if (m/^\s*No\.\s+Column name\s+St\s+Activity\s/) {
                        die if $found_section>0;
@@ -444,8 +467,9 @@ if ($qa->{'debug'}) {
                $flow->{OptCapital}= $flow->{OptQty} * $flow->{'org_price'};
        }
        print "</pre>\n" if $qa->{'debug'};
-       pipethrough_run_finish($output, 'glpsol');
-       die unless $found_section;
+       my $prerr= "\n=====\n$cplex\n=====\n$glpsol_out\n=====\n ";
+       pipethrough_run_finish($output,$prerr);
+       die $prerr unless $found_section;
 };
 
 $addcols->({ DoReverse => 1 }, qw(
@@ -472,6 +496,7 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 <colgroup span=2>
 <colgroup span=2>
 <colgroup span=3>
+<colgroup span=3>
 %      if ($optimise) {
 <colgroup span=3>
 %      }
@@ -484,6 +509,8 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 <th colspan=2>Deliver
 <th colspan=2>Profit
 <th colspan=3>Max
+<th colspan=1>
+<th colspan=2>Max
 %      if ($optimise) {
 <th colspan=3>Planned
 %      }
@@ -502,6 +529,9 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 <th>Qty
 <th>Capital
 <th>Profit
+<th>Dist
+<th>Mass
+<th>Vol
 %      if ($optimise) {
 <th>Qty
 <th>Capital
@@ -524,7 +554,8 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 %      foreach my $ci (1..$#cols) {
 %              my $col= $cols[$ci];
 %              my $v= $flow->{$col->{Name}};
-%              $col->{Total} += $v if defined $col->{Total};
+%              $col->{Total} += $v
+%                      if defined $col->{Total} and not $flow->{Suppress};
 %              $v='' if !$col->{Text} && !$v;
 %              my $sortkey= $col->{SortColKey} ?
 %                      $flow->{$col->{SortColKey}} : $v;
@@ -544,15 +575,10 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 % }
 </table>
 
-<& tabsort, cols => \@cols, table => 'trades', rowclass => 'datarow',
+<&| tabsort, cols => \@cols, table => 'trades', rowclass => 'datarow',
        throw => 'trades_sort', tbrow => 'trades_total' &>
-<&| script &>
   ts_sortkeys= <% to_json_protecttags(\%ts_sortkeys) %>;
-  function all_onload() {
-    ts_onload__trades();
-  }
-  window.onload= all_onload;
-</&script>
+</&tabsort>
 
 <input type=submit name=update value="Update">
 
@@ -561,57 +587,87 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 % my $iquery= $dbh->prepare('SELECT islandname FROM islands
 %                              WHERE islandid = ?');
 % my %da_ages;
+% my $total_total= 0;
+% my $total_dist= 0;
 %
 <h1>Voyage trading plan</h1>
-<table>
+<table rules=groups>
 % foreach my $i (0..$#islandids) {
-<tr><td colspan=3><strong>
+<tbody>
+<tr><td colspan=3>
 %      $iquery->execute($islandids[$i]);
 %      my ($islandname) = $iquery->fetchrow_array();
+%      my $this_dist= $distance->($islandids[$i-1],$islandids[$i]);
+%      $total_dist += $this_dist;
 %      if (!$i) {
-Start at <% $islandname |h %>
+<strong>Start at <% $islandname |h %></strong>
 %      } else {
-Sail to <% $islandname |h %>
-%      }
-</strong>
-%    my $age_reported= 0;
-%    foreach my $od (qw(dst org)) {
-%      my $sign= $od eq 'dst' ? -1 : +1;
-%      my %todo;
-%      foreach my $f (@flows) {
-%              next if $f->{Suppress};
-%              next unless $f->{"${od}_id"} == $islandids[$i];
-%              next unless $f->{OptQty};
-%              my $price= $f->{"${od}_price"};
-%              my $stallname= $f->{"${od}_stallname"};
-%              my $todo= \$todo{ $f->{'commodname'},
-%                                (sprintf "%07d", $price),
-%                                $stallname };
-%              $$todo= { Qty => 0 } unless $$todo;
-%              $$todo->{'commodname'}= $f->{'commodname'};
-%              $$todo->{'stallname'}= $stallname;
-%              $$todo->{Price}= $price;
-%              $$todo->{Timestamp}= $f->{"${od}_timestamp"};
-%              $$todo->{Qty} += $f->{OptQty};
-%              $$todo->{Total}= $$todo->{Price} * $$todo->{Qty};
-%              $$todo->{Stalls}= $f->{"${od}Stalls"};
+<strong>Sail to <% $islandname |h %></strong>
+- <% $this_dist |h %> leagues </td>
 %      }
-%      if (%todo && !$age_reported++) {
-%              my $age= $now - (values %todo)[0]->{Timestamp};
-%              my $cellid= "da_${i}";
-%              $da_ages{$cellid}= $age;
-<td colspan=3 align=right>\
+<%perl>
+     my $age_reported= 0;
+     my %flowlists;
+     foreach my $od (qw(org dst)) {
+       foreach my $f (@flows) {
+               next if $f->{Suppress};
+               next unless $f->{"${od}_id"} == $islandids[$i];
+               next unless $f->{OptQty};
+               my $arbitrage= $f->{'org_id'} == $f->{'dst_id'};
+               my $loop= $islandids[0] == $islandids[-1] &&
+                         ($i==0 || $i==$#islandids);
+               next if $loop and ($arbitrage ? $i :
+                       !!$i == !!($od eq 'org'));
+               my $price= $f->{"${od}_price"};
+               my $stallname= $f->{"${od}_stallname"};
+               my $todo= \$flowlists{$od}{
+                               $f->{'commodname'},
+                               (sprintf "%07d", ($od eq 'dst' ?
+                                               9999999-$price : $price)),
+                               $stallname
+                       };
+               $$todo= {
+                       Qty => 0,
+                       orgArbitrage => 0,
+                       dstArbitrage => 0,
+               } unless $$todo;
+               $$todo->{'commodname'}= $f->{'commodname'};
+               $$todo->{'stallname'}= $stallname;
+               $$todo->{Price}= $price;
+               $$todo->{Timestamp}= $f->{"${od}_timestamp"};
+               $$todo->{Qty} += $f->{OptQty};
+               $$todo->{Total}= $$todo->{Price} * $$todo->{Qty};
+               $$todo->{Stalls}= $f->{"${od}Stalls"};
+               $$todo->{"${od}Arbitrage"}= 1 if $arbitrage;
+       }
+     }
+
+     my $total;
+     my $dline= 0;
+     my $show_flows= sub {
+       my ($od,$arbitrage,$collectdeliver) = @_;
+</%perl>
+%
+%      my $todo= $flowlists{$od};
+%      return unless $todo;
+%      foreach my $tkey (sort keys %$todo) {
+%              my $t= $todo->{$tkey};
+%              next if $t->{"${od}Arbitrage"} != $arbitrage;
+%              if (!$age_reported++) {
+%                      my $age= $now - $t->{Timestamp};
+%                      my $cellid= "da_${i}";
+%                      $da_ages{$cellid}= $age;
+<td colspan=3>\
 (Data age: <span id="<% $cellid %>"><% prettyprint_age($age) %></span>)
-%      }
-%      my $total= 0;
-%      my $dline= 0;
-%      foreach my $tkey (sort keys %todo) {
-%              my $t= $todo{$tkey};
+%              } elsif (!defined $total) {
+%                      $total= 0;
+<tbody>
+%              }
 %              $total += $t->{Total};
 %              my $span= 0 + keys %{ $t->{Stalls} };
 %              my $td= "td rowspan=$span";
 <tr class="datarow<% $dline %>">
-<<% $td %>><% $od eq 'org' ? 'Collect' : 'Deliver' %>
+<<% $td %>><% $collectdeliver %>
 <<% $td %>><% $t->{'commodname'} |h %>
 %
 %              my @stalls= sort keys %{ $t->{Stalls} };
@@ -632,13 +688,34 @@ Sail to <% $islandname |h %>
 %
 %              $dline ^= 1;
 %      }
-%      if (%todo) {
+%    };
+%    my $show_total= sub {
+%      my ($totaldesc, $sign)= @_;
+%      if (defined $total) {
 <tr>
-<td colspan=4><td align=right><% $od eq 'org' ? 'Outlay' : 'Proceeds' %>
+<td colspan=3>
+<td colspan=2 align=right><% $totaldesc %>
 <td align=right><% $total |h %> total
+%              $total_total += $sign * $total;
 %      }
-%    }
-% }
+%      $total= undef;
+%      $dline= 0;
+<%perl>
+     };
+
+     $show_flows->('dst',0,'Deliver'); $show_total->('Proceeds',1);
+     $show_flows->('org',1,'Collect'); $show_total->('(Arbitrage) outlay',-1);
+     $show_flows->('dst',1,'Deliver'); $show_total->('(Arbitrage) proceeds',1);
+     $show_flows->('org',0,'Collect'); $show_total->('Outlay',-1);
+
+}
+</%perl>
+<tbody><tr>
+<td colspan=2>Total distance: <% $total_dist %> leagues.
+<td colspan=3 align=right>Overall net cash flow
+<td align=right><strong><%
+  $total_total < 0 ? -$total_total." loss" : $total_total." gain"
+ %></strong>
 </table>
 <& query_age:dataages, id2age => \%da_ages &>
 %