From 3ef0ff6c7925b8f8449b18d93afd567ac32860f3 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Fri, 3 Feb 2023 09:39:54 -0500 Subject: [PATCH] Tutorial changes (#145) Bump to 0.7.1, with lots of non-breaking changes for used classes, and lots of polish and removing inconsistencies in unused classes, so they will be used. ## Polish in al_income.py * Sets insertion value, and keep object type for ALExpense * update docstrings to reflect what's used and what function output actually looks like * Extend `recent_years` default to 25, to go back to 2001 and cover more cars ## Polish in al_income.yml * Added default tables and revisit screens for every type in documentation * show "income"/"deduction" in ALItemizedJob review screen * Deleted duplicate blocks that had been added earlier * Moved blocks around to make sense categorically * Align similar classes of blocks, like ALJob and ALItemizedJob, so they ask questions in the same way by default ("Zip or postal code", etc) * Added blocks from Affidavit to be default ones here, including * `.moved` and `.gathered` for ALIncomeList * `.market_value` only by default for ALVehicleList * Can set the name of an ALIncome if not set by the "select" screen * Show `.display_name` if ALIncome has one, instead of `.source` * don't require "how often is the income earned" if the value of an asset isn't required * ALVehicleList uses market_value by default, not value (not likely for vehicles). ## Reorg and fix-up income demo * added intermediate screens between each feature * removed all demo specific code blocks, to get the default experience with most classes * added the ability to directly start one particular feature with url_args * removed "all of the above" feature. Is in upstream docassemble now, but trying to avoid newer features, and the original feature broke with url_args, and url_args take priority for a useful feature. * show classes in the same order that the user gets to select them * Add ALExpenseList to demo --------- Co-authored-by: plocket <52798256+plocket@users.noreply.github.com> --- docassemble/ALToolbox/__init__.py | 2 +- docassemble/ALToolbox/al_income.py | 27 +- docassemble/ALToolbox/copy_button.py | 1 + .../ALToolbox/data/questions/al_income.yml | 246 ++++++++++------- .../data/questions/al_income_demo.yml | 252 ++++++++++-------- setup.py | 2 +- 6 files changed, 311 insertions(+), 219 deletions(-) diff --git a/docassemble/ALToolbox/__init__.py b/docassemble/ALToolbox/__init__.py index a71c5c7f..f0788a87 100644 --- a/docassemble/ALToolbox/__init__.py +++ b/docassemble/ALToolbox/__init__.py @@ -1 +1 @@ -__version__ = '0.7.0' +__version__ = '0.7.1' diff --git a/docassemble/ALToolbox/al_income.py b/docassemble/ALToolbox/al_income.py index be32778d..fad760a9 100644 --- a/docassemble/ALToolbox/al_income.py +++ b/docassemble/ALToolbox/al_income.py @@ -97,7 +97,7 @@ def times_per_year( def recent_years( - past: int = 15, order: str = "descending", future: int = 1 + past: int = 25, order: str = "descending", future: int = 1 ) -> List[int]: """ Returns a list of the most recent past years, continuing into the future. @@ -130,7 +130,6 @@ class ALPeriodicAmount(DAObject): .times_per_year {float | Decimal} Represents a number of the annual frequency of the income. E.g. 12 for a monthly income. .source {str} (Optional) The "source" of the income, like a "job" or a "house". - .owner {str} (Optional) Full name of the income's owner as a single string. .display_name {str} (Optional) If present, will have a translated string to show the user, as opposed to a raw english string from the program """ @@ -267,7 +266,8 @@ def matches( satifies_sources = _source_to_callable(source, exclude_source) # Construct the filtered list return ALIncomeList( - elements=[item for item in self.elements if satifies_sources(item.source)] + elements=[item for item in self.elements if satifies_sources(item.source)], + object_type=self.object_type, ) def total( @@ -325,7 +325,7 @@ def move_checks_to_list( if not selected_terms: selected_terms = {} self.elements.clear() - for source in selected_types.true_values(): + for source in selected_types.true_values(insertion_order=True): if source == "other": self.appendObject() else: @@ -416,7 +416,15 @@ def employer_name_address_phone(self) -> str: gathering the `.employer`, `.employer_address`, and `.employer_phone` attributes. """ - return f"{self.employer.name}: {self.employer.address}, {self.employer.phone}" + if self.employer.address.address and self.employer.phone: + return ( + f"{self.employer.name}: {self.employer.address}, {self.employer.phone}" + ) + if self.employer.address.address: + return f"{self.employer.name}: {self.employer.address}" + if self.employer.phone: + return f"{self.employer.name}: {self.employer.phone}" + return f"{self.employer.name}" def normalized_hours(self, times_per_year: float = 1) -> float: """ @@ -545,8 +553,8 @@ class ALExpenseList(ALIncomeList): A list of expenses * each element has a: + * value * source - * owner * display name """ @@ -910,8 +918,8 @@ def total(self) -> Decimal: def __str__(self) -> str: """ - Returns a string of the dictionary's key/value pairs in a list. E.g. - "['federal_taxes': '2500.00', 'wages': '15.50']" + Returns a string of the dictionary's key/value pairs as two-element lists in a list. + E.g. '[["federal_taxes", "2500.00"], ["wages", "15.50"]]' """ to_stringify = [] for key in self: @@ -1185,7 +1193,8 @@ def init(self, *pargs, **kwargs): def sources(self, which_side: Optional[str] = None) -> Set[str]: """Returns a set of the unique sources in all of the jobs. - By default gets from both sides, if which_side is "deductions", only gets from deductions.""" + By default gets from both sides, if which_side is "deductions", only gets from deductions. + """ sources = set() if not which_side: which_side = "all" diff --git a/docassemble/ALToolbox/copy_button.py b/docassemble/ALToolbox/copy_button.py index c8300252..74d7e628 100644 --- a/docassemble/ALToolbox/copy_button.py +++ b/docassemble/ALToolbox/copy_button.py @@ -1,5 +1,6 @@ from docassemble.base.functions import word + # See GitHub issue https://github.com/SuffolkLITLab/docassemble-ALToolbox/issues/16 def copy_button_html( text_to_copy: str, diff --git a/docassemble/ALToolbox/data/questions/al_income.yml b/docassemble/ALToolbox/data/questions/al_income.yml index fbd261a4..ad42d96b 100644 --- a/docassemble/ALToolbox/data/questions/al_income.yml +++ b/docassemble/ALToolbox/data/questions/al_income.yml @@ -1,5 +1,5 @@ --- -modules: +modules: - .al_income --- # Returns a list of lists, each of which contains the number of @@ -108,15 +108,6 @@ objects: --- ########## Questions --- -generic object: ALItemizedValueDict -continue button field: x.revisit -question: | - Edit values -subquestion: | - ${ x.table } - - ${ x.add_action() } ---- generic object: ALIncomeList continue button field: x.revisit question: | @@ -126,19 +117,36 @@ subquestion: | ${ x.add_action() } --- -generic object: ALItemizedValueDict +generic object: ALIncomeList table: x.table rows: x columns: - Type: | - row_index + row_item.source.replace("_", " ").lower() + - Times per year: | + times_per_year(times_per_year_list, row_item.times_per_year) - Amount: | - '$0' if hasattr(row_item, 'exists') and not row_item.exists else currency(row_item.value) + currency(row_item.value) edit: - - exists + - source - value + - times_per_year +--- +generic object: ALIncomeList +code: | + x.selected_types + x.move_checks_to_list(selected_terms=non_wage_income_terms) + x.moved = True +--- +generic object: ALIncomeList +code: | + x.moved + for income_type in x.elements: + income_type.complete + x.revisit = True + x.gathered = True --- -generic object: ALExpenseList +generic object: ALItemizedValueDict table: x.table rows: x columns: @@ -147,9 +155,18 @@ columns: - Amount: | '$0' if hasattr(row_item, 'exists') and not row_item.exists else currency(row_item.value) edit: - - source + - exists - value --- +generic object: ALItemizedValueDict +continue button field: x.revisit +question: | + Edit values +subquestion: | + ${ x.table } + + ${ x.add_action() } +--- generic object: ALItemizedJob code: | x.source @@ -199,11 +216,18 @@ question: | fields: - I am self-employed: x.is_self_employed datatype: yesno - - Name: x.employer.name.first + - Employer's name: x.employer.name.first show if: variable: x.is_self_employed is: False - - Street: x.employer.address.address + - note: | + --- + + Employer's contact information + show if: + variable: x.is_self_employed + is: False + - Street address: x.employer.address.address required: False show if: variable: x.is_self_employed @@ -223,7 +247,7 @@ fields: show if: variable: x.is_self_employed is: False - - Zip: x.employer.address.zip + - Zip or postal code: x.employer.address.zip required: False show if: variable: x.is_self_employed @@ -381,7 +405,7 @@ fields: - Deduction name: x.to_subtract.new_item_name validation code: | if x.to_subtract.new_item_name in x.to_subtract.complete_elements().keys(): - validation_error(f'You already told us about your { job_items_names.get(x.to_subtract.new_item_name, x.to_subtract.new_item_name) } that take out { currency( x.to_subtract[ x.to_subtract.new_item_name ].value )}. Pick a different name.') + validation_error(f'You already told us about your { job_items_names.get(x.to_subtract.new_item_name, x.to_subtract.new_item_name) } that takes out { currency( x.to_subtract[ x.to_subtract.new_item_name ].value )}. Pick a different name.') --- id: other itemized job reduction value generic object: ALItemizedJob @@ -414,8 +438,27 @@ generic object: ALItemizedJob code: | x.to_subtract[i].exists = True --- -# UNIQUE ITEMS FOR ALItemizedJobList +generic object: ALItemizedJob +continue button field: x.to_add.revisit +question: | + Edit incomes from ${ x.source } +subquestion: | + ${ x.to_add.table } + + ${ x.to_add.add_action() } +--- +generic object: ALItemizedJob +continue button field: x.to_subtract.revisit +question: | + Edit deductions from ${ x.source } +subquestion: | + These are amounts that are taken out of your pay. + + ${ x.to_subtract.table } + + ${ x.to_subtract.add_action() } --- +# UNIQUE ITEMS FOR ALItemizedJobList id: how many itemized jobs generic object: ALItemizedJobList question: | @@ -463,52 +506,6 @@ edit: - to_subtract.revisit confirm: True --- -generic object: ALItemizedJobList -continue button field: x.to_subtract.revisit -question: | - Edit deductions from ${ x.source } -subquestion: | - ${ x.to_subtract.table } - - ${ x.add_action() } ---- -generic object: ALItemizedJobList -continue button field: x.to_add.revisit -question: | - Edit incomes from ${ x.source } -subquestion: | - ${ x.table } - - ${ x.add_action() } ---- -# SHARED CODE FOR ALIncome and ALIncomeList and single ALAsset ---- -generic object: ALIncome -code: | - x.source # assigned where object is added to list - x.value - x.complete = True ---- -id: income info -generic object: ALIncome -question: | - How much do you get from ${ x.source }? -fields: - - Is it an hourly amount?: x.is_hourly - datatype: yesnoradio - - Period: x.times_per_year - input type: radio - code: | - times_per_year_list - datatype: integer - - How many hours do you work per period?: x.hours_per_period - datatype: number - show if: x.is_hourly - - Amount: x.value - datatype: currency - - Who owns this?: x.owner - required: False ---- # UNIQUE FOR ALExpenseList --- generic object: ALExpenseList @@ -618,15 +615,65 @@ subquestion: | ${ x.add_action() } --- -# Why is this not being triggered id: which incomes +generic object: ALIncomeList question: | - What are your sources of income? + What sources of income do you have, not including employment? fields: - - no label: income_sources + - no label: x.selected_types datatype: checkboxes code: | - income_terms_ordered + non_wage_income_terms_ordered +--- +id: income info for list +generic object: ALIncomeList +question: | + Tell us about your ${ ordinal(i) } income +subquestion: | + % if i > 1: + You have already told us about your incomes from ${ comma_and_list([income.source for income in x.complete_elements()]) }. + % elif i > 0: + You have already told us about your income from ${ comma_and_list([income.source for income in x.complete_elements()]) }. + % endif +fields: + - Source of income: x[i].source + input type: dropdown + code: | + non_wage_income_terms_ordered + default: other + - What type of income?: x[i].source + show if: + variable: x[i].source + is: other + - Times per year you receive this income: x[i].times_per_year + input type: radio + code: | + times_per_year_list + - Amount of income: x[i].value + datatype: currency +--- +# SHARED CODE FOR ALIncome and ALIncomeList and single ALAsset +--- +generic object: ALIncome +code: | + x.source # assigned where object is added to list + x.value + x.complete = True +--- +id: income info +generic object: ALIncome +question: | + How much do you get from ${ x.display_name if hasattr(x, 'display_name') else x.source }? +fields: + - Times per year you receive this income: x.times_per_year + input type: radio + code: | + times_per_year_list + datatype: integer + - Amount: x.value + datatype: currency + - Who owns this?: x.owner + required: False --- # SHARED BY ALJob AND ALJobList --- @@ -652,37 +699,44 @@ question: | fields: - I am self-employed: x.is_self_employed datatype: yesno - - First name: x.employer.name.first + - Employer's name: x.employer.name.first show if: variable: x.is_self_employed is: False - - Last name: x.employer.name.last - required: False + - note: | + --- + + Employer's contact information show if: variable: x.is_self_employed is: False - Street address: x.employer.address.address + required: False show if: variable: x.is_self_employed is: False - Unit: x.employer.address.unit + required: False show if: variable: x.is_self_employed is: False - required: False - City: x.employer.address.city + required: False show if: variable: x.is_self_employed is: False - State: x.employer.address.state + required: False show if: variable: x.is_self_employed is: False - - Postal code: x.employer.address.zip + - Zip or postal code: x.employer.address.zip + required: False show if: variable: x.is_self_employed is: False - Phone: x.employer.phone + required: False show if: variable: x.is_self_employed is: False @@ -711,10 +765,10 @@ fields: - How many hours do you work in each period?: x.hours_per_period datatype: number show if: x.is_hourly - - What do you get paid?: x.value + - What is your hourly pay for this job?: x.value datatype: currency show if: x.is_hourly - - Amount you get paid: x.value + - What are your wages for this job?: x.value datatype: currency show if: variable: x.is_hourly @@ -741,7 +795,7 @@ subquestion: | You've already told us about ${ comma_and_list([job.source for job in x.complete_elements()]) }. % endif fields: - - Job name: x[i].source + - Job title: x[i].source --- generic object: ALJobList continue button field: x.revisit @@ -829,6 +883,18 @@ fields: datatype: integer required: False --- +generic object: ALAssetList +table: x.table +rows: x +columns: + - Name: | + row_item.name + - Amount: | + currency(row_item.balance) +edit: + - name + - balance +--- # UNIQUE FOR ALVehicle --- generic object: ALVehicle @@ -862,23 +928,10 @@ fields: - Loan balance: x.balance datatype: currency required: False - - Income earned: x.value - datatype: currency - required: False - - How often is the income earned?: x.times_per_year - datatype: integer - code: times_per_year_list - Who owns this?: x.owner --- # UNIQUE FOR ALVehicleList --- -# Has to have its own. Otherwise it's overridden by ALAssetList. -generic object: ALVehicleList -code: | - # .source is automatically 'vehicle' - x[i].market_value - x[i].complete = True ---- id: how many vehicles generic object: ALVehicleList question: | @@ -909,12 +962,19 @@ fields: hint: e.g, Ford, Honda - Model: x[i].model hint: e.g., Fusion, Civic - - Current value: x[i].value + - Current value: x[i].market_value datatype: currency - Loan balance: x[i].balance datatype: currency - Who owns this?: x[i].owner --- +# Has to have its own. Otherwise it's overridden by ALAssetList. +generic object: ALVehicleList +code: | + # .source is automatically 'vehicle' + x[i].market_value + x[i].complete = True +--- # UNIQUE FOR ALSimpleValue --- comment: | diff --git a/docassemble/ALToolbox/data/questions/al_income_demo.yml b/docassemble/ALToolbox/data/questions/al_income_demo.yml index f14439ff..b6125911 100644 --- a/docassemble/ALToolbox/data/questions/al_income_demo.yml +++ b/docassemble/ALToolbox/data/questions/al_income_demo.yml @@ -1,5 +1,5 @@ metadata: - title: ALToolbox - Income + title: ALToolbox - Income Demo --- include: - al_income.yml @@ -8,13 +8,16 @@ comment: | translation options: - map dict/lookup from key to lang word. See https://github.com/nonprofittechy/docassemble-HousingCodeChecklist/blob/0cbfe02b29bbec66b8a2b925b36b3c67bb300e84/docassemble/HousingCodeChecklist/data/questions/language.yml#L41 --- +objects: + - to_show: DADict +--- objects: - itemized_job: ALItemizedJob.using() - itemized_job_list: ALItemizedJobList.using( complete_attribute='complete', ask_number=True) - al_income: ALIncome - - al_income_list: ALIncomeList.using(auto_gather=False) + - al_income_list: ALIncomeList.using(complete_attribute='complete', auto_gather=False) - al_job: ALJob - al_job_list: ALJobList.using( complete_attribute='complete', @@ -31,118 +34,117 @@ objects: - al_value_list: ALSimpleValueList.using( complete_attribute='complete', ask_number=True) + - al_expense_list: ALExpenseList.using(auto_gather=False, complete_attribute="exists") --- id: interview order mandatory: True code: | - if to_run['ALItemizedJob'] or to_run['all']: - itemized_job.source = 'itemized taxi driver' - itemized_job.complete - - if to_run['ALItemizedJobList'] or to_run['all']: - itemized_job_list.gather() + if to_run['ALSimpleValue']: + to_show['ALSimpleValue'] + al_simple_value.value + + if to_run['ALAsset']: + to_show['ALAsset'] + al_asset.complete - if to_run['ALIncome'] or to_run['all']: - al_income.source = 'pension' + if to_run['ALVehicle']: + to_show['ALVehicle'] + al_vehicle.complete + + if to_run['ALIncome']: + to_show['ALIncome'] + al_income.source = "Money Stash" al_income.complete - - if to_run['ALIncomeList'] or to_run['all']: - income_sources - al_income_list.gathered # get which sources in the list to use - get_alincome_list_item_values - - if to_run['ALJob'] or to_run['all']: - al_job.source = 'unitemized flight attendant' + + if to_run['ALJob']: + to_show['ALJob'] al_job.complete - if to_run['ALJobList'] or to_run['all']: - al_job_list.gather() + if to_run['ALItemizedJob']: + to_show['ALItemizedJob'] + itemized_job.complete - if to_run['ALAsset'] or to_run['all']: - al_asset.source = 'real estate' - al_asset.complete + if to_run['ALSimpleValueList']: + to_show['ALSimpleValueList'] + al_value_list.gather() - if to_run['ALAssetList'] or to_run['all']: + if to_run['ALAssetList']: + to_show['ALAssetList'] al_asset_list.gather() - if to_run['ALVehicle'] or to_run['all']: - al_vehicle.complete - - if to_run['ALVehicleList'] or to_run['all']: + if to_run['ALVehicleList']: + to_show['ALVehicleList'] al_vehicle_list.gather() - if to_run['ALSimpleValue'] or to_run['all']: - al_simple_value.value + if to_run['ALIncomeList']: + to_show['ALIncomeList'] + al_income_list.gathered # get which sources in the list to use - if to_run['ALSimpleValueList'] or to_run['all']: - al_value_list.gather() + if to_run['ALExpenseList']: + to_show['ALExpenseList'] + al_expense_list.gathered + + if to_run['ALJobList']: + to_show['ALJobList'] + al_job_list.gather() + + if to_run['ALItemizedJobList']: + to_show['ALItemizedJobList'] + itemized_job_list.gather() - before_end_so_answers_are_saved end --- -code: | - al_income_list.there_are_any = income_sources.any_true - al_income_list.target_number = len(income_sources.true_values()) ---- -# UNIQUE CODE FOR ALIncomeList ---- -code: | - if income_sources.any_true(): - for source in income_sources.true_values(): - one_income = al_income_list.appendObject(source=source) - # Never put anything else in here - al_income_list.gathered = True ---- -code: | - for income in al_income_list.elements: - income.complete - get_alincome_list_item_values = True +question: ${ i } +subquestion: | + The next screens will demonstrate how a ${ i } will appear in an interview. + + % if i == "ALIncome": + This income's source will be automatically set to "Money Stash". This info is + usually chosen from a selection when the income is part of an ALIncomeList. + % endif + +continue button field: to_show[i] --- id: which to test # TODO: Accumulate them all in one ALIncomeList at the end to test the ability of ALIncomeList to run its functions appropriately on all types of values (other than ALIncomeLists themselves) -# TODO: Check these: Does ALAssetList cover all of ALIncomeList? Does ALAsset cover all of ALIncome? question: | - Which functionality do you want to try? + ALIncome Demo subquestion: | - Before choosing what to test: - - * `ALItemizedJobList` covers all the same functionality as `ALItemizedJob`. - * `ALIncomeList` covers all the same functionality as `ALIncome`. - * `ALAsset` covers all the same functionality as `ALIncome`. - * `ALAssetList` covers all the same functionality as `ALAsset`, `ALIncomeList`, `ALIncome`. - * `ALJob` covers all the same functionality as `ALIncome`. - * `ALJobList` covers all the same functionality as `ALJob` and `ALIncome`. - * `ALAsset` covers all the same functionality as `ALIncome`. - * `ALAssetList` covers all the same functionality as `ALAsset` and `ALIncome`. - * `ALVehicleList` covers all the same functionality as `ALVehicle`. - * `ALSimpleValueList` covers all the same functionality as `ALSimpleValue`. - - Each item will only demo its own capabilities and those of the classes from which it inherits. It does not show additional features that you can add. For example, an individual ALAsset won't ask for a market value as it has no methods for calculating a market value. An ALAssetList, though, will demo summing up market values using the filters it has in its method. - + This interview lets you try out functionalities from the `al_income` module. You can also + look at the [Massachusetts fee waiver supplement](https://github.com/SuffolkLITLab/docassemble-ALAffidavitOfIndigency) + for other examples using the `al_income` module. fields: - - no label: to_run + - "What functionality do you want to try?": to_run datatype: checkboxes none of the above: False - choices: - - All of the them: all - - ALItemizedJob: ALItemizedJob - - ALItemizedJobList: ALItemizedJobList - - ALIncome: ALIncome - - ALIncomeList: ALIncomeList - - ALJob: ALJob - - ALJobList: ALJobList - - ALAsset: ALAsset - - ALAssetList: ALAssetList - - ALVehicle: ALVehicle - - ALVehicleList: ALVehicleList - - ALSimpleValue: ALSimpleValue - - ALSimpleValueList: ALSimpleValueList - #- times_per_year_list: times_per_year_list - #- times_per_year: times_per_year - #- recent_years: recent_years - #- asset_source_list: asset_source_list - #- income_source_list: income_source_list - #- non_wage_income_list: non_wage_income_list + code: | + func_ordered +--- +variable name: functionality_list +data: !!omap + - ALSimpleValue: ALSimpleValue + - ALAsset: ALAsset + - ALVehicle: ALVehicle + - ALIncome: ALIncome + - ALJob: ALJob + - ALItemizedJob: ALItemizedJob + - ALSimpleValueList: ALSimpleValueList + - ALAssetList: ALAssetList + - ALVehicleList: ALVehicleList + - ALIncomeList: ALIncomeList + - ALExpenseList: ALExpenseList + - ALJobList: ALJobList + - ALItemizedJobList: ALItemizedJobList +--- +objects: + - func_ordered: DAOrderedDict.using(elements=functionality_list, auto_gather=False, gathered=True) +--- +if: url_args.get('use_feature') +code: | + to_run = DADict('to_run') + for key in functionality_list: + to_run[functionality_list[key]] = False + to_run[url_args.get('use_feature')] = True --- comment: | Notes on itemized job items: @@ -155,11 +157,6 @@ comment: | --- # ENDING SCREENS --- -id: before_end_so_answers_are_saved -question: | - Avoid re-entering the answers during development -continue button field: before_end_so_answers_are_saved ---- id: end event: end question: | @@ -168,8 +165,8 @@ buttons: - Restart: restart subquestion: | - % if to_run['ALItemizedJob'] or to_run['all']: - ## Single ALItemizedJob: ${ itemized_job.name } + % if to_run['ALItemizedJob']: + ## Single ALItemizedJob: ${ itemized_job.source } Job's `.to_add` as a string: ${ itemized_job.to_add } @@ -189,7 +186,7 @@ subquestion: | % endif - % if to_run['ALItemizedJobList'] or to_run['all']: + % if to_run['ALItemizedJobList']: ### Itemized jobs list #### All jobs @@ -225,7 +222,7 @@ subquestion: | % for one_itemized_job in itemized_job_list: - #### ${ one_itemized_job.name } + #### ${ one_itemized_job.source } ${ one_itemized_job.output } @@ -235,7 +232,7 @@ subquestion: | % endif - % if to_run['ALIncome'] or to_run['all']: + % if to_run['ALIncome']: ### A single ALIncome: Plain `al_income` in Mako: ${ al_income } @@ -248,7 +245,7 @@ subquestion: | % endif - % if to_run['ALIncomeList'] or to_run['all']: + % if to_run['ALIncomeList']: ### ALIncomeList **Sources:** ${ al_income_list.sources() }[BR] @@ -266,7 +263,7 @@ subquestion: | % endif - % if to_run['ALJob'] or to_run['all']: + % if to_run['ALJob']: ### Single ALJob: ${ al_job.source } @@ -276,7 +273,7 @@ subquestion: | % endif - % if to_run['ALJobList'] or to_run['all']: + % if to_run['ALJobList']: ### ALJobList Calculation Type | Frequency | Total @@ -299,7 +296,7 @@ subquestion: | % endif - % if to_run['ALAsset'] or to_run['all']: + % if to_run['ALAsset']: ### A single ALAsset: Source | Owner | Period | Total @@ -310,7 +307,7 @@ subquestion: | % endif - % if to_run['ALAssetList'] or to_run['all']: + % if to_run['ALAssetList']: ### ALAssetList: **Sources:** ${ al_asset_list.sources() }[BR] @@ -341,7 +338,7 @@ subquestion: | % endif - % if to_run['ALVehicle'] or to_run['all']: + % if to_run['ALVehicle']: ### A single ALVehicle: Owner | Year/Make/Model | Current market value @@ -352,7 +349,7 @@ subquestion: | % endif - % if to_run['ALVehicleList'] or to_run['all']: + % if to_run['ALVehicleList']: ### ALVehicleList: ${ al_vehicle_list.output } @@ -361,7 +358,7 @@ subquestion: | % endif - % if to_run['ALSimpleValue'] or to_run['all']: + % if to_run['ALSimpleValue']: ### A single ALSimpleValue An ALSimpleValue is not a periodic value. Its `.total()` is negative if its `transaction_type` is an "expense", otherwise it's positive. @@ -373,13 +370,22 @@ subquestion: | % endif - % if to_run['ALSimpleValueList'] or to_run['all']: + % if to_run['ALSimpleValueList']: ### ALSimpleValueList ${ al_value_list.output } --- - + + % endif + + % if to_run['ALExpenseList']: + ### ALExpenseList + + ${ al_expense_list.output } + + --- + % endif --- # OUTPUT TEMPLATES @@ -403,11 +409,11 @@ content: | Name | Calculation type | Frequency | Gross amount - | - | - | - - % for name, item in x.to_add.complete_elements().items(): - ${ job_items_names.get(name, name) } | Gross | Monthly | ${ currency(x.gross_total( source=name, times_per_year=12 )) } + % for source, item in x.to_add.complete_elements().items(): + ${ job_items_names.get(source, source) } | Gross | Monthly | ${ currency(x.gross_total( source=source, times_per_year=12 )) } % endfor - % for name, item in x.to_subtract.complete_elements().items(): - ${ job_items_names.get(name, name) } | Net | Monthly | ${ currency(x.net_total( source=name, times_per_year=12 )) } + % for source, item in x.to_subtract.complete_elements().items(): + ${ job_items_names.get(source, source) } | Net | Monthly | ${ currency(x.net_total( source=source, times_per_year=12 )) } % endfor **Hours for hourly job:** @@ -416,7 +422,7 @@ content: | * ${ x.normalized_hours() } hours per year * ${ x.normalized_hours(times_per_year=52) } hours per week % else: - **${ x.name }** is not hourly + **${ x.source }** is not hourly % endif --- generic object: ALIncome @@ -469,7 +475,7 @@ template: x.output content: | * **sources:** ${ x.sources() } * **total:** ${ currency(x.total()) } - * **total of sources called "cash":** ${ currency(x.total(source=['cash'])) } + * **total of sources called "cash":** ${ currency(x.total(source='cash')) } An item's `.total()` results are negative if their `transaction_type` is an "expense", otherwise they're positive. @@ -481,4 +487,20 @@ content: | ${ val.source } | As string | ${ val } ${ val.source } | Total currency | ${ currency(val.total()) } % endfor ---- \ No newline at end of file +--- +generic object: ALExpenseList +template: x.output +content: | + * **sources:** ${ x.sources() } + * **total:** ${ currency(x.total()) } + * **monthly total:** ${ currency(x.total(times_per_year=12)) } + * **total of sources called "rent":** ${ currency(x.total(source='rent')) } + + **Expenses:** + + Source | Functionality | Value + - | - | - + % for exp in x: + ${ exp.source } | As string | ${ exp } + ${ exp.source } | Total cost | ${ currency(exp.total()) } + % endfor \ No newline at end of file diff --git a/setup.py b/setup.py index aa027c8a..13e9da74 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d return out setup(name='docassemble.ALToolbox', - version='0.7.0', + version='0.7.1', description=('Collection of small utility functions, classes, and web components for Docassemble interviews'), long_description='# ALToolbox\r\n\r\nThis repository is used to host small Python modules, widgets, and JavaScript web components js files that enhance Docassemble interviews. These modules were\r\nbuilt as part of the Suffolk University Law School LIT Lab\'s [Document Assembly Line project](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/).\r\nThey are placed here\r\nrather than in https://github.com/SuffolkLitLab/docassemble-AssemblyLine because we believe these small components can easily be used\r\nby anyone, regardless of whether they use any other code from the Document Assembly Line project.\r\n\r\nIf you want to add a small fuction to this project, consider adding it to the existing misc.py to avoid creating too many module files.\r\n\r\n## Documentation\r\n\r\nRead the [documentation for the functions and components](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/framework/altoolbox) to learn\r\nhow to use these components in your own [Docassemble](https://github.com/jhpyle/docassemble) projects.\r\n\r\n## Suffolk LIT Lab Document Assembly Line\r\n\r\ndrawing\r\n\r\nThe Assembly Line Project is a collection of volunteers, students, and institutions who joined together\r\nduring the COVID-19 pandemic to help increase access to the court system. Our vision is mobile-friendly,\r\neasy to use **guided** online forms that help empower litigants to access the court remotely.\r\n\r\nOur signature project is [CourtFormsOnline.org](https://courtformsonline.org).\r\n\r\nWe designed a step-by-step, assembly line style process for automating court forms on top of Docassemble\r\nand built several tools along the way that **you** can use in your home jurisdiction.\r\n\r\nThis package contains **runtime code** and **pre-written questions** to support authoring robust, \r\nconsistent, and attractive Docassemble interviews that help complete court forms.\r\n\r\nRead more on our [documentation page](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/).\r\n\r\n\r\n# Related repositories\r\n\r\n* https://github.com/SuffolkLitLab/docassemble-AssemblyLine\r\n* https://github.com/SuffolkLitLab/docassemble-ALWeaver\r\n* https://github.com/SuffolkLitLab/docassemble-ALMassachusetts\r\n* https://github.com/SuffolkLitLab/docassemble-MassAccess\r\n* https://github.com/SuffolkLitLab/docassemble-ALGenericJurisdiction\r\n* https://github.com/SuffolkLitLab/EfileProxyServer\r\n\r\n## Contributors: \r\n* @plocket \r\n* @nonprofittechy\r\n* @purplesky2016\r\n* @brycestevenwilley\r\n', long_description_content_type='text/markdown',