Advertisement
Guest User

Untitled

a guest
Jun 8th, 2019
133
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scheme 4.81 KB | None | 0 0
  1. (use-modules (srfi srfi-1))
  2.  
  3. (define FITID-WEIGHT 10000)             ;; fitid match carry highest wt
  4.  
  5. (define DATE-WEIGHT 500)                ;; a perfect date match score
  6. (define DATE-TOLERANCE (* 60 60 24 7))  ;; higher = more tolerant
  7.  
  8. (define AMOUNT-WEIGHT 500)              ;; perfect amount match score
  9. (define AMOUNT-TOLERANCE 1)
  10.  
  11. (define WORDS-WEIGHT 100)               ;; each word match score
  12.  
  13. (define MATCH-THRESHOLD 800)            ;; below threshold --> unmatched
  14.  
  15. ;; fake-gnucash API follows
  16. (define (dmy d m y)
  17.   (let ((zdate (localtime (current-time))))
  18.     (set-tm:year zdate (- y 1900))
  19.     (set-tm:mon zdate (1- m))
  20.     (set-tm:mday zdate d)
  21.     (car (mktime zdate))))
  22.  
  23. ;; fake-book. list-items are:
  24. ;; date, desc, memo, amount, online_id
  25. (define register-txns
  26.   (list
  27.    (list (dmy 31 12 2018) "EOY Bonus" "" 400 "001")
  28.    (list (dmy 01 01 2019) "Bought $100 of groceries ZMART" "" -100 "")
  29.    (list (dmy 04 01 2019) "Send $120 to wife" "" -120 "")
  30.    (list (dmy 20 01 2019) "Mortgage Payment" "" -3000 "")
  31.    (list (dmy 02 01 2019) "Income of $200" "" 200 "")))
  32.  
  33. ;; fake-ofx. list items are:
  34. ;; date, desc, memo, amount, fitid
  35. ;; note the first item has already been imported; see FitID matches above
  36. (define ofx-txns
  37.   (list
  38.    (list (dmy 01 01 2019) "BONUS" "" 400 "001")
  39.    (list (dmy 01 01 2019) "BANK FEE" "" -5 "002")
  40.    (list (dmy 02 01 2019) "EMPLOYER" "" 200 "003")
  41.    (list (dmy 03 01 2019) "ZMART" "*VISADEBIT" -100 "004")
  42.    (list (dmy 07 01 2019) "ZMART" "*VISADEBIT" -150 "005")
  43.    (list (dmy 18 01 2019) "Mortgage" "Regular" -3000 "006")))
  44.  
  45.  
  46. (define xaccTransGetDate car)
  47. (define xaccTransGetDescription cadr)
  48. (define xaccSplitGetMemo caddr)
  49. (define xaccSplitGetAmount cadddr)
  50. (define xaccTransGetOnlineID (compose cadddr cdr))
  51. (define xaccSplitGetParent identity)
  52.  
  53. (define ofxDate car)
  54. (define ofxDesc cadr)
  55. (define ofxMemo caddr)
  56. (define ofxAmount cadddr)
  57. (define ofxFitID (compose cadddr cdr))
  58.  
  59. ;; gaussian formula y(x)=exp(−(x/a)^2), returns 0..1
  60. ;; a should be in similar magnitude as x i.e. spread
  61. (define (gaussian x a)
  62.   (exp (- (/ (* x x) (* a a)))))
  63.  
  64. ;; counts how many tokens are same; duplicates are removed.
  65. ;; (count-equal? '("A" "B" "C" "B") '("B" "A" "Z")) should output 2.
  66. (define (count-equal A B)
  67.   (define lst1 (delete-duplicates A))
  68.   (define lst2 (delete-duplicates B))
  69.   (let lp ((lst1 lst1) (count 0))
  70.     (cond
  71.      ((null? lst1) count)
  72.      ((member (car lst1) lst2) (lp (cdr lst1) (1+ count)))
  73.      (else (lp (cdr lst1) count)))))
  74.  
  75. ;; "A Long Str!ing2Parsing" -> '("A" "LONG" "STR" "ING" "PARSING")
  76. (define (str->tokens str)
  77.   (filter (negate string-null?)
  78.           (string-split (string-upcase str) (negate char-alphabetic?))))
  79.  
  80. (define (match-score ofx-txn reg-txn)
  81.   (let* ((fitid-match? (equal? (xaccTransGetOnlineID reg-txn) (ofxFitID ofx-txn)))
  82.          (amt-diff (- (xaccSplitGetAmount (xaccSplitGetParent reg-txn))
  83.                       (ofxAmount ofx-txn)))
  84.          (words-match (count-equal (str->tokens (xaccTransGetDescription reg-txn))
  85.                                    (str->tokens (ofxDesc ofx-txn))))
  86.          (date-diff (- (xaccTransGetDate reg-txn) (ofxDate ofx-txn))))
  87.     (+ (if fitid-match? FITID-WEIGHT 0)
  88.        (* (gaussian amt-diff AMOUNT-TOLERANCE) AMOUNT-WEIGHT)
  89.        (* words-match WORDS-WEIGHT)
  90.        (* (gaussian date-diff DATE-TOLERANCE) DATE-WEIGHT))))
  91.  
  92. ;; algorithm follows:
  93. (define scores-list '())
  94.  
  95. ;; populate scores-list with num(ofx-txn) * num(reg-txn) scores
  96. (for-each
  97.  (lambda (A)
  98.    (for-each
  99.     (lambda (B)
  100.       (set! scores-list
  101.         (cons (list A B (match-score A B)) scores-list)))
  102.     register-txns))
  103.  ofx-txns)
  104.  
  105. ;; scores-list = (list ofx-split reg-split score), just generated
  106. ;; outputs = unique match-list with highest scores, and unmatched ofx
  107. ;; and reg txns
  108. (let loop ((scores-list (stable-sort! scores-list
  109.                                       (lambda (a b) (> (caddr a) (caddr b)))))
  110.            (lone-ofx '())
  111.            (lone-reg '())
  112.            (matched-list '()))
  113.   (cond
  114.    ((null? scores-list)
  115.     (pk "completed. matches are")
  116.     (for-each pk (stable-sort matched-list (lambda (a b) (< (caar a) (caar b)))))
  117.     (pk "lone ofx, must be added new:")
  118.     (for-each pk lone-ofx)
  119.     (pk "lone reg, may be mistakes in register:")
  120.     (for-each pk lone-reg))
  121.  
  122.    ((< (caddar scores-list) MATCH-THRESHOLD)
  123.     (loop '()
  124.           (delete-duplicates! (map car scores-list))
  125.           (delete-duplicates! (map cadr scores-list))
  126.           matched-list))
  127.  
  128.    (else
  129.     (let ((m (car scores-list)))
  130.       (loop (filter
  131.              (lambda (elt)
  132.                (not (or (equal? (car elt) (car m))
  133.                         (equal? (cadr elt) (cadr m)))))
  134.              scores-list)
  135.             lone-ofx
  136.             lone-reg
  137.             (cons m matched-list))))))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement