diff --git a/db/manifest b/db/manifest index e3db60d29..e23c3243d 100755 --- a/db/manifest +++ b/db/manifest @@ -196,4 +196,5 @@ score_histories.sql update_sms_index.sql connection_allow_null_locidispid.sql track_user_in_scores.sql -median_aggregate.sql \ No newline at end of file +median_aggregate.sql +current_scores_use_median.sql \ No newline at end of file diff --git a/db/up/current_scores_use_median.sql b/db/up/current_scores_use_median.sql new file mode 100644 index 000000000..44a02e0f8 --- /dev/null +++ b/db/up/current_scores_use_median.sql @@ -0,0 +1,13 @@ +-- this results in a rough median; the only problem is that we don't avg if it's an even number. not a big deal truthfully, since eventually you'll have > 5 + +DROP VIEW current_scores; +CREATE OR REPLACE VIEW current_scores AS + + SELECT * FROM (SELECT * , row_number() OVER (PARTITION BY alocidispid, blocidispid, scorer ORDER BY score DESC) AS pcnum FROM + (SELECT * FROM + (SELECT percent_rank() over (PARTITION BY alocidispid, blocidispid ORDER BY score ASC) AS pc, * FROM + (SELECT * FROM + (SELECT *, row_number() OVER (PARTITION BY alocidispid, blocidispid ORDER BY created_at DESC) AS rownum FROM scores) tmp + WHERE rownum < 6) AS score_ranked) + AS tmp2 WHERE pc <= .5 ORDER BY pc DESC) pcs ) + AS final WHERE pcnum < 2; diff --git a/ruby/lib/jam_ruby/models/search.rb b/ruby/lib/jam_ruby/models/search.rb index 4b1d1868b..409d0744a 100644 --- a/ruby/lib/jam_ruby/models/search.rb +++ b/ruby/lib/jam_ruby/models/search.rb @@ -390,7 +390,7 @@ module JamRuby limit = 50 rel = User.musicians_geocoded - .where(['created_at >= ? AND users.id != ?', since_date, usr.id]) + .where(['users.created_at >= ? AND users.id != ?', since_date, usr.id]) .joins('inner join current_scores on users.last_jam_locidispid = current_scores.alocidispid') .where(['current_scores.blocidispid = ?', locidispid]) .where(['current_scores.score <= ?', score_limit]) diff --git a/ruby/spec/jam_ruby/models/score_spec.rb b/ruby/spec/jam_ruby/models/score_spec.rb index c360cf881..195642167 100644 --- a/ruby/spec/jam_ruby/models/score_spec.rb +++ b/ruby/spec/jam_ruby/models/score_spec.rb @@ -10,141 +10,305 @@ describe Score do let(:latency_tester2) { FactoryGirl.create(:latency_tester) } let(:score_with_latency_tester) { s1, s2 = Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo', { alatencytestid: latency_tester1.id, blatencytestid: latency_tester2.id}); s1 } - before do - Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') - Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 30, nil) - Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 40, Time.new.utc-3600) - end - it "count" do - Score.count.should == 6 - end - it 'a to b' do - s = Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first - s.should_not be_nil - s.alocidispid.should == 1234 - s.anodeid.should eql('anodeid') - s.aaddr.should == 0x01020304 - s.blocidispid.should == 2345 - s.bnodeid.should eql('bnodeid') - s.baddr.should == 0x02030405 - s.score.should == 20 - s.scorer.should == 0 - s.score_dt.should_not be_nil - s.scoring_data.should eq('foo') - end - - it 'b to a' do - s = Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first - s.should_not be_nil - s.alocidispid.should == 2345 - s.anodeid.should eql('bnodeid') - s.aaddr.should == 0x02030405 - s.blocidispid.should == 1234 - s.bnodeid.should eql('anodeid') - s.baddr.should == 0x01020304 - s.score.should == 20 - s.scorer.should == 1 - s.score_dt.should_not be_nil - s.scoring_data.should be_nil - end - - it 'a to c' do - s = Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first - s.should_not be_nil - s.alocidispid.should == 1234 - s.anodeid.should eql('anodeid') - s.aaddr.should == 0x01020304 - s.blocidispid.should == 3456 - s.bnodeid.should eql('cnodeid') - s.baddr.should == 0x03040506 - s.score.should == 30 - s.scorer.should == 0 - s.score_dt.should_not be_nil - s.scoring_data.should be_nil - end - - it 'c to a' do - s = Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first - s.should_not be_nil - s.alocidispid.should == 3456 - s.anodeid.should eql('cnodeid') - s.aaddr.should == 0x03040506 - s.blocidispid.should == 1234 - s.bnodeid.should eql('anodeid') - s.baddr.should == 0x01020304 - s.score.should == 30 - s.scorer.should == 1 - s.score_dt.should_not be_nil - s.scoring_data.should be_nil - end - - it 'delete a to c' do - Score.deletex(1234, 3456) - Score.count.should == 2 - Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first.should be_nil - Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first.should be_nil - Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first.should_not be_nil - Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first.should_not be_nil - end - - it 'findx' do - Score.findx(1234, 1234).should == -1 - Score.findx(1234, 2345).should == 20 - Score.findx(1234, 3456).should == 30 - - Score.findx(2345, 1234).should == 20 - Score.findx(2345, 2345).should == -1 - Score.findx(2345, 3456).should == -1 - - Score.findx(3456, 1234).should == 30 - Score.findx(3456, 2345).should == -1 - Score.findx(3456, 3456).should == -1 - end - - it "test shortcut for making scores from connections" do - user1 = FactoryGirl.create(:user) - conn1 = FactoryGirl.create(:connection, user: user1, addr: 0x01020304, locidispid: 5) - user2 = FactoryGirl.create(:user) - conn2 = FactoryGirl.create(:connection, user: user2, addr: 0x11121314, locidispid: 6) - user3 = FactoryGirl.create(:user) - conn3 = FactoryGirl.create(:connection, user: user3, addr: 0x21222324, locidispid: 7) - - Score.findx(5, 6).should == -1 - Score.findx(6, 5).should == -1 - Score.findx(5, 7).should == -1 - Score.findx(7, 5).should == -1 - Score.findx(6, 7).should == -1 - Score.findx(7, 6).should == -1 - - Score.score_conns(conn1, conn2, 12) - Score.score_conns(conn1, conn3, 13) - Score.score_conns(conn2, conn3, 23) - - Score.findx(5, 6).should == 12 - Score.findx(6, 5).should == 12 - Score.findx(5, 7).should == 13 - Score.findx(7, 5).should == 13 - Score.findx(6, 7).should == 23 - Score.findx(7, 6).should == 23 - end - - describe "createx" do - it "creates with user info" do - score_with_user.touch - score_with_user.auserid.should == user1.id - score_with_user.buserid.should == user2.id - score_with_user.alatencytestid.should be_nil - score_with_user.blatencytestid.should be_nil + describe "with default scores" do + before do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 30, nil) + Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 40, Time.new.utc-3600) end - it "creates with latency-tester info" do - score_with_latency_tester.touch - score_with_latency_tester.auserid.should be_nil - score_with_latency_tester.buserid.should be_nil - score_with_latency_tester.alatencytestid.should == latency_tester1.id - score_with_latency_tester.blatencytestid.should == latency_tester2.id + + it "count" do + Score.count.should == 6 + end + + it 'a to b' do + s = Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first + s.should_not be_nil + s.alocidispid.should == 1234 + s.anodeid.should eql('anodeid') + s.aaddr.should == 0x01020304 + s.blocidispid.should == 2345 + s.bnodeid.should eql('bnodeid') + s.baddr.should == 0x02030405 + s.score.should == 20 + s.scorer.should == 0 + s.score_dt.should_not be_nil + s.scoring_data.should eq('foo') + end + + it 'b to a' do + s = Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first + s.should_not be_nil + s.alocidispid.should == 2345 + s.anodeid.should eql('bnodeid') + s.aaddr.should == 0x02030405 + s.blocidispid.should == 1234 + s.bnodeid.should eql('anodeid') + s.baddr.should == 0x01020304 + s.score.should == 20 + s.scorer.should == 1 + s.score_dt.should_not be_nil + s.scoring_data.should be_nil + end + + it 'a to c' do + s = Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first + s.should_not be_nil + s.alocidispid.should == 1234 + s.anodeid.should eql('anodeid') + s.aaddr.should == 0x01020304 + s.blocidispid.should == 3456 + s.bnodeid.should eql('cnodeid') + s.baddr.should == 0x03040506 + s.score.should == 30 + s.scorer.should == 0 + s.score_dt.should_not be_nil + s.scoring_data.should be_nil + end + + it 'c to a' do + s = Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first + s.should_not be_nil + s.alocidispid.should == 3456 + s.anodeid.should eql('cnodeid') + s.aaddr.should == 0x03040506 + s.blocidispid.should == 1234 + s.bnodeid.should eql('anodeid') + s.baddr.should == 0x01020304 + s.score.should == 30 + s.scorer.should == 1 + s.score_dt.should_not be_nil + s.scoring_data.should be_nil + end + + it 'delete a to c' do + Score.deletex(1234, 3456) + Score.count.should == 2 + Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first.should be_nil + Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first.should be_nil + Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first.should_not be_nil + Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first.should_not be_nil + end + + it 'findx' do + Score.findx(1234, 1234).should == -1 + Score.findx(1234, 2345).should == 20 + Score.findx(1234, 3456).should == 30 + + Score.findx(2345, 1234).should == 20 + Score.findx(2345, 2345).should == -1 + Score.findx(2345, 3456).should == -1 + + Score.findx(3456, 1234).should == 30 + Score.findx(3456, 2345).should == -1 + Score.findx(3456, 3456).should == -1 + end + + it "test shortcut for making scores from connections" do + user1 = FactoryGirl.create(:user) + conn1 = FactoryGirl.create(:connection, user: user1, addr: 0x01020304, locidispid: 5) + user2 = FactoryGirl.create(:user) + conn2 = FactoryGirl.create(:connection, user: user2, addr: 0x11121314, locidispid: 6) + user3 = FactoryGirl.create(:user) + conn3 = FactoryGirl.create(:connection, user: user3, addr: 0x21222324, locidispid: 7) + + Score.findx(5, 6).should == -1 + Score.findx(6, 5).should == -1 + Score.findx(5, 7).should == -1 + Score.findx(7, 5).should == -1 + Score.findx(6, 7).should == -1 + Score.findx(7, 6).should == -1 + + Score.score_conns(conn1, conn2, 12) + Score.score_conns(conn1, conn3, 13) + Score.score_conns(conn2, conn3, 23) + + Score.findx(5, 6).should == 12 + Score.findx(6, 5).should == 12 + Score.findx(5, 7).should == 13 + Score.findx(7, 5).should == 13 + Score.findx(6, 7).should == 23 + Score.findx(7, 6).should == 23 + end + + describe "createx" do + it "creates with user info" do + score_with_user.touch + score_with_user.auserid.should == user1.id + score_with_user.buserid.should == user2.id + score_with_user.alatencytestid.should be_nil + score_with_user.blatencytestid.should be_nil + end + + it "creates with latency-tester info" do + score_with_latency_tester.touch + score_with_latency_tester.auserid.should be_nil + score_with_latency_tester.buserid.should be_nil + score_with_latency_tester.alatencytestid.should == latency_tester1.id + score_with_latency_tester.blatencytestid.should == latency_tester2.id + end + end + end + + # current_scores is a view that tries to take the median of up to the last 5 entries + describe "current_scores" do + it "works with empty data set" do + result = Score.connection.execute('SELECT * FROM current_scores') + result.check + result.ntuples.should == 0 + end + + it "works with one score" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') + result = Score.connection.execute('SELECT * FROM current_scores') + result.check + result.ntuples.should == 2 + result[0]['alocidispid'].to_i.should == 1234 + result[0]['scorer'].to_i.should == 0 + result[1]['alocidispid'].to_i.should == 2345 + result[1]['scorer'].to_i.should == 1 + end + + it "works with two scores in same location" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 25, nil, 'foo') + + result = Score.connection.execute('SELECT * FROM current_scores') + result.check + result.ntuples.should == 2 + result[0]['score'].to_i.should == 20 + result[1]['score'].to_i.should == 20 + end + + it "works with three scores in same location" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 25, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo') + + result = Score.connection.execute('SELECT * FROM current_scores') + result.check + result.ntuples.should == 2 + result[0]['score'].to_i.should == 25 + result[1]['score'].to_i.should == 25 + end + + it "works with six scores in same location" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # we'll make sure this is old, so it won't be in the set + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 25, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 31, nil, 'foo')# median + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 32, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 33, nil, 'foo') + + Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{1.days.ago}' WHERE score = 20").cmdtuples.should == 2 + + result = Score.connection.execute('SELECT * FROM current_scores') + result.check + result.ntuples.should == 2 + result[0]['score'].to_i.should == 31 + result[1]['score'].to_i.should == 31 + + # now push back score with 33 to the very back, which will shift the median up to 30 + Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{2.days.ago}' WHERE score = 33").check + + result = Score.connection.execute('SELECT * FROM current_scores') + result.check + result.ntuples.should == 2 + result[0]['score'].to_i.should == 30 + result[1]['score'].to_i.should == 30 + end + + it "works with one score each in different locations" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo') + + result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score') + result.check + result.ntuples.should == 4 + + result[0]['score'].to_i.should == 20 + result[1]['score'].to_i.should == 20 + result[2]['score'].to_i.should == 25 + result[3]['score'].to_i.should == 25 + end + + it "works with multiple scores in different locations" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 35, nil, 'foo') + + result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score') + result.check + result.ntuples.should == 4 + + result[0]['score'].to_i.should == 20 + result[1]['score'].to_i.should == 20 + result[2]['score'].to_i.should == 25 + result[3]['score'].to_i.should == 25 + end + + it "works with multiple scores in different locations" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 35, nil, 'foo') # median + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 40, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 45, nil, 'foo') + + result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score') + result.check + result.ntuples.should == 4 + + result[0]['score'].to_i.should == 30 + result[1]['score'].to_i.should == 30 + result[2]['score'].to_i.should == 35 + result[3]['score'].to_i.should == 35 + + + end + + it "works with over 6 scores in different locations" do + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 35, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 40, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 45, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 45, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 50, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 55, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 60, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 65, nil, 'foo') + Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 70, nil, 'foo') + + Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{1.days.ago}' WHERE score = 20 OR score = 25").cmdtuples.should == 4 + + result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score') + result.check + result.ntuples.should == 4 + + result[0]['score'].to_i.should == 45 + result[1]['score'].to_i.should == 45 + result[2]['score'].to_i.should == 50 + result[3]['score'].to_i.should == 50 + + + Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{2.days.ago}' WHERE score = 65 OR score = 70").cmdtuples.should == 4 + + result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score') + result.check + result.ntuples.should == 4 + + result[0]['score'].to_i.should == 40 + result[1]['score'].to_i.should == 40 + result[2]['score'].to_i.should == 45 + result[3]['score'].to_i.should == 45 + + end end