# $Id: amtda2.pl,v 1.2 2005/04/12 19:50:26 ddoughty Exp $ # # Source File: amtda.pl # custom reports for amtda.org # use FileHandle; use Time::Local; use Data::Dumper; require 'sitecfg.pl'; require 'testlib.pl'; require "maillib.pl"; use strict; use vars qw(%FORM %SESSION %CLIENT %TEST_SESSION %SUBTEST_QUESTIONS %TEST %SUBTEST_SUMMARY %CANDIDATE %SUBTEST_ANSWERS %SYSTEM %REPORT %SUBTEST_RESPONSES); use vars qw($testcomplete $cgiroot $pathsep $dataroot ); &app_initialize; &LanguageSupportInit(); &get_client_profile($SESSION{'clid'}); &get_test_profile($CLIENT{'clid'}, $FORM{'tstid'}); $FORM{'tstid'} = ($CLIENT{'clid'} eq 'sandbox'? 'tutor': 'CMTSE2'); if (&get_session($FORM{'tid'})) { if (not $FORM{'reportname'}) { &ReportChooser($CLIENT{'clid'},$FORM{'tstid'}); } else { if ($FORM{'mofm'} < 10) { $FORM{'mofm'}="0$FORM{'mofm'}";} if ($FORM{'moto'} < 10) { $FORM{'moto'}="0$FORM{'moto'}";} if ($FORM{'dyfm'} < 10) { $FORM{'dyfm'}="0$FORM{'dyfm'}";} if ($FORM{'dyto'} < 10) { $FORM{'dyto'}="0$FORM{'dyto'}";} my $datefm="$FORM{'yrfm'}\-$FORM{'mofm'}\-$FORM{'dyfm'}"; my $dateto="$FORM{'yrto'}\-$FORM{'moto'}\-$FORM{'dyto'}"; my $questions = &get_test_questions($CLIENT{'clid'},$FORM{'tstid'}); my $results = &get_all_test_results($CLIENT{'clid'},$FORM{'tstid'}, $datefm, $dateto); my ($subjects, $categories) = &add_results_by_cnd($questions,$results); #print "
".Dumper($results)."
\n"; if (toGMSeconds($datefm." 00:00:00") > toGMSeconds($dateto." 00:00:00")) { print "

Error: Invalid date range


\n"; print ""; } elsif ($FORM{'reportname'} eq 'rawscorebycnd') { &RawScoreByCnd($questions, $results, $subjects, $categories); } elsif ($FORM{'reportname'} eq 'percentscorebycnd') { &PercentScoreByCnd($questions, $results, $subjects, $categories); } elsif ($FORM{'reportname'} eq 'rawscorebysubject') { my $bysubj = &results_by_subject($questions, $results, $subjects); &RawScoreBySubject($questions, $results, $subjects, $bysubj); } elsif ($FORM{'reportname'} eq 'passfailbycnd') { &PassFailByCnd($results); } elsif ($FORM{'reportname'} eq 'candidateletter') { my $letter = &CandidateLetter($questions, $results, $subjects, $categories,$FORM{"uid"}); my $note = ''; if ($FORM{'sendemail'}) { # email copies of the letter out #print STDERR "($FORM{'email'}|$FORM{'CC'})\n"; if ($FORM{'email'} or $FORM{'CC'}) { my $testdef = get_test_definition($CLIENT{'clid'},$FORM{'tstid'}); my $addresses = join(',',$FORM{'email'},$FORM{'CC'}); $note = "

NOTE: The following letter has been"; if ($FORM{'email'}) {$note .= " mailed to the candidate ($FORM{'email'})";} if ($FORM{'email'} and $FORM{'CC'}) {$note .= " and";} if ($FORM{'CC'}) {$note .= " CCed to these addresses - $FORM{'CC'}";} $note .= ".

\n"; send_mail($testdef->{'ntfy'}->[0],$addresses,'CMTSE Test Results',"Content-Type: text/html\n\n".$letter); } else { $note = "

WARNING: An emailed report was requested but no". " email addresses were given.

