<commit_before>var MongoClient = require('mongodb').MongoClient;
var cfg = require("./cfg.json");
var Q = require('q');
var extend = require('util')._extend;

// Note:
// using promises/Q for the first time in my life

var MAX_RATING_HISTORY_COUNT = 50;
var MOVING_AVERAGE_COUNT = 50;
var GAMETYPES_AVAILABLE = ["ad", "ctf", "tdm"];
var MATCH_RATING_CALC_METHODS = {
  'ad': { 'match_rating':
    { $multiply: [
      { $add: [
        { $divide: [ "$scoreboard.damage-dealt", 100 ] },
        "$scoreboard.kills",
      ]},
      { $divide: [ 1200, "$scoreboard.time" ] }
    ]}
  },
  'ctf': { 'match_rating':
    { $add: [
      { $multiply: [
        1/2.35,
        { $divide: [ "$scoreboard.damage-dealt", "$scoreboard.damage-taken" ] },
        { $add: [ "$scoreboard.score", { $divide: [ "$scoreboard.damage-dealt", 20 ] } ] },
        { $divide: [ 1200, "$scoreboard.time" ] }
      ]},
      { $multiply: [
        1/2.35,
        300,
        { $cond: ["$scoreboard.win", 1, 0] }
      ]}
    ]}
  },
  'tdm': { 'match_rating': 
    { $add: [
      { $multiply: [ 0.5, { $subtract: ["$scoreboard.kills", "$scoreboard.deaths"] } ] },
      { $multiply: [ 0.004, { $subtract: [ "$scoreboard.damage-dealt", "$scoreboard.damage-taken" ] } ] },
      { $multiply: [ 0.003, "$scoreboard.damage-dealt" ] }
    ]}
  }
};

var RATING_FACTORS = {
  'ad': 1,
  'ctf': 1,
  'tdm': {
    $add: [
      1,
      { $multiply: [ 0.15, { $subtract: [
        { $divide: ["$w", "$n"] }, { $divide: ["$l", "$n"] }
      ] } ] },
    ]
  }
};


var connect = function(callback, fuck) {
  MongoClient.connect(cfg['db-url'], function(err, db) {
    if (err)
      fuck(err);
    else
      callback(db);
  });
};


var in_array = function(what, array) {
  return array.some(function(param) {return param==what});
};


var get_aggregate_options = function(gametype, after_unwind, after_project) {
  return [].concat([
    { $match: { "gametype": gametype } },
    { $sort: { "timestamp": 1 } },
    { $unwind: "$scoreboard" },
    { $match: { "scoreboard.time": { $gt: 300 } } },
    { $match: { "scoreboard.damage-taken": { $gt: 100 } } }
  ], after_unwind, [
    { $project: extend({"scoreboard.steam-id": 1, "scoreboard.win": 1}, MATCH_RATING_CALC_METHODS[gametype]) },
    { $group: {
      _id: "$scoreboard.steam-id", 
      n: { $sum: 1 },
      w: { $sum: { $cond: ["$scoreboard.win", 1, 0] } },
      l: { $sum: { $cond: ["$scoreboard.win", 0, 1] } },
      last_match_timestamp: { $max: "$timestamp" },
      match_ratings: { $addToSet: "$match_rating" }
    }},
    { $project: {
      _id: 1, 
      n: 1,
      last_match_timestamp: 1,
      rating: { $multiply: [ { $avg: { $slice: [
        "$match_ratings",
        { $max: [ { $subtract: ["$n", MOVING_AVERAGE_COUNT] }, 0] }, // max(n-20,0)
        { $min: [ "$n", MOVING_AVERAGE_COUNT ] } // min(n,20)
      ] } }, RATING_FACTORS[gametype] ] }
    }}
  ], after_project);
};


