webservices/resources/reports.py

Summary

Maintainability
F
4 days
Test Coverage
import sqlalchemy as sa
from flask_apispec import doc, marshal_with

from webservices import args
from webservices import docs
from webservices import utils
from webservices import schemas
from webservices import filters
from webservices.common import counts
from webservices.common import models
from webservices.common import views
from webservices.utils import use_kwargs


reports_schema_map = {
    'P': (models.CommitteeReportsPresidential, schemas.CommitteeReportsPresidentialPageSchema),
    'H': (models.CommitteeReportsHouseSenate, schemas.CommitteeReportsHouseSenatePageSchema),
    'S': (models.CommitteeReportsHouseSenate, schemas.CommitteeReportsHouseSenatePageSchema),
    'I': (models.CommitteeReportsIEOnly, schemas.CommitteeReportsIEOnlyPageSchema),
}

efile_reports_schema_map = {
    'P': (models.BaseF3PFiling, schemas.BaseF3PFilingSchema, schemas.BaseF3PFilingPageSchema),
    'H': (models.BaseF3Filing, schemas.BaseF3FilingSchema, schemas.BaseF3FilingPageSchema),
    'S': (models.BaseF3Filing, schemas.BaseF3FilingSchema, schemas.BaseF3FilingPageSchema),
    'X': (models.BaseF3XFiling, schemas.BaseF3XFilingSchema, schemas.BaseF3XFilingPageSchema),
}
# We don't have report data for C and E yet
default_schemas = (models.CommitteeReportsPacParty, schemas.CommitteeReportsPacPartyPageSchema)

reports_type_map = {
    'house-senate': 'H',
    'presidential': 'P',
    'ie-only': 'I',
    'pac-party': None,
    'pac': 'O',
    'party': 'XY'
}

form_type_map = {
    'presidential': 'P',
    'pac-party': 'X',
    'house-senate': 'H',
}


def get_range_filters():
    filter_range_fields = [
        (('min_receipt_date', 'max_receipt_date'), models.CommitteeReports.receipt_date),
        (('min_disbursements_amount', 'max_disbursements_amount'), models.CommitteeReports.total_disbursements_period),
        (('min_receipts_amount', 'max_receipts_amount'), models.CommitteeReports.total_receipts_period),
        (('min_cash_on_hand_end_period_amount', 'max_cash_on_hand_end_period_amount'),
         models.CommitteeReports.cash_on_hand_end_period),
        (('min_debts_owed_amount', 'max_debts_owed_amount'), models.CommitteeReports.debts_owed_by_committee),
        (('min_independent_expenditures', 'max_independent_expenditures'),
         models.CommitteeReportsPacParty.independent_expenditures_period),
        (('min_party_coordinated_expenditures', 'max_party_coordinated_expenditures'),
         models.CommitteeReportsPacParty.coordinated_expenditures_by_party_committee_period),
        (('min_total_contributions', 'max_total_contributions'),
         models.CommitteeReportsIEOnly.independent_contributions_period),
    ]
    return filter_range_fields


def get_match_filters():
    filter_match_fields = [
        ('filer_type', models.CommitteeReports.means_filed),
        ('is_amended', models.CommitteeReports.is_amended),
        ('most_recent', models.CommitteeReports.most_recent),
    ]
    return filter_match_fields


@doc(
    tags=['financial'],
    description=docs.REPORTS,
    params={
        'committee_type': {
            'description': 'House, Senate, presidential, independent expenditure only',
            'enum': ['presidential', 'pac-party', 'house-senate', 'ie-only'],
        },
    },
)
class ReportsView(utils.Resource):

    @use_kwargs(args.paging)
    @use_kwargs(args.reports)
    @use_kwargs(
        args.make_multi_sort_args(default=['-coverage_end_date'])
    )
    @marshal_with(schemas.CommitteeReportsPageSchema(), apply=False)
    def get(self, committee_type=None, **kwargs):
        committee_id = kwargs.get('committee_id')
        query, reports_class, reports_schema = self.build_query(
            committee_type=committee_type,
            **kwargs
        )
        if kwargs['sort']:
            validator = args.IndicesValidator(reports_class)
            validator(kwargs['sort'])
        page = utils.fetch_page(query, kwargs, model=reports_class, multi=True)
        return reports_schema().dump(page).data

    def build_query(self, committee_type=None, **kwargs):
        #For this endpoint we now enforce the enpoint specified to map the right model.
        reports_class, reports_schema = reports_schema_map.get(
            reports_type_map.get(committee_type),
            default_schemas,
        )
        query = reports_class.query

        filter_multi_fields = [
            ('amendment_indicator', models.CommitteeReports.amendment_indicator),
            ('report_type', reports_class.report_type),
        ]

        if hasattr(reports_class, 'committee'):
            query = reports_class.query.outerjoin(reports_class.committee).options(sa.orm.contains_eager(reports_class.committee))

        if kwargs.get('committee_id'):
            query = query.filter(reports_class.committee_id.in_(kwargs['committee_id']))
        if kwargs.get('candidate_id'):
            query = query.filter(models.CommitteeHistory.candidate_ids.overlap([kwargs.get('candidate_id')]))
        if kwargs.get('type'):
            query = query.filter(models.CommitteeHistory.committee_type.in_(kwargs.get('type')))
        if kwargs.get('year'):
            query = query.filter(reports_class.report_year.in_(kwargs['year']))
        if kwargs.get('cycle'):
            query = query.filter(reports_class.cycle.in_(kwargs['cycle']))
        if kwargs.get('beginning_image_number'):
            query = query.filter(reports_class.beginning_image_number.in_(kwargs['beginning_image_number']))
        if kwargs.get('is_amended') is not None:
            query = query.filter(reports_class.is_amended == kwargs['is_amended'])

        query = filters.filter_range(query, kwargs, get_range_filters())
        query = filters.filter_match(query, kwargs, get_match_filters())
        query = filters.filter_multi(query, kwargs, filter_multi_fields)
        return query, reports_class, reports_schema


@doc(
    tags=['financial'],
    description=docs.REPORTS,
    params={
        'committee_id': {'description': docs.COMMITTEE_ID},
    },
)
class CommitteeReportsView(utils.Resource):

    @use_kwargs(args.paging)
    @use_kwargs(args.committee_reports)
    @use_kwargs(args.make_multi_sort_args(default=['-coverage_end_date']))
    @marshal_with(schemas.CommitteeReportsPageSchema(), apply=False)
    def get(self, committee_id=None, committee_type=None, **kwargs):
        query, reports_class, reports_schema = self.build_query(
            committee_id=committee_id,
            committee_type=committee_type,
            **kwargs
        )
        if kwargs['sort']:
            validator = args.IndicesValidator(reports_class)
            validator(kwargs['sort'])
        page = utils.fetch_page(query, kwargs, model=reports_class, multi=True)
        return reports_schema().dump(page).data

    def build_query(self, committee_id=None, committee_type=None, **kwargs):
        reports_class, reports_schema = reports_schema_map.get(
            self._resolve_committee_type(
                committee_id=committee_id,
                committee_type=committee_type,
                **kwargs
            ),
            default_schemas,
        )
        query = reports_class.query

        filter_multi_fields = [
            ('amendment_indicator', models.CommitteeReports.amendment_indicator),
            ('report_type', reports_class.report_type),
        ]
        # Eagerly load committees if applicable
        if hasattr(reports_class, 'committee'):
            query = reports_class.query.options(sa.orm.joinedload(reports_class.committee))

        if committee_id is not None:
            query = query.filter_by(committee_id=committee_id)
        if kwargs.get('year'):
            query = query.filter(reports_class.report_year.in_(kwargs['year']))
        if kwargs.get('cycle'):
            query = query.filter(reports_class.cycle.in_(kwargs['cycle']))
        if kwargs.get('beginning_image_number'):
            query = query.filter(reports_class.beginning_image_number.in_(kwargs['beginning_image_number']))
        if kwargs.get('is_amended') is not None:
            query = query.filter(reports_class.is_amended == kwargs['is_amended'])

        query = filters.filter_range(query, kwargs, get_range_filters())
        query = filters.filter_match(query, kwargs, get_match_filters())
        query = filters.filter_multi(query, kwargs, filter_multi_fields)
        return query, reports_class, reports_schema

    def _resolve_committee_type(self, committee_id=None, committee_type=None, **kwargs):
        if committee_id is not None:
            query = models.CommitteeHistory.query.filter_by(committee_id=committee_id)
            if kwargs.get('cycle'):
                query = query.filter(models.CommitteeHistory.cycle.in_(kwargs['cycle']))
            query = query.order_by(sa.desc(models.CommitteeHistory.cycle))
            committee = query.first_or_404()
            return committee.committee_type
        elif committee_type is not None:
            return reports_type_map.get(committee_type)


@doc(
    tags=['efiling'],
    description=docs.EFILE_REPORTS,
)
class EFilingHouseSenateSummaryView(views.ApiResource):

    model = models.BaseF3Filing
    schema = schemas.BaseF3FilingSchema
    page_schema = schemas.BaseF3FilingPageSchema

    filter_range_fields = [
        (('min_receipt_date', 'max_receipt_date'), models.BaseFiling.receipt_date),
    ]
    filter_multi_fields = [
        ('file_number', model.file_number),
        ('committee_id', model.committee_id)
    ]

    @property
    def args(self):
        return utils.extend(
            args.paging,
            args.efilings,
            args.make_sort_args(
                default='-receipt_date',
                # validator=args.IndexValidator(self.model),
            ),
        )

    @property
    def index_column(self):
        return self.model.file_number


@doc(
    tags=['efiling'],
    description=docs.EFILE_REPORTS,
)
class EFilingPresidentialSummaryView(views.ApiResource):

    model = models.BaseF3PFiling
    schema = schemas.BaseF3PFilingSchema
    page_schema = schemas.BaseF3PFilingPageSchema

    filter_range_fields = [
        (('min_receipt_date', 'max_receipt_date'), models.BaseFiling.receipt_date),
    ]
    filter_multi_fields = [
        ('file_number', model.file_number),
        ('committee_id', model.committee_id)
    ]

    @property
    def args(self):
        return utils.extend(
            args.paging,
            args.efilings,
            args.make_sort_args(
                default='-receipt_date',
                # validator=args.IndexValidator(self.model),
            ),
        )

    @property
    def index_column(self):
        return self.model.file_number


@doc(
    tags=['efiling'],
    description=docs.EFILE_REPORTS,
)
class EFilingPacPartySummaryView(views.ApiResource):

    model = models.BaseF3XFiling
    schema = schemas.BaseF3XFilingSchema
    page_schema = schemas.BaseF3XFilingPageSchema

    filter_range_fields = [
        (('min_receipt_date', 'max_receipt_date'), models.BaseFiling.receipt_date),
    ]
    filter_multi_fields = [
        ('file_number', model.file_number),
        ('committee_id', model.committee_id)
    ]

    @property
    def args(self):
        return utils.extend(
            args.paging,
            args.efilings,
            args.make_sort_args(
                default='-receipt_date',
                # validator=args.IndexValidator(self.model),
            ),
        )

    @property
    def index_column(self):
        return self.model.file_number