From a8d35a57cd4272b0c60a67ec5a3931fe7806a272 Mon Sep 17 00:00:00 2001 From: Phil Dominguez <142051477+phildominguez-gsa@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:13:05 -0400 Subject: [PATCH 1/2] Adding percent-of-readable-pages validation for PDFs (#4223) * Adding percent-of-readable-pages validation for PDFs * Lint * Adding unit test --- .../fixtures/not-enough-readable-pages.pdf | Bin 0 -> 36805 bytes backend/audit/test_validators.py | 6 +++++ backend/audit/validators.py | 25 +++++++++++++----- 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 backend/audit/fixtures/not-enough-readable-pages.pdf diff --git a/backend/audit/fixtures/not-enough-readable-pages.pdf b/backend/audit/fixtures/not-enough-readable-pages.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0fd580f6e072983bdb39a13c80d31bfaafb21b62 GIT binary patch literal 36805 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≻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ⅅSz~rHdBYE%1-$lh(6~&e#a4caQB(R z8?6-%VEJw;}G@kVoeuaE&oNf};KjN~`DEZyOw{L&)FPLlh7Y z5F<W?1ru%_eT$nTxduUeqW?TBX#DDJO7m%5h)tK^qh+dKLM<%#P_mUT_;^7 z``x&25F-&oqD+fK$h$vmjYJYw61EE?w#CIKl7A?LG76R(F}GU1k}2CL4|!#S)Vv0$ zphWF(k}8MY(OlMgp!wyYT%`Pfr$g;}J(t>aCY{U2r=H_|8Kvi3h3| z<3!G0l@?aaig6t2M$Qa6m`$8g7^mHmS-au!&~&Ce(oqp%XB^yWq1Tf04>$oc;@gHv&^y*YXRq$@i}pHJ_cSu7#B1 zSBizMu|2^T%8$=p|BG-*z|O!z#{>j0e`)?P@Q;d?iII_xospU03puCYU}&jCz|2C& z#?HnJ_=1)R=-P-Hni`up5C9p!v`h?uFQTN_7f8)ipU=wJ!jRyLnE6j@`2U64q-AAg zrek4Y0J0I#0+^WTfDAwez!zzaf#D0@_Ej?*3p;@A3&QzNi7y>VW@o;VT*V|5<_N*MJdJCSYOs zC&K?j_xmr}@BbfH55V|Uh~WR-|IQ^%GiKGwj{!FD@)ouEjE;%`cR~DzX>Ms>KRot2 z=v1FWv~V!LTlXscQm|MU*ir5KYA0?^3p0FZSAH@uStcJDStM86_U6gmGv)qSdk!&o-R0&kE<)^TYY|^RiA6D?i3XleGet)Q$mYoz37-C=26)-#a`J~uHf0N z+Sywpg;j*yU%5m%@&!rFojB2CsetnOk4{Os zd8(-T_Nk|LcW%htlN9rLQ$lXSZ|*l4;#WWYVY)JH-ha&U2uBu$=QvPS0~QOo^QLQF za()UZE5UQ1t_Q3gIsp=7Q4lcpL-IrOGJ#rB6^y_~eIn991@PEL;8EE| zW?aBB1_~6+OihhV719*oV!*5jmQ%=siWwT40+TALn1O*I=txNrfU7e$GXUlaR574B zQ{dT>DC!K2&4FndRm{NL*celtr6sx@hQK@pJVF!IJQG6;V4;sHW@HIEW)@Y<#Mm6e zJYxeB3^5Z^Gj#VE8XKAePaZ`x&m4H#IGPyH8^BTrO`W9)hF)N$g5hRULu2&t1D0GS z82&H?hBTTT#wM1)m_W7H7-$~4`;1M^fCUF!T}e?Quwp6V0+!*ynN`3P1FY48^7Bg+ mzz2c?s|;`n4|D+daOdKZ#G(>#A~H5JGBD>-Rdw}u;{pINe7Gq9 literal 0 HcmV?d00001 diff --git a/backend/audit/test_validators.py b/backend/audit/test_validators.py index 5e1490a25f..ef76feef5c 100644 --- a/backend/audit/test_validators.py +++ b/backend/audit/test_validators.py @@ -681,9 +681,15 @@ def test_locked_pdf_file(self): self.assertRaises(ValidationError, validate_pdf_file_integrity, file) def test_scanned_pdf_file(self): + """PDF files that have too few parsable characters are invalid""" with open("audit/fixtures/scanned.pdf", "rb") as file: self.assertRaises(ValidationError, validate_pdf_file_integrity, file) + def test_not_enough_readable_pages_pdf_file(self): + """PDF files whose percentage of readable pages is too low are invalid""" + with open("audit/fixtures/not-enough-readable-pages.pdf", "rb") as file: + self.assertRaises(ValidationError, validate_pdf_file_integrity, file) + def test_valid_pdf_file(self): with open("audit/fixtures/basic.pdf", "rb") as file: validate_pdf_file_integrity(file) diff --git a/backend/audit/validators.py b/backend/audit/validators.py index bf9e995fa4..ad24de5958 100644 --- a/backend/audit/validators.py +++ b/backend/audit/validators.py @@ -663,6 +663,7 @@ def validate_single_audit_report_file_extension(file): def validate_pdf_file_integrity(file): """Files must be readable PDFs""" MIN_CHARARACTERS_IN_PDF = 6000 + MIN_PERCENT_READABLE_PAGES = 0.50 try: reader = PdfReader(file) @@ -672,17 +673,27 @@ def validate_pdf_file_integrity(file): "We were unable to process the file you uploaded because it is encrypted." ) - text_length = 0 + total_chars = 0 + num_pages_with_text = 0 + for page in reader.pages: page_text = page.extract_text() - text_length += len(page_text) - # If we find enough characters, we're content. - if text_length >= MIN_CHARARACTERS_IN_PDF: - break + total_chars += len(page_text) + num_pages_with_text += 1 if len(page_text) else 0 + + percent_readable_pages = num_pages_with_text / len(reader.pages) - if text_length < MIN_CHARARACTERS_IN_PDF: + if total_chars == 0: + raise ValidationError( + "We were unable to process the file you uploaded because it contains no readable text." + ) + elif total_chars < MIN_CHARARACTERS_IN_PDF: + raise ValidationError( + "We were unable to process the file you uploaded because it contains too little readable text." + ) + elif percent_readable_pages < MIN_PERCENT_READABLE_PAGES: raise ValidationError( - "We were unable to process the file you uploaded because it contains no readable text or too little text." + f"We were unable to process the file you uploaded because only {percent_readable_pages:.0%} of the pages contain readable text (minimum {MIN_PERCENT_READABLE_PAGES:.0%} required.)" ) except ValidationError: From 11db765b0fc9ccd2a71915329ac9f4029bb449ee Mon Sep 17 00:00:00 2001 From: Bobby Novak <176936850+rnovak338@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:14:03 -0400 Subject: [PATCH 2/2] Consider leap years when generating fiscal period start date (#4239) * Account for leap year when calculating fiscal start date - Using factory calendar library for determining leap year, and then subtracting either 366 or 365 days from the fiscal end date accordingly. - Added similar logic to general information test: `test_when_auditee_fiscal_period_end_is_valid`. * Add Leap Year Utility Added a new method in Util that checks the year for a leap year. This method is now referenced in `general_information` and the test. * Sanity checks Handling more edge cases. This is the expected behavior as of this commit: - If the end date is `10/1/2020`, the start date will be `10/1/2019` (366 days) - If the end date is `10/1/2021`, the start date will be `10/1/2020` (365 days) - If the end date is `2/1/2020`, the start date will be `2/1/2019` (365 days) - If the end date is `2/1/2021`, the start date will be `2/1/2020` (366 days) * Refactor changes for leap year calculation Found a library `dateutil` that fulfills the same purpose as my utility method. I removed this utility and am using relativedelta() in its place. I also have rewired the fiscal end date in `test_sac_creation.py` so that the date is based on the helpdesk ticket that created this issue. The end date is `6/30/2022` and so the expected start date is `7/1/2021`. * Linting fix * Linting fix - flake8 * More linting fixes Accounted for dateutil in `requirements.txt`. * Sanity check for linting Added `python-dateutil` to requirements.in. * Update linting.yml Running --install-types to see if this passes recent lint failures. * Yet another sanity check on linting * Update linting.yml * Document changes Recording why this change was made, how it worked previously, and noting that this change is being made after the census historical migrations have been completed. --- .github/workflows/linting.yml | 4 +- .../sac_general_lib/general_information.py | 15 ++-- .../test_general_information_xforms.py | 11 +-- .../test_sac_creation.py | 2 +- backend/requirements.txt | 15 ++-- backend/requirements/requirements.in | 78 ++++++++++--------- 6 files changed, 68 insertions(+), 57 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 42c491ca01..f59fcf87ad 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -61,7 +61,9 @@ jobs: - name: Run type checking working-directory: ./backend - run: mypy . + run: | + mypy . --install-types --non-interactive + mypy . - name: Run HTML template linting working-directory: ./backend diff --git a/backend/census_historical_migration/sac_general_lib/general_information.py b/backend/census_historical_migration/sac_general_lib/general_information.py index 8a972bfa30..eb5bbdb767 100644 --- a/backend/census_historical_migration/sac_general_lib/general_information.py +++ b/backend/census_historical_migration/sac_general_lib/general_information.py @@ -1,6 +1,7 @@ import json import re -from datetime import timedelta +import datetime +from dateutil.relativedelta import relativedelta from django.conf import settings @@ -310,14 +311,18 @@ def xform_auditee_fiscal_period_end(general_information): def xform_auditee_fiscal_period_start(general_information): """Constructs the fiscal period start from the fiscal period end""" - # Transformation to be documented. - fiscal_start_date = xform_census_date_to_datetime( + # As of 8/30/2024 this logic has been adjusted to handle invalid start dates of the fiscal year. + # Previously, fiscal_start date was subtracting 365 days from the end date. + # All migrations of historical census records had been completed prior to this change. + fiscal_end_date = xform_census_date_to_datetime( general_information.get("auditee_fiscal_period_end") - ) - timedelta(days=365) + ) + fiscal_start_date = ( + fiscal_end_date - relativedelta(years=1) + datetime.timedelta(days=1) + ) general_information["auditee_fiscal_period_start"] = fiscal_start_date.strftime( "%Y-%m-%d" ) - return general_information diff --git a/backend/census_historical_migration/test_general_information_xforms.py b/backend/census_historical_migration/test_general_information_xforms.py index 9139888cdc..5d9ae5cd7c 100644 --- a/backend/census_historical_migration/test_general_information_xforms.py +++ b/backend/census_historical_migration/test_general_information_xforms.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta import json from unittest.mock import mock_open, patch from django.conf import settings @@ -190,12 +191,12 @@ def setUp(self): def test_when_auditee_fiscal_period_end_is_valid(self): """Test that the function returns the correct results when the fiscal period end is valid.""" result = xform_auditee_fiscal_period_start(self.general_information) + fiscal_end = datetime.strptime( + self.general_information["auditee_fiscal_period_end"], + "%m/%d/%Y %H:%M:%S", + ) expected_date = ( - datetime.strptime( - self.general_information["auditee_fiscal_period_end"], - "%m/%d/%Y %H:%M:%S", - ) - - timedelta(days=365) + fiscal_end - relativedelta(years=1) + timedelta(days=1) ).strftime("%Y-%m-%d") self.assertEqual(result["auditee_fiscal_period_start"], expected_date) diff --git a/backend/census_historical_migration/test_sac_creation.py b/backend/census_historical_migration/test_sac_creation.py index bf60ff967d..21bd95c658 100644 --- a/backend/census_historical_migration/test_sac_creation.py +++ b/backend/census_historical_migration/test_sac_creation.py @@ -36,7 +36,7 @@ def __init__( self.DOLLARTHRESHOLD = "750000" self.EIN = "134278617" self.ENTITY_TYPE = entity_type - self.FYENDDATE = "01/01/2022 00:00:00" + self.FYENDDATE = "06/30/2022 00:00:00" self.GOINGCONCERN = "N" self.LOWRISK = "N" self.MATERIALNONCOMPLIANCE = "N" diff --git a/backend/requirements.txt b/backend/requirements.txt index 87eca94878..7e82c0675d 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements.txt ./requirements/requirements.in @@ -97,9 +97,7 @@ cffi==1.16.0 \ --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 - # via - # cryptography - # gevent + # via cryptography charset-normalizer==3.3.2 \ --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ @@ -897,6 +895,7 @@ python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via + # -r ./requirements/requirements.in # botocore # faker # pandas @@ -1163,6 +1162,10 @@ text-unidecode==1.3 \ --hash=sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8 \ --hash=sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93 # via python-slugify +types-python-dateutil==2.9.0.20240821 \ + --hash=sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98 \ + --hash=sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57 + # via -r ./requirements/requirements.in typing-extensions==4.11.0 \ --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \ --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a @@ -1175,9 +1178,7 @@ typing-extensions==4.11.0 \ tzdata==2024.1 \ --hash=sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd \ --hash=sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252 - # via - # django - # pandas + # via pandas uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e diff --git a/backend/requirements/requirements.in b/backend/requirements/requirements.in index 26d5533450..e14e3b9c09 100644 --- a/backend/requirements/requirements.in +++ b/backend/requirements/requirements.in @@ -1,38 +1,40 @@ -boto3 -cfenv -cryptography>=42.0.5 -django-cors-headers -django-csp -django-dbbackup -django-storages[boto3] -Django>=5.0.8 -djangorestframework>=3.15.2 -djangorestframework-simplejwt -django-fsm -environs[django] -faker -fs -greenlet>=3.0rc3 -gunicorn[gevent]>=22.0.0 -jsonpath-ng -jsonschema -newrelic>=9.11.0 -oic -openpyxl -pandas -peewee -psycogreen -psycopg2-binary -pycryptodome>=3.19.1 -pycryptodomex>=3.19.1 -pydash -pyjwt -pypdf>=3.17.0 -python-json-logger -python-slugify -pyyaml -requests>=2.32.3 -sqlalchemy -sqlparse>=0.5.0 -uritemplate -urllib3>=2.2.2 +boto3 +cfenv +cryptography>=42.0.5 +django-cors-headers +django-csp +django-dbbackup +django-storages[boto3] +Django>=5.0.8 +djangorestframework>=3.15.2 +djangorestframework-simplejwt +django-fsm +environs[django] +faker +fs +greenlet>=3.0rc3 +gunicorn[gevent]>=22.0.0 +jsonpath-ng +jsonschema +newrelic>=9.11.0 +oic +openpyxl +pandas +peewee +psycogreen +psycopg2-binary +pycryptodome>=3.19.1 +pycryptodomex>=3.19.1 +pydash +pyjwt +pypdf>=3.17.0 +python-dateutil==2.9.0.post0 +python-json-logger +python-slugify +pyyaml +requests>=2.32.3 +sqlalchemy +sqlparse>=0.5.0 +types-python-dateutil==2.9.0.20240821 +uritemplate +urllib3>=2.2.2