\n"; } } $letter =~ s/()/$1$note/i; print $letter; } else { &ReportChooser($CLIENT{'clid'},$FORM{'tstid'}); } } } # There should only be function definitions beyond this point. exit(0); sub get_test_definition { my ($client,$testid) = @_; my @tests = get_test_list($client); my @keys = split(/&/,shift(@tests)); chomp(@keys); my $test; foreach (@tests) { if (/^$testid/) { chomp; @{$test}{@keys} = split(/&/,$_); last; } } if ($test) { $test->{'ntfy'} = [split(/,/,$test->{'ntfy'})]; } return $test; } sub get_test_questions { # populate an anonymous Assoc. array of arrays. my ($client,$test) = @_; my @questions = &get_question_list($test,$client); my @keys = split(/&/,shift(@questions)); chomp(@keys); my $questions; foreach (@questions) { chomp; my @values = split(/&/,$_); #if ($values[3] eq 'Y') {next;} @{$questions->{$values[0]}}{@keys} = @values; } return $questions; } sub get_all_test_results { my ($client,$test,$datefm,$dateto) = @_; my @filelist = &get_test_result_files($testcomplete, $client, $test); my $results = {}; my $historyfile = join($pathsep,$testcomplete,"$client.$test.history"); open (HISTFILE,"<$historyfile") or return 0; my %histentries; while ($_ = ) { my ($date) = split(/<<>>/,$_); my $completedat = &format_date_time("yyyy-mm-dd", "1", "-10000", toGMSeconds($date)); #print STDERR "Date: $date Completedate: $completedat\n"; if (!&date_out_of_range($completedat,$datefm,$dateto)) { my (undef,$user) = split(/&/,$_); $histentries{$user} = 1; } } close HISTFILE; foreach my $file (@filelist) { my $user = $file; $user =~ s/.$test$//; $user =~ s/^$client.//; if (not exists $histentries{$user}) { # no test after jan 1 2004 exist for this user next; } &get_candidate_profile($client,$user); $results->{$user}->{'fullname'} = "$CANDIDATE{'nml'}, $CANDIDATE{'nmf'} $CANDIDATE{'nmm'}"; &get_test_sequence_for_reports($client,$user,$test); my @questions = split(/\&/, $SUBTEST_QUESTIONS{2}); my @answers = split(/\&/,$SUBTEST_ANSWERS{2}); my @responses = split(/&/,$SUBTEST_RESPONSES{2}); my @summary = split(/\&/, $SUBTEST_SUMMARY{2}); my @results = split(/\//, $summary[$#summary]); @{$results->{$user}->{'overall'}}{'right','wrong','percent'} = @summary[0,1,2]; #print STDERR "$user @summary[0,1,2]\n"; for (my $i=1; $i < @questions; $i++) { @{$results->{$user}->{'questions'}->{$questions[$i]}}{'question','answer','response','result'} = ($questions[$i], $answers[$i], $responses[$i], [split(/\./,$results[$i])]); } } return $results; } sub add_results_by_cnd { my ($questions,$results) = @_; my (%subjects,%categories); foreach my $user (keys %$results) { my $subject = {}; my $category = {}; foreach my $quest (values %{$results->{$user}->{'questions'}}) { my $subj = $questions->{$quest->{'question'}}->{'subj'}; $subj =~ s/\..*$//; $subjects{$subj} = 1; if (not exists $subject->{$subj}) {$subject->{$subj} = [0,0];} $subject->{$subj}->[$quest->{'result'}->[0]]++; my $cat = $questions->{$quest->{'question'}}->{'ques2'}; if (not $cat) {print STDERR "$questions->{$quest->{'question'}}->{'id'} $questions->{$quest->{'question'}}->{'qil'}\n";} $categories{$cat} = 1; if (not exists $category->{$cat}) {$category->{$cat} = [0,0];} $category->{$cat}->[$quest->{'result'}->[0]]++; } $results->{$user}->{'subject'} = $subject; $results->{$user}->{'category'} = $category; } return ([sort(keys(%subjects))],[sort(keys(%categories))]); } sub sum { my $sum = 0; foreach (@_) { $sum += $_; } return $sum; } sub results_by_subject { my ($questions,$results,$subjects) = @_; my %init = ('count' => 0, 'mean' => 0, 'stddev' => 0, 'min' => 1000000, 'max' => 0, 'pass' => 0); my $res = {'overall' => {%init}}; foreach (@$subjects) { $res->{'subjects'}->{$_} = {%init}; } my $overall = $res->{'overall'}; foreach my $result (values(%$results)) { my $right = $result->{'overall'}->{'right'}; if (not exists $overall->{'total'}) {$overall->{'total'} = $right + $result->{'overall'}->{'wrong'};} $overall->{'count'}++; $overall->{'mean'} += $right; $overall->{'min'} = ($overall->{'min'} < $right ? $overall->{'min'}: $right); $overall->{'max'} = ($overall->{'max'} > $right ? $overall->{'max'}: $right); $overall->{'percent'} += $result->{'overall'}->{'percent'}; if ($result->{'overall'}->{'percent'} >= 70) {$overall->{'pass'}++;} push @{$overall->{'values'}} , $right; foreach my $subj (@$subjects) { my $current = $res->{'subjects'}->{$subj}; $right = $result->{'subject'}->{$subj}->[1]; if (not exists $current->{'total'}) {$current->{'total'} = ($right+$result->{'subject'}->{$subj}->[0]);} #$current->{'count'}++; $current->{'mean'} += $right; $current->{'min'} = ($current->{'min'} < $right ? $current->{'min'}: $right); $current->{'max'} = ($current->{'max'} > $right ? $current->{'max'}: $right); $current->{'percent'} += 100*$right/($current->{'total'}); push @{$current->{'values'}} , $right; } } # calculate standard deviations foreach my $s ($overall, values(%{$res->{'subjects'}})) { $s->{'mean'} /= $overall->{'count'}; $s->{'percent'} /= $overall->{'count'}; $s->{'stddev'} = sqrt(sum(map(($s->{'mean'}-$_)**2,@{$s->{'values'}}))/($overall->{'count'}-1)); } return $res; } sub HTMLHeader { return "\n\n$_[0]\n". "\n\n". "\n"; } sub HTMLHeaderPlain { return "\n\n$_[0]\n". "\n\n". "\n"; } sub HTMLFooter { return "
Copyright (c) 2004, AMTDA
\n\n"; } sub HTMLReportHeader { my ($reporttype) = @_; my $html = "$reporttype\n"; $html .= "American Machine Tool Distributors' Association
\n"; $html .= "Certified Machine Tool Sales Engineer (CMTSE) Examination
\n"; $html .= "Form 575070
\n"; $html .= "Report Generated ".scalar(localtime(time))."
\n"; $html .= "$reporttype
\n"; $html .= "Passing Score = Total Raw Score of 105 (70 % Correct)

