@@ -893,6 +893,55 @@ def test_image_matches_groups_spec_not_in_lookup(self) -> None:
893893 spec_lookup = {}
894894 assert _image_matches_groups ("unknown" , "matplotlib" , [], spec_lookup , {}) is False
895895
896+ def test_image_matches_groups_dep_match (self ) -> None :
897+ """Dependencies filter should match impl_tags."""
898+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
899+ impl_lookup = {("scatter-basic" , "matplotlib" ): {"dependencies" : ["scipy" , "sklearn" ]}}
900+ groups = [{"category" : "dep" , "values" : ["scipy" ]}]
901+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is True
902+
903+ def test_image_matches_groups_dep_no_match (self ) -> None :
904+ """Dependencies filter should not match if not in impl_tags."""
905+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
906+ impl_lookup = {("scatter-basic" , "matplotlib" ): {"dependencies" : ["scipy" ]}}
907+ groups = [{"category" : "dep" , "values" : ["networkx" ]}]
908+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is False
909+
910+ def test_image_matches_groups_tech_match (self ) -> None :
911+ """Techniques filter should match impl_tags."""
912+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
913+ impl_lookup = {("scatter-basic" , "matplotlib" ): {"techniques" : ["annotations" , "colorbar" ]}}
914+ groups = [{"category" : "tech" , "values" : ["annotations" ]}]
915+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is True
916+
917+ def test_image_matches_groups_pat_match (self ) -> None :
918+ """Patterns filter should match impl_tags."""
919+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
920+ impl_lookup = {("scatter-basic" , "matplotlib" ): {"patterns" : ["data-generation" , "iteration-over-groups" ]}}
921+ groups = [{"category" : "pat" , "values" : ["data-generation" ]}]
922+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is True
923+
924+ def test_image_matches_groups_prep_match (self ) -> None :
925+ """Dataprep filter should match impl_tags."""
926+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
927+ impl_lookup = {("scatter-basic" , "matplotlib" ): {"dataprep" : ["kde" , "binning" ]}}
928+ groups = [{"category" : "prep" , "values" : ["kde" ]}]
929+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is True
930+
931+ def test_image_matches_groups_style_match (self ) -> None :
932+ """Styling filter should match impl_tags."""
933+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
934+ impl_lookup = {("scatter-basic" , "matplotlib" ): {"styling" : ["alpha-blending" , "minimal-chrome" ]}}
935+ groups = [{"category" : "style" , "values" : ["alpha-blending" ]}]
936+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is True
937+
938+ def test_image_matches_groups_impl_not_in_lookup (self ) -> None :
939+ """Impl not in lookup should not match impl-level filters."""
940+ spec_lookup = {"scatter-basic" : {"tags" : {}}}
941+ impl_lookup = {} # Empty - no impl data
942+ groups = [{"category" : "dep" , "values" : ["scipy" ]}]
943+ assert _image_matches_groups ("scatter-basic" , "matplotlib" , groups , spec_lookup , impl_lookup ) is False
944+
896945 def test_calculate_global_counts (self ) -> None :
897946 """Global counts should tally all implementations."""
898947 mock_impl = MagicMock ()
@@ -910,6 +959,36 @@ def test_calculate_global_counts(self) -> None:
910959 assert counts ["plot" ]["scatter" ] == 1
911960 assert counts ["dom" ]["statistics" ] == 1
912961
962+ def test_calculate_global_counts_with_impl_tags (self ) -> None :
963+ """Global counts should include impl-level tags."""
964+ mock_impl = MagicMock ()
965+ mock_impl .library_id = "matplotlib"
966+ mock_impl .preview_url = TEST_IMAGE_URL
967+ mock_impl .impl_tags = {
968+ "dependencies" : ["scipy" , "sklearn" ],
969+ "techniques" : ["annotations" ],
970+ "patterns" : ["data-generation" ],
971+ "dataprep" : ["kde" ],
972+ "styling" : ["alpha-blending" ],
973+ }
974+
975+ mock_spec = MagicMock ()
976+ mock_spec .id = "scatter-basic"
977+ mock_spec .tags = {"plot_type" : ["scatter" ]}
978+ mock_spec .impls = [mock_impl ]
979+
980+ counts = _calculate_global_counts ([mock_spec ])
981+ # Spec-level counts
982+ assert counts ["lib" ]["matplotlib" ] == 1
983+ assert counts ["plot" ]["scatter" ] == 1
984+ # Impl-level counts
985+ assert counts ["dep" ]["scipy" ] == 1
986+ assert counts ["dep" ]["sklearn" ] == 1
987+ assert counts ["tech" ]["annotations" ] == 1
988+ assert counts ["pat" ]["data-generation" ] == 1
989+ assert counts ["prep" ]["kde" ] == 1
990+ assert counts ["style" ]["alpha-blending" ] == 1
991+
913992 def test_calculate_global_counts_no_impls (self ) -> None :
914993 """Spec without impls should not be counted."""
915994 mock_spec = MagicMock ()
@@ -948,6 +1027,42 @@ def test_calculate_contextual_counts(self) -> None:
9481027 assert counts ["spec" ]["scatter-basic" ] == 2
9491028 assert counts ["plot" ]["scatter" ] == 2
9501029
1030+ def test_calculate_contextual_counts_with_impl_tags (self ) -> None :
1031+ """Contextual counts should include impl-level tags."""
1032+ images = [
1033+ {"spec_id" : "scatter-basic" , "library" : "matplotlib" },
1034+ {"spec_id" : "scatter-basic" , "library" : "seaborn" },
1035+ ]
1036+ spec_tags = {"scatter-basic" : {"plot_type" : ["scatter" ]}}
1037+ impl_lookup = {
1038+ ("scatter-basic" , "matplotlib" ): {
1039+ "dependencies" : ["scipy" ],
1040+ "techniques" : ["annotations" ],
1041+ "patterns" : ["data-generation" ],
1042+ "dataprep" : ["kde" ],
1043+ "styling" : ["alpha-blending" ],
1044+ },
1045+ ("scatter-basic" , "seaborn" ): {
1046+ "dependencies" : ["scipy" , "sklearn" ],
1047+ "techniques" : ["colorbar" ],
1048+ "patterns" : ["data-generation" ],
1049+ "dataprep" : [],
1050+ "styling" : [],
1051+ },
1052+ }
1053+
1054+ counts = _calculate_contextual_counts (images , spec_tags , impl_lookup )
1055+ # Spec-level counts
1056+ assert counts ["plot" ]["scatter" ] == 2
1057+ # Impl-level counts
1058+ assert counts ["dep" ]["scipy" ] == 2
1059+ assert counts ["dep" ]["sklearn" ] == 1
1060+ assert counts ["tech" ]["annotations" ] == 1
1061+ assert counts ["tech" ]["colorbar" ] == 1
1062+ assert counts ["pat" ]["data-generation" ] == 2
1063+ assert counts ["prep" ]["kde" ] == 1
1064+ assert counts ["style" ]["alpha-blending" ] == 1
1065+
9511066 def test_calculate_or_counts_empty_groups (self ) -> None :
9521067 """Empty groups should return empty or_counts."""
9531068 counts = _calculate_or_counts ([], [], {}, {}, {})
0 commit comments