@@ -257,6 +257,7 @@ def _evaluate_version(version, scope_results):
257257 for tname , tc_ids in version ['targets' ].items ()
258258 }
259259 return {
260+ '_idx' : version ['_idx' ],
260261 'result' : target_results ['main' ]['result' ],
261262 'targets' : target_results ,
262263 'tc_target' : version ['tc_target' ],
@@ -273,21 +274,22 @@ def _evaluate_scope(spec, scope_results, include_drafts=False):
273274 for vname , version in versions .items ()
274275 if version ['_explicit_validity' ]
275276 }
276- by_validity = defaultdict (list )
277- for vname , version in versions .items ():
278- by_validity [version ['_explicit_validity' ]].append (vname )
279- # go through worsening validity values until a passing version is found
277+ winner = None # first passed version that's not a draft
278+ result = - 1
279+ passed = []
280280 relevant = []
281- best_passed = None
282- for validity in ('effective' , 'warn' , 'deprecated' ):
283- vnames = by_validity [validity ]
284- relevant .extend (vnames )
285- if any (version_results [vname ]['result' ] == 1 for vname in vnames ):
286- best_passed = validity
287- break
288- if include_drafts :
289- relevant .extend (by_validity ['draft' ])
290- passed = [vname for vname in relevant if version_results [vname ]['result' ] == 1 ]
281+ # assumption: versions are listed in spec in descending order recency
282+ # first the drafts, then effective, then warn, then the rest
283+ for vname , version_result in version_results .items ():
284+ if version_result ['validity' ] == 'draft' and not include_drafts :
285+ continue
286+ relevant .append (vname )
287+ result = version_result ['result' ]
288+ if result != - 1 :
289+ passed .append (vname )
290+ if version_result ['validity' ] != 'draft' :
291+ winner = vname
292+ break
291293 # only list testcases that occur in any relevant version
292294 relevant_testcases = set ()
293295 for vname in relevant :
@@ -304,12 +306,14 @@ def _evaluate_scope(spec, scope_results, include_drafts=False):
304306 },
305307 'versions' : version_results ,
306308 'relevant' : relevant ,
309+ 'result' : result ,
307310 'passed' : passed ,
308311 'passed_str' : ', ' .join ([
309- vname + ASTERISK_LOOKUP [versions [vname ]['validity' ]]
312+ vname + ASTERISK_LOOKUP [version_results [vname ]['validity' ]]
310313 for vname in passed
311314 ]),
312- 'best_passed' : best_passed ,
315+ 'best_passed' : None if winner is None else version_results [winner ]['_idx' ],
316+ 'validity' : 'deprecated' if winner is None else version_results [winner ]['validity' ],
313317 }
314318
315319
@@ -672,20 +676,25 @@ async def get_detail(
672676 subject : str ,
673677 scopeuuid : str ,
674678):
679+ return _make_detail_view (conn , view_type , subject , scopeuuid )
680+
681+
682+ def _make_detail_view (conn , view_type , subject , scopeuuid , include_drafts = False ):
675683 scopeuuid = _resolve_scope (scopeuuid )
676684 with conn .cursor () as cur :
677685 group , subjects = _resolve_group (cur , subject )
678686 rows2 = []
679687 for subj in subjects :
680- rows2 .extend (db_get_relevant_results2 (cur , subj , scopeuuid , approved_only = True ))
688+ rows2 .extend (db_get_relevant_results2 (cur , subj , scopeuuid ))
681689 results2 = convert_result_rows_to_dict2 (
682- rows2 , get_scopes (), include_report = True , grace_period_days = GRACE_PERIOD_DAYS ,
690+ rows2 , get_scopes (), include_report = True , include_drafts = include_drafts ,
683691 subjects = subjects , scopes = (scopeuuid , ),
684692 )
685693 title = f'Details for group { group } ' if group else f'Details for subject { subject } '
694+ if include_drafts :
695+ title += ' (incl. drafts)'
686696 return render_view (
687- VIEW_DETAIL , view_type , results = results2 , base_url = settings .base_url ,
688- title = title ,
697+ VIEW_DETAIL , view_type , results = results2 , base_url = settings .base_url , title = title ,
689698 )
690699
691700
@@ -697,21 +706,7 @@ async def get_detail_full(
697706 subject : str ,
698707 scopeuuid : str ,
699708):
700- scopeuuid = _resolve_scope (scopeuuid )
701- with conn .cursor () as cur :
702- group , subjects = _resolve_group (cur , subject )
703- rows2 = []
704- for subj in subjects :
705- rows2 .extend (db_get_relevant_results2 (cur , subj , scopeuuid , approved_only = False ))
706- results2 = convert_result_rows_to_dict2 (
707- rows2 , get_scopes (), include_report = True , include_drafts = True ,
708- subjects = subjects , scopes = (scopeuuid , ),
709- )
710- title = f'Details for group { group } ' if group else f'Details for subject { subject } '
711- return render_view (
712- VIEW_DETAIL , view_type , results = results2 , base_url = settings .base_url ,
713- title = f'{ title } (incl. unverified results)' ,
714- )
709+ return _make_detail_view (conn , view_type , subject , scopeuuid , include_drafts = True )
715710
716711
717712@app .get ("/{view_type}/table" )
@@ -720,13 +715,20 @@ async def get_table(
720715 conn : Annotated [connection , Depends (get_conn )],
721716 view_type : ViewType ,
722717):
718+ return _make_table_view (conn , view_type , detail_page = 'detail' )
719+
720+
721+ def _make_table_view (conn , view_type , detail_page , include_drafts = False ):
723722 with conn .cursor () as cur :
724723 groups = db_get_groups (cur )
725- rows2 = db_get_relevant_results2 (cur , approved_only = True )
726- results2 = convert_result_rows_to_dict2 (rows2 , get_scopes (), grace_period_days = GRACE_PERIOD_DAYS )
724+ rows2 = db_get_relevant_results2 (cur )
725+ results2 = convert_result_rows_to_dict2 (rows2 , get_scopes (), include_drafts = include_drafts )
726+ title = 'SCS compliance overview'
727+ if include_drafts :
728+ title += ' (incl. drafts)'
727729 return render_view (
728- VIEW_TABLE , view_type , results = results2 , base_url = settings .base_url , detail_page = 'detail' ,
729- title = "SCS compliance overview" , groups = groups ,
730+ VIEW_TABLE , view_type , results = results2 , base_url = settings .base_url , detail_page = detail_page ,
731+ title = title , groups = groups ,
730732 )
731733
732734
@@ -736,14 +738,7 @@ async def get_table_full(
736738 conn : Annotated [connection , Depends (get_conn )],
737739 view_type : ViewType ,
738740):
739- with conn .cursor () as cur :
740- groups = db_get_groups (cur )
741- rows2 = db_get_relevant_results2 (cur , approved_only = False )
742- results2 = convert_result_rows_to_dict2 (rows2 , get_scopes (), include_drafts = True )
743- return render_view (
744- VIEW_TABLE , view_type , results = results2 , base_url = settings .base_url , detail_page = 'detail_full' ,
745- title = "SCS compliance overview (incl. unverified results)" , unverified = True , groups = groups ,
746- )
741+ return _make_table_view (conn , view_type , detail_page = 'detail_full' , include_drafts = True )
747742
748743
749744@app .get ("/{view_type}/scope/{scopeuuid}" )
@@ -756,13 +751,12 @@ async def get_scope(
756751 scopeuuid = _resolve_scope (scopeuuid )
757752 spec = get_scopes ()[scopeuuid ]
758753 versions = spec ['versions' ]
759- # sort by name, and all drafts after all non-drafts
760- column_data = [
761- ( version [ '_explicit_validity' ]. lower () == 'draft' , name )
754+ # use same order as in details view
755+ relevant = [
756+ name
762757 for name , version in versions .items ()
763758 if version ['_explicit_validity' ]
764759 ]
765- relevant = [name for _ , name in sorted (column_data )]
766760 modules_chart = {}
767761 for name in relevant :
768762 for include in versions [name ]['include' ]:
@@ -839,40 +833,50 @@ def pick_filter(ctx, results, scopeuuid, *subjects):
839833 return [r for r in rs if r is not None ]
840834
841835
842- STATUS_ORDERING = {
843- 'effective' : 10 ,
844- 'warn' : 5 ,
845- 'deprecated' : 1 ,
836+ NIL = object () # the version in question does not have a result
837+ # used to sort multiple versions according to the "goodness" of their result
838+ RESULT_SCORE = {
839+ - 1 : 0 ,
840+ None : 1 ,
841+ NIL : 2 , # NIL and None are basically the same, but prefer None because it has more info
842+ 0 : 3 ,
843+ 1 : 4 ,
844+ }
845+ COLOR_MAP = {
846+ - 1 : '🛑' , # fail
847+ None : '🟧' , # missing
848+ 0 : '✅*' , # inconclusive
849+ 1 : '✅' , # pass
846850}
847851
848852
849853def summary_filter (scope_results ):
850854 """Jinja filter to construct summary from `scope_results`"""
855+ # be prepared for empty dicts here because they are created to avoid KeyError in jinja2
851856 if not isinstance (scope_results , dict ):
852857 # new generalized case: "aggregate" results for multiple subjects
853858 # simplified computation: just select the worst subject to represent the group
854859 scope_results = min (
855860 scope_results ,
856861 default = {},
857- key = lambda sr : STATUS_ORDERING . get ( sr .get ('best_passed' ), - 1 ) ,
862+ key = lambda sr : RESULT_SCORE [ sr .get ('result' , NIL )] ,
858863 )
859- passed_str = scope_results .get ('passed_str' , '' ) or '–'
860- best_passed = scope_results .get ('best_passed' )
861- # avoid simple 🟢🔴 (hard to distinguish for color-blind folks)
862- color = {
863- 'effective' : '✅' ,
864- 'warn' : '✅' , # forgo differentiation here in favor of simplicity (will be apparent in version list)
865- 'deprecated' : '🟧' ,
866- }.get (best_passed , '🛑' )
864+ if not scope_results :
865+ return '🛑 –'
866+ result = scope_results ['result' ]
867+ color = COLOR_MAP [result ]
868+ # if the result is not pass anyway, deduct points if the version is outdated
869+ # (this case should happen very rarely because we usually don't consider those)
870+ if result != - 1 :
871+ validity = scope_results ['validity' ]
872+ if validity == 'warn' :
873+ color = '🟧'
874+ elif validity == 'deprecated' :
875+ color = '🛑'
876+ passed_str = scope_results ['passed_str' ] or '–'
867877 return f'{ color } { passed_str } '
868878
869879
870- def verdict_filter (value ):
871- """Jinja filter to turn a canonical result value into a written verdict (PASS, MISS, or FAIL)"""
872- # be fault-tolerant here and turn every non-canonical value into a MISS
873- return {1 : 'PASS' , - 1 : 'FAIL' }.get (value , 'MISS' )
874-
875-
876880def verdict_check_filter (value ):
877881 """Jinja filter to turn a canonical result value into a symbolic verdict (✔, ⚠, or ✘)"""
878882 # be fault-tolerant here and turn every non-canonical value into a MISS
@@ -905,7 +909,6 @@ def reload_static_config(*args, do_ensure_schema=False):
905909 env .filters .update (
906910 pick = pick_filter ,
907911 summary = summary_filter ,
908- verdict = verdict_filter ,
909912 verdict_check = verdict_check_filter ,
910913 markdown = markdown ,
911914 validity_symbol = ASTERISK_LOOKUP .get ,
0 commit comments