var parse_stats_submission = function(body) {
  var len = function(obj) {
    return Object.keys(obj).length;
  };

  // storage vars for the request body
  var game_meta = {};
  var events = {};
  var players = [];
  var teams = [];

  // we're not in either stanza to start
  var in_P = false;
  var in_Q = false;
  var lines = body.split("\n");
  for(var k=0; k<lines.length; k++) {
    var line = lines[k];
    var key = line.substr(0, line.indexOf(' '));
    var value = line.substr(line.indexOf(' ')+1);

    if (in_array(key, ['P', 'Q', 'n', 'e', 't', 'i']) == false) {
      game_meta[key] = value;
    }
    
    if (in_array(key, ['Q', 'P'])) {
      //log.debug('Found a {0}'.format(key))
      //log.debug('in_Q: {0}'.format(in_Q))
      //log.debug('in_P: {0}'.format(in_P))
      //log.debug('events: {0}'.format(events))

      // check where we were before and append events accordingly
      if ( in_Q && (len(events) > 0) ) {
        // log.debug('creating a team (Q) entry')
        teams.push(events);
        events = {};
      } else if (in_P && (len(events) > 0) ) {
        // log.debug('creating a player (P) entry')
        players.push(events);
        events = {};
      }

      if (key == 'P') {
        //log.debug('key == P')
        in_P = true;
        in_Q = false;
      } else if (key == 'Q') {
        //log.debug('key == Q')
        in_P = false;
        in_Q = true;
      }

      events[key] = value;
    }
    
    
    if (key == 'e') {
      var subkey = value.split(' ')[0];
      var subvalue = value.split(' ')[1];
      events[subkey] = subvalue;
    }
    if (key == 'n') {
      events[key] = value;
    }
    if (key == 't') {
      events[key] = value;
    }
  }

  // add the last entity we were working on
  if (in_P && (len(events) > 0) ) {
    players.push(events);
  } else if (in_Q && (len(events) > 0) ) {
    teams.push(events);
  }
  
  return {game_meta: game_meta, players: players, teams: teams};
};