\n"; return $html; } sub HTMLTable { my ($data,$caption,%options) = @_; my %options = ('aligndef' => 'center', 'alignrow' => [], 'bold' => {0 => 1}, @_); my $html = "\n"; if ($caption or $caption eq '0') { $html .= ""; } #$html .= "".join("",map("",@{$data->[0]}))."\n"; for (my $row = 0; $row < @{$data}; $row++) { my $celltype = 'td'; my $align = $options{'aligndef'}; if ($options{'alignrow'}->[$row]) {$align = $options{'alignrow'}->[$row];} if ($options{'bold'}->{$row}) {$celltype = 'th';} $html .= "".join("",map("<$celltype align=\"$align\">$_",@{$data->[$row]}))."\n"; } $html .= "
$caption
$_
\n"; return $html; } sub ReportChooser { my ($client,$test) = @_; my $js = "function parmsAMTDA(oform,rpt) {\n\t". "oform.reportname.value=rpt;\n\t". "oform.action='/cgi-bin/creports.pl';\n\t". "oform.submit();\n};\n"; $js .= "function cndSelect(form) { select = form.userview; params = String(select.options[select.selectedIndex].value).split('&'); form.uid.value=params[0]; form.firstname.value=params[3]; form.middlename.value=params[4]; form.lastname.value=params[5]; form.address.value=params[6]; form.city.value=params[7]; form.state.value=params[8]; form.zip.value=params[9]; form.country.value=params[10]; form.email.value=params[11]; }"; $js .="\nfunction onWdwLoad() {\n\t". "var oform=document.amtdarpt;\n\t". "oform.mofm.selectedIndex=0;\n\t". "oform.dyfm.selectedIndex=0;\n\t". "oform.yrfm.selectedIndex=0;\n\t". "oform.moto.selectedIndex=oform.moto.options.length-1;\n\t". "oform.dyto.selectedIndex=oform.dyto.options.length-1;\n\t". "oform.yrto.selectedIndex=oform.yrto.options.length-1;\n\t". "oform.testsummary[0].checked=true;\n\t". "oform.showcmts[0].checked=true;\n\t". "}\n". "window.onload=onWdwLoad;\n"; print HTMLHeader("AMTDA CMTSE Test Reports",$js); print "

