From b6556a93f56bcf2280a20377db189b9fb9c15e1a Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 8 Dec 2021 23:52:52 -0600 Subject: [PATCH] Change rating system (DMOJ) --- .gitignore | 1 + judge/migrations/0117_auto_20211209_0612.py | 18 ++ judge/migrations/0118_rating.py | 208 ++++++++++++++++++ judge/models/contest.py | 3 +- judge/ratings.py | 226 +++++++++++--------- judge/views/api/api_v1.py | 9 +- judge/views/api/api_v2.py | 1 - templates/user/user-about.html | 16 +- templates/user/user-base.html | 4 - 9 files changed, 369 insertions(+), 117 deletions(-) create mode 100644 judge/migrations/0117_auto_20211209_0612.py create mode 100644 judge/migrations/0118_rating.py diff --git a/.gitignore b/.gitignore index 849cda5..a8a51bc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ sass_processed node_modules/ package-lock.json +/src diff --git a/judge/migrations/0117_auto_20211209_0612.py b/judge/migrations/0117_auto_20211209_0612.py new file mode 100644 index 0000000..dd3879a --- /dev/null +++ b/judge/migrations/0117_auto_20211209_0612.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.25 on 2021-12-08 23:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0116_auto_20211011_0645'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='timezone', + field=models.CharField(choices=[('Africa', [('Africa/Abidjan', 'Abidjan'), ('Africa/Accra', 'Accra'), ('Africa/Addis_Ababa', 'Addis_Ababa'), ('Africa/Algiers', 'Algiers'), ('Africa/Asmara', 'Asmara'), ('Africa/Asmera', 'Asmera'), ('Africa/Bamako', 'Bamako'), ('Africa/Bangui', 'Bangui'), ('Africa/Banjul', 'Banjul'), ('Africa/Bissau', 'Bissau'), ('Africa/Blantyre', 'Blantyre'), ('Africa/Brazzaville', 'Brazzaville'), ('Africa/Bujumbura', 'Bujumbura'), ('Africa/Cairo', 'Cairo'), ('Africa/Casablanca', 'Casablanca'), ('Africa/Ceuta', 'Ceuta'), ('Africa/Conakry', 'Conakry'), ('Africa/Dakar', 'Dakar'), ('Africa/Dar_es_Salaam', 'Dar_es_Salaam'), ('Africa/Djibouti', 'Djibouti'), ('Africa/Douala', 'Douala'), ('Africa/El_Aaiun', 'El_Aaiun'), ('Africa/Freetown', 'Freetown'), ('Africa/Gaborone', 'Gaborone'), ('Africa/Harare', 'Harare'), ('Africa/Johannesburg', 'Johannesburg'), ('Africa/Juba', 'Juba'), ('Africa/Kampala', 'Kampala'), ('Africa/Khartoum', 'Khartoum'), ('Africa/Kigali', 'Kigali'), ('Africa/Kinshasa', 'Kinshasa'), ('Africa/Lagos', 'Lagos'), ('Africa/Libreville', 'Libreville'), ('Africa/Lome', 'Lome'), ('Africa/Luanda', 'Luanda'), ('Africa/Lubumbashi', 'Lubumbashi'), ('Africa/Lusaka', 'Lusaka'), ('Africa/Malabo', 'Malabo'), ('Africa/Maputo', 'Maputo'), ('Africa/Maseru', 'Maseru'), ('Africa/Mbabane', 'Mbabane'), ('Africa/Mogadishu', 'Mogadishu'), ('Africa/Monrovia', 'Monrovia'), ('Africa/Nairobi', 'Nairobi'), ('Africa/Ndjamena', 'Ndjamena'), ('Africa/Niamey', 'Niamey'), ('Africa/Nouakchott', 'Nouakchott'), ('Africa/Ouagadougou', 'Ouagadougou'), ('Africa/Porto-Novo', 'Porto-Novo'), ('Africa/Sao_Tome', 'Sao_Tome'), ('Africa/Timbuktu', 'Timbuktu'), ('Africa/Tripoli', 'Tripoli'), ('Africa/Tunis', 'Tunis'), ('Africa/Windhoek', 'Windhoek')]), ('America', [('America/Adak', 'Adak'), ('America/Anchorage', 'Anchorage'), ('America/Anguilla', 'Anguilla'), ('America/Antigua', 'Antigua'), ('America/Araguaina', 'Araguaina'), ('America/Argentina/Buenos_Aires', 'Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'Argentina/Cordoba'), ('America/Argentina/Jujuy', 'Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'Argentina/Salta'), ('America/Argentina/San_Juan', 'Argentina/San_Juan'), ('America/Argentina/San_Luis', 'Argentina/San_Luis'), ('America/Argentina/Tucuman', 'Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'Argentina/Ushuaia'), ('America/Aruba', 'Aruba'), ('America/Asuncion', 'Asuncion'), ('America/Atikokan', 'Atikokan'), ('America/Atka', 'Atka'), ('America/Bahia', 'Bahia'), ('America/Bahia_Banderas', 'Bahia_Banderas'), ('America/Barbados', 'Barbados'), ('America/Belem', 'Belem'), ('America/Belize', 'Belize'), ('America/Blanc-Sablon', 'Blanc-Sablon'), ('America/Boa_Vista', 'Boa_Vista'), ('America/Bogota', 'Bogota'), ('America/Boise', 'Boise'), ('America/Buenos_Aires', 'Buenos_Aires'), ('America/Cambridge_Bay', 'Cambridge_Bay'), ('America/Campo_Grande', 'Campo_Grande'), ('America/Cancun', 'Cancun'), ('America/Caracas', 'Caracas'), ('America/Catamarca', 'Catamarca'), ('America/Cayenne', 'Cayenne'), ('America/Cayman', 'Cayman'), ('America/Chicago', 'Chicago'), ('America/Chihuahua', 'Chihuahua'), ('America/Coral_Harbour', 'Coral_Harbour'), ('America/Cordoba', 'Cordoba'), ('America/Costa_Rica', 'Costa_Rica'), ('America/Creston', 'Creston'), ('America/Cuiaba', 'Cuiaba'), ('America/Curacao', 'Curacao'), ('America/Danmarkshavn', 'Danmarkshavn'), ('America/Dawson', 'Dawson'), ('America/Dawson_Creek', 'Dawson_Creek'), ('America/Denver', 'Denver'), ('America/Detroit', 'Detroit'), ('America/Dominica', 'Dominica'), ('America/Edmonton', 'Edmonton'), ('America/Eirunepe', 'Eirunepe'), ('America/El_Salvador', 'El_Salvador'), ('America/Ensenada', 'Ensenada'), ('America/Fort_Nelson', 'Fort_Nelson'), ('America/Fort_Wayne', 'Fort_Wayne'), ('America/Fortaleza', 'Fortaleza'), ('America/Glace_Bay', 'Glace_Bay'), ('America/Godthab', 'Godthab'), ('America/Goose_Bay', 'Goose_Bay'), ('America/Grand_Turk', 'Grand_Turk'), ('America/Grenada', 'Grenada'), ('America/Guadeloupe', 'Guadeloupe'), ('America/Guatemala', 'Guatemala'), ('America/Guayaquil', 'Guayaquil'), ('America/Guyana', 'Guyana'), ('America/Halifax', 'Halifax'), ('America/Havana', 'Havana'), ('America/Hermosillo', 'Hermosillo'), ('America/Indiana/Indianapolis', 'Indiana/Indianapolis'), ('America/Indiana/Knox', 'Indiana/Knox'), ('America/Indiana/Marengo', 'Indiana/Marengo'), ('America/Indiana/Petersburg', 'Indiana/Petersburg'), ('America/Indiana/Tell_City', 'Indiana/Tell_City'), ('America/Indiana/Vevay', 'Indiana/Vevay'), ('America/Indiana/Vincennes', 'Indiana/Vincennes'), ('America/Indiana/Winamac', 'Indiana/Winamac'), ('America/Indianapolis', 'Indianapolis'), ('America/Inuvik', 'Inuvik'), ('America/Iqaluit', 'Iqaluit'), ('America/Jamaica', 'Jamaica'), ('America/Jujuy', 'Jujuy'), ('America/Juneau', 'Juneau'), ('America/Kentucky/Louisville', 'Kentucky/Louisville'), ('America/Kentucky/Monticello', 'Kentucky/Monticello'), ('America/Knox_IN', 'Knox_IN'), ('America/Kralendijk', 'Kralendijk'), ('America/La_Paz', 'La_Paz'), ('America/Lima', 'Lima'), ('America/Los_Angeles', 'Los_Angeles'), ('America/Louisville', 'Louisville'), ('America/Lower_Princes', 'Lower_Princes'), ('America/Maceio', 'Maceio'), ('America/Managua', 'Managua'), ('America/Manaus', 'Manaus'), ('America/Marigot', 'Marigot'), ('America/Martinique', 'Martinique'), ('America/Matamoros', 'Matamoros'), ('America/Mazatlan', 'Mazatlan'), ('America/Mendoza', 'Mendoza'), ('America/Menominee', 'Menominee'), ('America/Merida', 'Merida'), ('America/Metlakatla', 'Metlakatla'), ('America/Mexico_City', 'Mexico_City'), ('America/Miquelon', 'Miquelon'), ('America/Moncton', 'Moncton'), ('America/Monterrey', 'Monterrey'), ('America/Montevideo', 'Montevideo'), ('America/Montreal', 'Montreal'), ('America/Montserrat', 'Montserrat'), ('America/Nassau', 'Nassau'), ('America/New_York', 'New_York'), ('America/Nipigon', 'Nipigon'), ('America/Nome', 'Nome'), ('America/Noronha', 'Noronha'), ('America/North_Dakota/Beulah', 'North_Dakota/Beulah'), ('America/North_Dakota/Center', 'North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'North_Dakota/New_Salem'), ('America/Nuuk', 'Nuuk'), ('America/Ojinaga', 'Ojinaga'), ('America/Panama', 'Panama'), ('America/Pangnirtung', 'Pangnirtung'), ('America/Paramaribo', 'Paramaribo'), ('America/Phoenix', 'Phoenix'), ('America/Port-au-Prince', 'Port-au-Prince'), ('America/Port_of_Spain', 'Port_of_Spain'), ('America/Porto_Acre', 'Porto_Acre'), ('America/Porto_Velho', 'Porto_Velho'), ('America/Puerto_Rico', 'Puerto_Rico'), ('America/Punta_Arenas', 'Punta_Arenas'), ('America/Rainy_River', 'Rainy_River'), ('America/Rankin_Inlet', 'Rankin_Inlet'), ('America/Recife', 'Recife'), ('America/Regina', 'Regina'), ('America/Resolute', 'Resolute'), ('America/Rio_Branco', 'Rio_Branco'), ('America/Rosario', 'Rosario'), ('America/Santa_Isabel', 'Santa_Isabel'), ('America/Santarem', 'Santarem'), ('America/Santiago', 'Santiago'), ('America/Santo_Domingo', 'Santo_Domingo'), ('America/Sao_Paulo', 'Sao_Paulo'), ('America/Scoresbysund', 'Scoresbysund'), ('America/Shiprock', 'Shiprock'), ('America/Sitka', 'Sitka'), ('America/St_Barthelemy', 'St_Barthelemy'), ('America/St_Johns', 'St_Johns'), ('America/St_Kitts', 'St_Kitts'), ('America/St_Lucia', 'St_Lucia'), ('America/St_Thomas', 'St_Thomas'), ('America/St_Vincent', 'St_Vincent'), ('America/Swift_Current', 'Swift_Current'), ('America/Tegucigalpa', 'Tegucigalpa'), ('America/Thule', 'Thule'), ('America/Thunder_Bay', 'Thunder_Bay'), ('America/Tijuana', 'Tijuana'), ('America/Toronto', 'Toronto'), ('America/Tortola', 'Tortola'), ('America/Vancouver', 'Vancouver'), ('America/Virgin', 'Virgin'), ('America/Whitehorse', 'Whitehorse'), ('America/Winnipeg', 'Winnipeg'), ('America/Yakutat', 'Yakutat'), ('America/Yellowknife', 'Yellowknife')]), ('Antarctica', [('Antarctica/Casey', 'Casey'), ('Antarctica/Davis', 'Davis'), ('Antarctica/DumontDUrville', 'DumontDUrville'), ('Antarctica/Macquarie', 'Macquarie'), ('Antarctica/Mawson', 'Mawson'), ('Antarctica/McMurdo', 'McMurdo'), ('Antarctica/Palmer', 'Palmer'), ('Antarctica/Rothera', 'Rothera'), ('Antarctica/South_Pole', 'South_Pole'), ('Antarctica/Syowa', 'Syowa'), ('Antarctica/Troll', 'Troll'), ('Antarctica/Vostok', 'Vostok')]), ('Arctic', [('Arctic/Longyearbyen', 'Longyearbyen')]), ('Asia', [('Asia/Aden', 'Aden'), ('Asia/Almaty', 'Almaty'), ('Asia/Amman', 'Amman'), ('Asia/Anadyr', 'Anadyr'), ('Asia/Aqtau', 'Aqtau'), ('Asia/Aqtobe', 'Aqtobe'), ('Asia/Ashgabat', 'Ashgabat'), ('Asia/Ashkhabad', 'Ashkhabad'), ('Asia/Atyrau', 'Atyrau'), ('Asia/Baghdad', 'Baghdad'), ('Asia/Bahrain', 'Bahrain'), ('Asia/Baku', 'Baku'), ('Asia/Bangkok', 'Bangkok'), ('Asia/Barnaul', 'Barnaul'), ('Asia/Beirut', 'Beirut'), ('Asia/Bishkek', 'Bishkek'), ('Asia/Brunei', 'Brunei'), ('Asia/Calcutta', 'Calcutta'), ('Asia/Chita', 'Chita'), ('Asia/Choibalsan', 'Choibalsan'), ('Asia/Chongqing', 'Chongqing'), ('Asia/Chungking', 'Chungking'), ('Asia/Colombo', 'Colombo'), ('Asia/Dacca', 'Dacca'), ('Asia/Damascus', 'Damascus'), ('Asia/Dhaka', 'Dhaka'), ('Asia/Dili', 'Dili'), ('Asia/Dubai', 'Dubai'), ('Asia/Dushanbe', 'Dushanbe'), ('Asia/Famagusta', 'Famagusta'), ('Asia/Gaza', 'Gaza'), ('Asia/Harbin', 'Harbin'), ('Asia/Hebron', 'Hebron'), ('Asia/Ho_Chi_Minh', 'Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Hong_Kong'), ('Asia/Hovd', 'Hovd'), ('Asia/Irkutsk', 'Irkutsk'), ('Asia/Istanbul', 'Istanbul'), ('Asia/Jakarta', 'Jakarta'), ('Asia/Jayapura', 'Jayapura'), ('Asia/Jerusalem', 'Jerusalem'), ('Asia/Kabul', 'Kabul'), ('Asia/Kamchatka', 'Kamchatka'), ('Asia/Karachi', 'Karachi'), ('Asia/Kashgar', 'Kashgar'), ('Asia/Kathmandu', 'Kathmandu'), ('Asia/Katmandu', 'Katmandu'), ('Asia/Khandyga', 'Khandyga'), ('Asia/Kolkata', 'Kolkata'), ('Asia/Krasnoyarsk', 'Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Kuala_Lumpur'), ('Asia/Kuching', 'Kuching'), ('Asia/Kuwait', 'Kuwait'), ('Asia/Macao', 'Macao'), ('Asia/Macau', 'Macau'), ('Asia/Magadan', 'Magadan'), ('Asia/Makassar', 'Makassar'), ('Asia/Manila', 'Manila'), ('Asia/Muscat', 'Muscat'), ('Asia/Nicosia', 'Nicosia'), ('Asia/Novokuznetsk', 'Novokuznetsk'), ('Asia/Novosibirsk', 'Novosibirsk'), ('Asia/Omsk', 'Omsk'), ('Asia/Oral', 'Oral'), ('Asia/Phnom_Penh', 'Phnom_Penh'), ('Asia/Pontianak', 'Pontianak'), ('Asia/Pyongyang', 'Pyongyang'), ('Asia/Qatar', 'Qatar'), ('Asia/Qostanay', 'Qostanay'), ('Asia/Qyzylorda', 'Qyzylorda'), ('Asia/Rangoon', 'Rangoon'), ('Asia/Riyadh', 'Riyadh'), ('Asia/Saigon', 'Saigon'), ('Asia/Sakhalin', 'Sakhalin'), ('Asia/Samarkand', 'Samarkand'), ('Asia/Seoul', 'Seoul'), ('Asia/Shanghai', 'Shanghai'), ('Asia/Singapore', 'Singapore'), ('Asia/Srednekolymsk', 'Srednekolymsk'), ('Asia/Taipei', 'Taipei'), ('Asia/Tashkent', 'Tashkent'), ('Asia/Tbilisi', 'Tbilisi'), ('Asia/Tehran', 'Tehran'), ('Asia/Tel_Aviv', 'Tel_Aviv'), ('Asia/Thimbu', 'Thimbu'), ('Asia/Thimphu', 'Thimphu'), ('Asia/Tokyo', 'Tokyo'), ('Asia/Tomsk', 'Tomsk'), ('Asia/Ujung_Pandang', 'Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Ulaanbaatar'), ('Asia/Ulan_Bator', 'Ulan_Bator'), ('Asia/Urumqi', 'Urumqi'), ('Asia/Ust-Nera', 'Ust-Nera'), ('Asia/Vientiane', 'Vientiane'), ('Asia/Vladivostok', 'Vladivostok'), ('Asia/Yakutsk', 'Yakutsk'), ('Asia/Yangon', 'Yangon'), ('Asia/Yekaterinburg', 'Yekaterinburg'), ('Asia/Yerevan', 'Yerevan')]), ('Atlantic', [('Atlantic/Azores', 'Azores'), ('Atlantic/Bermuda', 'Bermuda'), ('Atlantic/Canary', 'Canary'), ('Atlantic/Cape_Verde', 'Cape_Verde'), ('Atlantic/Faeroe', 'Faeroe'), ('Atlantic/Faroe', 'Faroe'), ('Atlantic/Jan_Mayen', 'Jan_Mayen'), ('Atlantic/Madeira', 'Madeira'), ('Atlantic/Reykjavik', 'Reykjavik'), ('Atlantic/South_Georgia', 'South_Georgia'), ('Atlantic/St_Helena', 'St_Helena'), ('Atlantic/Stanley', 'Stanley')]), ('Australia', [('Australia/ACT', 'ACT'), ('Australia/Adelaide', 'Adelaide'), ('Australia/Brisbane', 'Brisbane'), ('Australia/Broken_Hill', 'Broken_Hill'), ('Australia/Canberra', 'Canberra'), ('Australia/Currie', 'Currie'), ('Australia/Darwin', 'Darwin'), ('Australia/Eucla', 'Eucla'), ('Australia/Hobart', 'Hobart'), ('Australia/LHI', 'LHI'), ('Australia/Lindeman', 'Lindeman'), ('Australia/Lord_Howe', 'Lord_Howe'), ('Australia/Melbourne', 'Melbourne'), ('Australia/NSW', 'NSW'), ('Australia/North', 'North'), ('Australia/Perth', 'Perth'), ('Australia/Queensland', 'Queensland'), ('Australia/South', 'South'), ('Australia/Sydney', 'Sydney'), ('Australia/Tasmania', 'Tasmania'), ('Australia/Victoria', 'Victoria'), ('Australia/West', 'West'), ('Australia/Yancowinna', 'Yancowinna')]), ('Brazil', [('Brazil/Acre', 'Acre'), ('Brazil/DeNoronha', 'DeNoronha'), ('Brazil/East', 'East'), ('Brazil/West', 'West')]), ('Canada', [('Canada/Atlantic', 'Atlantic'), ('Canada/Central', 'Central'), ('Canada/Eastern', 'Eastern'), ('Canada/Mountain', 'Mountain'), ('Canada/Newfoundland', 'Newfoundland'), ('Canada/Pacific', 'Pacific'), ('Canada/Saskatchewan', 'Saskatchewan'), ('Canada/Yukon', 'Yukon')]), ('Chile', [('Chile/Continental', 'Continental'), ('Chile/EasterIsland', 'EasterIsland')]), ('Etc', [('Etc/Greenwich', 'Greenwich'), ('Etc/UCT', 'UCT'), ('Etc/UTC', 'UTC'), ('Etc/Universal', 'Universal'), ('Etc/Zulu', 'Zulu')]), ('Europe', [('Europe/Amsterdam', 'Amsterdam'), ('Europe/Andorra', 'Andorra'), ('Europe/Astrakhan', 'Astrakhan'), ('Europe/Athens', 'Athens'), ('Europe/Belfast', 'Belfast'), ('Europe/Belgrade', 'Belgrade'), ('Europe/Berlin', 'Berlin'), ('Europe/Bratislava', 'Bratislava'), ('Europe/Brussels', 'Brussels'), ('Europe/Bucharest', 'Bucharest'), ('Europe/Budapest', 'Budapest'), ('Europe/Busingen', 'Busingen'), ('Europe/Chisinau', 'Chisinau'), ('Europe/Copenhagen', 'Copenhagen'), ('Europe/Dublin', 'Dublin'), ('Europe/Gibraltar', 'Gibraltar'), ('Europe/Guernsey', 'Guernsey'), ('Europe/Helsinki', 'Helsinki'), ('Europe/Isle_of_Man', 'Isle_of_Man'), ('Europe/Istanbul', 'Istanbul'), ('Europe/Jersey', 'Jersey'), ('Europe/Kaliningrad', 'Kaliningrad'), ('Europe/Kiev', 'Kiev'), ('Europe/Kirov', 'Kirov'), ('Europe/Lisbon', 'Lisbon'), ('Europe/Ljubljana', 'Ljubljana'), ('Europe/London', 'London'), ('Europe/Luxembourg', 'Luxembourg'), ('Europe/Madrid', 'Madrid'), ('Europe/Malta', 'Malta'), ('Europe/Mariehamn', 'Mariehamn'), ('Europe/Minsk', 'Minsk'), ('Europe/Monaco', 'Monaco'), ('Europe/Moscow', 'Moscow'), ('Europe/Nicosia', 'Nicosia'), ('Europe/Oslo', 'Oslo'), ('Europe/Paris', 'Paris'), ('Europe/Podgorica', 'Podgorica'), ('Europe/Prague', 'Prague'), ('Europe/Riga', 'Riga'), ('Europe/Rome', 'Rome'), ('Europe/Samara', 'Samara'), ('Europe/San_Marino', 'San_Marino'), ('Europe/Sarajevo', 'Sarajevo'), ('Europe/Saratov', 'Saratov'), ('Europe/Simferopol', 'Simferopol'), ('Europe/Skopje', 'Skopje'), ('Europe/Sofia', 'Sofia'), ('Europe/Stockholm', 'Stockholm'), ('Europe/Tallinn', 'Tallinn'), ('Europe/Tirane', 'Tirane'), ('Europe/Tiraspol', 'Tiraspol'), ('Europe/Ulyanovsk', 'Ulyanovsk'), ('Europe/Uzhgorod', 'Uzhgorod'), ('Europe/Vaduz', 'Vaduz'), ('Europe/Vatican', 'Vatican'), ('Europe/Vienna', 'Vienna'), ('Europe/Vilnius', 'Vilnius'), ('Europe/Volgograd', 'Volgograd'), ('Europe/Warsaw', 'Warsaw'), ('Europe/Zagreb', 'Zagreb'), ('Europe/Zaporozhye', 'Zaporozhye'), ('Europe/Zurich', 'Zurich')]), ('Indian', [('Indian/Antananarivo', 'Antananarivo'), ('Indian/Chagos', 'Chagos'), ('Indian/Christmas', 'Christmas'), ('Indian/Cocos', 'Cocos'), ('Indian/Comoro', 'Comoro'), ('Indian/Kerguelen', 'Kerguelen'), ('Indian/Mahe', 'Mahe'), ('Indian/Maldives', 'Maldives'), ('Indian/Mauritius', 'Mauritius'), ('Indian/Mayotte', 'Mayotte'), ('Indian/Reunion', 'Reunion')]), ('Mexico', [('Mexico/BajaNorte', 'BajaNorte'), ('Mexico/BajaSur', 'BajaSur'), ('Mexico/General', 'General')]), ('Other', [('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu')]), ('Pacific', [('Pacific/Apia', 'Apia'), ('Pacific/Auckland', 'Auckland'), ('Pacific/Bougainville', 'Bougainville'), ('Pacific/Chatham', 'Chatham'), ('Pacific/Chuuk', 'Chuuk'), ('Pacific/Easter', 'Easter'), ('Pacific/Efate', 'Efate'), ('Pacific/Enderbury', 'Enderbury'), ('Pacific/Fakaofo', 'Fakaofo'), ('Pacific/Fiji', 'Fiji'), ('Pacific/Funafuti', 'Funafuti'), ('Pacific/Galapagos', 'Galapagos'), ('Pacific/Gambier', 'Gambier'), ('Pacific/Guadalcanal', 'Guadalcanal'), ('Pacific/Guam', 'Guam'), ('Pacific/Honolulu', 'Honolulu'), ('Pacific/Johnston', 'Johnston'), ('Pacific/Kanton', 'Kanton'), ('Pacific/Kiritimati', 'Kiritimati'), ('Pacific/Kosrae', 'Kosrae'), ('Pacific/Kwajalein', 'Kwajalein'), ('Pacific/Majuro', 'Majuro'), ('Pacific/Marquesas', 'Marquesas'), ('Pacific/Midway', 'Midway'), ('Pacific/Nauru', 'Nauru'), ('Pacific/Niue', 'Niue'), ('Pacific/Norfolk', 'Norfolk'), ('Pacific/Noumea', 'Noumea'), ('Pacific/Pago_Pago', 'Pago_Pago'), ('Pacific/Palau', 'Palau'), ('Pacific/Pitcairn', 'Pitcairn'), ('Pacific/Pohnpei', 'Pohnpei'), ('Pacific/Ponape', 'Ponape'), ('Pacific/Port_Moresby', 'Port_Moresby'), ('Pacific/Rarotonga', 'Rarotonga'), ('Pacific/Saipan', 'Saipan'), ('Pacific/Samoa', 'Samoa'), ('Pacific/Tahiti', 'Tahiti'), ('Pacific/Tarawa', 'Tarawa'), ('Pacific/Tongatapu', 'Tongatapu'), ('Pacific/Truk', 'Truk'), ('Pacific/Wake', 'Wake'), ('Pacific/Wallis', 'Wallis'), ('Pacific/Yap', 'Yap')]), ('US', [('US/Alaska', 'Alaska'), ('US/Aleutian', 'Aleutian'), ('US/Arizona', 'Arizona'), ('US/Central', 'Central'), ('US/East-Indiana', 'East-Indiana'), ('US/Eastern', 'Eastern'), ('US/Hawaii', 'Hawaii'), ('US/Indiana-Starke', 'Indiana-Starke'), ('US/Michigan', 'Michigan'), ('US/Mountain', 'Mountain'), ('US/Pacific', 'Pacific'), ('US/Samoa', 'Samoa')])], default='Asia/Ho_Chi_Minh', max_length=50, verbose_name='location'), + ), + ] diff --git a/judge/migrations/0118_rating.py b/judge/migrations/0118_rating.py new file mode 100644 index 0000000..8ad6ad2 --- /dev/null +++ b/judge/migrations/0118_rating.py @@ -0,0 +1,208 @@ +import math +from operator import attrgetter, itemgetter + +from django.db import migrations, models +from django.db.models import Count, OuterRef, Subquery +from django.db.models.functions import Coalesce +from django.utils import timezone + + +def tie_ranker(iterable, key=attrgetter('points')): + rank = 0 + delta = 1 + last = None + buf = [] + for item in iterable: + new = key(item) + if new != last: + for _ in buf: + yield rank + (delta - 1) / 2.0 + rank += delta + delta = 0 + buf = [] + delta += 1 + buf.append(item) + last = key(item) + for _ in buf: + yield rank + (delta - 1) / 2.0 + + +def rational_approximation(t): + # Abramowitz and Stegun formula 26.2.23. + # The absolute value of the error should be less than 4.5 e-4. + c = [2.515517, 0.802853, 0.010328] + d = [1.432788, 0.189269, 0.001308] + numerator = (c[2] * t + c[1]) * t + c[0] + denominator = ((d[2] * t + d[1]) * t + d[0]) * t + 1.0 + return t - numerator / denominator + + +def normal_CDF_inverse(p): + assert 0.0 < p < 1 + + # See article above for explanation of this section. + if p < 0.5: + # F^-1(p) = - G^-1(p) + return -rational_approximation(math.sqrt(-2.0 * math.log(p))) + else: + # F^-1(p) = G^-1(1-p) + return rational_approximation(math.sqrt(-2.0 * math.log(1.0 - p))) + + +def WP(RA, RB, VA, VB): + return (math.erf((RB - RA) / math.sqrt(2 * (VA * VA + VB * VB))) + 1) / 2.0 + + +def recalculate_ratings(old_rating, old_volatility, actual_rank, times_rated, is_disqualified): + # actual_rank: 1 is first place, N is last place + # if there are ties, use the average of places (if places 2, 3, 4, 5 tie, use 3.5 for all of them) + + N = len(old_rating) + new_rating = old_rating[:] + new_volatility = old_volatility[:] + if N <= 1: + return new_rating, new_volatility + + ranking = list(range(N)) + ranking.sort(key=old_rating.__getitem__, reverse=True) + + ave_rating = float(sum(old_rating)) / N + sum1 = sum(i * i for i in old_volatility) / N + sum2 = sum((i - ave_rating) ** 2 for i in old_rating) / (N - 1) + CF = math.sqrt(sum1 + sum2) + + for i in range(N): + ERank = 0.5 + for j in range(N): + ERank += WP(old_rating[i], old_rating[j], old_volatility[i], old_volatility[j]) + + EPerf = -normal_CDF_inverse((ERank - 0.5) / N) + APerf = -normal_CDF_inverse((actual_rank[i] - 0.5) / N) + PerfAs = old_rating[i] + CF * (APerf - EPerf) + Weight = 1.0 / (1 - (0.42 / (times_rated[i] + 1) + 0.18)) - 1.0 + if old_rating[i] > 2500: + Weight *= 0.8 + elif old_rating[i] >= 2000: + Weight *= 0.9 + + Cap = 150.0 + 1500.0 / (times_rated[i] + 2) + + new_rating[i] = (old_rating[i] + Weight * PerfAs) / (1.0 + Weight) + + if abs(old_rating[i] - new_rating[i]) > Cap: + if old_rating[i] < new_rating[i]: + new_rating[i] = old_rating[i] + Cap + else: + new_rating[i] = old_rating[i] - Cap + + if times_rated[i] == 0: + new_volatility[i] = 385 + else: + new_volatility[i] = math.sqrt(((new_rating[i] - old_rating[i]) ** 2) / Weight + + (old_volatility[i] ** 2) / (Weight + 1)) + + if is_disqualified[i]: + # DQed users can manipulate TopCoder ratings to get higher volatility in order to increase their rating + # later on, prohibit this by ensuring their volatility never increases in this situation + new_volatility[i] = min(new_volatility[i], old_volatility[i]) + + # try to keep the sum of ratings constant + adjust = float(sum(old_rating) - sum(new_rating)) / N + new_rating = list(map(adjust.__add__, new_rating)) + # inflate a little if we have to so people who placed first don't lose rating + best_rank = min(actual_rank) + for i in range(N): + if abs(actual_rank[i] - best_rank) <= 1e-3 and new_rating[i] < old_rating[i] + 1: + new_rating[i] = old_rating[i] + 1 + return list(map(int, map(round, new_rating))), list(map(int, map(round, new_volatility))) + + +def tc_rate_contest(contest, Rating, Profile): + rating_subquery = Rating.objects.filter(user=OuterRef('user')) + rating_sorted = rating_subquery.order_by('-contest__end_time') + users = contest.users.order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker') \ + .annotate(submissions=Count('submission'), + last_rating=Coalesce(Subquery(rating_sorted.values('rating')[:1]), 1200), + volatility=Coalesce(Subquery(rating_sorted.values('volatility')[:1]), 535), + times=Coalesce(Subquery(rating_subquery.order_by().values('user_id') + .annotate(count=Count('id')).values('count')), 0)) \ + .exclude(user_id__in=contest.rate_exclude.all()) \ + .filter(virtual=0).values('id', 'user_id', 'score', 'cumtime', 'tiebreaker', 'is_disqualified', + 'last_rating', 'volatility', 'times') + if not contest.rate_all: + users = users.filter(submissions__gt=0) + if contest.rating_floor is not None: + users = users.exclude(last_rating__lt=contest.rating_floor) + if contest.rating_ceiling is not None: + users = users.exclude(last_rating__gt=contest.rating_ceiling) + + users = list(users) + participation_ids = list(map(itemgetter('id'), users)) + user_ids = list(map(itemgetter('user_id'), users)) + is_disqualified = list(map(itemgetter('is_disqualified'), users)) + ranking = list(tie_ranker(users, key=itemgetter('score', 'cumtime', 'tiebreaker'))) + old_rating = list(map(itemgetter('last_rating'), users)) + old_volatility = list(map(itemgetter('volatility'), users)) + times_ranked = list(map(itemgetter('times'), users)) + rating, volatility = recalculate_ratings(old_rating, old_volatility, ranking, times_ranked, is_disqualified) + + now = timezone.now() + ratings = [Rating(user_id=i, contest=contest, rating=r, volatility=v, last_rated=now, participation_id=p, rank=z) + for i, p, r, v, z in zip(user_ids, participation_ids, rating, volatility, ranking)] + + Rating.objects.bulk_create(ratings) + + Profile.objects.filter(contest_history__contest=contest, contest_history__virtual=0).update( + rating=Subquery(Rating.objects.filter(user=OuterRef('id')) + .order_by('-contest__end_time').values('rating')[:1])) + + +# inspired by rate_all_view +def rate_tc(apps, schema_editor): + Contest = apps.get_model('judge', 'Contest') + Rating = apps.get_model('judge', 'Rating') + Profile = apps.get_model('judge', 'Profile') + + with schema_editor.connection.cursor() as cursor: + cursor.execute('TRUNCATE TABLE `%s`' % Rating._meta.db_table) + Profile.objects.update(rating=None) + for contest in Contest.objects.filter(is_rated=True, end_time__lte=timezone.now()).order_by('end_time'): + tc_rate_contest(contest, Rating, Profile) + + +# inspired by rate_all_view +def rate_elo_mmr(apps, schema_editor): + Rating = apps.get_model('judge', 'Rating') + Profile = apps.get_model('judge', 'Profile') + + with schema_editor.connection.cursor() as cursor: + cursor.execute('TRUNCATE TABLE `%s`' % Rating._meta.db_table) + Profile.objects.update(rating=None) + # Don't populate Rating + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0117_auto_20211209_0612'), + ] + + operations = [ + migrations.RunPython(migrations.RunPython.noop, rate_tc, atomic=True), + migrations.AddField( + model_name='rating', + name='mean', + field=models.FloatField(verbose_name='raw rating'), + ), + migrations.AddField( + model_name='rating', + name='performance', + field=models.FloatField(verbose_name='contest performance'), + ), + migrations.RemoveField( + model_name='rating', + name='volatility', + field=models.IntegerField(verbose_name='volatility'), + ), + migrations.RunPython(rate_elo_mmr, migrations.RunPython.noop, atomic=True), + ] \ No newline at end of file diff --git a/judge/models/contest.py b/judge/models/contest.py index 46a6e06..8d75ee3 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -531,7 +531,8 @@ class Rating(models.Model): related_name='rating', on_delete=CASCADE) rank = models.IntegerField(verbose_name=_('rank')) rating = models.IntegerField(verbose_name=_('rating')) - volatility = models.IntegerField(verbose_name=_('volatility')) + mean = models.FloatField(verbose_name=_('raw rating')) + performance = models.FloatField(verbose_name=_('contest performance')) last_rated = models.DateTimeField(db_index=True, verbose_name=_('last rated')) class Meta: diff --git a/judge/ratings.py b/judge/ratings.py index b3ff7c6..46a5d7a 100644 --- a/judge/ratings.py +++ b/judge/ratings.py @@ -1,5 +1,5 @@ -import math from bisect import bisect +from math import pi, sqrt, tanh from operator import attrgetter, itemgetter from django.db import transaction @@ -8,6 +8,18 @@ from django.db.models.functions import Coalesce from django.utils import timezone +BETA2 = 328.33 ** 2 +RATING_INIT = 1200 # Newcomer's rating when applying the rating floor/ceiling +MEAN_INIT = 1500. +VAR_INIT = 350**2 * (BETA2 / 212**2) +SD_INIT = sqrt(VAR_INIT) +VALID_RANGE = MEAN_INIT - 20 * SD_INIT, MEAN_INIT + 20 * SD_INIT +VAR_PER_CONTEST = 1219.047619 * (BETA2 / 212**2) +VAR_LIM = (sqrt(VAR_PER_CONTEST**2 + 4 * BETA2 * VAR_PER_CONTEST) - VAR_PER_CONTEST) / 2 +SD_LIM = sqrt(VAR_LIM) +TANH_C = sqrt(3) / pi + + def tie_ranker(iterable, key=attrgetter('points')): rank = 0 delta = 1 @@ -28,94 +40,103 @@ def tie_ranker(iterable, key=attrgetter('points')): yield rank + (delta - 1) / 2.0 -def rational_approximation(t): - # Abramowitz and Stegun formula 26.2.23. - # The absolute value of the error should be less than 4.5 e-4. - c = [2.515517, 0.802853, 0.010328] - d = [1.432788, 0.189269, 0.001308] - numerator = (c[2] * t + c[1]) * t + c[0] - denominator = ((d[2] * t + d[1]) * t + d[0]) * t + 1.0 - return t - numerator / denominator +def eval_tanhs(tanh_terms, x): + return sum((wt / sd) * tanh((x - mu) / (2 * sd)) for mu, sd, wt in tanh_terms) -def normal_CDF_inverse(p): - assert 0.0 < p < 1 - - # See article above for explanation of this section. - if p < 0.5: - # F^-1(p) = - G^-1(p) - return -rational_approximation(math.sqrt(-2.0 * math.log(p))) - else: - # F^-1(p) = G^-1(1-p) - return rational_approximation(math.sqrt(-2.0 * math.log(1.0 - p))) - - -def WP(RA, RB, VA, VB): - return (math.erf((RB - RA) / math.sqrt(2 * (VA * VA + VB * VB))) + 1) / 2.0 - - -def recalculate_ratings(old_rating, old_volatility, actual_rank, times_rated, is_disqualified): - # actual_rank: 1 is first place, N is last place - # if there are ties, use the average of places (if places 2, 3, 4, 5 tie, use 3.5 for all of them) - - N = len(old_rating) - new_rating = old_rating[:] - new_volatility = old_volatility[:] - if N <= 1: - return new_rating, new_volatility - - ranking = list(range(N)) - ranking.sort(key=old_rating.__getitem__, reverse=True) - - ave_rating = float(sum(old_rating)) / N - sum1 = sum(i * i for i in old_volatility) / N - sum2 = sum((i - ave_rating) ** 2 for i in old_rating) / (N - 1) - CF = math.sqrt(sum1 + sum2) - - for i in range(N): - ERank = 0.5 - for j in range(N): - ERank += WP(old_rating[i], old_rating[j], old_volatility[i], old_volatility[j]) - - EPerf = -normal_CDF_inverse((ERank - 0.5) / N) - APerf = -normal_CDF_inverse((actual_rank[i] - 0.5) / N) - PerfAs = old_rating[i] + CF * (APerf - EPerf) - Weight = 1.0 / (1 - (0.42 / (times_rated[i] + 1) + 0.18)) - 1.0 - if old_rating[i] > 2500: - Weight *= 0.8 - elif old_rating[i] >= 2000: - Weight *= 0.9 - - Cap = 150.0 + 1500.0 / (times_rated[i] + 2) - - new_rating[i] = (old_rating[i] + Weight * PerfAs) / (1.0 + Weight) - - if times_rated[i] == 0: - new_volatility[i] = 385 +def solve(tanh_terms, y_tg, lin_factor=0, bounds=VALID_RANGE): + L, R = bounds + Ly, Ry = None, None + while R - L > 2: + x = (L + R) / 2 + y = lin_factor * x + eval_tanhs(tanh_terms, x) + if y > y_tg: + R, Ry = x, y + elif y < y_tg: + L, Ly = x, y else: - new_volatility[i] = math.sqrt(((new_rating[i] - old_rating[i]) ** 2) / Weight + - (old_volatility[i] ** 2) / (Weight + 1)) + return x + # Use linear interpolation to be slightly more accurate. + if Ly is None: + Ly = lin_factor * L + eval_tanhs(tanh_terms, L) + if y_tg <= Ly: + return L + if Ry is None: + Ry = lin_factor * R + eval_tanhs(tanh_terms, R) + if y_tg >= Ry: + return R + ratio = (y_tg - Ly) / (Ry - Ly) + return L * (1 - ratio) + R * ratio - if is_disqualified[i]: - # DQed users can manipulate TopCoder ratings to get higher volatility in order to increase their rating - # later on, prohibit this by ensuring their volatility never increases in this situation - new_volatility[i] = min(new_volatility[i], old_volatility[i]) - if abs(old_rating[i] - new_rating[i]) > Cap: - if old_rating[i] < new_rating[i]: - new_rating[i] = old_rating[i] + Cap - else: - new_rating[i] = old_rating[i] - Cap +def get_var(times_ranked, cache=[VAR_INIT]): + while times_ranked >= len(cache): + next_var = 1. / (1. / (cache[-1] + VAR_PER_CONTEST) + 1. / BETA2) + cache.append(next_var) + return cache[times_ranked] - # try to keep the sum of ratings constant - adjust = float(sum(old_rating) - sum(new_rating)) / N - new_rating = list(map(adjust.__add__, new_rating)) - # inflate a little if we have to so people who placed first don't lose rating - best_rank = min(actual_rank) - for i in range(N): - if abs(actual_rank[i] - best_rank) <= 1e-3 and new_rating[i] < old_rating[i] + 1: - new_rating[i] = old_rating[i] + 1 - return list(map(int, map(round, new_rating))), list(map(int, map(round, new_volatility))) + +def recalculate_ratings(ranking, old_mean, times_ranked, historical_p): + n = len(ranking) + new_p = [0.] * n + new_mean = [0.] * n + + # Note: pre-multiply delta by TANH_C to improve efficiency. + delta = [TANH_C * sqrt(get_var(t) + VAR_PER_CONTEST + BETA2) for t in times_ranked] + p_tanh_terms = [(m, d, 1) for m, d in zip(old_mean, delta)] + + # Calculate performance at index i. + def solve_idx(i, bounds=VALID_RANGE): + r = ranking[i] + y_tg = 0 + for d, s in zip(delta, ranking): + if s > r: # s loses to r + y_tg += 1. / d + elif s < r: # s beats r + y_tg -= 1. / d + # Otherwise, this is a tie that counts as half a win, as per Elo-MMR. + new_p[i] = solve(p_tanh_terms, y_tg, bounds=bounds) + + # Fill all indices between i and j, inclusive. Use the fact that new_p is non-increasing. + def divconq(i, j): + if j - i > 1: + k = (i + j) // 2 + solve_idx(k, bounds=(new_p[j], new_p[i])) + divconq(i, k) + divconq(k, j) + + if n < 2: + new_p = list(old_mean) + new_mean = list(old_mean) + else: + # Calculate performance. + solve_idx(0) + solve_idx(n - 1) + divconq(0, n - 1) + + # Calculate mean. + for i, r in enumerate(ranking): + tanh_terms = [] + w_prev = 1. + w_sum = 0. + for j, h in enumerate([new_p[i]] + historical_p[i]): + gamma2 = (VAR_PER_CONTEST if j > 0 else 0) + h_var = get_var(times_ranked[i] + 1 - j) + k = h_var / (h_var + gamma2) + w = w_prev * k**2 + # Future optimization: If j is around 20, then w < 1e-3 and it is possible to break early. + tanh_terms.append((h, sqrt(BETA2) * TANH_C, w)) + w_prev = w + w_sum += w / BETA2 + w0 = 1. / get_var(times_ranked[i] + 1) - w_sum + p0 = eval_tanhs(tanh_terms[1:], old_mean[i]) / w0 + old_mean[i] + new_mean[i] = solve(tanh_terms, w0 * p0, lin_factor=w0) + + # Display a slightly lower rating to incentivize participation. + # As times_ranked increases, new_rating converges to new_mean. + new_rating = [max(1, round(m - (sqrt(get_var(t + 1)) - SD_LIM))) for m, t in zip(new_mean, times_ranked)] + + return new_rating, new_mean, new_p def rate_contest(contest): @@ -125,13 +146,13 @@ def rate_contest(contest): rating_sorted = rating_subquery.order_by('-contest__end_time') users = contest.users.order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker') \ .annotate(submissions=Count('submission'), - last_rating=Coalesce(Subquery(rating_sorted.values('rating')[:1]), 1200), - volatility=Coalesce(Subquery(rating_sorted.values('volatility')[:1]), 535), + last_rating=Coalesce(Subquery(rating_sorted.values('rating')[:1]), RATING_INIT), + last_mean=Coalesce(Subquery(rating_sorted.values('mean')[:1]), MEAN_INIT), times=Coalesce(Subquery(rating_subquery.order_by().values('user_id') .annotate(count=Count('id')).values('count')), 0)) \ .exclude(user_id__in=contest.rate_exclude.all()) \ - .filter(virtual=0).values('id', 'user_id', 'score', 'cumtime', 'tiebreaker', 'is_disqualified', - 'last_rating', 'volatility', 'times') + .filter(virtual=0).values('id', 'user_id', 'score', 'cumtime', 'tiebreaker', + 'last_rating', 'last_mean', 'times') if not contest.rate_all: users = users.filter(submissions__gt=0) if contest.rating_floor is not None: @@ -142,27 +163,34 @@ def rate_contest(contest): users = list(users) participation_ids = list(map(itemgetter('id'), users)) user_ids = list(map(itemgetter('user_id'), users)) - is_disqualified = list(map(itemgetter('is_disqualified'), users)) ranking = list(tie_ranker(users, key=itemgetter('score', 'cumtime', 'tiebreaker'))) - old_rating = list(map(itemgetter('last_rating'), users)) - old_volatility = list(map(itemgetter('volatility'), users)) + old_mean = list(map(itemgetter('last_mean'), users)) times_ranked = list(map(itemgetter('times'), users)) - rating, volatility = recalculate_ratings(old_rating, old_volatility, ranking, times_ranked, is_disqualified) + historical_p = [[] for _ in users] + + user_id_to_idx = {uid: i for i, uid in enumerate(user_ids)} + for h in Rating.objects.filter(user_id__in=user_ids) \ + .order_by('-contest__end_time') \ + .values('user_id', 'performance'): + idx = user_id_to_idx[h['user_id']] + historical_p[idx].append(h['performance']) + + rating, mean, performance = recalculate_ratings(ranking, old_mean, times_ranked, historical_p) now = timezone.now() - ratings = [Rating(user_id=i, contest=contest, rating=r, volatility=v, last_rated=now, participation_id=p, rank=z) - for i, p, r, v, z in zip(user_ids, participation_ids, rating, volatility, ranking)] + ratings = [Rating(user_id=i, contest=contest, rating=r, mean=m, performance=perf, + last_rated=now, participation_id=pid, rank=z) + for i, pid, r, m, perf, z in zip(user_ids, participation_ids, rating, mean, performance, ranking)] with transaction.atomic(): - Rating.objects.filter(contest=contest).delete() Rating.objects.bulk_create(ratings) - Profile.objects.filter(contest_history__contest=contest, contest_history__virtual=0) \ - .update(rating=Subquery(Rating.objects.filter(user=OuterRef('id')) \ - .order_by('-contest__end_time').values('rating')[:1])) - return old_rating, old_volatility, ranking, times_ranked, rating, volatility + + Profile.objects.filter(contest_history__contest=contest, contest_history__virtual=0).update( + rating=Subquery(Rating.objects.filter(user=OuterRef('id')) + .order_by('-contest__end_time').values('rating')[:1])) RATING_LEVELS = ['Newbie', 'Amateur', 'Expert', 'Candidate Master', 'Master', 'Grandmaster', 'Target'] -RATING_VALUES = [1000, 1200, 1500, 1800, 2200, 3000] +RATING_VALUES = [1000, 1300, 1600, 1900, 2400, 3000] RATING_CLASS = ['rate-newbie', 'rate-amateur', 'rate-expert', 'rate-candidate-master', 'rate-master', 'rate-grandmaster', 'rate-target'] diff --git a/judge/views/api/api_v1.py b/judge/views/api/api_v1.py index c0c4ade..bad7486 100644 --- a/judge/views/api/api_v1.py +++ b/judge/views/api/api_v1.py @@ -137,16 +137,17 @@ def api_v1_user_info(request, user): participations = ContestParticipation.objects.filter(user=profile, virtual=0, contest__is_visible=True, contest__is_private=False, contest__is_organization_private=False) - for contest_key, rating, volatility in participations.values_list('contest__key', 'rating__rating', - 'rating__volatility'): + for contest_key, rating, mean, performance in participations.values_list( + 'contest__key', 'rating__rating', 'rating__mean', 'rating__performance', + ): contest_history[contest_key] = { 'rating': rating, - 'volatility': volatility, + 'raw_rating': mean, + 'performance': performance, } resp['contests'] = { 'current_rating': last_rating.rating if last_rating else None, - 'volatility': last_rating.volatility if last_rating else None, 'history': contest_history, } diff --git a/judge/views/api/api_v2.py b/judge/views/api/api_v2.py index 7850d96..8e512b3 100644 --- a/judge/views/api/api_v2.py +++ b/judge/views/api/api_v2.py @@ -89,7 +89,6 @@ def api_v2_user_info(request): resp['contests'] = { "current_rating": last_rating[0].rating if last_rating else None, - "volatility": last_rating[0].volatility if last_rating else None, 'history': contest_history, } diff --git a/templates/user/user-about.html b/templates/user/user-about.html index d390028..a342ebe 100644 --- a/templates/user/user-about.html +++ b/templates/user/user-about.html @@ -467,26 +467,26 @@ }, { begin: 1000, - end: 1200, + end: 1300, color: 'rgb(0, 169, 0, 0.4)' }, { - begin: 1200, - end: 1500, + begin: 1300, + end: 1600, color: 'rgb(0, 0, 255, 0.4)' }, { - begin: 1500, - end: 1800, + begin: 1600, + end: 1900, color: 'rgb(128, 0, 128, 0.37)' }, { - begin: 1800, - end: 2200, + begin: 1900, + end: 2400, color: 'rgb(255, 177, 0, 0.4)' }, { - begin: 2200, + begin: 2400, end: 3000, color: 'rgb(238, 0, 0, 0.4)' }, diff --git a/templates/user/user-base.html b/templates/user/user-base.html index bf9de1e..5978ba7 100644 --- a/templates/user/user-base.html +++ b/templates/user/user-base.html @@ -120,10 +120,6 @@
{{_('Contests written')}}:
{{ratings|length}}
-
-
{{ _('Volatility:') }}
-
{{ rating.volatility }}
-
{{ _('Min. rating:') }}
{{ rating_number(min_rating) }}