@xcpcio/core 0.45.1 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -548,6 +548,36 @@ class ICPCStandingsCsvConverter {
548
548
  }
549
549
  }
550
550
 
551
+ class Person {
552
+ constructor(name = "") {
553
+ this.name = name;
554
+ }
555
+ toJSON() {
556
+ return {
557
+ name: this.name
558
+ };
559
+ }
560
+ static fromJSON(iPerson) {
561
+ if (typeof iPerson === "string") {
562
+ iPerson = JSON.parse(iPerson);
563
+ }
564
+ const person = new Person();
565
+ person.name = iPerson.name;
566
+ return person;
567
+ }
568
+ }
569
+ function createPersons(iPersons) {
570
+ if (typeof iPersons === "string") {
571
+ for (const c of " ,\u3001|") {
572
+ if (iPersons.includes(c)) {
573
+ return iPersons.split(c).map((name) => new Person(name));
574
+ }
575
+ }
576
+ return [new Person(iPersons)];
577
+ }
578
+ return iPersons.map((name) => new Person(name));
579
+ }
580
+
551
581
  function calcDirt(attemptedNum, solvedNum) {
552
582
  if (solvedNum === 0) {
553
583
  return 0;
@@ -576,6 +606,268 @@ function getWhiteOrBlackColor(background) {
576
606
  return brightness <= threshold ? "#fff" : "#000";
577
607
  }
578
608
 
609
+ class RatingHistory {
610
+ constructor() {
611
+ this.rank = 0;
612
+ this.rating = 0;
613
+ this.teamName = "";
614
+ this.organization = "";
615
+ this.members = [];
616
+ this.coaches = [];
617
+ this.contestID = "";
618
+ this.contestName = "";
619
+ this.contestLink = "";
620
+ this.contestTime = createDayJS();
621
+ }
622
+ toJSON() {
623
+ return {
624
+ rank: this.rank,
625
+ rating: this.rating,
626
+ teamName: this.teamName,
627
+ organization: this.organization,
628
+ members: this.members.map((member) => member.toJSON()),
629
+ coaches: this.coaches.map((coach) => coach.toJSON()),
630
+ contestID: this.contestID,
631
+ contestName: this.contestName,
632
+ contestLink: this.contestLink,
633
+ contestTime: this.contestTime.toDate()
634
+ };
635
+ }
636
+ static fromJSON(iRatingHistory) {
637
+ if (typeof iRatingHistory === "string") {
638
+ iRatingHistory = JSON.parse(iRatingHistory);
639
+ }
640
+ const ratingHistory = new RatingHistory();
641
+ ratingHistory.rank = iRatingHistory.rank;
642
+ ratingHistory.rating = iRatingHistory.rating;
643
+ ratingHistory.teamName = iRatingHistory.teamName;
644
+ ratingHistory.organization = iRatingHistory.organization;
645
+ ratingHistory.members = iRatingHistory.members.map((iMember) => Person.fromJSON(iMember));
646
+ ratingHistory.coaches = iRatingHistory.coaches.map((iCoach) => Person.fromJSON(iCoach));
647
+ ratingHistory.contestID = iRatingHistory.contestID;
648
+ ratingHistory.contestName = iRatingHistory.contestName;
649
+ ratingHistory.contestLink = iRatingHistory.contestLink;
650
+ ratingHistory.contestTime = createDayJS(iRatingHistory.contestTime);
651
+ return ratingHistory;
652
+ }
653
+ }
654
+
655
+ class RatingUser {
656
+ constructor() {
657
+ this.id = "";
658
+ this.name = "";
659
+ this.organization = "";
660
+ this.members = [];
661
+ this.coaches = [];
662
+ this.rating = 0;
663
+ this.minRating = 1061109567;
664
+ this.maxRating = -1061109567;
665
+ this.rank = 0;
666
+ this.oldRating = 0;
667
+ this.seed = 1;
668
+ this.delta = 0;
669
+ this.ratingHistories = [];
670
+ }
671
+ UpdateRating(rating) {
672
+ this.rating = rating;
673
+ this.minRating = Math.min(this.minRating, rating);
674
+ this.maxRating = Math.max(this.maxRating, rating);
675
+ }
676
+ toJSON() {
677
+ return {
678
+ id: this.id,
679
+ name: this.name,
680
+ organization: this.organization,
681
+ members: this.members.map((member) => member.toJSON()),
682
+ coaches: this.coaches.map((coach) => coach.toJSON()),
683
+ rating: this.rating,
684
+ minRating: this.minRating,
685
+ maxRating: this.maxRating,
686
+ ratingHistories: this.ratingHistories.map((ratingHistory) => ratingHistory.toJSON())
687
+ };
688
+ }
689
+ static fromJSON(iRatingUser) {
690
+ if (typeof iRatingUser === "string") {
691
+ iRatingUser = JSON.parse(iRatingUser);
692
+ }
693
+ const ratingUser = new RatingUser();
694
+ ratingUser.id = iRatingUser.id;
695
+ ratingUser.name = iRatingUser.name;
696
+ ratingUser.organization = iRatingUser.organization;
697
+ ratingUser.members = iRatingUser.members.map((member) => Person.fromJSON(member));
698
+ ratingUser.coaches = iRatingUser.coaches.map((coach) => Person.fromJSON(coach));
699
+ ratingUser.rating = iRatingUser.rating;
700
+ ratingUser.minRating = iRatingUser.minRating;
701
+ ratingUser.maxRating = iRatingUser.maxRating;
702
+ for (const iRatingHistory of iRatingUser.ratingHistories) {
703
+ ratingUser.ratingHistories.push(RatingHistory.fromJSON(iRatingHistory));
704
+ }
705
+ return ratingUser;
706
+ }
707
+ }
708
+
709
+ class RatingCalculator {
710
+ constructor() {
711
+ this.users = [];
712
+ }
713
+ calculate() {
714
+ this.calculateInternal();
715
+ }
716
+ calcP(userA, userB) {
717
+ return 1 / (1 + 10 ** ((userB.oldRating - userA.oldRating) / 400));
718
+ }
719
+ getExSeed(users, rating, ownUser) {
720
+ const exUser = new RatingUser();
721
+ exUser.oldRating = rating;
722
+ let res = 0;
723
+ users.forEach((user) => {
724
+ if (user.id !== ownUser.id) {
725
+ res += this.calcP(user, exUser);
726
+ }
727
+ });
728
+ return res;
729
+ }
730
+ calcRating(users, rank, user) {
731
+ let left = 1;
732
+ let right = 8e3;
733
+ while (right - left > 1) {
734
+ const mid = Math.floor((left + right) / 2);
735
+ if (this.getExSeed(users, mid, user) < rank) {
736
+ right = mid;
737
+ } else {
738
+ left = mid;
739
+ }
740
+ }
741
+ return left;
742
+ }
743
+ calculateInternal() {
744
+ for (let i = 0; i < this.users.length; i++) {
745
+ const u = this.users[i];
746
+ u.seed = 1;
747
+ for (let j = 0; j < this.users.length; j++) {
748
+ if (i !== j) {
749
+ const otherUser = this.users[j];
750
+ u.seed += this.calcP(otherUser, u);
751
+ }
752
+ }
753
+ }
754
+ let sumDelta = 0;
755
+ for (let i = 0; i < this.users.length; i++) {
756
+ const u = this.users[i];
757
+ u.delta = Math.floor(
758
+ (this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u) - u.oldRating) / 2
759
+ );
760
+ sumDelta += u.delta;
761
+ }
762
+ let inc = Math.floor(-sumDelta / this.users.length) - 1;
763
+ for (let i = 0; i < this.users.length; i++) {
764
+ const u = this.users[i];
765
+ u.delta += inc;
766
+ }
767
+ this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
768
+ const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
769
+ let sumS = 0;
770
+ for (let i = 0; i < s; i++) {
771
+ sumS += this.users[i].delta;
772
+ }
773
+ inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
774
+ this.users.forEach((u) => {
775
+ u.delta += inc;
776
+ u.UpdateRating(u.oldRating + u.delta);
777
+ });
778
+ this.users = this.users.sort((a, b) => a.rank - b.rank);
779
+ }
780
+ }
781
+
782
+ class Rating {
783
+ constructor() {
784
+ this.id = "";
785
+ this.name = "";
786
+ this.baseRating = 1500;
787
+ this.contestIDs = [];
788
+ this.users = [];
789
+ this.ranks = [];
790
+ this.userMap = /* @__PURE__ */ new Map();
791
+ }
792
+ buildRating() {
793
+ for (const rank of this.ranks) {
794
+ rank.buildRank();
795
+ const ratingCalculator = new RatingCalculator();
796
+ for (const t of rank.teams) {
797
+ const id = this.generateTeamId(t);
798
+ let u = null;
799
+ if (!this.userMap.has(id)) {
800
+ u = new RatingUser();
801
+ u.id = id;
802
+ u.name = t.name;
803
+ u.organization = t.organization;
804
+ u.members = createPersons(t.members ?? []);
805
+ u.coaches = createPersons(t.coach ?? []);
806
+ u.rank = t.rank;
807
+ u.oldRating = this.baseRating;
808
+ u.UpdateRating(this.baseRating);
809
+ this.userMap.set(id, u);
810
+ this.users.push(u);
811
+ ratingCalculator.users.push(u);
812
+ } else {
813
+ u = this.userMap.get(id);
814
+ u.rank = t.rank;
815
+ u.oldRating = u.rating;
816
+ ratingCalculator.users.push(u);
817
+ }
818
+ {
819
+ const h = new RatingHistory();
820
+ h.rank = t.rank;
821
+ h.rating = u.rating;
822
+ h.teamName = t.name;
823
+ h.organization = t.organization;
824
+ h.members = createPersons(t.members ?? []);
825
+ h.coaches = createPersons(t.coach ?? []);
826
+ h.contestID = rank.contest.id;
827
+ h.contestLink = h.contestID;
828
+ h.contestName = rank.contest.name;
829
+ h.contestTime = rank.contest.startTime;
830
+ u.ratingHistories.push(h);
831
+ }
832
+ }
833
+ ratingCalculator.calculate();
834
+ for (const u of ratingCalculator.users) {
835
+ u.ratingHistories.at(-1).rating = u.rating;
836
+ }
837
+ }
838
+ }
839
+ generateTeamId(t) {
840
+ const persons = createPersons(t.members ?? []);
841
+ if (persons.length > 0) {
842
+ return persons.map((person) => person.name).join("|");
843
+ }
844
+ return `${t.organization}-${t.name}`;
845
+ }
846
+ toJSON() {
847
+ return {
848
+ id: this.id,
849
+ name: this.name,
850
+ baseRating: this.baseRating,
851
+ contestIDs: this.contestIDs,
852
+ users: this.users.map((ratingUser) => ratingUser.toJSON())
853
+ };
854
+ }
855
+ static fromJSON(iRating) {
856
+ if (typeof iRating === "string") {
857
+ iRating = JSON.parse(iRating);
858
+ }
859
+ const rating = new Rating();
860
+ rating.id = iRating.id;
861
+ rating.name = iRating.name;
862
+ rating.baseRating = iRating.baseRating;
863
+ rating.contestIDs = iRating.contestIDs;
864
+ for (const iUser of iRating.users) {
865
+ rating.users.push(RatingUser.fromJSON(iUser));
866
+ }
867
+ return rating;
868
+ }
869
+ }
870
+
579
871
  class ProblemStatistics {
580
872
  constructor() {
581
873
  this.acceptedNum = 0;
@@ -1183,6 +1475,7 @@ function createContestOptions(contestOptionsJSON = {}) {
1183
1475
 
1184
1476
  class Contest {
1185
1477
  constructor() {
1478
+ this.id = "";
1186
1479
  this.name = "";
1187
1480
  this.startTime = createDayJS();
1188
1481
  this.endTime = createDayJS();
@@ -2036,12 +2329,17 @@ exports.Giants = Giants;
2036
2329
  exports.GiantsType = GiantsType;
2037
2330
  exports.ICPCStandingsCsvConverter = ICPCStandingsCsvConverter;
2038
2331
  exports.MedalType = MedalType;
2332
+ exports.Person = Person;
2039
2333
  exports.PlaceChartPointData = PlaceChartPointData;
2040
2334
  exports.Problem = Problem;
2041
2335
  exports.ProblemStatistics = ProblemStatistics;
2042
2336
  exports.Rank = Rank;
2043
2337
  exports.RankOptions = RankOptions;
2044
2338
  exports.RankStatistics = RankStatistics;
2339
+ exports.Rating = Rating;
2340
+ exports.RatingCalculator = RatingCalculator;
2341
+ exports.RatingHistory = RatingHistory;
2342
+ exports.RatingUser = RatingUser;
2045
2343
  exports.Resolver = Resolver;
2046
2344
  exports.Submission = Submission;
2047
2345
  exports.Team = Team;
@@ -2051,6 +2349,7 @@ exports.createContest = createContest;
2051
2349
  exports.createContestIndex = createContestIndex;
2052
2350
  exports.createContestIndexList = createContestIndexList;
2053
2351
  exports.createDayJS = createDayJS;
2352
+ exports.createPersons = createPersons;
2054
2353
  exports.createProblem = createProblem;
2055
2354
  exports.createProblems = createProblems;
2056
2355
  exports.createProblemsByProblemIds = createProblemsByProblemIds;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TimeUnit, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Lang, CalculationOfPenalty, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, Team as Team$1, Teams as Teams$1, ContestIndex as ContestIndex$1 } from '@xcpcio/types';
1
+ import { TimeUnit, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Lang, CalculationOfPenalty, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, Team as Team$1, Teams as Teams$1, IPerson, IRatingHistory, IRatingUser, IRating, ContestIndex as ContestIndex$1 } from '@xcpcio/types';
2
2
  import dayjs from 'dayjs';
3
3
  export { default as dayjs } from 'dayjs';
4
4
  import * as XLSX from 'xlsx-js-style';
@@ -126,6 +126,7 @@ declare class ContestOptions {
126
126
  }
127
127
 
128
128
  declare class Contest {
129
+ id: string;
129
130
  name: string;
130
131
  startTime: dayjs.Dayjs;
131
132
  endTime: dayjs.Dayjs;
@@ -331,6 +332,7 @@ declare class Rank {
331
332
  buildBalloons(): void;
332
333
  setReplayTime(replayStartTimestamp: number): void;
333
334
  }
335
+ type Ranks = Array<Rank>;
334
336
 
335
337
  declare class CodeforcesGymGhostDATConverter {
336
338
  constructor();
@@ -352,6 +354,79 @@ declare class ICPCStandingsCsvConverter {
352
354
  private getMedalCitation;
353
355
  }
354
356
 
357
+ declare class Person {
358
+ name: string;
359
+ constructor(name?: string);
360
+ toJSON(): IPerson;
361
+ static fromJSON(iPerson: IPerson | string): Person;
362
+ }
363
+ type Persons = Array<Person>;
364
+ declare function createPersons(iPersons: string | Array<string>): Persons;
365
+
366
+ declare class RatingHistory {
367
+ rank: number;
368
+ rating: number;
369
+ teamName: string;
370
+ organization: string;
371
+ members: Persons;
372
+ coaches: Persons;
373
+ contestID: string;
374
+ contestName: string;
375
+ contestLink: string;
376
+ contestTime: dayjs.Dayjs;
377
+ constructor();
378
+ toJSON(): IRatingHistory;
379
+ static fromJSON(iRatingHistory: IRatingHistory | string): RatingHistory;
380
+ }
381
+ type RatingHistories = Array<RatingHistory>;
382
+
383
+ declare class RatingUser {
384
+ id: string;
385
+ name: string;
386
+ organization: string;
387
+ members: Persons;
388
+ coaches: Persons;
389
+ rating: number;
390
+ minRating: number;
391
+ maxRating: number;
392
+ rank: number;
393
+ oldRating: number;
394
+ seed: number;
395
+ delta: number;
396
+ ratingHistories: RatingHistories;
397
+ constructor();
398
+ UpdateRating(rating: number): void;
399
+ toJSON(): IRatingUser;
400
+ static fromJSON(iRatingUser: IRatingUser | string): RatingUser;
401
+ }
402
+ type RatingUsers = Array<RatingUser>;
403
+ type RatingUserMap = Map<string, RatingUser>;
404
+
405
+ declare class RatingCalculator {
406
+ users: RatingUsers;
407
+ constructor();
408
+ calculate(): void;
409
+ private calcP;
410
+ private getExSeed;
411
+ private calcRating;
412
+ private calculateInternal;
413
+ }
414
+
415
+ declare class Rating {
416
+ id: string;
417
+ name: string;
418
+ baseRating: number;
419
+ contestIDs: string[];
420
+ users: RatingUsers;
421
+ ranks: Ranks;
422
+ userMap: RatingUserMap;
423
+ constructor();
424
+ buildRating(): void;
425
+ generateTeamId(t: Team): string;
426
+ toJSON(): IRating;
427
+ static fromJSON(iRating: IRating | string): Rating;
428
+ }
429
+
355
430
  declare class ContestIndexConfig {
356
431
  contestName: string;
357
432
  startTime: dayjs.Dayjs;
@@ -398,4 +473,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
398
473
  declare function isPending(status: SubmissionStatus): boolean;
399
474
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
400
475
 
401
- export { Award, Awards, Balloon, Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Resolver, SelectOptionItem, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
476
+ export { Award, Awards, Balloon, Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, Persons, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Ranks, Rating, RatingCalculator, RatingHistories, RatingHistory, RatingUser, RatingUserMap, RatingUsers, Resolver, SelectOptionItem, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
package/dist/index.mjs CHANGED
@@ -515,6 +515,36 @@ class ICPCStandingsCsvConverter {
515
515
  }
516
516
  }
517
517
 
518
+ class Person {
519
+ constructor(name = "") {
520
+ this.name = name;
521
+ }
522
+ toJSON() {
523
+ return {
524
+ name: this.name
525
+ };
526
+ }
527
+ static fromJSON(iPerson) {
528
+ if (typeof iPerson === "string") {
529
+ iPerson = JSON.parse(iPerson);
530
+ }
531
+ const person = new Person();
532
+ person.name = iPerson.name;
533
+ return person;
534
+ }
535
+ }
536
+ function createPersons(iPersons) {
537
+ if (typeof iPersons === "string") {
538
+ for (const c of " ,\u3001|") {
539
+ if (iPersons.includes(c)) {
540
+ return iPersons.split(c).map((name) => new Person(name));
541
+ }
542
+ }
543
+ return [new Person(iPersons)];
544
+ }
545
+ return iPersons.map((name) => new Person(name));
546
+ }
547
+
518
548
  function calcDirt(attemptedNum, solvedNum) {
519
549
  if (solvedNum === 0) {
520
550
  return 0;
@@ -543,6 +573,268 @@ function getWhiteOrBlackColor(background) {
543
573
  return brightness <= threshold ? "#fff" : "#000";
544
574
  }
545
575
 
576
+ class RatingHistory {
577
+ constructor() {
578
+ this.rank = 0;
579
+ this.rating = 0;
580
+ this.teamName = "";
581
+ this.organization = "";
582
+ this.members = [];
583
+ this.coaches = [];
584
+ this.contestID = "";
585
+ this.contestName = "";
586
+ this.contestLink = "";
587
+ this.contestTime = createDayJS();
588
+ }
589
+ toJSON() {
590
+ return {
591
+ rank: this.rank,
592
+ rating: this.rating,
593
+ teamName: this.teamName,
594
+ organization: this.organization,
595
+ members: this.members.map((member) => member.toJSON()),
596
+ coaches: this.coaches.map((coach) => coach.toJSON()),
597
+ contestID: this.contestID,
598
+ contestName: this.contestName,
599
+ contestLink: this.contestLink,
600
+ contestTime: this.contestTime.toDate()
601
+ };
602
+ }
603
+ static fromJSON(iRatingHistory) {
604
+ if (typeof iRatingHistory === "string") {
605
+ iRatingHistory = JSON.parse(iRatingHistory);
606
+ }
607
+ const ratingHistory = new RatingHistory();
608
+ ratingHistory.rank = iRatingHistory.rank;
609
+ ratingHistory.rating = iRatingHistory.rating;
610
+ ratingHistory.teamName = iRatingHistory.teamName;
611
+ ratingHistory.organization = iRatingHistory.organization;
612
+ ratingHistory.members = iRatingHistory.members.map((iMember) => Person.fromJSON(iMember));
613
+ ratingHistory.coaches = iRatingHistory.coaches.map((iCoach) => Person.fromJSON(iCoach));
614
+ ratingHistory.contestID = iRatingHistory.contestID;
615
+ ratingHistory.contestName = iRatingHistory.contestName;
616
+ ratingHistory.contestLink = iRatingHistory.contestLink;
617
+ ratingHistory.contestTime = createDayJS(iRatingHistory.contestTime);
618
+ return ratingHistory;
619
+ }
620
+ }
621
+
622
+ class RatingUser {
623
+ constructor() {
624
+ this.id = "";
625
+ this.name = "";
626
+ this.organization = "";
627
+ this.members = [];
628
+ this.coaches = [];
629
+ this.rating = 0;
630
+ this.minRating = 1061109567;
631
+ this.maxRating = -1061109567;
632
+ this.rank = 0;
633
+ this.oldRating = 0;
634
+ this.seed = 1;
635
+ this.delta = 0;
636
+ this.ratingHistories = [];
637
+ }
638
+ UpdateRating(rating) {
639
+ this.rating = rating;
640
+ this.minRating = Math.min(this.minRating, rating);
641
+ this.maxRating = Math.max(this.maxRating, rating);
642
+ }
643
+ toJSON() {
644
+ return {
645
+ id: this.id,
646
+ name: this.name,
647
+ organization: this.organization,
648
+ members: this.members.map((member) => member.toJSON()),
649
+ coaches: this.coaches.map((coach) => coach.toJSON()),
650
+ rating: this.rating,
651
+ minRating: this.minRating,
652
+ maxRating: this.maxRating,
653
+ ratingHistories: this.ratingHistories.map((ratingHistory) => ratingHistory.toJSON())
654
+ };
655
+ }
656
+ static fromJSON(iRatingUser) {
657
+ if (typeof iRatingUser === "string") {
658
+ iRatingUser = JSON.parse(iRatingUser);
659
+ }
660
+ const ratingUser = new RatingUser();
661
+ ratingUser.id = iRatingUser.id;
662
+ ratingUser.name = iRatingUser.name;
663
+ ratingUser.organization = iRatingUser.organization;
664
+ ratingUser.members = iRatingUser.members.map((member) => Person.fromJSON(member));
665
+ ratingUser.coaches = iRatingUser.coaches.map((coach) => Person.fromJSON(coach));
666
+ ratingUser.rating = iRatingUser.rating;
667
+ ratingUser.minRating = iRatingUser.minRating;
668
+ ratingUser.maxRating = iRatingUser.maxRating;
669
+ for (const iRatingHistory of iRatingUser.ratingHistories) {
670
+ ratingUser.ratingHistories.push(RatingHistory.fromJSON(iRatingHistory));
671
+ }
672
+ return ratingUser;
673
+ }
674
+ }
675
+
676
+ class RatingCalculator {
677
+ constructor() {
678
+ this.users = [];
679
+ }
680
+ calculate() {
681
+ this.calculateInternal();
682
+ }
683
+ calcP(userA, userB) {
684
+ return 1 / (1 + 10 ** ((userB.oldRating - userA.oldRating) / 400));
685
+ }
686
+ getExSeed(users, rating, ownUser) {
687
+ const exUser = new RatingUser();
688
+ exUser.oldRating = rating;
689
+ let res = 0;
690
+ users.forEach((user) => {
691
+ if (user.id !== ownUser.id) {
692
+ res += this.calcP(user, exUser);
693
+ }
694
+ });
695
+ return res;
696
+ }
697
+ calcRating(users, rank, user) {
698
+ let left = 1;
699
+ let right = 8e3;
700
+ while (right - left > 1) {
701
+ const mid = Math.floor((left + right) / 2);
702
+ if (this.getExSeed(users, mid, user) < rank) {
703
+ right = mid;
704
+ } else {
705
+ left = mid;
706
+ }
707
+ }
708
+ return left;
709
+ }
710
+ calculateInternal() {
711
+ for (let i = 0; i < this.users.length; i++) {
712
+ const u = this.users[i];
713
+ u.seed = 1;
714
+ for (let j = 0; j < this.users.length; j++) {
715
+ if (i !== j) {
716
+ const otherUser = this.users[j];
717
+ u.seed += this.calcP(otherUser, u);
718
+ }
719
+ }
720
+ }
721
+ let sumDelta = 0;
722
+ for (let i = 0; i < this.users.length; i++) {
723
+ const u = this.users[i];
724
+ u.delta = Math.floor(
725
+ (this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u) - u.oldRating) / 2
726
+ );
727
+ sumDelta += u.delta;
728
+ }
729
+ let inc = Math.floor(-sumDelta / this.users.length) - 1;
730
+ for (let i = 0; i < this.users.length; i++) {
731
+ const u = this.users[i];
732
+ u.delta += inc;
733
+ }
734
+ this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
735
+ const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
736
+ let sumS = 0;
737
+ for (let i = 0; i < s; i++) {
738
+ sumS += this.users[i].delta;
739
+ }
740
+ inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
741
+ this.users.forEach((u) => {
742
+ u.delta += inc;
743
+ u.UpdateRating(u.oldRating + u.delta);
744
+ });
745
+ this.users = this.users.sort((a, b) => a.rank - b.rank);
746
+ }
747
+ }
748
+
749
+ class Rating {
750
+ constructor() {
751
+ this.id = "";
752
+ this.name = "";
753
+ this.baseRating = 1500;
754
+ this.contestIDs = [];
755
+ this.users = [];
756
+ this.ranks = [];
757
+ this.userMap = /* @__PURE__ */ new Map();
758
+ }
759
+ buildRating() {
760
+ for (const rank of this.ranks) {
761
+ rank.buildRank();
762
+ const ratingCalculator = new RatingCalculator();
763
+ for (const t of rank.teams) {
764
+ const id = this.generateTeamId(t);
765
+ let u = null;
766
+ if (!this.userMap.has(id)) {
767
+ u = new RatingUser();
768
+ u.id = id;
769
+ u.name = t.name;
770
+ u.organization = t.organization;
771
+ u.members = createPersons(t.members ?? []);
772
+ u.coaches = createPersons(t.coach ?? []);
773
+ u.rank = t.rank;
774
+ u.oldRating = this.baseRating;
775
+ u.UpdateRating(this.baseRating);
776
+ this.userMap.set(id, u);
777
+ this.users.push(u);
778
+ ratingCalculator.users.push(u);
779
+ } else {
780
+ u = this.userMap.get(id);
781
+ u.rank = t.rank;
782
+ u.oldRating = u.rating;
783
+ ratingCalculator.users.push(u);
784
+ }
785
+ {
786
+ const h = new RatingHistory();
787
+ h.rank = t.rank;
788
+ h.rating = u.rating;
789
+ h.teamName = t.name;
790
+ h.organization = t.organization;
791
+ h.members = createPersons(t.members ?? []);
792
+ h.coaches = createPersons(t.coach ?? []);
793
+ h.contestID = rank.contest.id;
794
+ h.contestLink = h.contestID;
795
+ h.contestName = rank.contest.name;
796
+ h.contestTime = rank.contest.startTime;
797
+ u.ratingHistories.push(h);
798
+ }
799
+ }
800
+ ratingCalculator.calculate();
801
+ for (const u of ratingCalculator.users) {
802
+ u.ratingHistories.at(-1).rating = u.rating;
803
+ }
804
+ }
805
+ }
806
+ generateTeamId(t) {
807
+ const persons = createPersons(t.members ?? []);
808
+ if (persons.length > 0) {
809
+ return persons.map((person) => person.name).join("|");
810
+ }
811
+ return `${t.organization}-${t.name}`;
812
+ }
813
+ toJSON() {
814
+ return {
815
+ id: this.id,
816
+ name: this.name,
817
+ baseRating: this.baseRating,
818
+ contestIDs: this.contestIDs,
819
+ users: this.users.map((ratingUser) => ratingUser.toJSON())
820
+ };
821
+ }
822
+ static fromJSON(iRating) {
823
+ if (typeof iRating === "string") {
824
+ iRating = JSON.parse(iRating);
825
+ }
826
+ const rating = new Rating();
827
+ rating.id = iRating.id;
828
+ rating.name = iRating.name;
829
+ rating.baseRating = iRating.baseRating;
830
+ rating.contestIDs = iRating.contestIDs;
831
+ for (const iUser of iRating.users) {
832
+ rating.users.push(RatingUser.fromJSON(iUser));
833
+ }
834
+ return rating;
835
+ }
836
+ }
837
+
546
838
  class ProblemStatistics {
547
839
  constructor() {
548
840
  this.acceptedNum = 0;
@@ -1150,6 +1442,7 @@ function createContestOptions(contestOptionsJSON = {}) {
1150
1442
 
1151
1443
  class Contest {
1152
1444
  constructor() {
1445
+ this.id = "";
1153
1446
  this.name = "";
1154
1447
  this.startTime = createDayJS();
1155
1448
  this.endTime = createDayJS();
@@ -1989,4 +2282,4 @@ class Resolver extends Rank {
1989
2282
  }
1990
2283
  }
1991
2284
 
1992
- export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Resolver, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
2285
+ export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Rating, RatingCalculator, RatingHistory, RatingUser, Resolver, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcpcio/core",
3
- "version": "0.45.1",
3
+ "version": "0.46.1",
4
4
  "description": "XCPCIO Core",
5
5
  "author": "Dup4 <lyuzhi.pan@gmail.com>",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "papaparse": "^5.4.1",
50
50
  "string-width": "^6.1.0",
51
51
  "xlsx-js-style": "^1.2.0",
52
- "@xcpcio/types": "0.45.1"
52
+ "@xcpcio/types": "0.46.1"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@babel/types": "^7.22.4",
package/src/contest.ts CHANGED
@@ -10,6 +10,7 @@ import { type Awards, MedalType } from "./award";
10
10
  import { ContestOptions, createContestOptions } from "./contest-options";
11
11
 
12
12
  export class Contest {
13
+ id = "";
13
14
  name = "";
14
15
 
15
16
  startTime: dayjs.Dayjs;
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./export";
2
+ export * from "./rating";
2
3
  export * from "./utils";
3
4
 
4
5
  export * from "./award";
@@ -8,6 +9,7 @@ export * from "./battle-of-giants";
8
9
  export * from "./contest-index";
9
10
  export * from "./contest";
10
11
  export * from "./image";
12
+ export * from "./person";
11
13
  export * from "./problem";
12
14
  export * from "./rank-statistics";
13
15
  export * from "./rank";
package/src/person.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { IPerson } from "@xcpcio/types";
2
+
3
+ export class Person {
4
+ name: string;
5
+
6
+ constructor(name = "") {
7
+ this.name = name;
8
+ }
9
+
10
+ toJSON(): IPerson {
11
+ return {
12
+ name: this.name,
13
+ };
14
+ }
15
+
16
+ static fromJSON(iPerson: IPerson | string): Person {
17
+ if (typeof iPerson === "string") {
18
+ iPerson = JSON.parse(iPerson) as IPerson;
19
+ }
20
+
21
+ const person = new Person();
22
+ person.name = iPerson.name;
23
+
24
+ return person;
25
+ }
26
+ }
27
+
28
+ export type Persons = Array<Person>;
29
+
30
+ export function createPersons(iPersons: string | Array<string>): Persons {
31
+ if (typeof iPersons === "string") {
32
+ for (const c of " ,、|") {
33
+ if (iPersons.includes(c)) {
34
+ return iPersons.split(c).map(name => new Person(name));
35
+ }
36
+ }
37
+
38
+ return [new Person(iPersons)];
39
+ }
40
+
41
+ return iPersons.map(name => new Person(name));
42
+ }
package/src/rank.ts CHANGED
@@ -608,3 +608,5 @@ export class Rank {
608
608
  this.contest.setReplayTime(replayStartTimestamp);
609
609
  }
610
610
  }
611
+
612
+ export type Ranks = Array<Rank>;
@@ -0,0 +1,4 @@
1
+ export * from "./rating-calculator";
2
+ export * from "./rating-history";
3
+ export * from "./rating-user";
4
+ export * from "./rating";
@@ -0,0 +1,100 @@
1
+ import type { RatingUsers } from "./rating-user";
2
+ import { RatingUser } from "./rating-user";
3
+
4
+ // https://www.wikiwand.com/en/Elo_rating_system
5
+ export class RatingCalculator {
6
+ users: RatingUsers;
7
+
8
+ constructor() {
9
+ this.users = [];
10
+ }
11
+
12
+ calculate() {
13
+ this.calculateInternal();
14
+ }
15
+
16
+ private calcP(userA: RatingUser, userB: RatingUser) {
17
+ return 1.0 / (1.0 + 10 ** ((userB.oldRating - userA.oldRating) / 400.0));
18
+ }
19
+
20
+ private getExSeed(users: RatingUsers, rating: number, ownUser: RatingUser) {
21
+ const exUser = new RatingUser();
22
+ exUser.oldRating = rating;
23
+
24
+ let res = 0;
25
+
26
+ users.forEach((user) => {
27
+ if (user.id !== ownUser.id) {
28
+ res += this.calcP(user, exUser);
29
+ }
30
+ });
31
+
32
+ return res;
33
+ }
34
+
35
+ private calcRating(users: RatingUsers, rank: number, user: RatingUser) {
36
+ let left = 1;
37
+ let right = 8000;
38
+
39
+ while (right - left > 1) {
40
+ const mid = Math.floor((left + right) / 2);
41
+ if (this.getExSeed(users, mid, user) < rank) {
42
+ right = mid;
43
+ } else {
44
+ left = mid;
45
+ }
46
+ }
47
+
48
+ return left;
49
+ }
50
+
51
+ private calculateInternal() {
52
+ // Calculate seed
53
+ for (let i = 0; i < this.users.length; i++) {
54
+ const u = this.users[i];
55
+ u.seed = 1.0;
56
+ for (let j = 0; j < this.users.length; j++) {
57
+ if (i !== j) {
58
+ const otherUser = this.users[j];
59
+ u.seed += this.calcP(otherUser, u);
60
+ }
61
+ }
62
+ }
63
+
64
+ // Calculate initial delta and sumDelta
65
+ let sumDelta = 0;
66
+ for (let i = 0; i < this.users.length; i++) {
67
+ const u = this.users[i];
68
+ u.delta = Math.floor(
69
+ (this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u)
70
+ - u.oldRating)
71
+ / 2,
72
+ );
73
+ sumDelta += u.delta;
74
+ }
75
+
76
+ // Calculate first inc
77
+ let inc = Math.floor(-sumDelta / this.users.length) - 1;
78
+ for (let i = 0; i < this.users.length; i++) {
79
+ const u = this.users[i];
80
+ u.delta += inc;
81
+ }
82
+
83
+ // Calculate second inc
84
+ this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
85
+ const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
86
+ let sumS = 0;
87
+ for (let i = 0; i < s; i++) {
88
+ sumS += this.users[i].delta;
89
+ }
90
+ inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
91
+
92
+ // Calculate new rating
93
+ this.users.forEach((u) => {
94
+ u.delta += inc;
95
+ u.UpdateRating(u.oldRating + u.delta);
96
+ });
97
+
98
+ this.users = this.users.sort((a, b) => a.rank - b.rank);
99
+ }
100
+ }
@@ -0,0 +1,81 @@
1
+ import type { IRatingHistory } from "@xcpcio/types";
2
+
3
+ import type { dayjs } from "../utils";
4
+ import { createDayJS } from "../utils";
5
+ import { Person } from "../person";
6
+ import type { Persons } from "../person";
7
+
8
+ export class RatingHistory {
9
+ rank: number;
10
+ rating: number;
11
+
12
+ teamName: string;
13
+ organization: string;
14
+
15
+ members: Persons;
16
+ coaches: Persons;
17
+
18
+ contestID: string;
19
+ contestName: string;
20
+ contestLink: string;
21
+ contestTime: dayjs.Dayjs;
22
+
23
+ constructor() {
24
+ this.rank = 0;
25
+ this.rating = 0;
26
+
27
+ this.teamName = "";
28
+ this.organization = "";
29
+
30
+ this.members = [];
31
+ this.coaches = [];
32
+
33
+ this.contestID = "";
34
+ this.contestName = "";
35
+ this.contestLink = "";
36
+ this.contestTime = createDayJS();
37
+ }
38
+
39
+ toJSON(): IRatingHistory {
40
+ return {
41
+ rank: this.rank,
42
+ rating: this.rating,
43
+
44
+ teamName: this.teamName,
45
+ organization: this.organization,
46
+
47
+ members: this.members.map(member => member.toJSON()),
48
+ coaches: this.coaches.map(coach => coach.toJSON()),
49
+
50
+ contestID: this.contestID,
51
+ contestName: this.contestName,
52
+ contestLink: this.contestLink,
53
+ contestTime: this.contestTime.toDate(),
54
+ };
55
+ }
56
+
57
+ static fromJSON(iRatingHistory: IRatingHistory | string): RatingHistory {
58
+ if (typeof iRatingHistory === "string") {
59
+ iRatingHistory = JSON.parse(iRatingHistory) as IRatingHistory;
60
+ }
61
+
62
+ const ratingHistory = new RatingHistory();
63
+ ratingHistory.rank = iRatingHistory.rank;
64
+ ratingHistory.rating = iRatingHistory.rating;
65
+
66
+ ratingHistory.teamName = iRatingHistory.teamName;
67
+ ratingHistory.organization = iRatingHistory.organization;
68
+
69
+ ratingHistory.members = iRatingHistory.members.map(iMember => Person.fromJSON(iMember));
70
+ ratingHistory.coaches = iRatingHistory.coaches.map(iCoach => Person.fromJSON(iCoach));
71
+
72
+ ratingHistory.contestID = iRatingHistory.contestID;
73
+ ratingHistory.contestName = iRatingHistory.contestName;
74
+ ratingHistory.contestLink = iRatingHistory.contestLink;
75
+ ratingHistory.contestTime = createDayJS(iRatingHistory.contestTime);
76
+
77
+ return ratingHistory;
78
+ }
79
+ }
80
+
81
+ export type RatingHistories = Array<RatingHistory>;
@@ -0,0 +1,98 @@
1
+ import type { IRatingUser } from "@xcpcio/types";
2
+
3
+ import type { Persons } from "../person";
4
+ import { Person } from "../person";
5
+ import { type RatingHistories, RatingHistory } from "./rating-history";
6
+
7
+ export class RatingUser {
8
+ id: string;
9
+ name: string;
10
+ organization: string;
11
+
12
+ members: Persons;
13
+ coaches: Persons;
14
+
15
+ rating: number;
16
+ minRating: number;
17
+ maxRating: number;
18
+
19
+ rank: number;
20
+ oldRating: number;
21
+
22
+ seed: number;
23
+ delta: number;
24
+
25
+ ratingHistories: RatingHistories;
26
+
27
+ constructor() {
28
+ this.id = "";
29
+ this.name = "";
30
+ this.organization = "";
31
+
32
+ this.members = [];
33
+ this.coaches = [];
34
+
35
+ this.rating = 0;
36
+ this.minRating = 0x3F3F3F3F;
37
+ this.maxRating = -0x3F3F3F3F;
38
+
39
+ this.rank = 0;
40
+ this.oldRating = 0;
41
+
42
+ this.seed = 1.0;
43
+ this.delta = 0;
44
+
45
+ this.ratingHistories = [];
46
+ }
47
+
48
+ UpdateRating(rating: number) {
49
+ this.rating = rating;
50
+ this.minRating = Math.min(this.minRating, rating);
51
+ this.maxRating = Math.max(this.maxRating, rating);
52
+ }
53
+
54
+ toJSON(): IRatingUser {
55
+ return {
56
+ id: this.id,
57
+ name: this.name,
58
+ organization: this.organization,
59
+
60
+ members: this.members.map(member => member.toJSON()),
61
+ coaches: this.coaches.map(coach => coach.toJSON()),
62
+
63
+ rating: this.rating,
64
+ minRating: this.minRating,
65
+ maxRating: this.maxRating,
66
+
67
+ ratingHistories: this.ratingHistories.map(ratingHistory => ratingHistory.toJSON()),
68
+ };
69
+ }
70
+
71
+ static fromJSON(iRatingUser: IRatingUser | string): RatingUser {
72
+ if (typeof iRatingUser === "string") {
73
+ iRatingUser = JSON.parse(iRatingUser) as IRatingUser;
74
+ }
75
+
76
+ const ratingUser = new RatingUser();
77
+
78
+ ratingUser.id = iRatingUser.id;
79
+ ratingUser.name = iRatingUser.name;
80
+ ratingUser.organization = iRatingUser.organization;
81
+
82
+ ratingUser.members = iRatingUser.members.map(member => Person.fromJSON(member));
83
+ ratingUser.coaches = iRatingUser.coaches.map(coach => Person.fromJSON(coach));
84
+
85
+ ratingUser.rating = iRatingUser.rating;
86
+ ratingUser.minRating = iRatingUser.minRating;
87
+ ratingUser.maxRating = iRatingUser.maxRating;
88
+
89
+ for (const iRatingHistory of iRatingUser.ratingHistories) {
90
+ ratingUser.ratingHistories.push(RatingHistory.fromJSON(iRatingHistory));
91
+ }
92
+
93
+ return ratingUser;
94
+ }
95
+ }
96
+
97
+ export type RatingUsers = Array<RatingUser>;
98
+ export type RatingUserMap = Map<string, RatingUser>;
@@ -0,0 +1,137 @@
1
+ import type { IRating } from "@xcpcio/types";
2
+
3
+ import type { Ranks } from "../rank";
4
+ import type { Team } from "../team";
5
+ import { createPersons } from "../person";
6
+
7
+ import { RatingCalculator } from "./rating-calculator";
8
+ import { RatingHistory } from "./rating-history";
9
+ import type { RatingUserMap, RatingUsers } from "./rating-user";
10
+ import { RatingUser } from "./rating-user";
11
+
12
+ export class Rating {
13
+ id: string;
14
+ name: string;
15
+ baseRating: number;
16
+
17
+ contestIDs: string[];
18
+ users: RatingUsers;
19
+
20
+ ranks: Ranks;
21
+ userMap: RatingUserMap;
22
+
23
+ constructor() {
24
+ this.id = "";
25
+ this.name = "";
26
+ this.baseRating = 1500;
27
+
28
+ this.contestIDs = [];
29
+ this.users = [];
30
+
31
+ this.ranks = [];
32
+ this.userMap = new Map<string, RatingUser>();
33
+ }
34
+
35
+ buildRating() {
36
+ for (const rank of this.ranks) {
37
+ rank.buildRank();
38
+
39
+ const ratingCalculator = new RatingCalculator();
40
+
41
+ for (const t of rank.teams) {
42
+ const id = this.generateTeamId(t);
43
+
44
+ let u = null;
45
+
46
+ if (!this.userMap.has(id)) {
47
+ u = new RatingUser();
48
+ u.id = id;
49
+ u.name = t.name;
50
+ u.organization = t.organization;
51
+
52
+ u.members = createPersons(t.members ?? []);
53
+ u.coaches = createPersons(t.coach ?? []);
54
+
55
+ u.rank = t.rank;
56
+ u.oldRating = this.baseRating;
57
+ u.UpdateRating(this.baseRating);
58
+
59
+ this.userMap.set(id, u);
60
+ this.users.push(u);
61
+
62
+ ratingCalculator.users.push(u);
63
+ } else {
64
+ u = this.userMap.get(id)!;
65
+ u.rank = t.rank;
66
+ u.oldRating = u.rating;
67
+ ratingCalculator.users.push(u);
68
+ }
69
+
70
+ {
71
+ const h = new RatingHistory();
72
+ h.rank = t.rank;
73
+ h.rating = u.rating;
74
+
75
+ h.teamName = t.name;
76
+ h.organization = t.organization;
77
+
78
+ h.members = createPersons(t.members ?? []);
79
+ h.coaches = createPersons(t.coach ?? []);
80
+
81
+ h.contestID = rank.contest.id;
82
+ h.contestLink = h.contestID;
83
+ h.contestName = rank.contest.name;
84
+ h.contestTime = rank.contest.startTime;
85
+
86
+ u.ratingHistories.push(h);
87
+ }
88
+ }
89
+
90
+ ratingCalculator.calculate();
91
+
92
+ for (const u of ratingCalculator.users) {
93
+ u.ratingHistories.at(-1)!.rating = u.rating;
94
+ }
95
+ }
96
+ }
97
+
98
+ generateTeamId(t: Team) {
99
+ const persons = createPersons(t.members ?? []);
100
+ if (persons.length > 0) {
101
+ return persons.map(person => person.name).join("|");
102
+ }
103
+
104
+ return `${t.organization}-${t.name}`;
105
+ }
106
+
107
+ toJSON(): IRating {
108
+ return {
109
+ id: this.id,
110
+ name: this.name,
111
+ baseRating: this.baseRating,
112
+
113
+ contestIDs: this.contestIDs,
114
+ users: this.users.map(ratingUser => ratingUser.toJSON()),
115
+ };
116
+ }
117
+
118
+ static fromJSON(iRating: IRating | string): Rating {
119
+ if (typeof iRating === "string") {
120
+ iRating = JSON.parse(iRating) as IRating;
121
+ }
122
+
123
+ const rating = new Rating();
124
+
125
+ rating.id = iRating.id;
126
+ rating.name = iRating.name;
127
+ rating.baseRating = iRating.baseRating;
128
+
129
+ rating.contestIDs = iRating.contestIDs;
130
+
131
+ for (const iUser of iRating.users) {
132
+ rating.users.push(RatingUser.fromJSON(iUser));
133
+ }
134
+
135
+ return rating;
136
+ }
137
+ }