Introducing CurationAssistant! Helpful tool for curation!

in #utopian-io5 years ago

FirstImage.png

Repository

https://github.com/lion-200/CurationAssistant

URL

http://www.curationassistant.com

What is Curation Assistant?

I recently started with curation activities on Steem by submitting good posts I read to curator groups like @c-squared and @curie.
Now that I have started the curation training for @curie from @gibic, I see that the life of a curator is not easy at all.
There are a lot of items in the checklist a curator needs to check before a post can be submitted for a nice upvote. This is something all curator groups have in common.
Curator groups with a higher upvote even have a more strict criteria before upvoting a submitted post.

Curation Assistant is an "assistant" for the curator to help him/her out by checking some basic rules defined by the curator.
The intention of Curation Assistant is to reduce the time spent on checking administrative facts, so that the curator can have more time to curate good stuff, which is good for the whole community. This project is set up generic, so that it can be used by more curator groups like @curie, @ocd, @c-squared etc.

An example of a rule is the post creation date range for a submitted post to be eligable for an upvote from curie.

Posts must be more than 30 minutes old, but less than 72 hours old, with maximum $2 pending payout.

There are more rules of this kind in other curation groups too.

Curation Assistant is developed to reduce time to check the technical/administrative details, so that curators can focus more on the content of the post.
There are still a lot of other manual checks needed to validate a post of course, like the check for plagiarism etc.
At least I made a start to build a tool which might be useful and hopefully helpful for curators of all groups.

Technology stack

c#net.jpg
I am a C# .NET developer, that's why I developed this tool with C# .NET.
There are not many projects on Steem developed with this programming language. I hope it grows in time, and I hope this tool can help others to build something of their own in .NET.

I created this tool with Visual Studio 2019 Community Edition.
The projects are targeting .NET Framework 4.7.2.
The following NuGet packages are used in this project frontend and backend:

  • ASP.NET MVC 5.2
  • Newtonsoft.Json 12.0
  • KnockoutJS 3.5
  • Bootstrap 4.3.1
  • jQuery 3.4.12
  • Log4net 2.0

For the calls I make to Steem API, I used the Steem.NET project created by @arcange: https://github.com/VIM-Arcange/Steem.NET
I am contributing to his project by sending PR's for new methods I use in Curation Assistant. Thanks for your contribution @arcange, and of course your support!
I added Steem.NET as a separate class library to the solution to easily keep track of the changes and keep the functionalities separated.

steem.net.png

For parsing the responses received from Steem API, I use Newtonsoft.Json to map the results to classes.
For displaying the data in a proper way, I use KnockoutJS which I also used for a different project before at my work. I like the simplicity of KnockoutJS.
And for some basic logging I use log4net.

What can Curation Assistant already do?

Currently there are 3 sections on the page which needs some explanation:
home.png

  1. Validation rules
  2. Searchbox
  3. Detailed information

1. Validation rules

validation.png
In this section, the user of the tool can decide on the Validation variables/rules which will be run on the post he/she wants to validate.
Currently the following rules are implemented:

  • Post create date range rule
  • Post max payout rule
  • Author reputation range rule
  • Engagement statistics:
    • Min. posts in x days rule
    • Min. comments in x days rule
  • Curation group upvote account VP rule

2. Seachbox

There is not much necessary to explain here then to put the link of the post here to validate & show details in the section below.

3. Detailed information

In this section there are 3 blocks which can be useful to check:

  • Account details containing SP, Rep, VP and create date of the author
  • Author transaction history containing top 10 most recent posts, comments and votes of the author
  • Post details containing (for now) basic post information and link

Running the validation

After putting the post link and hitting enter or "validate post" link, this is the result:
Validation summary
result1.png

Author details
result2.png

Author transaction history
result3.png

Post details
result4.png

Some code

Backend

For some statistics I needed to convert some solutions for calculating SP, Rep, VP etc. to C# code.
I created a ViewModel which contains the response of the Steem Api method mapped to a class, enriched with calculation data.
Below is an example of the response processing I implemented for get_accounts method and the enrichment with calculations:
For better readabilty I tried syntax highlighting but couldn't get it to work for C# code, so please bear with me for standard code syntax.

private AuthorViewModel GetAuthor(string accountName)
{
    var author = new AuthorViewModel();
    try
    {
        var globalProperties = GetDynamicGlobalProperties();
        var steemPerMVests = CalculationHelper.CalculateSteemPerMVests(globalProperties);
        author.Details = GetAccountDetails(accountName);
        author.ReputationCalculated = CalculationHelper.CalculateReputation(author.Details.reputation);
        author.SteemPowerCalculated = CalculationHelper.CalculateSteemPower(author.Details.vesting_shares, steemPerMVests);
        author.VotingManaPercentageCalculated = CalculationHelper.CalculateVotingManaPercentage(author.Details);
    }
    catch (Exception ex)
    {
        log.Error(ex);
    }

    return author;
}

This is the actual call to get response from Steem API:

