def test_compile_nested(self): """Expand a Rule into actionable steps""" rule = yaml.load(""" match: narration: AirBnB(.*) match-narration: - Air BnB(.*) - BnB Payments(.*) set-account: Income:AirBnB set-tags: passive-income """, Loader=yaml.FullLoader) r = Rule(rule) r.add_directives(rule) expected_rule = MatchRule( directive='transaction', parameter='narration', meta_key=None, regular_expressions={ re.compile("AirBnB(.*)"), re.compile("Air BnB(.*)"), re.compile("BnB Payments(.*)") } ) self.assertEqual( {'transaction-narration': expected_rule}, r.match_requirements )
def test_match_1(self): entry = parser.parse_one(""" 2020-04-08 ! "AMZN Mktp US*L08746BB3" match-key: "2020040824692160098100992944500" ofx-type: "DEBIT" * Liabilities:CreditCard:Chase:Amazon -39.98 USD ! Expenses:FIXME 39.98 USD """) assert entry.flag == "!" ruler = Rule({ 'match-narration': "AMZN Mktp *.", 'set-account': 'Expenses:Shopping' }) result = ruler.check(entry) self.assertIsNotNone(result) new_entry = ruler.modify_entry(entry, result) self.assertEqual(new_entry.flag, '*')
def test_add_rule_meta_acct(self): rules = yaml.load(""" - match-narration: E*TRADE DES:ACH.* match-account: Assets:Banking:.* set-posting-account: Assets:Transfer set-posting-account-meta-account: Assets:Banking:ETrade:Cash """, Loader=yaml.FullLoader) for rule in rules: Rule(rule)
def rule_from_meta(entry: data.Transaction) -> Rule: """We use the Entry as a template to the Rule Copy the Narration, Payee, Tags, Expense Account etc. """ rs = {} if entry.tags: rs['set-tags'] = entry.tags if entry.payee: rs['set-payee'] = entry.payee for posting in entry.postings: if posting.account.startswith('Expenses'): rs['set-posting-account'] = posting.account for k, v in entry.meta.items(): if k.startswith('match-') or k.startswith('set-'): rs[k] = v r = Rule(rs) logger.info(f"Created a Fancy Rule: {rs} -> {repr(r)}") return r
def test_compile_basic(self): """Expand a Rule into actionable steps""" rule = yaml.load(""" match: narration: AirBnB(.*) """, Loader=yaml.FullLoader) r = Rule(rule) expected_rule = MatchRule( directive='transaction', parameter='narration', meta_key=None, regular_expressions={ re.compile("AirBnB(.*)") } ) self.assertEqual( {'transaction-narration': expected_rule}, r.match_requirements )
def match_directives(entries, options_map, *args): """Modify any entries that Match existing Rules""" MATCH_CHECK.clear() rules = [] # Make sure to have run the apply_coolbeans_settings_plugin settings = options_map['coolbeans'] # Load a rules.yaml type file if 'rules-file' in settings: # We support multiple Rules Files for file_path in settings['rules-file']: file = pathlib.Path(file_path) if not file.exists(): logger.warning(f"Unable to find Rules File {file}") continue # Read the YAML file with file.open("r") as stream: new_rules = yaml.full_load(stream) for rule in new_rules: rules.append(Rule(rule)) output_file = settings.get('output-file', ['matched.bean'])[0] output_file = pathlib.Path(output_file) new_entries = [] mod_entries = [] no_match_entries = [] possible_rules = {} # Now, see what we can actually Match: for entry in entries: # We're only interested in Pending Entries if getattr(entry, 'flag', None) != '!': # Pass through to new_entries new_entries.append(entry) continue # Check against all the Rules: modified = False for rule in rules: match_values = rule.check(entry) if entry.narration.lower().startswith('airbnb') and match_values: logger.info( f"{entry.narration.lower()}: {rule.match_requirements}: {match_values}" ) if match_values is None: continue entry = rule.modify_entry(entry, match_values) modified = True # Always pass it to our output stream new_entries.append(entry) if modified: mod_entries.append(entry) else: no_match_entries.append(entry) # We update the "suggestions" file dcontext = DisplayContext() dcontext.set_commas(True) with output_file.open("w") as outstream: try: print_entries(mod_entries, file=outstream, dcontext=dcontext) logger.info( f"cached: wrote {len(mod_entries)} entries to {output_file}") except Exception as exc: logger.exception('while printing entries. ') try: for entry in mod_entries: print_entries([entry], file=sys.stderr) except Exception: logger.exception(f"{entry}") raise return new_entries, []
def add_rules(self, rules: List[dict]): for rule_dict in rules: self.rules.append(Rule(rule_dict))