chiark / gitweb /
Capitalise some keywords
[ypp-sc-tools.db-live.git] / yarrg / web / routetrade
index 9c2a5a423fdda83fe833c43ceb2ba02462df6a79..d152eed6a0f6485456340deb404546fcc4cb0620 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
+
+16:39 <ceb> Also, maybe colour to highlight the suggested trades?
+
+16:46 <ceb> Also trading plan not functional but I guess you know that :-)
+
+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
+
+16:38 <ceb> I don't know how hard this is, but can you show only the suggested 
+            trades to start ith and have a button to show all?
+========== TODO ==========
+
 </%doc>
 <%args>
 $dbh
@@ -41,6 +60,8 @@ $qa
 </%args>
 <%perl>
 
+my $loss_per_league= 1e-7;
+
 my @flow_conds;
 my @query_params;
 
@@ -98,30 +119,45 @@ my $stmt= "
        SELECT  sell_islands.islandname                         org_name,
                sell_islands.islandid                           org_id,
                sell.price                                      org_price,
-               sum(sell.qty)                                   org_qty,
                buy_islands.islandname                          dst_name,
                buy_islands.islandid                            dst_id,
                buy.price                                       dst_price,
+".($qa->{ShowStalls} ? "
+               sell.stallid                                    org_stallid,
+               sell_stalls.stallname                           org_stallname,
+               sell.qty                                        org_qty,
+               buy.stallid                                     dst_stallid,
+               buy_stalls.stallname                            dst_stallname,
+               buy.qty                                         dst_qty,
+" : "
+               sum(sell.qty)                                   org_qty,
                sum(buy.qty)                                    dst_qty,
+")."
                commods.commodname                              commodname,
                commods.commodid                                commodid,
                commods.unitmass                                unitmass,
                commods.unitvolume                              unitvolume,
-               buy.price - sell.price                          unitprofit,
-               min(sell.qty,buy.qty)                           max_qty,
-               min(sell.qty,buy.qty) * (buy.price-sell.price)  max_profit
+               dist                                            dist,
+               buy.price - sell.price                          unitprofit
        FROM commods
-       JOIN buy  on commods.commodid = buy.commodid
-       JOIN sell on commods.commodid = sell.commodid
-       JOIN islands as sell_islands on sell.islandid = sell_islands.islandid
-       JOIN islands as buy_islands  on buy.islandid  = buy_islands.islandid
+       JOIN buy  ON commods.commodid = buy.commodid
+       JOIN sell ON commods.commodid = sell.commodid
+       JOIN islands AS sell_islands ON sell.islandid = sell_islands.islandid
+       JOIN islands AS buy_islands  ON buy.islandid  = buy_islands.islandid
+".($qa->{ShowStalls} ? "
+       JOIN stalls  AS sell_stalls  ON sell.stallid  = sell_stalls.stallid
+       JOIN stalls  AS buy_stalls   ON buy.stallid   = buy_stalls.stallid
+" : "")."
+       JOIN dists ON aiid = sell.islandid AND biid = buy.islandid
        WHERE   (
                ".join("
           OR   ", @flow_conds)."
        )
          AND   buy.price > sell.price
+".($qa->{ShowStalls} ? "" : "
        GROUP BY commods.commodid, org_id, org_price, dst_id, dst_price
-       ORDER BY org_name, dst_name, max_profit DESC, commodname,
+")."
+       ORDER BY org_name, dst_name, commodname, unitprofit DESC,
                 org_price, dst_price DESC
      ";
 
@@ -129,9 +165,33 @@ my $sth= $dbh->prepare($stmt);
 $sth->execute(@query_params);
 my @flows;
 
