📜 ⬆️ ⬇️

Coursera in Russian: about achievements and awards

It seems that we only announced the official launch of the project “ Translate the Coursera ”, and our volunteers have already crossed the milestone in the first million translated words. We thank all the participants - you are real good fellows. We have prepared a special page for you , where we collected congratulations and infographics in honor of such an event.

As you know, the project is based on SmartCAT - ABBYY Language Services' own cloud platform for automating translation. It allows all Coursera translators to work with modern technologies for translation in a convenient interface: participants can use Translation Memory, automated support for the integrity of terminology, machine translation, view lecture video clips. We are constantly updating this technological foundation so that the resource becomes more convenient and helps our participants with the translation of lectures.

We think not only about convenience, but also about how to interest volunteers. Recently, the project has earned a system of awards - achivki. They reflect the participants' success in translating Coursera. Our developers have shared how this system is arranged and how it works. Here we will tell you about it today.

Given
Achivki are issued to the user in the course of his work on the platform and are a form of non-material rewards for good results. For participants, they look like medals and are displayed next to the avatar. They are issued for some success in translation. For example, a Specialist badge can be obtained by completing the translation of a specific part of the course (1, 7, ... 60, 100 %%). This is a multilevel level - the more you translate, the higher the level. There are also one-level: the transfer after the delivery is not subject to exchange.

Full list:
Number One - to get this honorary award, you need to take first place at least once.
Leader - achivka earned with hit in the top ten translators.
Expert - is given for voting activity: the more you evaluate the work of other participants, the higher the expert's skills, and therefore the more valuable the reward. An expert of the first level is awarded for the evaluation of five translations.
Specialist - a reward in several levels, which can be obtained for the transfer of a certain percentage of the course. For the first level it is enough to translate 1% of the entire course - and this is more than it seems. For each course for which this award is introduced, you can get your own Specialist.
The finalist is the translator whose work became the best and was chosen for placement on Coursera. The first level can be obtained for one translation, included in the final version of the subtitle for the site Coursera.

Technically, the reward is reduced to two actions of the program:
  1. Calculation for the user of a certain value - "progress" on the achivka;
  2. When certain thresholds of "progress" are reached, a reward is issued or its level is increased.

The reward should be visible:
  1. Immediately after receiving it in the working window of the application where the transfer is performed;
  2. All awards received are displayed in the user profile;
  3. And they are all visible in the ranking of participants.

It is important to remember that the value on the basis of which the next level of achievement is awarded can either increase or decrease with time. For example, the Leader award is given for getting into the top ten of the rating, and it is obvious that, once in the top 10, you can retire with time. We proceed from the assumption that once received a reward or its level is assigned to a user and can only change upwards.
')
Decision
What does the reward system do most of the time? Periodically recalculates the amount of progress on progress and when a certain threshold is exceeded, it generates an event of issuing a new entry or its new level.

Our developers have decided that it is best to place the code responsible for calculating progress, closer to the assembly of tools and away from the assemblies responsible for the subject area. What they did.

As a result, the code responsible for achievements is located in two assemblies:
  1. Achieve.Module - contains logic that does not depend on the subject area, the specific award and the conditions for its assignment. This assembly can be used "as is" in any other project.
  2. Achieve.Configuration.Module - contains information about the conditions for assigning achievements and algorithms for calculating progress. The localization of the calculation algorithms in this assembly made it possible to minimize changes in domain assemblies due to the introduction of tools. In particular, the revision of the subject domain assemblies has been reduced to declaring the public interfaces of event handlers, for example, IRatingUpdatedHandler, and calling their methods in appropriate places.

If a little more, then:
  1. The subject domain assembly declares the ISomeEventHandler event handler public interface and injects dependency on
    ICollection<ISomeEventHandler> 
    to the services that should trigger this event;
  2. The Achieve.Configuration.Module assembly references the domain assembly and implements the ISomeEventHandler the necessary number of times, recording actions in the Dependency Injection container;
  3. Assembling the domain calls ISomeEventHandler.Handle (some event params) after saving changes to the database;
  4. ISomeEventHandler.Handle (), implemented in Achieve.Configuration.Module, uses any domain services to read the information needed to recalculate progress;
  5. ISomeEventHandler.Handle () calculates progress and, if necessary, generates events of assigning new awards or new levels.

In other words, the calculation of progress is separated from the domain assemblies using the event mechanism. And in order to avoid problems associated with “ordinary” .net events, they are implemented on the basis of a combination of interfaces and Dependency Injection.

For example, the code of the module that registers in the DI container all the classes necessary for calculating the award Finalist :
 using System.Collections.Generic; using AbbyyLS.SmartCAT.BL.Crowd.Interfaces; using Ninject; using Ninject.Activation; using Ninject.Modules; namespace AbbyyLS.Achieve { class AchieveFinalistModule : NinjectModule { public override void Load() { Kernel.MultiBind<AchievmentConfig>() .ToMethodSafe(CreateFinalistAchievmentConfig); Kernel.MultiBind<IInitialAchieveProgressCalculator>() .To<ConfirmedVariantsCountCalculator>(); Kernel.MultiBind<IConfirmedTranslationVariantHandler>() .To<ConfirmedTranslationVariantHandler>(); } private AchievmentConfig CreateFinalistAchievmentConfig(IContext context) { var result = new AchievmentConfig { AchievmentName = AchievmentNames.Finalist, LevelSteps = new List<Dictionary<string, int>> { new Dictionary<string, int>{{ProgressNames.ConfirmedVariantsCount, 1}}, new Dictionary<string, int>{{ProgressNames.ConfirmedVariantsCount, 5}}, new Dictionary<string, int>{{ProgressNames.ConfirmedVariantsCount, 25}}, new Dictionary<string, int>{{ProgressNames.ConfirmedVariantsCount, 75}}, new Dictionary<string, int>{{ProgressNames.ConfirmedVariantsCount, 150}}, }, ProgressSteps = new List<Dictionary<string, int>>(), Ventures = new[] { KnownVentures.Coursera } }; return result; } } } 

So, we know that with some user actions there is a recalculation of progress and the generation of awards assignment events or their increase. What should have happened when the system was first started after the introduction of the tools? Obviously, it was necessary to calculate the progress of each user without any events.

This could be done with the help of methods that are called in event handlers that affect actions. But such methods are sharpened by calculating the progress for a single user and can use some data passed to the ISomeEventHandler.Handle (...) method from the domain assembly, which cannot be obtained when the application starts. For example, ISegmentTranslatedHandler.Handle (userId, segmentId). That is, it is necessary to implement the algorithm of its initial calculation separately from the algorithms for recalculating progress. In our case, in the Achieve.Module assembly, we declared the IInitialProgressCalculator interface, whose actions are implemented and registered in the DI container in the Achieve.Configuration.Module assembly.

Competitive initialization
One of the most unexpected surprises at the testing stage was connected with the initial calculation of progress. We did not take into account that its initialization can occur simultaneously on several servers of our application. Accordingly, if the user received the award or its level at the stage of the initial calculation of progress, then a notification about this was sent immediately from all servers of the application. The problem was solved with the help of an exclusive lock on the initial calculation through the database: the server, which did not get exclusive access to the original calculation, missed it.

Repeated recalculation of progress in event handling
At the testing stage, another interesting story emerged: it turned out that an event requiring the same calculation can be triggered en masse. It is obvious that to execute events with identical parameters simultaneously and repeatedly is not the best tactic. Therefore, we again used the mechanism of exclusive blocking through the database - this time to execute an event handler with specific values ​​of the event parameters. The lock was implemented not everywhere, but only where the problem was fixed.

Push notifications and periodic polling
Information about the awards earned is immediately displayed in the application's working window due to SignalR, a push notification system of the web client from the server side. Since we already used it to send other notifications, it was easy to add events to the events. Of course, the notification of the receipt of the next award should be shown on all pages. However, some of them are static web pages, and it would be too expensive and inappropriate to introduce SignalR there. Therefore, we decided to use a periodic server poll for this: it checks whether new achievements or their levels have appeared since the last request, and only then gives a command to the notification system.

Data storage
Achieve.Module stores the following data:
  1. Achivok configuration - thresholds of progress at which the award is awarded to the participant;
  2. The current value of progress in all achievements;
  3. The maximum level reached by the user for each award;
  4. Not viewed events assignment achivki.

Text localization
We are a linguistic company, and therefore we could not ignore this issue. When constructing texts that a participant sees together with the receipt of an award, there are nuances associated with the use of numerals in Russian: 1 translation, 2 translations, 5 translations, 11 translations, 21 translations. (As we know, everything in English is much simpler and comes down to the distinction of the singular and the plural: 1 translation, 2 translations.)

Therefore, we simply entered different text patterns for different numbers:


An example of how the template for localizing the text of the text looks like.

Method for autotests
Since the most important part of the logic of progressors - the initial calculation of progress - is performed once, then when writing tests (especially integration tests) there is a need for an API method that will allow you to reproduce this action many times. To do this, our developers have made the Recalculate () method, which causes the same recalculation of progress as when the application was first launched after the implementation of the reward system.

Sample code for the event handler that calls the progress recalculation:
 using System; using System.Collections.Generic; using System.Threading.Tasks; using AbbyyLS.Core.Helpers; using AbbyyLS.SmartCAT.BL.Crowd; using Ninject; using NLog; namespace AbbyyLS.Achieve { class VotedVariantCountChangedHandler : ITranslationVotedHandler { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); [Inject] public IAchieveService AchieveService { private get; set; } private readonly ITranslationVariantRepository _translationVariantRepository; public VotedVariantCountChangedHandler(ITranslationVariantRepository translationVariantRepository) { _translationVariantRepository = translationVariantRepository; } public void TranslationVoted(Guid accountId, Guid userId, Guid variantId) { Task.Run(() => translationVotedTask(accountId, userId)); } private void translationVotedTask(Guid accountId, Guid userId) { Log.Debug("Achieve event VotedVariantCountChangedHandler.TranslationVoted start"); try { using (new TimerLog(t => Log.Trace("Achieve event VotedVariantCountChangedHandler.TranslationVoted complete in {0} ms", t))) { var votedSegmentCount = (int) _translationVariantRepository.GetVotedTranslationsCount(userId, accountId); AchieveService.SetProgress(accountId, userId, new Dictionary<string, int> { {ProgressNames.VotedVariantCount, votedSegmentCount} }); } } catch (Exception ex) { Log.ErrorException(string.Format("VotedVariantCountChangedHandler.TranslationVotedTask accountId : {0} userId: {1}", accountId, userId), ex); } } } } 

Actually, today is all. To see in practice how it works, join the Coursera translation into Russian right here - we will be glad to see you. Please send all questions, wishes and suggestions to coursera@abbyy-ls.com or post them here in the comments.

Source: https://habr.com/ru/post/230057/


All Articles