var updateRatings = function(db, docs, gametype, update_history) {
  // ignoring not supported gametypes
  if (typeof(GAMETYPES_AVAILABLE[gametype]) == 'undefined') {
    throw new Error('gametype is not supported: ' + gametype);
  }

  return Q.all(docs.map(function(doc) {
    var result = {};
    result[gametype + ".n"] = doc.n;
    result[gametype + ".rating"] = parseFloat(doc.rating.toFixed(2));
    if (update_history) {
      var history = {};
      history[gametype + ".history"] = {
        "timestamp": doc.last_match_timestamp,
        "rating": parseFloat(doc.rating.toFixed(2))
      }
      return db.collection('players').update(
        {_id: doc._id},
        { $set: result },
        { $push:
          { $each: [history], $slice: -MAX_RATING_HISTORY_COUNT }
        }<commit_msg>:cat:<commit_after>var MongoClient = require('mongodb').MongoClient;
var cfg = require("./cfg.json");
var Q = require('q');
var extend = require('util')._extend;

// Note:
// using promises/Q for the first time in my life

var MAX_RATING_HISTORY_COUNT = 50;
var MOVING_AVERAGE_COUNT = 50;
var GAMETYPES_AVAILABLE = ["ad", "ctf", "tdm"];
var MATCH_RATING_CALC_METHODS = {
  'ad': { 'match_rating':
    { $multiply: [
      { $add: [
        { $divide: [ "$scoreboard.damage-dealt", 100 ] },
        "$scoreboard.kills",
      ]},
      { $divide: [ 1200, "$scoreboard.time" ] }
    ]}
  },
  'ctf': { 'match_rating':
    { $add: [
      { $multiply: [
        1/2.35,
        { $divide: [ "$scoreboard.damage-dealt", "$scoreboard.damage-taken" ] },
        { $add: [ "$scoreboard.score", { $divide: [ "$scoreboard.damage-dealt", 20 ] } ] },
        { $divide: [ 1200, "$scoreboard.time" ] }
      ]},
      { $multiply: [
        1/2.35,
        300,
        { $cond: ["$scoreboard.win", 1, 0] }
      ]}
    ]}
  },
  'tdm': { 'match_rating': 
    { $add: [
      { $multiply: [ 0.5, { $subtract: ["$scoreboard.kills", "$scoreboard.deaths"] } ] },
      { $multiply: [ 0.004, { $subtract: [ "$scoreboard.damage-dealt", "$scoreboard.damage-taken" ] } ] },
      { $multiply: [ 0.003, "$scoreboard.damage-dealt" ] }
    ]}
  }
};

var RATING_FACTORS = {
  'ad': 1,
  'ctf': 1,
  'tdm': {
    $add: [
      1,
      { $multiply: [ 0.15, { $subtract: [
        { $divide: ["$w", "$n"] }, { $divide: ["$l", "$n"] }
      ] } ] },
    ]
  }
};


var connect = function(callback, fuck) {
  MongoClient.connect(cfg['db-url'], function(err, db) {
    if (err)
      fuck(err);
    else
      callback(db);
  });
};


var in_array = function(what, array) {
  return array.some(function(param) {return param==what});
};


var get_aggregate_options = function(gametype, after_unwind, after_project) {
  return [].concat([
    { $match: { "gametype": gametype } },
    { $sort: { "timestamp": 1 } },
    { $unwind: "$scoreboard" },
    { $match: { "scoreboard.time": { $gt: 300 } } },
    { $match: { "scoreboard.damage-taken": { $gt: 100 } } }
  ], after_unwind, [
    { $project: extend({"scoreboard.steam-id": 1, "scoreboard.win": 1}, MATCH_RATING_CALC_METHODS[gametype]) },
    { $group: {
      _id: "$scoreboard.steam-id", 
      n: { $sum: 1 },
      w: { $sum: { $cond: ["$scoreboard.win", 1, 0] } },
      l: { $sum: { $cond: ["$scoreboard.win", 0, 1] } },
      last_match_timestamp: { $max: "$timestamp" },
      match_ratings: { $addToSet: "$match_rating" }
    }},
    { $project: {
      _id: 1, 
      n: 1,
      last_match_timestamp: 1,
      rating: { $multiply: [ { $avg: { $slice: [
        "$match_ratings",
        { $max: [ { $subtract: ["$n", MOVING_AVERAGE_COUNT] }, 0] }, // max(n-20,0)
        { $min: [ "$n", MOVING_AVERAGE_COUNT ] } // min(n,20)
      ] } }, RATING_FACTORS[gametype] ] }
    }}
  ], after_project);
};


var parse_stats_submission = function(body) {
  var len = function(obj) {
    return Object.keys(obj).length;
  };

  // storage vars for the request body
  var game_meta = {};
  var events = {};
  var players = [];
  var teams = [];

  // we're not in either stanza to start
  var in_P = false;
  var in_Q = false;
  var lines = body.split("\n");
  for(var k=0; k<lines.length; k++) {
    var line = lines[k];
    var key = line.substr(0, line.indexOf(' '));
    var value = line.substr(line.indexOf(' ')+1);

    if (in_array(key, ['P', 'Q', 'n', 'e', 't', 'i']) == false) {
      game_meta[key] = value;
    }
    
    if (in_array(key, ['Q', 'P'])) {
      //log.debug('Found a {0}'.format(key))
      //log.debug('in_Q: {0}'.format(in_Q))
      //log.debug('in_P: {0}'.format(in_P))
      //log.debug('events: {0}'.format(events))

      // check where we were before and append events accordingly
      if ( in_Q && (len(events) > 0) ) {
        // log.debug('creating a team (Q) entry')
        teams.push(events);
        events = {};
      } else if (in_P && (len(events) > 0) ) {
        // log.debug('creating a player (P) entry')
        players.push(events);
        events = {};
      }

      if (key == 'P') {
        //log.debug('key == P')
        in_P = true;
        in_Q = false;
      } else if (key == 'Q') {
        //log.debug('key == Q')
        in_P = false;
        in_Q = true;
      }

      events[key] = value;
    }
    
    
    if (key == 'e') {
      var subkey = value.split(' ')[0];
      var subvalue = value.split(' ')[1];
      events[subkey] = subvalue;
    }
    if (key == 'n') {
      events[key] = value;
    }
    if (key == 't') {
      events[key] = value;
    }
  }

  // add the last entity we were working on
  if (in_P && (len(events) > 0) ) {
    players.push(events);
  } else if (in_Q && (len(events) > 0) ) {
    teams.push(events);
  }
  
  return {game_meta: game_meta, players: players, teams: teams};
};


var updateRatings = function(db, docs, gametype, update_history) {
  return Q.all(docs.map(function(doc) {
    var result = {};
    result[gametype + ".n"] = doc.n;
    result[gametype + ".rating"] = parseFloat(doc.rating.toFixed(2));
    if (update_history) {
      var history = {};
      history[gametype + ".history"] = {
        "timestamp": doc.last_match_timestamp,
        "rating": parseFloat(doc.rating.toFixed(2))
      }
      return db.collection('players').update(
        {_id: doc._id},
        { $set: result },
        { $push:
          { $each: [history], $slice: -MAX_RATING_HISTORY_COUNT }
        }
{'fraction_non_alphanumeric': 0.20336718332442544, 'fraction_numerical': 0.014965259219668627, 'mean_word_length': 2.379590608067429, 'pattern_counts': {'":': 16, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6660031', 'n_tokens_mistral': 4416, 'n_tokens_neox': 4217, 'n_words': 848}