Grades

 view release on metacpan or  search on metacpan

lib/Grades.pm  view on Meta::CPAN


	use Grades;

	my $script = Grades::Script->new_with_options( league => getcwd );
	my $league = League->new( id => $script->league );
	my $grades = Grades->new( league => $league );

	$league->approach->meta->apply( $grades );
	my $classworkgrades = $grades->classwork;
	my $homeworkgrades = $grades->homework;
	my $examgrades = $grades->examGrade;

=head1 DESCRIPTION

An alternative to a spreadsheet for grading students, using YAML files and scripts. The students are the players in a league ( class.) See the README and example emile league in t/emile in the distribution for the layout of the league directory in wh...

Grades are a collocation of Classwork, Homework and Exams roles, but the Classwork role 'delegates' its methods to one of a number of approaches, each of which has a 'total' and 'totalPercent' method. Current approaches, or forms of curriculum, inclu...

Keywords: gold stars, token economies, bean counter

=cut

=head1 ATTRIBUTES & METHODS

=cut

=head2 LEAGUE CLASS

=cut

class League {
	use YAML qw/LoadFile DumpFile/;
	use List::MoreUtils qw/any/;
	use Grades::Types qw/PlayerName PlayerNames Members/;
	use Try::Tiny;
	use Carp;

=head3 leagues

The path to the league directory.

=cut

	has 'leagues' => (is => 'ro', isa => 'Str', required => 1, lazy => 1,
	    default => '/home/drbean/022' );

=head3 id

Actually, it's a path to the league directory, below the $grades->leagues dir.

=cut

	has 'id' => (is => 'ro', isa => 'Str', required => 1);

=head3 yaml

The content of the league configuration file.

=cut

	has 'yaml' => (is => 'ro', isa => 'HashRef', lazy_build => 1);
	method _build_yaml {
			my $leaguedirs = $self->leagues;
			my $league = $self->id;
			$self->inspect( "$leaguedirs/$league/league.yaml" );
	}

=head3 name

The name of the league (class).

=cut

	has 'name' => (is => 'ro', isa => 'Str', lazy_build => 1);
	method _build_name {
		my $data = $self->yaml;
		$data->{league};
	}


=head3 field

The field of the league (class). What is the subject or description, the area of endeavor?

=cut

	has 'field' => (is => 'ro', isa => 'Str', lazy_build => 1);
	method _build_field {
		my $data = $self->yaml;
		$data->{field};
	}


=head3 approach

The style of classwork competition, eg Compcomp, or Groupwork. This is the name of the class (think OOP) to which 'classwork' and other methods are delegated.

=cut

	has 'approach' => (is => 'ro', isa => 'Str', lazy => 1,
	    default => sub { shift->yaml->{approach} } );

=head3 members

Hash refs of the players (students) in the league. The module assumes each of the members in the arrayref returned by this attribute is a hash ref containing an id and name of the member.

=cut

	has 'members', is => 'ro', isa => Members, lazy_build => 1;
	method _build_members {
		my $data = $self->yaml;
		$data->{member};
	}

=head3 session

The first week in each session, like { 1 => 1, 2 => 5, 3 => 10, 4 => 14 }, monotonically increasing week numbers.

=cut

	has 'session', (is => 'ro', isa => 'HashRef',
	    lazy => 1, default => sub { shift->yaml->{session} } );


=head3 absentees

Students who have stopped coming to class and so won't be included in classwork scoring.

=cut

	has 'absentees', (is => 'ro', isa => PlayerNames,
	    lazy => 1, default => sub { shift->yaml->{out} } );


=head3 transfer

    $oldleague = $newleague->transfer->{V9731059}

Players who have transferred to this league from some other league at some point and the leagues they transferred from.

=cut

	has 'transfer', (is => 'ro', isa => 'HashRef',
	    lazy => 1, default => sub { shift->yaml->{transfer} } );


=head3 is_member

Whether the passed id is that of a member in the league (class).

=cut

