29

Update to clarify what I'm asking for (based on discussions on a few other questions there seems to be conflation of two distinct things). I'm not interested in badges for answers that I have accepted, but in badges and statistics for answers that I have given that have been accepted.

I have currently answered 460 questions. Of those answers, 149 have been accepted, giving an answer acceptance rate of 32.4%. I would like to be able to see my and other user's answer acceptance rate on the user's profile page. In my opinion this gives some measure of the quality of their answers.

I've found some related posts that indicate this request has broad appeal. I've added links to this question in the comments for those questions:

I understand that badges are provided to drive behaviour. When your answer is accepted it indicates that you've helped the user who posted the question. I have yet to get round to it, but I strongly suspect that answers to popular tags are voted for more heavily because of the broader user base, and some of the simplest answers I've seen have yielded many votes because everyone can grok the answer (pretty much the bike shed principle).

There seems little incentive to answer hard, or tightly focused technical questions, as they can take a lot more effort, and few people are interested/will understand if the answer is correct or helpful, therefore I suspect they tend to yield few votes.

Provide badges for having a number of answers accepted in a particular tag seems an effective way of encouraging users to answer less popular questions.

I suggest providing badges for accepted answers in a tag because it will encourage users to answer harder or less popular questions, and give those that do so something to aim for.

Here's a couple of screenshots from my GM script to illustrate what it could look like.

Modified answer summary:

accepted

Additional badges for accepted answers (with threshold for bronze at 5 and silver at 25, gold at 100 for testing). Obviously if this were implemented properly the accepted badges would appear in the normal Badges section.

badges

4
  • +1 meta.stackexchange.com/questions/8482/… Commented Aug 26, 2009 at 17:07
  • Will this work for the other sites too?
    – ChrisF Mod
    Commented Sep 15, 2009 at 22:27
  • @ChrisF currently it will work on one site at a time by changing the @include statement at the top of the script. I'll modify it to use different caches for each site when I get a chance Commented Sep 16, 2009 at 9:59
  • @ChrisF added support for the other sites, now caches results using the first part of the host name to separate them Commented Sep 16, 2009 at 11:20

3 Answers 3

7

You can see my profile for example to see that I keep track of my accepted answers (I got the Meta-Check) with a custom badge that increments for every accepted. Unfortunately I have to keep it up to date with manual counting.

I like the idea of a badge being given for X accepted answers (silver and gold levels).

5

This is possible using the advanced search options.

https://meta.stackoverflow.com/search

try searching for

user:me isaccepted:1

2
  • 1
    Thanks for the information, the other part of my question was about having badges for having answers accepted by tag, is that a worthwhile feature? I've hacked together a script to illustrate how it could be done (please see my own answer, I'll add some screenshots once I find a suitable site to host them) Commented Aug 23, 2009 at 22:24
  • Oooh, another hidden feature...
    – Shog9
    Commented Aug 23, 2009 at 23:14
3

As a learning exercise I wrote a (not so small anymore) GreaseMonkey script to add the number of accepted answers to the user's profile page. Any feedback welcome

Updated some more: The script has been updated some more:

  • display the total accepted answers and 'hit rate' for all users (where hit rate is the percentage of questions answered that have been accepted).
  • insert an "Accepted Badges" section after the Badges on the user's profile page.
  • each "accepted badge" has the number of answers accepted for that tag displayed next to it,
  • each "accepted badge" has a tooltip showing the total answers, total accepted, and percentage for that tag.
  • clicking an "accepted badge" will take you to a search page listing all answers you've given that have been accepted for that tag
  • caches the accepted badges data and updates it no more than once an hour.
  • You get a bronze badge for 50 accepted answers in a given tag, a silver for 100 accepted and a gold for 250. This gives me 1 silver badge.
  • requests data for each tag asynchronously, one tag every 2 seconds to avoid spamming the server

New! Now with added working! New!

  • New! Adds tags for total accepted answers as per this answer (Gold for 1000 is Savant, Silver for 400 is Sage and Bronze for 50 is Seer - I like the alliteration).
  • New! now doesn't set the page size to 1 but uses the current page size (much less annoying).
  • New! sets the column widths to match the badges table (4 or 5 columns)

Thanks to this answer, it now chains the requests

My User page ends up with a line like this in the answers section:

accepted answer totals screenshot

The results for a few users from the SO front page:

Jon Skeet:      5299 Answers 2347 Accepted (44.3 %)
                3 Gold (c#, .net, java), 2 Silver (linq, generics), 6 Bronze
Mark Gravel:    3931 Answers 1547 Accepted (39.4 %) 
                2 Gold (c#, .net), 2 Silver (linq, linq-to-sql), 8 Bronze
Alex Martelli:  1817 Answers 585 Accepted (32.2%), 
                1 Gold (python)

I have streamlined the processing by working through the list of tags on the profile page, and only requesting results for tags above the bronze threshold (50). This is much quicker, and works for all users for the medium term (even Mr Skeet currently only has 11 tags over the threshold).


// ==UserScript==
// @name           StackOverflow - Accepted Answer Munger
// @namespace      StackOverflow
// @description    Adds accepted answer count and accepted badges section to the user profile page
// @include        https://stackoverflow.com/users/*
// @include        http://meta.stackoverflow.com/users/*
// @include        http://superuser.com/users/*
// @include        http://serverfault.com/users/*
// ==/UserScript==

(function(){
    function GM_init() {
        if(typeof unsafeWindow.jQuery == 'undefined') {
            window.setTimeout(GM_wait,100);
        } else {
            jQuery_init(unsafeWindow.jQuery);
        }
    }

    GM_init();

    unsafeWindow.update_answers = function(accepted) {
        window.setTimeout(function() {
            GM_setValue(accepted.host+"_tag_stats", accepted.toSource());
            GM_setValue(accepted.host+"_accepted_count_last", new Date().getHours());
        }, 0);
    };

    function get_accepted_ratio_text(total_answers, accepted_count) {
        if(accepted_count === 0 || total_answers === 0 ) {
            return 'N/A';
        }
        return '(' + (Math.round((accepted_count*1000)/total_answers) /10) + ' %)';
    }

    function jQuery_init($) {
        var user_id = $("link[rel=canonical]").attr("href").split("/")[4];
        var current_user_id = $("#hlinks a[href^='/users/recent/']").attr("href").split("/")[3];

        var host = window.location.host;
        host = host.substring(0, host.indexOf('.'));

        var last_updated = GM_getValue(host+"_accepted_count_last","0");

        var current_time = new Date().getTime();

        var accepted = eval(GM_getValue(host+"_tag_stats", {tag_stats:[], host:""}));
        accepted.host = host;

        var total_answers = $('.summarycount:eq(2)').html();

        var current_hours = new Date().getHours();

        var threshold = 50;

        var page_size = $(".answer-pager > a:first").attr("href").split("&")[1].split("=")[1];

        function update_total(accepted, accepted_count) {
            accepted.accepted_count =accepted_count;
            var curr = $('h1:eq(2)').parents("td:first");

            if(accepted_count == undefined) {
                accepted_count=0;
            }

            var aggregate_badges = 0;

            var accepted_descs = [["Savant", 1000, 1], ["Sage", 400, 2], ["Seer", threshold, 3]];

            for (var j=0; j<accepted_descs.length; j++) {
                if(accepted.accepted_count >= accepted_descs[j][1] && aggregate_badges < accepted_descs[j][2]) {
                    aggregate_badges++;
                    accepted.tag_stats[accepted.tag_stats.length] = accepted_descs[j];
                }
            }

            curr.after('<td style="padding-left: 10px;"><div class="summarycount acceptedCount" style="text-align: left;">'+accepted_count+'</div></td><td style="vertical-align: middle; padding-left: 10px;"><h1>Accepted</h1></td><td style="vertical-align: middle; padding-left: 10px;"><div class="item-multiplier acceptedRatio" style="text-align: right;" title="ratio of answers given that have been accepted">' + get_accepted_ratio_text(total_answers, accepted_count) + '</div></td>');
        }

        function get_search_text(user_id, page_size, is_accepted, page, tag_name) {
            var search_text = "/search?q=user%3A"+user_id;

            if(is_accepted) {
                search_text = search_text + "+isaccepted%3A1";
            }

            if(tag_name !== undefined) {
                search_text = search_text + "+["+tag_name+"]";
            }

            search_text = search_text+"&pagesize=" + page_size;

            if(page !== undefined) {
                search_text = search_text + "&page="+page;
            }

            return search_text;
        }

        function first_callback(data, update_callback, is_accepted, tag_name, wrapper_count) {
            var page_count = $(".page-numbers", data).length - 6;
            var count = 0;

            if(page_count > 0) {
                count = $(".page-numbers:eq("+page_count+")", data).html();
                $.get(get_search_text(user_id, page_size, is_accepted, count, tag_name),
                    function(data){answer_callback(data, update_callback, count, tag_name, wrapper_count)}, "html");
            } else {
                count = $(".answer-hyperlink", data).length;
                update_callback(accepted, count, tag_name, wrapper_count);
            }
        };

        function answer_callback(data, update_callback, page_count, tag_name, wrapper_count) {
            var count = ((page_count-1)* page_size) + $(".answer-hyperlink", data).length;

            update_callback(accepted, count, tag_name, wrapper_count);
        };

        //get the total accepted answers
        $.get(get_search_text(user_id, page_size, true, 1),
            function(data){first_callback(data, update_total, true)}, "html");

        //only show the total for other users, badges is too many requests
        if (current_user_id !=  user_id) { return ; } //Looking at someone else's page, cancel ///

        if(last_updated === current_hours) {
            insert_badges(accepted);

            var unread = $('.accepted_badges_summary:last');
            unread.html('');

            return;
        }

        accepted.tag_stats=[];

        curr = $('.user-stats-table:eq(3)');

        var post_tags = $(".post-tag", curr);

        function update_answer_tags(accepted) {
            accepted.tag_stats.sort(function (a,b) {
                return b[1]-a[1];
            });

            insert_badges(accepted);

            unsafeWindow.update_answers(accepted);
        }

        function insert_badges(accepted) {
            var tag_stats = accepted.tag_stats;

            //find the last item over the threshold and process only those elements over it
            for (var j=0; j<tag_stats.length; j++) {
                if(accepted.tag_stats[j][1] < threshold) {
                   tag_stats = tag_stats.slice(0,j);
                   break;
                }
            }

            curr = $('.user-stats-table:eq(4)');

            var column_count = $('tr:first > td', curr).length;

            var acceptedbadges = $('.accepted_badges_summary:first');

            var badge_html = "<tr>";
            var badge_count = tag_stats.length;

            if(acceptedbadges.length === 0 ) {
                curr.after('<p> </p><table><tbody><tr><td><div class="summarycount accepted_badges_summary" style="text-align: right;"> '+badge_count+'</div></td><td style="vertical-align: middle; padding-left: 10px;"><h1>Accepted Badges</h1></td><td style="vertical-align: middle; padding-left: 10px;"><td><div class="accepted_badges_summary"></div></td></tr></tbody></table><div class="user-stats-table"><table><tbody class="accepted_badges"></tbody></table></div>');
                var unread = $('.accepted_badges_summary:last');
                unread.html('<img src="http://sstatic.net/so/img/ajax-loader.gif" alt="">');
            } else {
                acceptedbadges.html(badge_count);
            }

            curr = $('.accepted_badges:first');

            for (var i=0; i<tag_stats.length; i++) {
               badge_html = badge_html+'<td style="width: 200px;">'+get_badge(tag_stats[i])+'</td>';

               if((i % column_count) == (column_count - 1)) {
                   badge_html = badge_html+'</tr><tr>';
               }

               if(i == badge_count - 1) {
                   badge_html = badge_html+'</tr>';
               }
            }

            curr.html(badge_html);

            function get_badge_desc(badge_type) {
                if(badge_type==3) {
                    return "bronze";
                }
                if(badge_type==2) {
                    return "silver";
                }
                return "gold";
            }

            function get_badge_html(badge_type, accepted_count, tag_name, total_count) {
                var badge_tooltip = accepted_count +" accepted answers";
                var multiplier_text = "";
                var href_insert_text = "";
                if(total_count !== undefined) {
                    badge_tooltip = accepted_count + '/'+total_count+' '+get_accepted_ratio_text(total_count, accepted_count)+' accepted answers for tag ' + unescape(tag_name);
                    multiplier_text = '<span class="item-multiplier">'+accepted_count+'</span><br/>';
                    href_insert_text = "["+tag_name+"]";
                }

                return '<a class="badge" title="'+ get_badge_desc(badge_type)+' badge:' + badge_tooltip + '" href="/search?q=user%3A'+user_id+'+isaccepted%3A1'+href_insert_text+'&pagesize='+page_size+'"><span class="badge'+badge_type+'">&#9679;</span> '+unescape(tag_name)+'</a>' + multiplier_text;
            }

            function get_badge(tag_stats) {
                var tag = tag_stats[0];
                var accepted_count =tag_stats[1];
                var badge_type =tag_stats[2];

                if(tag_stats.length > 3) {
                    return get_badge_html(badge_type, accepted_count, tag, tag_stats[3]);
                }

                return get_badge_html(badge_type, accepted_count, tag);
            }
        }

        function load_answers() {
            if (!GM_getValue) {
                alert('Please upgrade to the latest version of Greasemonkey.');
                return;
            }

            //callback to get the accepted answers for a tag
            function get_accepted(accepted, accepted_count, tag_name) {
                if(accepted_count >= threshold) {
                    $.get(get_search_text(user_id, page_size, false, 1, tag_name),
                        function(data){first_callback(data, update_tag, false, tag_name, accepted_count)}, "html");
                }
            }

            //callback to get the accepted answers for a tag
            function update_tag(accepted, total_count, tag_name, accepted_count) {
                if(accepted_count > 0) {
                    var badge_type = 3;
            if(accepted_count >= 100) {
                badge_type = 2;
            }
            if(accepted_count >= 400) {
                badge_type = 1;
            }
                    accepted.tag_stats[accepted.tag_stats.length] = [tag_name, accepted_count, badge_type, total_count];
                    update_answer_tags(accepted);
                }
            }

            curr = $('.user-stats-table:eq(3)');

            var SerialAjaxExecuter = function( onComplete, delay ) {
                this.requests = [];
                this.results  = [];
                this.delay    = delay || 1;
                this.onComplete = onComplete;
            }

            SerialAjaxExecuter.prototype.addRequest = function( method, url, data, callback, format ) {
                var self = this;
                this.requests.push( {
                    "method"    : method
                  , "url"       : url
                  , "data"      : data
                  , "format"    : format
                  , "callback"  : callback
                } );
                var numRequests = this.requests.length;
                if ( numRequests > 1 ) {
                    this.requests[numRequests-2].callback = function( nextRequest, completionCallback ) {
                        return function( data ) {
                            completionCallback( data );
                            setTimeout( function(){ self.execute( nextRequest ); }, self.delay );
                        }
                    }( this.requests[numRequests-1], this.requests[numRequests-2].callback )
                }
            }

            SerialAjaxExecuter.prototype.execute = function( request ) {
              var self = this;
              if ( 'undefined' == typeof request ) {
                  request = this.requests[0];
                  var lastRequest = this.requests[this.requests.length-1];
                  lastRequest.callback = function( completionCallback ) {
                      return function( data  ) {
                          completionCallback( data )
                          self.onComplete( self.results );
                      }
                  }( lastRequest.callback )
              }
              request.method( request.url, request.data, function( r ) {
                  return function( data ) {
                      self.results.push( data );
                      r.callback( data );
                  }
              }( request ) )
            }

            $(function(){
                var se = new SerialAjaxExecuter( function( results ) {
                    console.log( results );
                }, 2000 );

                for (var page_count=0; page_count < post_tags.length; page_count++) {
                    var current_tag = post_tags[page_count].toString().split("[")[1].split("]")[0];
                    var tag_count = $("a[href*='["+current_tag+"]']", curr).next();

                    if(tag_count.html().length > 0) {
                        tag_count = tag_count.html().substring(7);

                        if(tag_count >= threshold) {
                            (function delay_tranche() {
                                var tag_name = current_tag;

                                se.addRequest( $.get, get_search_text(user_id, page_size, true, 1, tag_name),
                                    {n:page_count},
                                    function(data){first_callback(data, get_accepted, true, tag_name)}, "html" );
                            })();
                        }
                    }
                }

                //remove the progress marker
                //TODO how to do this without a request?
                se.addRequest( $.get, "/search?q=user%3A"+user_id+"&pagesize="+page_size, function(data){
                    var unread = $('.accepted_badges_summary:last');
                    unread.html('');
                }, "html" );


                se.execute();
            });
        }

        load_answers();
    }
})();
5
  • There is no way to fix the scrolling.
    – jjnguy
    Commented Aug 21, 2009 at 13:23
  • it doesn't looks so bad now I've posted it, on the preview I had an inner scrolling section inside the normal scroll area Commented Aug 21, 2009 at 13:29
  • Very cool idea.
    – Kelsey
    Commented Sep 11, 2009 at 22:52
  • Nice script! Why not upload it to userscripts.org, so it's easier to install and track for new version.
    – iamamac
    Commented Jan 2, 2010 at 12:21
  • @Iamamac, what would be the reason for doing that? Commented Jan 2, 2010 at 15:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .