Skip to content

Commit

Permalink
Merge pull request #8479 from cfpb/tccp/apr-rating-ranges
Browse files Browse the repository at this point in the history
TCCP: Design iteration
  • Loading branch information
chosak authored Jun 20, 2024
2 parents 41244e8 + 8e62706 commit 0026a76
Show file tree
Hide file tree
Showing 18 changed files with 145 additions and 198 deletions.
30 changes: 16 additions & 14 deletions cfgov/tccp/jinja2/tccp/cards.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,26 @@

<h1 class="u-mb45">{{ heading }}</h1>

<div id="o-filterable-list-controls" class="o-filterable-list-controls u-cap-width">
{% call() expandable({
"label": "Customize card features",
"is_bordered": true,
"is_expanded": not situations or not cards,
"is_midtone": true
}) %}
{{ filter_form(form) }}
{% endcall %}
</div>

{% if not situations %}
<div class="block block--sub">
<p>
<a href="{{ url('tccp:landing_page') }}">Choose what you're looking for in a card</a>
<a href="{{ url('tccp:landing_page') }}">
Change what you're looking for in a card
</a>
</p>
</div>
{% endif %}

<div class="block block--sub">
<div id="o-filterable-list-controls" class="o-filterable-list-controls u-cap-width">
{% call() expandable({
"label": "Customize card features",
"is_bordered": true,
"is_expanded": not situations or not cards,
"is_midtone": true
}) %}
{{ filter_form(form) }}
{% endcall %}
</div>
</div>

<div class="block block--flush-top">
<div class="o-filterable-list-results o-filterable-list-results--partial" id="htmx-results" aria-live="polite" aria-busy="false">
Expand Down
17 changes: 7 additions & 10 deletions cfgov/tccp/jinja2/tccp/includes/apr_rating.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{%- macro apr_rating(rating, count=none) -%}
{% from 'tccp/includes/fields.html' import apr_range %}

{%- macro apr_rating(rating, apr_min=none, apr_max=none) -%}
{%- set label = apr_rating_lookup[rating] -%}

<div class="m-apr-rating m-apr-rating--{{ label }}">
Expand All @@ -12,15 +14,10 @@
</dt>
<dd>
Pay {{ label }} interest
{{-
(
' ('
~ count
~ ' card'
~ count | pluralize()
~ ')'
) if count is not none
}}

{% if apr_min is not none and apr_max is not none -%}
({{- apr_range(apr_min, apr_max, spaceless=true) -}})
{%- endif %}
</dd>
</div>
{%- endmacro -%}
37 changes: 14 additions & 23 deletions cfgov/tccp/jinja2/tccp/includes/card_list.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
{% from 'tccp/includes/apr_rating.html' import apr_rating with context %}
{% from 'tccp/includes/data_published.html' import data_published %}
{% from 'tccp/includes/fields.html' import apr, apr_range, currency %}
{% from 'tccp/includes/fields.html' import apr, apr_range, currency, date %}
{% from 'tccp/includes/speed_bump.html' import speed_bump %}
{% import 'v1/includes/molecules/notification.html' as notification %}

{% if situation_features -%}
<div class="block block--sub">
<h2>Cards you're looking for have these features:</h2>

<ul>
{% for feature in situation_features -%}
<li>{{ feature.results_html }}</li>
{%- endfor %}
</ul>

<p>
<a href="{{ url('tccp:landing_page') }}">Change what you're looking for in a card</a>
</p>
</div>
{%- endif %}

<div class="u-cap-width">
{% if count -%}
{{- notification.render(
Expand All @@ -41,8 +25,8 @@ <h2>Cards you're looking for have these features:</h2>
Use the ratings to compare your results:
</p>
<dl>
{% for rating, rating_count in purchase_apr_rating_counts.items() %}
{{ apr_rating(rating, count=rating_count) }}
{% for rating, (apr_min, apr_max) in purchase_apr_rating_ranges.items() %}
{{ apr_rating(rating, apr_min=apr_min, apr_max=apr_max) }}
{% endfor %}
</dl>
</div>
Expand Down Expand Up @@ -70,19 +54,25 @@ <h2>Cards you're looking for have these features:</h2>
<article class="m-card m-card--tabular{% if show_more %} u-show-more{% endif %}">
<a href="{{ card.url }}">
<div class="m-card__heading-group">
<h3 class="m-card__heading">{{ card.institution_name }}</h3>
<h2 class="h3 m-card__heading">{{ card.institution_name }}</h2>
<p class="m-card__subtitle">{{ card.product_name }}</p>
</div>
<dl>
{{ apr_rating(card.purchase_apr_for_tier_rating) }}
</dl>
<dl class="m-data-specs">
<div class="m-data-spec m-data-spec--apr">
<dt><strong>Purchase APR</strong></dt>
<dt>
<strong>Purchase APR</strong>
<div class="m-data-spec--apr-disclaimer">
*As of {{ date(stats_all.first_report_date) }}
</div>
</dt>
<dd>
<strong>{{ apr_range(
card.purchase_apr_for_tier_min,
card.purchase_apr_for_tier_max
card.purchase_apr_for_tier_max,
asterisk=true
) }}</strong>
</dd>
</div>
Expand All @@ -102,7 +92,8 @@ <h3 class="m-card__heading">{{ card.institution_name }}</h3>
{%- if ordering_by == 'transfer_apr' -%}<strong>{%- endif -%}
{{- apr_range(
card.transfer_apr_for_tier_min,
card.transfer_apr_for_tier_max
card.transfer_apr_for_tier_max,
asterisk=true
) -}}
{%- if ordering_by == 'transfer_apr' -%}</strong>{%- endif -%}
</dd>
Expand Down
10 changes: 3 additions & 7 deletions cfgov/tccp/jinja2/tccp/includes/data_published.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
{%- import 'v1/includes/macros/time.html' as time -%}
{% from 'tccp/includes/fields.html' import date %}

{%- macro data_published(date, verbose=false) -%}
{%- macro data_published(value, verbose=false) -%}

<p class="u-small-text u-small-text--subtle">
Data reported
{{- ' directly by the company ' if verbose else ' ' -}}
on {{ time.render(
ensure_date(date), {"date": true}, text_format=true
) }}
Data as of {{ date(value) }}
({{- 'credit card terms may have changed since then, double-check with the company
for the most current terms or ' if verbose -}}
<a href="{{ url('tccp:about') }}">{{ 'l' if verbose else 'L' }}earn about the data</a>)
Expand Down
22 changes: 17 additions & 5 deletions cfgov/tccp/jinja2/tccp/includes/fields.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
{%- macro apr(value) -%}
{%- macro apr(value, asterisk=asterisk) -%}
{% if value is none -%}
N/A
{%- elif value == 0 -%}
0%
{%- else -%}
{{ ('%.2f%%' | format(value * 100)) }}
{{ ('%g%%' | format(value * 100)) }}
{%- endif %}
{%- if asterisk and value is not none %}*{% endif %}
{%- endmacro -%}

{%- macro apr_range(min, max) -%}
{%- macro apr_range(min, max, spaceless=false, asterisk=false) -%}
{% if min == max -%}
{{ apr(min) }}
{{ apr(min, asterisk=asterisk) }}
{%- else -%}
{{ apr(min) }} - {{ apr(max) }}
{{ apr(min) }}
{{- ' ' if not spaceless -}}
&ndash;
{{- ' ' if not spaceless }}
{{- apr(max, asterisk=asterisk) }}
{%- endif %}
{%- endmacro -%}

Expand All @@ -24,6 +29,13 @@
}}
{%- endmacro -%}

{%- macro date(value) -%}
{%- import 'v1/includes/macros/time.html' as time -%}
{{ time.render(
ensure_date(value), {"date": true}, text_format=true
) }}
{%- endmacro -%}

{%- macro lower_first_letter(s) -%}
{{- (s[0].lower() ~ (s[1:] if s | length > 1)) if s -}}
{%- endmacro -%}
Expand Down
1 change: 0 additions & 1 deletion cfgov/tccp/jinja2/tccp/situations/results/avoid-fees.html

This file was deleted.

4 changes: 0 additions & 4 deletions cfgov/tccp/jinja2/tccp/situations/results/build-credit.html

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

29 changes: 19 additions & 10 deletions cfgov/tccp/management/commands/validate_tccp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@


def fmt(value):
return (
("%.2f%%" % (100 * value)) if isinstance(value, float) else str(value)
)
return ("%g%%" % (100 * value)) if isinstance(value, float) else str(value)


def fmt_range(min, max):
if min == max:
return fmt(min)
else:
return f"{fmt(min)} - {fmt(max)}"


class Command(BaseCommand):
Expand Down Expand Up @@ -70,19 +75,23 @@ def handle(self, **options):

cards_for_tier = cards.for_credit_tier(tier_name)

purchase_apr_rating_counts = (
CardListView.get_purchase_apr_rating_counts(
cards_for_tier.values("purchase_apr_for_tier_rating")
purchase_apr_rating_ranges = (
CardListView.get_purchase_apr_rating_ranges(
cards_for_tier.values(
"purchase_apr_for_tier_max",
"purchase_apr_for_tier_rating",
)
)
)

# Write out counts for each rating label.
# Write out ranges for each rating label.
for (
rating,
rating_count,
) in purchase_apr_rating_counts.items():
(rating_apr_min, rating_apr_max),
) in purchase_apr_rating_ranges.items():
self.stdout.write(
f"{apr_rating_lookup[rating]}: {rating_count}"
f"{apr_rating_lookup[rating]}: "
+ fmt_range(rating_apr_min, rating_apr_max)
)

self.stdout.write()
Expand Down
30 changes: 0 additions & 30 deletions cfgov/tccp/situations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from dataclasses import dataclass, field
from itertools import chain
from operator import attrgetter

from django.template import loader
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -30,10 +29,6 @@ def render(self, viewname):
def select_html(self):
return self.render("select")

@property
def results_html(self):
return self.render("results")

@classmethod
def get_nonconflicting_params(cls, situations):
"""Get nonconflicting parameters for a list of situations.
Expand Down Expand Up @@ -184,31 +179,6 @@ def get_situation_by_title(title):
return {v: k for k, v in SituationChoices}[title]


class SituationFeatures:
def __init__(self, situations):
# Each selected situation normally displays one feature to the user,
# but if multiple are selected we may want to combine them.
combos = [("Pay less interest", "Make a big purchase")]

for combo in combos:
if set(combo).issubset(set(map(attrgetter("title"), situations))):
situations = [
# Create a fake situation identified by the concatenation
# of the titles of the situations being combined.
Situation(title=" ".join(combo)),
# Remove the situations that were combined.
*(s for s in situations if s.title not in combo),
]

self.situations = situations

def __bool__(self):
return bool(self.situations)

def __iter__(self):
return self.situations.__iter__()


class SituationSpeedBumps:
INTERVAL = 5

Expand Down
34 changes: 3 additions & 31 deletions cfgov/tccp/tests/test_situations.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from itertools import product

from django.test import SimpleTestCase

from tccp.situations import (
SITUATIONS,
Situation,
SituationFeatures,
SituationSpeedBumps,
get_situation_by_title,
)
Expand Down Expand Up @@ -49,34 +46,9 @@ def test_get_get_nonconflicting_params_multiple_duplicates(self):

class SituationContentTests(SimpleTestCase):
def test_situation_content(self):
for situation, content in product(SITUATIONS, ["select", "results"]):
with self.subTest(title=situation.title, content=content):
self.assertTrue(getattr(situation, f"{content}_html"))


class SituationFeatureTests(SimpleTestCase):
def test_no_combinations(self):
features = SituationFeatures(
[
Situation("Pay less interest"),
Situation("Foo"),
]
)
self.assertSequenceEqual(
[s.title for s in features], ["Pay less interest", "Foo"]
)

def test_combination(self):
features = SituationFeatures(
[
Situation("Pay less interest"),
Situation("Make a big purchase"),
]
)
self.assertSequenceEqual(
[s.title for s in features],
["Pay less interest Make a big purchase"],
)
for situation in SITUATIONS:
with self.subTest(title=situation.title):
self.assertTrue(situation.select_html)


class SituationSpeedBumpTests(SimpleTestCase):
Expand Down
Loading

0 comments on commit 0026a76

Please sign in to comment.