private GetAccountsModel GetAccountDetails(string accountName)
{
    var account = new GetAccountsModel();

    try
    {
        if (!String.IsNullOrEmpty(accountName))
        {
            using (var csteemd = new CSteemd(ConfigurationHelper.HostName))
            {
                // account details
                var response = csteemd.get_accounts(new ArrayList() { accountName });
                if (response != null)
                {
                    var accountResult = response.ToObject<List<GetAccountsModel>>();
                    account = accountResult.FirstOrDefault();
                }
            }
        }
    }
    catch (Exception ex)
    {
        log.Error(ex);
    }

    return account;
}

Here are the C# variants of calculating SP, Reputation and VP %:
Calculate reputation

public static decimal CalculateReputation(string repString)
{
    decimal repResult = 0;
    double rep = 0;
    double.TryParse(repString, out rep);

    rep = Math.Log10(rep);
    rep -= 9;
    rep *= 9;
    rep += 25;

    if (rep > 0)
    {
        repResult = (Decimal)Math.Round(rep, 2);
    }

    return repResult;
}

Calculate steem power

public static decimal CalculateSteemPower(string spString, decimal steemPerMVests)
{
    var veryLargeNumber = 1e6;//Equals to 1*10^6

    double sp = 0;
    if (!String.IsNullOrEmpty(spString))
    {
        spString = spString.Replace("VESTS", "").Trim();
        double.TryParse(spString, out sp);

        sp = sp / veryLargeNumber * (double)steemPerMVests;
    }

    return Math.Round((decimal)sp, 2);
}

Calculate VP %

public static decimal CalculateVotingManaPercentage(GetAccountsModel accountDetails)
{
    var secondsAgo = DateTime.UtcNow.Subtract(accountDetails.last_vote_time).TotalSeconds;
    var vpNow = accountDetails.voting_power + (10000 * secondsAgo / 432000);
    var vp = Math.Min(Math.Round(vpNow / 100, 2), 100);
    return (decimal)vp;
}

Validation
I chose not to be very generic regarding the validation rules. That might change in the future but for now, these are the predefined rules and how I implemented them.
Basically I have a list of variables provided by user input, and pass these to the ValidationHelper methods to run specific validations:

public ValidationSummaryViewModel RunValidation(CurationDetailsViewModel model, ValidationVariables vars)
{
    var result = new ValidationSummaryViewModel();

    var validationItems = new List<ValidationItemViewModel>();
    try
    {
        validationItems.Add(ValidationHelper.ValidatePostCreateDateRule(model, vars));
        validationItems.Add(ValidationHelper.ValidatePostMaxPendingPayoutRule(model, vars));
        validationItems.Add(ValidationHelper.ValidateAuthorReputationRule(model, vars));
        validationItems.Add(ValidationHelper.ValidateMinPostsRule(model, vars));
        validationItems.Add(ValidationHelper.ValidateMinCommentsRule(model, vars));

        var upvoteAccountDetails = GetAccountDetails(vars.UpvoteAccount.Replace("@", ""));
        validationItems.Add(ValidationHelper.ValidateUpvoteAccountMinVPRule(upvoteAccountDetails, vars));
    }
    catch (Exception ex)
    {
        log.Error(ex);
    }

    result.Items = validationItems;

    return result;
}

Below you can see the actual implementation of Post Create Date Rule:

public static ValidationItemViewModel ValidatePostCreateDateRule(CurationDetailsViewModel model, ValidationVariables vars)
{
    ValidationPriority prio = ValidationPriority.High;
    ValidationResultType resultType = ValidationResultType.Failure;

    var validationItem = new ValidationItemViewModel();
    validationItem.Title = string.Format("Post creation date is > {0} minutes and < {1} hours", vars.PostCreatedAtMin, vars.PostCreatedAtMax / 60);
    validationItem.Priority = prio;
    validationItem.PriorityDescription = prio.ToString();
    validationItem.OrderId = 1;

    var postCreatedDate = model.BlogPost.Details.created;

    // Check if the post creation date is between the required ranges
    if (DateTime.Now > postCreatedDate.AddMinutes(vars.PostCreatedAtMin) &&
        DateTime.Now < postCreatedDate.AddMinutes(vars.PostCreatedAtMax))
    {
        resultType = ValidationResultType.Success;
    }
    else
    {
        resultType = ValidationResultType.Failure;
    }

    validationItem.ResultType = resultType;
    validationItem.ResultTypeDescription = resultType.ToString();

    var span = (TimeSpan)(DateTime.Now - postCreatedDate);
    validationItem.ResultMessage = string.Format("Post created {0} days, {1} hours, {2} minutes ago.", span.Days, span.Hours, span.Minutes);

    return validationItem;
}

Each Validation method returns a ValidationItemViewModel containing positive or negative result along with the rule.
And in the end, the result is posted to Frontend as Json:

json.png

Frontend

As I mentioned, I used KnockoutJS to bind the Backend classes to Frontend objects.
As an example, this is how the Author details block looks like in KnockoutJs and HTML.

First we make sure the response we got from Backend to be mapped to our object:
frontend1.png

