A script to convert CSV exported from Sparebanken Sør to a format YNAB can import
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

120 lignes
3.2KB

  1. #!/bin/python3
  2. import sys
  3. import csv
  4. import re
  5. from pathlib import Path
  6. whitelist_cards = ["7756"]
  7. in_header = ['Utført dato', 'Bokført dato', 'Rentedato', 'Beskrivelse', 'Type', 'Undertype', 'Fra konto', 'Avsender', 'Til konto', 'Mottakernavn', 'Beløp inn', 'Beløp ut', 'Valuta', 'Status', 'Melding']
  8. out_header = ['Date', 'Payee', 'Memo', 'Outflow', 'Inflow']
  9. def usage():
  10. print('Usage: ynab.py <filename>')
  11. def is_reserved(row):
  12. return row['Status'] == "Reservert"
  13. def convert_memo(original):
  14. original = original.replace(" Kurs: 1.0000", "")
  15. words = original.split(" ")
  16. for i in range(20):
  17. if words[0] == "":
  18. # It's empty
  19. del words[0]
  20. elif m := re.match(r'\*(\d{4})', words[0]):
  21. # It's the last four digits of a card
  22. if m.groups()[0] in whitelist_cards:
  23. # It's an expected card, ignore it
  24. del words[0]
  25. else:
  26. # It's an unexpected card, move it to the end
  27. words.append(words.pop(0))
  28. elif m := re.match(r'\d{2}\.\d{2}', words[0]):
  29. # It's the date. Move it to the end
  30. words.append(words.pop(0))
  31. elif (m1 := re.match(r'^[A-Z]{3}$', words[0])) and (m2 := re.match(r'[\d]+\.[\d]+', words[1])):
  32. # It's the original currency
  33. if words[0] == "NOK":
  34. # It's Norwegian kroner, ignoring
  35. del words[0]
  36. del words[0]
  37. else:
  38. # It's some other currency, move it to the end
  39. words.append(words.pop(0))
  40. words.append(words.pop(0))
  41. else:
  42. break
  43. else:
  44. raise Exception(f"Infinite loop while parsing \"{original}\"")
  45. return " ".join(words)
  46. def convert_row(row):
  47. if is_reserved(row):
  48. return None
  49. if row['Valuta'] != 'NOK':
  50. raise ValueError(f"Unknown currency {row['Valuta']}")
  51. return [
  52. row['Bokført dato'],
  53. row['Beskrivelse'],
  54. convert_memo(row['Melding']),
  55. -float(row['Beløp ut'] or '0'),
  56. float(row['Beløp inn'] or '0'),
  57. ]
  58. def convert(reader, writer):
  59. writer.writerow(out_header)
  60. # Ignore header
  61. reader.__next__()
  62. for raw_row in reader:
  63. # Stop when we hit an empty row
  64. for field in raw_row:
  65. if field:
  66. break
  67. else:
  68. break
  69. # Create dictionary with proper field names
  70. row = {x[0]: x[1] for x in zip(in_header, raw_row)}
  71. try:
  72. if (result := convert_row(row)) is not None:
  73. writer.writerow(result)
  74. except Exception as e:
  75. print(f'Error in row {row}')
  76. raise e
  77. def main():
  78. if len(sys.argv) != 2:
  79. usage()
  80. exit(1)
  81. filepath = Path(sys.argv[1])
  82. new_basename = f'ynab-{filepath.name}'
  83. new_filepath = filepath.parent / new_basename
  84. with filepath.open(mode='r', encoding='latin1') as old_file:
  85. reader = csv.reader(old_file, delimiter=';')
  86. with new_filepath.open(mode='w') as new_file:
  87. writer = csv.writer(new_file)
  88. convert(reader, writer)
  89. if __name__ == '__main__':
  90. main()