Skip to content

Commit

Permalink
adding ability to generate a list of recommended posts for each user
Browse files Browse the repository at this point in the history
  • Loading branch information
revflash committed May 25, 2016
1 parent e8d3937 commit a01b04e
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 3 deletions.
60 changes: 59 additions & 1 deletion libraries/app/database_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,16 @@ state database_api::get_state( string path )const
auto acnt = part[0].substr(1);
_state.accounts[acnt] = my->_db.get_account(acnt);
auto& eacnt = _state.accounts[acnt];
if( part[1] == "transfers" ) {
if( part[1] == "recommended" ) {
auto discussions = get_recommended_for( acnt, 100 );
eacnt.recommended = vector<string>();
for( const auto& d : discussions ) {
auto ref = d.author+"/"+d.permlink;
_state.content[ ref ] = d;
eacnt.recommended->push_back( ref );
}
}
else if( part[1] == "transfers" ) {
auto history = get_account_history( acnt, uint64_t(-1), 1000 );
for( auto& item : history ) {
switch( item.second.op.which() ) {
Expand Down Expand Up @@ -1351,4 +1360,53 @@ annotated_signed_transaction database_api::get_transaction( transaction_id_type
FC_ASSERT( false, "Unknown Transaction ${t}", ("t",id));
}

vector<discussion> database_api::get_recommended_for( const string& u, uint32_t limit )const {
FC_ASSERT( limit <= 1000 );
auto start_time = fc::time_point::now();

const auto& user = my->_db.get_account(u);
const auto& rank_idx = my->_db.get_index_type<tags::peer_stats_index>().indices().get<tags::by_rank>();
const auto& vote_idx = my->_db.get_index_type<comment_vote_index>().indices().get<by_voter_last_update>();

map<comment_id_type,float> stage;

auto max_age = my->_db.head_block_time() - fc::days(1);

auto itr = rank_idx.lower_bound( boost::make_tuple( user.get_id(), std::numeric_limits<float>::max() ) );
while( itr != rank_idx.end() && itr->voter == user.id && stage.size() < 1000 && itr->rank > 0 ) {
auto vitr = vote_idx.lower_bound( boost::make_tuple( itr->peer, fc::time_point_sec::maximum() ) );
int32_t max_votes_per_peer = 50;
while( max_votes_per_peer && vitr->voter == itr->peer && vitr->last_update >= max_age ) {
const auto& c = vitr->comment(my->_db);
if( c.parent_author.size() == 0 && c.created > max_age && c.author != u && c.net_rshares > 0 )
{
stage[vitr->comment] += itr->rank * vitr->vote_percent;
max_votes_per_peer--;
}
++vitr;
}
++itr;
}
auto vitr = vote_idx.lower_bound( boost::make_tuple( user.get_id(), fc::time_point_sec::maximum() ) );
while( vitr != vote_idx.end() && vitr->voter == user.get_id() && vitr->last_update > max_age ) {
stage.erase( vitr->comment );
++vitr;
}

vector< pair<float,comment_id_type> > result;
result.reserve(stage.size());
for( const auto& item : stage ) result.push_back({item.second,item.first});
std::sort( result.begin(), result.end(), std::greater<pair<float,comment_id_type>>() );

vector<discussion> dresult; dresult.reserve(limit);
for( const auto& item : result ) {
dresult.push_back( get_discussion( item.second ) );
if( dresult.size() == limit ) break;
}
auto end_time = fc::time_point::now();
idump((start_time-end_time));

return dresult;
}

} } // steemit::app
3 changes: 3 additions & 0 deletions libraries/app/include/steemit/app/database_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ class database_api
vector<discussion> get_discussions_by_children( const discussion_query& query )const;
vector<discussion> get_discussions_by_hot( const discussion_query& query )const;

vector<discussion> get_recommended_for( const string& user, uint32_t limit )const;

///@}

/**
Expand Down Expand Up @@ -387,6 +389,7 @@ FC_API(steemit::app::database_api,
(get_discussions_by_votes)
(get_discussions_by_children)
(get_discussions_by_hot)
(get_recommended_for)

// Blocks and transactions
(get_block_header)
Expand Down
3 changes: 2 additions & 1 deletion libraries/app/include/steemit/app/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ namespace steemit { namespace app {
optional<vector<string>> blog; /// blog posts for this user
optional<vector<string>> recent_replies; /// blog posts for this user
map<string,vector<string>> blog_category; /// blog posts for this user
optional<vector<string>> recommended; /// posts recommened for this user
};


Expand Down Expand Up @@ -151,7 +152,7 @@ namespace steemit { namespace app {
FC_REFLECT_DERIVED( steemit::app::extended_account,
(steemit::chain::account_object),
(vesting_balance)
(transfer_history)(market_history)(post_history)(vote_history)(other_history)(witness_votes)(posts)(blog)(recent_replies)(blog_category) )
(transfer_history)(market_history)(post_history)(vote_history)(other_history)(witness_votes)(posts)(blog)(recent_replies)(blog_category)(recommended) )


FC_REFLECT( steemit::app::vote_state, (voter)(weight)(rshares)(percent)(time) );
Expand Down
9 changes: 9 additions & 0 deletions libraries/chain/include/steemit/chain/comment_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ namespace steemit { namespace chain {
struct by_comment_voter;
struct by_voter_comment;
struct by_comment_weight_voter;
struct by_voter_last_update;
typedef multi_index_container<
comment_vote_object,
indexed_by<
Expand All @@ -144,6 +145,14 @@ namespace steemit { namespace chain {
member< comment_vote_object, comment_id_type, &comment_vote_object::comment>
>
>,
ordered_unique< tag< by_voter_last_update >,
composite_key< comment_vote_object,
member< comment_vote_object, account_id_type, &comment_vote_object::voter>,
member< comment_vote_object, time_point_sec, &comment_vote_object::last_update>,
member< comment_vote_object, comment_id_type, &comment_vote_object::comment>
>,
composite_key_compare< std::less< account_id_type >, std::greater< time_point_sec >, std::less<comment_id_type> >
>,
ordered_unique< tag< by_comment_weight_voter >,
composite_key< comment_vote_object,
member< comment_vote_object, comment_id_type, &comment_vote_object::comment>,
Expand Down
1 change: 1 addition & 0 deletions libraries/db/include/graphene/db/object_database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ namespace graphene { namespace db {
if( _index[ObjectType::space_id].size() <= ObjectType::type_id )
_index[ObjectType::space_id].resize( 255 );
assert(!_index[ObjectType::space_id][ObjectType::type_id]);
FC_ASSERT(!_index[ObjectType::space_id][ObjectType::type_id], "duplicate index id detected");
//idump((fc::get_typename<ObjectType>::name())(ObjectType::space_id)(ObjectType::type_id));
unique_ptr<index> indexptr( new IndexType(*this) );
_index[ObjectType::space_id][ObjectType::type_id] = std::move(indexptr);
Expand Down
73 changes: 72 additions & 1 deletion libraries/plugins/tags/include/steemit/tags/tags_plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ enum tags_object_type
bucket_object_type = 1,///< used in market_history_plugin
message_object_type = 2,
tag_object_type = 3,
tag_stats_object_type = 4
tag_stats_object_type = 4,
peer_stats_object_type = 5
};


Expand Down Expand Up @@ -78,6 +79,7 @@ class tag_object : public abstract_object<tag_object> {
comment_id_type comment;
};


struct by_id;
struct by_cashout; /// all posts regardless of depth
struct by_net_rshares; /// all comments regardless of depth
Expand Down Expand Up @@ -244,6 +246,74 @@ typedef multi_index_container<
typedef graphene::db::generic_index< tag_stats_object, tag_stats_multi_index_type> tag_stats_index;


/**
* The purpose of this object is to track the relationship between accounts based upon how a user votes. Every time
* a user votes on a post, the relationship between voter and author increases direct rshares.
*/
class peer_stats_object : public abstract_object<peer_stats_object> {
public:
static const uint8_t space_id = TAG_SPACE_ID;
static const uint8_t type_id = peer_stats_object_type;

account_id_type voter;
account_id_type peer;
int32_t direct_positive_votes = 0;
int32_t direct_votes = 1;

int32_t indirect_positive_votes = 0;
int32_t indirect_votes = 1;

float rank = 0;

void update_rank() {
auto direct = float( direct_positive_votes ) / direct_votes;
auto indirect = float( indirect_positive_votes ) / indirect_votes;
auto direct_order = log( direct_votes );
auto indirect_order = log( indirect_votes );

if( !(direct_positive_votes+indirect_positive_votes) ){
direct_order *= -1;
indirect_order *= -1;
}

direct *= direct;
indirect *= indirect;

direct *= direct_order * 10;
indirect *= indirect_order;

rank = direct + indirect;
}
};


struct by_rank;
struct by_voter_peer;
typedef multi_index_container<
peer_stats_object,
indexed_by<
ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >,
ordered_unique< tag< by_rank >,
composite_key< peer_stats_object,
member< peer_stats_object, account_id_type, &peer_stats_object::voter >,
member< peer_stats_object, float, &peer_stats_object::rank >,
member< peer_stats_object, account_id_type, &peer_stats_object::peer >
>,
composite_key_compare< std::less<account_id_type>, std::greater<float>, std::less<account_id_type> >
>,
ordered_unique< tag< by_voter_peer >,
composite_key< peer_stats_object,
member< peer_stats_object, account_id_type, &peer_stats_object::voter >,
member< peer_stats_object, account_id_type, &peer_stats_object::peer >
>,
composite_key_compare< std::less<account_id_type>, std::less<account_id_type> >
>
>
> peer_stats_multi_index_type;
typedef graphene::db::generic_index< peer_stats_object, peer_stats_multi_index_type> peer_stats_index;




/**
* Used to parse the metadata from the comment json_meta field.
Expand Down Expand Up @@ -302,4 +372,5 @@ FC_REFLECT_DERIVED( steemit::tags::tag_object, (graphene::db::object),
FC_REFLECT_DERIVED( steemit::tags::tag_stats_object, (graphene::db::object),
(tag)(total_children_rshares2)(net_votes)(top_posts)(comments) );

FC_REFLECT_DERIVED( steemit::tags::peer_stats_object, (graphene::db::object), (voter)(peer)(direct_positive_votes)(direct_votes)(indirect_positive_votes)(indirect_votes)(rank) );
FC_REFLECT( steemit::tags::comment_metadata, (tags) );
53 changes: 53 additions & 0 deletions libraries/plugins/tags/tags_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,65 @@ struct operation_visitor {
} FC_CAPTURE_LOG_AND_RETHROW( (c) )
}

const peer_stats_object& get_or_create_peer_stats( account_id_type voter, account_id_type peer )const {
const auto& peeridx = _db.get_index_type<peer_stats_index>().indices().get<by_voter_peer>();
auto itr = peeridx.find( boost::make_tuple( voter, peer ) );
if( itr == peeridx.end() ) {
return _db.create<peer_stats_object>( [&]( peer_stats_object& obj ) {
obj.voter = voter;
obj.peer = peer;
});
}
return *itr;
}
void update_indirect_vote( account_id_type a, account_id_type b, int positive )const {
if( a == b ) return;
const auto& ab = get_or_create_peer_stats( a, b );
const auto& ba = get_or_create_peer_stats( b, a );
_db.modify( ab, [&]( peer_stats_object& o ) {
o.indirect_positive_votes += positive;
o.indirect_votes++;
o.update_rank();
});
_db.modify( ba, [&]( peer_stats_object& o ) {
o.indirect_positive_votes += positive;
o.indirect_votes++;
o.update_rank();
});
}

void update_peer_stats( const account_object& voter, const account_object& author, const comment_object& c, int vote )const {
if( voter.id == author.id ) return; /// ignore votes for yourself
if( c.parent_author.size() ) return; /// only count top level posts

const auto& stat = get_or_create_peer_stats( voter.get_id(), author.get_id() );
_db.modify( stat, [&]( peer_stats_object& obj ) {
obj.direct_votes++;
obj.direct_positive_votes += vote > 0;
obj.update_rank();
});

const auto& voteidx = _db.get_index_type<comment_vote_index>().indices().get<by_comment_voter>();
auto itr = voteidx.lower_bound( boost::make_tuple( comment_id_type(c.id), account_id_type() ) );
while( itr != voteidx.end() && itr->comment == c.id ) {
update_indirect_vote( voter.id, itr->voter, (itr->vote_percent > 0) == (vote > 0) );
++itr;
}
}

void operator()( const comment_operation& op )const {
update_tags( _db.get_comment( op.author, op.permlink ) );
}

void operator()( const vote_operation& op )const {
update_tags( _db.get_comment( op.author, op.permlink ) );
update_peer_stats( _db.get_account(op.voter),
_db.get_account(op.author),
_db.get_comment(op.author, op.permlink),
op.weight );
}


template<typename Op>
void operator()( Op&& )const{} /// ignore all other ops
};
Expand Down Expand Up @@ -258,6 +310,7 @@ void tags_plugin::plugin_initialize(const boost::program_options::variables_map&
database().post_apply_operation.connect( [&]( const operation_object& b){ my->on_operation(b); } );
database().add_index< primary_index< tag_index > >();
database().add_index< primary_index< tag_stats_index > >();
database().add_index< primary_index< peer_stats_index > >();

app().register_api_factory<tag_api>("tag_api");
}
Expand Down

0 comments on commit a01b04e

Please sign in to comment.