\n"; print "\n"; # For development purposes we hardcode the survey id. # Fix this before production print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "

AMTDA CMTSE Test Reports

\n"; print "
    \n"; print "
  • ". "Raw Scores by Candidate
  • \n"; print "
  • ". "Percent Scores by Candidate
  • \n"; print "
  • ". "Raw Scores by Subject
  • \n"; print "
  • ". "Pass/Fail by Candidate
  • \n"; print "
  • ". "Candidate Letter

    \n"; my $users = get_users($client,$test); print "

      \n"; print "
    • \n"; print "\n"; my $finputs="\t\t\n"); print "$finputs
      \n"; my $j; $finputs=join('',$finputs,"From:
      \n"); $finputs .= "\t\t
      \n"; $finputs=join('',$finputs,"To:
      \n"); $finputs=join('',$finputs,"\t\t
    • \n"; my (undef,undef,undef,$first,$middle,$last,$address,$city,$state,$zip,$country,$email) = split(/&/,$users->{$defuser}); print "
    • \n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
      Last Name:First:Middle:
      Address:
      City:State:Zip:
      Country:
      E-mail:
    • "; print "
    •  Send Email to Candidate\n
    • "; print "
    • CC: 
    • \n"; # print "
    • BCC: 
    • \n"; print "
    • (Separate multiple email addresses with a comma, e.g. name\@place.com,name2\@place2.com)
    • \n"; print "
    \n"; print "
  • \n"; print HTMLFooter(); } sub RawScoreByCnd { my ($questions,$results,$subjects,$categories) = @_; my $data = []; my @subjects = sort {$a <=> $b} @$subjects; $data->[0] = ['ID#','Name','Total Score',@subjects]; foreach my $user (sort {$results->{$a}->{'fullname'} cmp $results->{$b}->{'fullname'}} keys %$results) { my $bysubject = $results->{$user}->{'subject'}; push @$data, [$user, $results->{$user}->{'fullname'}, $results->{$user}->{'overall'}->{'right'}, map($bysubject->{$_}->[1],@subjects)]; } print &HTMLReportHeader("Raw Score Report for PES File"); #print "
    ".Dumper($data)."
    \n"; print &HTMLTable($data); print &HTMLFooter(); } sub PercentScoreByCnd { my ($questions,$results,$subjects,$categories) = @_; my $data = []; my @subjects = sort {$a <=> $b} @$subjects; $data->[0] = ['ID#','Name','Total Score',@subjects]; foreach my $user (sort {$results->{$a}->{'fullname'} cmp $results->{$b}->{'fullname'}} keys %$results) { my $bysubject = $results->{$user}->{'subject'}; push @$data, [$user, $results->{$user}->{'fullname'}, $results->{$user}->{'overall'}->{'percent'}." %", map(int(100*$bysubject->{$_}->[1]/($bysubject->{$_}->[1]+$bysubject->{$_}->[0])+0.5)." %",@subjects)]; } print &HTMLReportHeader("Percent Score Report for PES File"); #print "
    ".Dumper($data)."
    \n"; print &HTMLTable($data); print &HTMLFooter(); } sub RawScoreBySubject { my ($questions,$results,$subjects,$bysubj) = @_; #print "
    ".Dumper($bysubj)."
    "; my $data = []; my @subjects = sort {$a <=> $b} @$subjects; $data->[0] = ['Subject','Max Raw Scores','Range of Raw Scores','Average Raw Scores', 'Standard Deviation','Average Percent Correct','Number Passing','Percent Passing']; my $current = $bysubj->{'overall'}; $data->[1] = ['Total Score',$current->{'total'},"$current->{'min'} - $current->{'max'}", sprintf("%.2f",$current->{'mean'}),sprintf("%.2f",$current->{'stddev'}), sprintf("%.2f",$current->{'percent'}),$current->{'pass'}, sprintf("%.2f %%",100*$current->{'pass'}/$current->{'count'})]; foreach my $subj (@subjects) { $current = $bysubj->{'subjects'}->{$subj}; push @$data, [$subj, $current->{'total'},"$current->{'min'} - $current->{'max'}", sprintf("%.2f",$current->{'mean'}),sprintf("%.2f",$current->{'stddev'}), sprintf("%.2f",$current->{'percent'})]; } print &HTMLReportHeader("Subject Breakdown"); #print "
    ".Dumper($data)."
    \n"; print &HTMLTable($data,"Data Based on $bysubj->{'overall'}->{'count'} Candidates"); print &HTMLFooter(); } sub PassFailByCnd { my ($results) = @_; my $data = []; $data->[0] = ['ID#','Name','Status']; foreach my $user (sort {$results->{$a}->{'fullname'} cmp $results->{$b}->{'fullname'}} keys %$results) { push @$data, [$user, $results->{$user}->{'fullname'}, ($results->{$user}->{'overall'}->{'percent'} < 70 ? 'Fail': 'Pass')]; } print &HTMLReportHeader("Alphabetical List of Candidates with Pass/Fail Status"); print &HTMLTable($data); print &HTMLFooter(); } sub CandidateLetter { my ($questions, $results, $subjects, $categories, $user) = @_; my $history = &get_cnd_test_from_history($testcomplete,$CLIENT{'clid'},$user,$FORM{'tstid'}); my %chapter = ('1' => 'Market Management', '2' => 'Customer Engagement', '3' => 'Needs Analysis', '4' => 'Contract Proposal', '5' => 'Customer Acceptance', '6' => 'Order Management', '7' => 'Post-Installation Management'); my %content = ('2' =>'Ch. 02: Territory and Customer Development', '3' =>'Ch. 03: Planning and Organizing', '4' =>'Ch. 04: The Selling Process', '5' =>'Ch. 05: Business Practices & Ethics', '6' =>'Ch. 06: Manufacturing Processes', '7' =>'Ch. 07: CNC Technology and Machine Control', '8' =>'Ch. 08: Blueprint Reading and Geometric Tolerancing', '9' =>'Ch. 09: Cutting Tool Technology', '10' =>'Ch. 10: Machine Tool Design', '11' =>'Ch. 11: Metal Forming Technology', '12' =>'Ch. 12: Machine Tool Accuracy', '13' =>'Ch. 13: General Mathematics and Geometry'); my $data1 = [[1,2,3,4], ['Study Guide Chapter', "Maximum Possible","Raw Score","Percent Score"]]; my $data2 = [[1,2,3,4],['Content Domain', "Maximum Possible","Raw Score","Percent Score"]]; push @$data1, (['Total Score', $results->{$user}->{'overall'}->{'right'}+$results->{$user}->{'overall'}->{'wrong'}, $results->{$user}->{'overall'}->{'right'}, $results->{$user}->{'overall'}->{'percent'}.' %'], [' ',' ',' ',' ']); #print STDERR Dumper($subjects, $data1, $results->{$user}); my @subjects = sort {$a <=> $b} @$subjects; foreach my $subj (@subjects) { my $current = $results->{$user}->{'subject'}->{$subj}; push @$data1, [$content{$subj}, $current->[0]+$current->[1], $current->[1], sprintf("%.2f %%",100*$current->[1]/($current->[0]+$current->[1] or 1))]; } foreach my $cat (@$categories) { my $current = $results->{$user}->{'category'}->{$cat}; push @$data2, [$chapter{$cat}, $current->[0]+$current->[1], $current->[1], sprintf("%.2f %%",100*$current->[1]/($current->[0]+$current->[1] or 1))]; } my $html = ""; $html .= &HTMLHeaderPlain("Notice of Test Results"); $html .= "
    Notice of Test Results
    "; $html .= "for the
    \n"; $html .= "American Machine Tool Distributors' Association
    \n"; $html .= "Certified Machine Tool Sales Engineer (CMTSE) Examination
    \n"; my $date = scalar(localtime($history->{'end'})); $date =~ s/ \d\d:\d\d:\d\d/,/; $html .= "Tested $date
    \n"; $html .= "

    ID \# $user

    \n"; $html .= "

      \n"; $html .= "
    • $FORM{'firstname'}".($FORM{'middlename'}?" $FORM{'middlename'}":''). ($FORM{'lastname'}?" $FORM{'lastname'}":'')."
    • \n"; $html .= "
    • $FORM{'address'}
    • \n" if ($FORM{'address'}); $html .= "
    • $FORM{'city'}, $FORM{'state'} $FORM{'zip'}
    • " if ($FORM{'city'} or $FORM{'state'} or $FORM{'zip'}); $html .= "
    • $FORM{'country'}
    • \n" if ($FORM{'country'}); $html .= "
    • E-mail: $FORM{'email'}
    • \n" if ($FORM{'email'}); $html .= "
    \n"; $html .= "You must achieve a percent score of 70 % or higher to pass this examination.\n"; $html .= &HTMLTable($data1,undef,'bold' => {1=>1},'aligndef' => 'left','alignrow' => ['center','center']); $html .= "

    \n"; $html .= &HTMLTable($data2,"Scores Based on Content Domains",'bold' => {1=>1},'aligndef' => 'left','alignrow' => ['center','center']); $html .= "

    \n"; if ($results->{$user}->{'overall'}->{'percent'} >= 70) { $html .= "Congratulations on successfully completing the Certified ". "Machine Tool Sales Engineer (CMTSE) Examination. The information ". "contained in this report includes your Total Score, your scores on ". "the seven content domains that comprise the examination, and your ". "score by Candidate Study Guide chapter. Column 2 lists the total ". "number of exam questions for each area; Column 3 lists the actual ". "number of questions you answered correctly; and, Column 4 ". "indicates the percentage of questions you answered correctly.

    \n"; $html .= "Thank you for ". "participating in the Certified Machine Tool Sales Engineer ". "(CMTSE) Examination.

    "; } else { $html .= "Regretfully, we must inform you that you failed to ". "pass the Certified Machine Tool Sales Engineer (CMTSE) ". "Examination. The information contained in this report ". "includes your Total Score, your scores on the seven content ". "domains that comprise the examination, and your score by ". "Candidate Study Guide chapter. Column 2 lists the total ". "number of exam questions for each area; Column 3 lists the ". "actual number of questions you answered correctly; and, Column ". "4 indicates the percentage of questions you answered ". "correctly.

    \n". "CMTSE candidates who do not pass the exam are welcome and ". "encouraged to retake it at their earliest opportunity. ". "Your application is valid for two years from the exam date ". "for which you originally registered. To participate in an ". "upcoming exam, please contact AMTDA at 1-800-878-2683.

    \n"; } $html .= HTMLFooter(); return $html; } sub date_out_of_range { my ($completedat,$datefm,$dateto) = @_; my @unsorted=(); push @unsorted, $completedat; push @unsorted, $datefm; push @unsorted, $dateto; my @sorted = sort @unsorted; my $bretyes = ($sorted[1] eq $unsorted[0]) ? 0 : 1; @unsorted=(); @sorted=(); return $bretyes; }