-my @columns= qw(org_name dst_name commodname
-               org_price org_qty dst_price dst_qty
-               max_qty max_profit);
+my @cols= ({ NoSort => 1 });
+
+my $addcols= sub {
+       my $base= shift @_;
+       foreach my $name (@_) {
+               my $col= { Name => $name, %$base };
+               $col->{Numeric}=1 if !$col->{Text};
+               push @cols, $col;
+       }
+};
+
+if ($qa->{ShowStalls}) {
+       $addcols->({ Text => 1 }, qw(
+               org_name org_stallname
+               dst_name dst_stallname
+       ));
+} else {
+       $addcols->({Text => 1 }, qw(
+               org_name dst_name
+       ));
+}
+$addcols->({ Text => 1 }, qw(commodname));
+$addcols->({ DoReverse => 1 },
+       qw(     org_price org_qty dst_price dst_qty
+               Margin unitprofit MaxQty
+               MaxCapital MaxProfit
+       ));
 
 </%perl>
 
@@ -142,19 +202,108 @@ my @columns= qw(org_name dst_name commodname
 </pre>
 % }
 
+<& dumptable:start, qa => $qa, sth => $sth &>
 % {
-<& dumptable:start, sth => $sth &>
-%   my $flow;
-%   while ($flow= $sth->fetchrow_hashref()) {
-%      $flow->{Ix}= @flows;
-%      $flow->{Var}= "f$flow->{Ix}";
-%      push @flows, $flow;
-<& dumptable:row, sth => $sth, row => $flow &>
+%   my $f;
+%   while ($f= $sth->fetchrow_hashref()) {
+<%perl>
+
+       $f->{Ix}= @flows;
+       $f->{Var}= "f$f->{Ix}";
+
+       $f->{MaxQty}= $f->{'org_qty'} < $f->{'dst_qty'}
+               ? $f->{'org_qty'} : $f->{'dst_qty'};
+       $f->{MaxProfit}= $f->{MaxQty} * $f->{'unitprofit'};
+       $f->{MaxCapital}= $f->{MaxQty} * $f->{'org_price'};
+
+       $f->{Margin}= sprintf "%3.1f%%",
+               $f->{'dst_price'} * 100.0 / $f->{'org_price'} - 100.0;
+
+       $f->{"org_stallid"}= $f->{"dst_stallid"}= 'all'
+               if !$qa->{ShowStalls};
+
+       $f->{ExpectedUnitProfit}=
+               $f->{'dst_price'} * (1.0 - $loss_per_league) ** $f->{'dist'}
+               - $f->{'src_price'};
+
+       my @uid= $f->{commodid};
+       foreach my $od (qw(org dst)) {
+               push @uid,
+                       $f->{"${od}_id"},
+                       $f->{"${od}_price"};
+               push @uid,
+                       $f->{"${od}_stallid"}
+                               if $qa->{ShowStalls};
+       }
+       $f->{UidLong}= join '_', @uid;
+
+       my $base= 31;
+       my $cmpu= '';
+       map {
+               my $uue= $_;
+               my $first= $base;
+               do {
+                       my $this= $uue % $base;
+print STDERR "uue=$uue this=$this ";
+                       $uue -= $this;
+                       $uue /= $base;
+                       $this += $first;
+                       $first= 0;
+                       $cmpu .= chr($this + ($this < 26 ? ord('a') :
+                                             $this < 52 ? ord('A')-26
+                                                        : ord('0')-52));
+print STDERR " uue=$uue this=$this cmpu=$cmpu\n";
+die "$cmpu $uue ?" if length $cmpu > 20;
+               } while ($uue);
+               $cmpu;
+       } @uid;
+       $f->{UidShort}= $cmpu;
+
+       if ($qa->{'debug'}) {
+               my @outuid;
+               $_= $f->{UidShort};
+               my $mul;
+               while (m/./) {
+                       my $v= m/^[a-z]/ ? ord($&)-ord('a') :
+                              m/^[A-Z]/ ? ord($&)-ord('A')+26 :
+                              m/^[0-9]/ ? ord($&)-ord('0')+52 :
+                              die "$_ ?";
+                       if ($v >= $base) {
+                               push @outuid, 0;
+                               $v -= $base;
+                               $mul= 1;
+#print STDERR "(next)\n";
+                       }
+                       die "$f->{UidShort} $_ ?" unless defined $mul;
+                       $outuid[$#outuid] += $v * $mul;
+
+#print STDERR "$f->{UidShort}  $_  $&  v=$v  mul=$mul  ord()=".ord($&).
+#                      "[vs.".ord('a').",".ord('A').",".ord('0')."]".
+#                      "  outuid=@outuid\n";
+
+                       $mul *= $base;
+                       s/^.//;
+               }
+               my $recons_long= join '_', @outuid;
+               $f->{UidLong} eq $recons_long or
+                       die "$f->{UidLong} = $f->{UidShort} = $recons_long ?";
+       }
+
+       if (defined $qa->{"R$f->{UidShort}"} &&
+           !defined $qa->{"T$f->{UidShort}"}) {
+               $f->{Suppress}= 1;
+       }
+
+       push @flows, $f;
+
+</%perl>
+<& dumptable:row, qa => $qa, sth => $sth, row => $f &>
 %   }
-<& dumptable:end &>
+<& dumptable:end, qa => $qa &>
 % }
 
-% if (!$specific || $confusing || @islandids<=1) {
+% my $optimise= $specific && !$confusing && @islandids>1;
+% if (!$optimise) {
 
 <p>
 % if (@islandids<=1) {
@@ -177,20 +326,31 @@ Maximize
 
   totalprofit:
                   ".(join " +
-                  ", map { "$_->{unit_profit} $_->{Var}" } @flows)."
+                  ", map {
+                       sprintf "%.20f %s", $_->{ExpectedUnitProfit}, $_->{Var}
+                       } @flows)."
 
 Subject To
 ";
 
 my %avail_csts;
 foreach my $flow (@flows) {
+       if ($flow->{Suppress}) {
+               $cplex .= "
+   $flow->{Var} = 0
+";
+               next;
+       }
        foreach my $od (qw(org dst)) {
-               my $cstname= join '_',
+               my $cstname= join '_', (
                        'avail',
                        $flow->{'commodid'},
                        $od,
                        $flow->{"${od}_id"},
-                       $flow->{"${od}_price"};
+                       $flow->{"${od}_price"},
+                       $flow->{"${od}_stallid"},
+               );
+                       
                push @{ $avail_csts{$cstname}{Flows} }, $flow->{Var};
                $avail_csts{$cstname}{Qty}= $flow->{"${od}_qty"};
        }
@@ -242,24 +402,155 @@ if ($qa->{'debug'}) {
                my ($ix, $qty) =
                        m/^\s*\d+\s+f(\d+)\s+\S+\s+(\d+)\s/ or die "$_ ?";
                my $flow= $flows[$ix] or die;
-               $flow->{Opt_qty}= $qty;
-               $flow->{Opt_profit}= $flow->{'unitprofit'} * $qty;
+               $flow->{OptQty}= $qty;
+               $flow->{OptProfit}= $flow->{'unitprofit'} * $qty;
+               $flow->{OptCapital}= $flow->{OptQty} * $flow->{'org_price'};
        }
        print "</pre>\n" if $qa->{'debug'};
        pipethrough_run_finish($output, 'glpsol');
        die unless $found_section;
 };
 
-print join ' ', map { $_->{Optimal} } @flows;
-
-push @columns, qw(Opt_qty Opt_profit);
+$addcols->({ DoReverse => 1 }, qw(
+               OptQty
+       ));
+$addcols->({ Total => 0, DoReverse => 1 }, qw(
+               OptCapital OptProfit
+       ));
 
 </%perl>
 
-<& dumptable:literal, cols => \@columns, rows => \@flows &>
-
 % } # ========== OPTIMISATION ==========
 
+% my %ts_sortkeys;
+% {
+%      my $cdspan= $qa->{ShowStalls} ? ' colspan=2' : '';
+%      my $cdstall= $qa->{ShowStalls} ? '<th>Stall</th>' : '';
+<table id="trades" rules=groups>
+<colgroup span=1>
+<colgroup span=2>
+<% $qa->{ShowStalls} ? '<colgroup span=2>' : '' %>
+<colgroup span=1>
+<colgroup span=2>
+<colgroup span=2>
+<colgroup span=2>
+<colgroup span=3>
+%      if ($optimise) {
+<colgroup span=3>
+%      }
+<tr>
+<th>
+<th<% $cdspan %>>Collect
+<th<% $cdspan %>>Deliver
+<th>
+<th colspan=2>Collect
+<th colspan=2>Deliver
+<th colspan=2>Profit
+<th colspan=3>Max
+%      if ($optimise) {
+<th colspan=3>Planned
+%      }
+
+<tr>
+<th>
+<th>Island <% $cdstall %>
+<th>Island <% $cdstall %>
+<th>Commodity
+<th>Price
+<th>Qty
+<th>Price
+<th>Qty
+<th>Margin
+<th>Unit
+<th>Qty
+<th>Capital
+<th>Profit
+%      if ($optimise) {
+<th>Qty
+<th>Capital
+<th>Profit
+%      }
+% }
+
+<tr id="trades_sort">
+% foreach my $col (@cols) {
+<th>
+% }
+
+% foreach my $flow (@flows) {
+%      my $rowid= "id_row_$flow->{UidShort}";
+<tr id="<% $rowid %>">
+<td><input type=hidden   name=R<% $flow->{UidShort} %> value="">
+    <input type=checkbox name=T<% $flow->{UidShort} %> value=""
+       <% $flow->{Suppress} ? '' : 'checked' %> >
+%      foreach my $ci (1..$#cols) {
+%              my $col= $cols[$ci];
+%              my $v= $flow->{$col->{Name}};
+%              $col->{Total} += $v if defined $col->{Total};
+%              $v='' if !$col->{Text} && !$v;
+%              $ts_sortkeys{$ci}{$rowid}= $v;
+<td <% $col->{Text} ? '' : 'align=right' %>><% $v |h %>
+%      }
+% }
+<tr id="trades_total">
+<th>
+<th colspan=2>Total
+% foreach my $ci (3..$#cols) {
+%      my $col= $cols[$ci];
+<td align=right>
+%      if (defined $col->{Total}) {
+<% $col->{Total} |h %>
+%      }
+% }
+</table>
+
+<& tabsort, cols => \@cols, table => 'trades',
+       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>
+
+<input type=submit name=update value="Update">
+
+% if ($optimise) { # ========== TRADING PLAN ==========
+%
+% my $iquery= $dbh->prepare('SELECT islandname FROM islands
+%                              WHERE islandid = ?');
+%
+<h1>Voyage trading plan</h1>
+<table>
+% foreach my $i (0..$#islandids) {
+<tr><td colspan=4><strong>
+%      $iquery->execute($islandids[$i]);
+%      my ($islandname) = $iquery->fetchrow_array();
+%      if (!$i) {
+Start at <% $islandname |h %>
+%      } else {
+Sail to <% $islandname |h %>
+%      }
+</strong>
+%    foreach my $od (qw(dst org)) {
+%      my $sign= $od eq 'dst' ? -1 : +1;
+%      foreach my $f (sort {
+%                      $a->{'commodname'} cmp $b->{'commodname'}
+%              or $sign * ($a->{"${od}_price"} <=> $b->{"${od}_price"})
+%              or      $a->{"${od}_stallname"} cmp $b->{"${od}_stallname"}
+%              } @flows) {
+%              next if $f->{Suppress};
+%              next unless $f->{"${od}_id"} == $islandids[$i];
+%              next unless $f->{OptQty};
+<tr><td>Buy or sell flow 
+%      }
+%    }
+% }
+</table>
+%
+% } # ========== TRADING PLAN ==========
+
 <%init>
 use CommodsWeb;
 use Commods;