We can map all child properties of the returned result automatically by using Knockout.Mapping library.
If you don't plan to do any fancy stuff, you can leave out the mapping and print out the values already. But because I am combining some values and present them differently than how they are received, I mapped them to observables.

function CurationDetailsViewModel(data) {
    var self = this;
    self.Author = ko.observable();
    self.BlogPost = ko.observable();
    self.ValidationSummary = ko.observable();
    self.BaseUrl = ko.observable("https://steemit.com");

    var mapping = {
        'Author': {
            create: function (options) {
                return new AuthorViewModel(options.data, self);
            }
        },
        'BlogPost': {
            create: function (options) {
                return new BlogPostViewModel(options.data, self);
            }
        },
        'Comments': {
            create: function (options) {
                return new ActionViewModel(options.data, self);
            }
        },
        'Posts': {
            create: function (options) {
                return new ActionViewModel(options.data, self);
            }
        },
        'Votes': {
            create: function (options) {
                return new ActionViewModel(options.data, self);
            }
        },
        'ValidationSummary': {
            create: function (options) {
                return new ValidationSummaryViewModel(options.data, self);
            }
        }
    }

    if (data != null)
        ko.mapping.fromJS(data, mapping, self);
}

The html is pretty clean:
frontend3.png

<tr>
    <th scope="row">Account</th>
    <td>
        <a data-bind="attr: { href: $root.BaseUrl() + '/@@' + Author().Details.name() }" target="_blank">
            <span data-bind="text: Author().Details.name"></span>
        </a>
    </td>
</tr>

<tr>
    <th scope="row">SteemPower</th>
    <td>
        <span class="btn btn-primary" data-bind="text: Author().SteemPowerCalculated()"></span>
    </td>
</tr>

For other KnockoutJS examples and Backend code, please feel free to check out the code in my GitHub repository.

Roadmap

I think there is now a good foundation to build more rules on.
Although there is not a strict roadmap, I think the following items can be very useful to implement:

  • Rule for total pending payout of the author
  • Rule for minimum amount of days ago the author received a big upvote
  • Rule for how many times an author received an upvote from the Upvote account of the curation community
  • SteemConnect or other type of authentication
  • Adding functionality to upvote a post right from the tool
  • I also consider using other sources (like SteemSQL) besides the Steem API. There are also other alternatives like streaming the blockchain and store the data I use in the app. This still needs some thinking...

This list can grow with some feedback from Curation Groups and individual curators.

How to contribute?

For Backend as well as Frontend you are welcome to contribute to this project.
I am a C# .NET backend developer for more than a decade. Although I do some frontend work too, my skills and interests lie more on the backend.
The project I have worked on and currently I am working on are Web projects for mostly internal use for the companies I work(ed) for. That's why putting a lot of things I did on Open Source was not an option.
I will post more of my work (which can be shared) via my GitHub repo in the future.

Ways to reach me?

Steem account obviously: @lion200
Discord account: @Lion_200
E-mail: [email protected] or [email protected]

GitHub Account

https://github.com/lion-200

Sort:  
  • Great article with all of the good stuff, images, code samples and explanation of coding choices.
  • Please provide the PR link of list of commit in your next article.
  • To improve the quality of the code, you should delete all of the dead code.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

Thanks for your review @helo!
If I don't have Pull Requests, because mainly I am developing myself, do you need the links to the commits next time?

Thank you for your review, @helo! Keep up the good work!

Hey @lion200. thanks for this. I think as someone who has always asked why I didn't get curated at some point it is good to understand the system behind it. It is not as though you are expecting it or targeting it in any way, but just to understand how it works is great.

Hey @cryptoandcoffee, thanks for your comment. Yes, it is indeed good to know how a post can qualify for a nice upvote. Each group has its own selection criteria of course. It is good know thesse, but like you mention, it is best to post in our own ways instead of targeting.

Posted using Partiko iOS

This is amazing.

You are amazing.

This would totally be one of my favourite tool to curate with, O M G

GAMECHANGER~

Thanks for your comment @veryspider :)
There is still a lot of work to do obviously, but this is a start. I'd be glad hearing feedback from you, also new ideas are welcome. In the end this tool is supposed to help curators.

Really neat to see all this detail, great work

Thank you! :)

Posted using Partiko iOS

how damn cool is that, and from the new kid on the block. I knew you were one to watch. Resteeming :-)

Thanks for your continuous support @paulag! :)

Hey, @lion200!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Thanks for the upvote!

Quite a helping hand for all the curators. Who put a lot of effort to choose the best

Posted using Partiko Android

Indeed @steemflow. Curating does cost a lot of time :)

I learn a little bit every day. Thanks

Posted using Partiko Android


This post was shared in the Curation Collective Discord community for curators, and upvoted and resteemed by the @c-squared community account after manual review.
@c-squared runs a community witness. Please consider using one of your witness votes on us here

Thanks for your support @c-squared and the community behind it ;)

Posted using Partiko iOS

Hi @lion200!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Coin Marketplace

STEEM 0.17
TRX 0.13
JST 0.027
BTC 60944.89
ETH 2608.11
USDT 1.00
SBD 2.67