	method is_member (Str $id) {
		my $data = $self->yaml;
		any { $_->{id} eq $id } @{$data->{member}};
	}


=head3 ided

The id of the member with the given player name.

=cut

    method ided( Str $player) {
	my $members = $self->members;
	my %ids = map { $_->{id} => $_->{name} }
	    grep { $_->{name} eq $player } @$members;
	my @ids = keys %ids;
	my @names = values %ids;
	local $" = ', ';
	carp @ids . " players named @names, with ids: @ids," unless @ids==1;
	if ( @ids == 1 ) { return $ids[0] }
	else { return $ids{$player}; }  
    }

=head3 inspect

Loads a YAML file.

=cut

    method inspect (Str $file) {
	my ($warning, $data);
	try { $data = LoadFile $file }
	    catch { carp "Couldn't open $file," };
	return $data;
	}

=head3 save

Dumps a YAML file

=cut

    method save (Str $file, HashRef $data) {
	try { DumpFile $file, $data }
	    catch { warn "Couldn't save $data to $file," };
	}

}


lib/Grades.pm  view on Meta::CPAN

		my $id = $self->id;
		my $members = $league->members;
		my $member = firstval { $_->{id} eq $id } @$members;
		$member->{name};
	}

	has 'Chinese' => (is => 'ro', isa => 'Str');
}


=head2 NONENTITY CLASS

=cut 

class Nonentity extends Player {

=head3 name

The name is 'Bye'. The id is too, as a matter of fact.

=cut

    has 'name' => (is => 'ro', isa => 'Str', required => 1 );

}


=head2	GRADES CLASS

=head2 Grades' Homework Methods
=cut

role Homework {
	use YAML qw/LoadFile DumpFile/;
	use List::Util qw/min sum/;
	use Scalar::Util qw/looks_like_number/;
	use Carp;
    use Grades::Types qw/PlayerId HomeworkResult HomeworkRound HomeworkRounds
	RoundsResults/;

=head3 hwdir

The directory where the homework is.

=cut

    has 'hwdir' => (is => 'ro', isa => 'Str', lazy_build => 1);
    method _build_hwdir {
	my $league = $self->league->id;
	my $leaguedir = $self->league->leagues . "/" . $league;
	my $basename = shift->league->yaml->{hw} || "exams";
	my $hwdir = $leaguedir . '/' . $basename;
    }

=head3 rounds

An arrayref of the rounds for which there are homework grades for players in the league, in round order, of the form, [1, 3 .. 7, 9 ..].

=cut

	has 'rounds', (is => 'ro', isa => 'ArrayRef[Int]', lazy_build => 1);
	method _build_rounds {
		my $hwdir = $self->hwdir;
		my @hw = glob "$hwdir/*.yaml";
		[ sort {$a<=>$b} map m/^$hwdir\/(\d+)\.yaml$/, @hw ];
	}

=head3 roundIndex

Given a round name (ie number), returns the ordinal position in which this round was played, with the first round numbered 0. Returns undef if the round was not played.

=cut

	method roundIndex (Int $round) {
		my $rounds = $self->rounds;
		my $n = 0;
		for ( @$rounds ) {
			return $n if $_ eq $round;
			$n++;
		}
	}

=head3 roundfiles

An hashref of the files with data for the rounds for which there are homework grades for players in the league, keyed on rounds.

=cut

	has 'roundfiles', (is => 'ro', isa => 'HashRef[ArrayRef]', lazy_build => 1);
	method _build_roundfiles {
		my $hwdir = $self->hwdir;
		my @hw = glob "$hwdir/*.yaml";
		my @rounds = map m/^$hwdir\/(\d+)\.yaml$/, @hw;
		+{ map { $_ => [ glob "$hwdir/${_}*.yaml" ] } @rounds }
	}

=head3 hwbyround 

A hashref of the homework grades for players in the league for each round.

=cut

	has 'hwbyround', (is => 'ro', isa => RoundsResults, lazy_build => 1);
	method _build_hwbyround {
		my $hwdir = $self->hwdir;
		my $rounds = $self->rounds;
		my %results =
		    map { $_ => $self->inspect("$hwdir/$_.yaml") } @$rounds;
		my %grades = map { $_ => $results{$_}{grade} } @$rounds;
		return \%grades;
	}

=head3 hwMax

The highest possible score in the homework

=cut

	has 'hwMax' => (is => 'ro', isa => 'Int', lazy => 1, default =>
					sub { shift->league->yaml->{hwMax} } );

=head3 totalMax

The total maximum points that a Player could have gotten to this point in the whole season. There may be more (or fewer) rounds played than expected, so the actual top possible score returned by totalMax may be more (or less) than the figure planned.

=cut

	has 'totalMax' => (is => 'ro', isa => 'Int', lazy_build => 1);
	method _build_totalMax {
		my $rounds = $self->rounds;
		my $hwMax = $self->hwMax;
		$hwMax * @$rounds;
	}

=head3 rawscoresinRound

Given a round, returns a hashref of the raw scores for that round, keyed on the names of the exercises. These are in files in the hwdir with names of the form ^\d+[_.]\w+\.yaml$

=cut

	method rawscoresinRound (Int $round) {
		my $hwdir = $self->hwdir;
		my $files = $self->roundfiles->{$round};
		my @ex = map m/^$hwdir\/$round([_.]\w+)\.yaml$/, @$files;
		my $results = $self->inspect("$hwdir/$round.yaml");
		return { $results->{exercise} => $results->{points} };
	}

=head3 hwforid

lib/Grades.pm  view on Meta::CPAN


    method options ( Str $jigsaw, Str $group, Int $question ) {
	my $quiz = $self->quiz( $jigsaw, $group );
	my $options = $quiz->[$question]->{option};
	return $options || '';
    }

=head3 qn

The number of questions in the given jigsaw for the given group.

=cut

    method qn ( Str $jigsaw, Str $group ) {
	my $quiz = $self->quiz( $jigsaw, $group );
	warn "No quiz for $group group in jigsaw $jigsaw," unless $quiz;
	return scalar @$quiz;
    }

=head3 responses

The responses of the members of the given group in the given jigsaw (as an anon hash keyed on the ids of the members). In a file in the jigsaw directory called 'response.yaml'.

=cut


    method responses ( Str $jigsaw, Str $group ) {
	my $jigsaws = $self->jigsawdirs;
	my $responses = $self->inspect( "$jigsaws/$jigsaw/response.yaml" );
	return $responses->{$group};
    }

=head3 jigsawGroups

A hash ref of all the groups in the given jigsaw and the names of members of the groups, keyed on groupnames. There may be duplicated names if one player did the activity twice as an 'assistant' for a group with not enough players, and missing names ...

=cut

	method jigsawGroups (Str $jigsaw ) {
		my $config = $self->config('Jigsaw', $jigsaw );
		$config->{group};
	}

=head3 jigsawGroupMembers

An array (was hash ref) of the names of the members of the given group in the given jigsaw, in order of the roles, A..D.

=cut

	method jigsawGroupMembers (Str $jigsaw, Str $group) {
		my $groups = $self->jigsawGroups( $jigsaw );
		my $members = $groups->{$group};
	}

=head3 roles

At the moment, just A .. D.

=cut

	has 'roles' => (is => 'ro', isa => 'ArrayRef[Str]',
	    default => sub { [ qw/A B C D/ ] } );


=head3 idsbyRole

Ids in array, in A-D role order

=cut


    method idsbyRole ( Str $jigsaw, Str $group ) {
	my $members = $self->league->members;
	my %namedMembers = map { $_->{name} => $_ } @$members;
	my $namesbyRole = $self->jigsawGroupMembers( $jigsaw, $group );
	my @idsbyRole = map { $namedMembers{$_}->{id} } @$namesbyRole;
	return \@idsbyRole;
    }

=head3 assistants

A array ref of all the players in the (sub)jigsaw who did the the activity twice to 'assist' groups with not enough (or absent) players, or individuals with no groups, or people who arrived late.

=cut

	method assistants (Str $jigsaw) {
		my $round = $self->config( $jigsaw );
		$round->{assistants};
	}

=head3 jigsawGroupRole

An hash ref of the roles of the members of the given group in the given jigsaw, keyed on the name of the player.

=cut

	method jigsawGroupRole (Str $jigsaw, Str $group) {
		my $members = $self->jigsawGroupMembers( $jigsaw, $group );
		my %roles;
		@roles{ @$members } = $self->roles->flatten;
		return \%roles;
	}

=head3 id2jigsawGroupRole

An hash ref of the roles of the members of the given group in the given jigsaw, keyed on the id of the player.

=cut

	method id2jigsawGroupRole (Str $jigsaw, Str $group) {
		my $members = $self->jigsawGroupMembers( $jigsaw, $group );
		my @ids = map { $self->league->ided($_) } @$members;
		my $roles = $self->roles;
		my %id2role; @id2role{@ids} = @$roles;
		return \%id2role;
	}

=head3 name2jigsawGroup

An array ref of the group(s) to which the given name belonged in the given jigsaw. Normally, the array ref has only one element. But if the player was an assistant an array ref of more than one group is returned. If the player did not do the jigsaw, ...

lib/Grades.pm  view on Meta::CPAN


=cut

class Classwork {
	use Grades::Types qw/Results/;

=head3 approach

Delegatee handling classwork_total, classworkPercent

=cut

    has 'approach' => ( is => 'ro', isa => 'Approach', required => 1,
	    handles => [ qw/
		series beancans
		all_events points
		classwork_total classworkPercent / ] );

}

=head2 Classwork Approach

Handles Classwork's classwork_total and classworkPercent methods. Calls the total or totalPercent methods of the class whose name is in the 'type' accessor.

=cut

class Approach {

=head3 league

The league (object) whose approach this is.

=cut

    has 'league' => (is =>'ro', isa => 'League', required => 1,
				handles => [ 'inspect' ] );

=head3 groupworkdirs

The directory under which there are subdirectories containing data for the group/pair-work sessions. Look first in 'groupwork', then 'compcomp' mappings, else use 'classwork' dir.

=cut

    has 'groupworkdirs' => (is => 'ro', isa => 'Str', lazy_build => 1);
    method _build_groupworkdirs {
	my $league = $self->league;
	my $id = $league->id;
	my $leaguedir = $self->league->leagues . "/" . $id;
	my $basename = $league->yaml->{groupwork} ||
			$league->yaml->{compcomp} || "classwork";
	my $groupworkdirs = $leaguedir .'/' . $basename;
	}

=head3 series

The sessions (weeks) over the series (semester) in each of which there was a different grouping and results of players. This method returns an arrayref of the names (numbers) of the sessions, in numerical order, of the form, [1, 3 .. 7, 9, 10 .. 99 ]...

=cut

    has 'series' =>
      ( is => 'ro', isa => 'Maybe[ArrayRef[Int]]', lazy_build => 1 );
    method _build_series {
        my $dir = $self->groupworkdirs;
        my @subdirs = grep { -d } glob "$dir/*";
        [ sort { $a <=> $b } map m/^$dir\/(\d+)$/, @subdirs ];
    }

#=head3 all_events
#
#All the weeks, or sessions or lessons for which grade data is being assembled from for the grade component.
#
#=cut
#
#    method all_events {
#	my $league = $self->league;
#	my $type = $league->approach;
#	my $meta = $type->meta;
#	my $total = $type->new( league => $league )->all_events;
#    }
#
#=head3 points
#
#Week-by-weeks, or session scores for the individual players in the league.
#
#=cut
#
#    method points (Str $week) {
#	my $league = $self->league;
#	my $type = $league->approach;
#	my $meta = $type->meta;
#	my $total = $type->new( league => $league )->points( $week );
#    }
#
#=head3 classwork_total
#
#Calls the pluginned approach's classwork_total.
#
#=cut
#
#    method classwork_total {
#	my $league = $self->league;
#	my $type = $league->approach;
#	my $total = $type->new( league => $league )->total;
#    }
#
=head3 classworkPercent

Calls the pluginned approach's classworkPercent.

=cut

    method classworkPercent {
	my $league = $self->league;
	my $type = $league->approach;
	my $total = $type->new( league => $league )->totalPercent;
    }
}


=head2 Grades' Compcomp Methods

The comprehension question competition is a Swiss tournament regulated 2-partner conversation competition where players try to understand more of their opponent's information than their partners understand of theirs.

=cut

class Compcomp extends Approach {
    use Try::Tiny;
    use Moose::Autobox;
    use List::Util qw/max min/;
    use List::MoreUtils qw/any all/;
    use Carp qw/carp/;
    use Grades::Types qw/Results/;

=head3 compcompdirs

The directory under which there are subdirectories containing data for the Compcomp rounds.

=cut

    has 'compcompdirs' => (is => 'ro', isa => 'Str', lazy_build => 1 );
    method _build_compcompdirs { 
	my $leaguedir = $self->league->leagues . "/" . $self->league->id;
	my $compcompdir = $leaguedir .'/' . shift->league->yaml->{compcomp};
    }

=head3 all_events

The pair conversations over the series (semester). This method returns an arrayref of the numbers of the conversations, in numerical order, of the form, [1, 3 .. 7, 9, 10 .. 99 ]. Results are in sub directories of the same name, under compcompdirs.

=cut

    has 'all_events' =>
      ( is => 'ro', isa => 'Maybe[ArrayRef[Int]]', lazy_build => 1 );
    method _build_all_events {
        my $dir = $self->compcompdirs;
        my @subdirs = grep { -d } glob "$dir/*";
        [ sort { $a <=> $b } map m/^$dir\/(\d+)$/, @subdirs ];
    }

=head3 config

The round.yaml file with data about the Compcomp activity for the given conversation (directory.)

=cut

    method config( Str $round) {
	my $comp = $self->compcompdirs;
	my $file = "$comp/$round/round.yaml";
        my $config;
	try { $config = $self->inspect($file) }
	    catch { warn "No config file for Compcomp round $round at $file" };
	return $config;
    }

=head3 activities

The activities which individual tables did in the given round. Keys are topics, keyed are forms. These, in turn, are keys of tables doing those topics and those forms.

=cut

    method activities( Str $round ) {
	my $config = $self->config( $round );
	return $config->{activity};
    }

=head3 tables

The tables with players according to their roles for the given round, as an hash ref. In the 'group' or 'activities' mapping in the config file. Make sure each table has a unique table number. Some code here is same as in Swiss's round_table.pl and d...

activities:
  drbean:
    1:
      - U9931007
      - U9933022
  novak:
    1:
      - U9931028
      - U9933045

=cut

    method tables ( Str $round ) {
	my $config = $self->config($round);
	my (@pairs, %pairs, @dupes, $wantlist);
	my $groups = $config->{group};
	return $groups if $groups;
	my $activities = $config->{activity};
	for my $key ( keys %$activities ) {
	    my $topic = $activities->{$key};
	    for my $form ( keys %$topic ) {
		my $pairs = $topic->{$form};
		if ( ref( $pairs ) eq 'ARRAY' ) {
		    $wantlist = 1;

lib/Grades.pm  view on Meta::CPAN

	    for my $id ( @ids ) {
		    next unless defined $points->{$id};
		$totals->{$id} += $points->{$id};
	    }
	}
	return $totals;
    }


=head3 totalPercent

The total over the conversations over the series expressed as a percentage of the possible score. The average should be 80 percent if every player participates in every comp.

=cut

    has 'totalPercent' => ( is => 'ro', isa => Results, lazy_build => 1 );
    method _build_totalPercent {
	my $rounds = $self->all_events;
	my $n = scalar @$rounds;
	my $totals = $self->total;
	my %percentages = $n? 
	    map { $_ => $totals->{$_} * 100 / (5*$n) } keys %$totals:
	    map { $_ => 0 } keys %$totals;
	return \%percentages;
    }

}


=head2 Grades' Exams Methods
=cut

role Exams {
	use List::Util qw/max sum/;
	use List::MoreUtils qw/any all/;
	use Carp;
	use Grades::Types qw/Exam/;

=head3 examdirs

The directory where the exams are.

=cut

    has 'examdirs' => (is => 'ro', isa => 'Str', lazy_build => 1);
    method _build_examdirs {
	my $league = $self->league->id;
	my $leaguedir = $self->league->leagues . "/" . $league;
	my $basename = $self->league->yaml->{jigsaw} ||
			$self->league->yaml->{exams} || "exams";
	my $examdirs = $leaguedir .'/' . $basename;
    }

=head3 examids

An arrayref of the ids of the exams for which there are grades for players in the league, in numerical order, of the form, [1, 3 .. 7, 9, 10 .. 99 ]. Results are in sub directories of the same name, under examdir.

=cut

    has 'examids',
      ( is => 'ro', isa => 'Maybe[ArrayRef[Int]]', lazy_build => 1 );
    method _build_examids {
        my $examdirs = $self->examdirs;
        my @exams   = grep { -d } glob "$examdirs/[0-9] $examdirs/[1-9][0-9]";
        [ sort { $a <=> $b } map m/^$examdirs\/(\d+)$/, @exams ];
    }

=head3 examrounds

The rounds over which the given exam was conducted. Should be an array ref. If there were no rounds, ie the exam was conducted in one round, a null anonymous array is returned. The results for the rounds are in sub directories underneath the 'examid'...

=cut

    method examrounds( Str $exam ) {
	my $examdirs = $self->examdirs;
        my $examids = $self->examids;
        carp "No exam $exam in exams @$examids"
	    unless any { $_ eq $exam } @$examids;
        my @rounds = glob "$examdirs/$exam/[0-9] $examdirs/$exam/[0-9][0-9]";
        [ sort { $a <=> $b } map m/^$examdirs\/$exam\/(\d+)$/, @rounds ];
      }

=head3 examMax

The maximum score possible in each individual exam. That is, what the exam is out of.

=cut

	has 'examMax' => (is => 'ro', isa => 'Int', lazy => 1, required => 1,
			default => sub { shift->league->yaml->{examMax} } );

=head3 exam

    $grades->exam($id)

The scores of the players on an individual (round of an) exam (in a 'g.yaml file in the $id subdir of the league dir.

=cut

	method exam ( Str $id ) {
	    my $examdirs = $self->examdirs;
	    my $exam = $self->inspect( "$examdirs/$id/g.yaml" );
	    if ( is_Exam($exam) ) {
		return $exam ;
	    }
	    else {
		croak
"Exam $id probably has undefined or non-numeric Exam scores, or possibly illegal PlayerIds." ;
	    }
	}

=head3 examResults

A hash ref of the ids of the players and arrays of their results over the exam series, ie examids, in files named 'g.yaml', TODO but only if such a file exists in all examdirs. Otherwise, calculate from raw 'response.yaml' files. Croak if any result ...

=cut

    has 'examResults' => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );
    method _build_examResults {
        my $examids = $self->examids;
	my $members = $self->league->members;
	my @playerids = map { $_->{id} } @$members;
	my %results;
	for my $id  ( @$examids ) {
	    my $exam    = $self->exam( $id );
	    my $max      = $self->examMax;
	    for my $playerid ( @playerids ) {
		my $result = $exam->{$playerid};
		carp "No exam $id results for $playerid,"
		  unless defined $result;
		croak "${playerid}'s $result greater than exam max, $max"
		  if defined $result and $result > $max;
		my $results = $results{$playerid};
		push @$results, $result;
		$results{$playerid} = $results;
	    }
	}
	return \%results;
    }

=head3 examResultHash

A hash ref of the ids of the players and hashrefs of their results for each exam. Croak if any result is larger than examMax.

=cut

	has 'examResultHash' => (is => 'ro', isa => 'HashRef', lazy_build => 1);
	method _build_examResultHash {
		my $examids = $self->examids;
		my $examResults = $self->examResults;
		my %examResults;
		for my $id ( keys %$examResults ) {
			my $results = $examResults->{$id};
			my %results;
			@results{@$examids} = @$results;
			$examResults{$id} = \%results;
		}
		return \%examResults;
	}

=head3 examResultsasPercent

A hashref of the ids of the players and arrays of their results over the exams expressed as percentages of the maximum possible score for the exams.

=cut

	has 'examResultsasPercent' => (is=>'ro', isa=>'HashRef', lazy_build=>1);
	method _build_examResultsasPercent {
		my $scores = $self->examResults;
		my @ids = keys %$scores;
		my $max = $self->examMax;
		my %percent =  map { my $id = $_; my $myscores = $scores->{$id};
		    $id => [ map { ($_||0) * (100/$max) } @$myscores ] } @ids;
		return \%percent;
	}

=head3 examGrade

A hash ref of the ids of the players and their total scores on exams.

=cut

	has 'examGrade' => (is => 'ro', isa => 'HashRef', lazy_build => 1);
	method _build_examGrade {
		my $grades = $self->examResults;
		+{ map { my $numbers=$grades->{$_};
			$_ => sum(@$numbers) }
					keys %$grades };
	}

=head3 examPercent

A hash ref of the ids of the players and their total score on exams, expressed as a percentage of the possible exam score. This is the average of their exam scores.

=cut

    has 'examPercent' => (is => 'ro', isa => 'HashRef', lazy_build => 1);
    method _build_examPercent {
	my $grades = $self->examResultsasPercent;
	my %totals = map {
		my $numbers=$grades->{$_};
		$_ => sum(@$numbers)/@{$numbers} } keys %$grades;
	return \%totals;
    }

}


=head2 Grades' Core Methods

=cut

class Grades with Homework with Exams with Jigsaw

{
#    with 'Jigsaw'
#	=> { -alias => { config => 'jigsaw_config' }, -excludes => 'config' };
    require Grades::Groupwork;
    use Carp;
    use Grades::Types qw/Weights/;

=head3 BUILDARGS

Have Moose find out the classwork approach the league has adopted and create an object of that approach for the classwork accessor. This is preferable to requiring the user to create the object and pass it at construction time.

=cut

    around BUILDARGS (ClassName $class: HashRef $args) {
        my $league = $args->{league} or die "$args->{league} league?";
        my $approach = $league->approach or die "approach?";
        my $classwork = $approach->new( league => $league ) or die "classwork?";
        $args->{classwork} = $classwork;
        return $class->$orig({ league => $league, classwork => $classwork });
    }
    # around BUILDARGS(@args) { $self->$orig(@args) }

=head3 classwork

An accessor for the object that handles classwork methods. Required at construction time.

=cut

	has 'classwork' => ( is => 'ro', isa => 'Approach', required => 1,
		handles => [ 'series', 'beancans',
			    'points', 'all_events',
		    'classwork_total', 'classworkPercent' ] );

=head3 config

The possible grades config files. Including Jigsaw, Compcomp.

=cut

	method config ( $role, $round ) {
	    my $config = "${role}::config"; $self->$config( $round );
	}



( run in 1.792 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )