You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
3.9KB

  1. package main
  2. import (
  3. "biukop.com/sfm/loan"
  4. "encoding/json"
  5. "errors"
  6. log "github.com/sirupsen/logrus"
  7. "io/ioutil"
  8. "os"
  9. "os/exec"
  10. "strings"
  11. )
  12. type AiDecodeIncome struct {
  13. Id int64
  14. Input loan.Uploads
  15. ul uploadsOnDisk // internal data
  16. Mime string // mime actually detected.
  17. PayIn []loan.PayIn // generated PayIn Info
  18. DecodeIncomeDetails
  19. }
  20. type DecodeIncomeDetails struct {
  21. Lender loan.LenderType
  22. AAA []PayInAAARow
  23. Connective []ConnectiveRow
  24. }
  25. type AiDecodeJson struct {
  26. Version string
  27. V1 DecodeIncomeDetails
  28. }
  29. func (m *AiDecodeIncome) decodeUploadToPayIn(ulMeta loan.Uploads, allowCachedResult bool) (e error) {
  30. m.Id = ulMeta.Id
  31. m.Input = ulMeta
  32. m.ul.Upload = ulMeta
  33. m.PayIn = make([]loan.PayIn, 0, 100) // finalized payIns, generated
  34. if allowCachedResult {
  35. e = m.ReadJson()
  36. if e == nil {
  37. return // already decoded
  38. } else {
  39. log.Warn("trying to read existing json failed ", e.Error())
  40. e = nil
  41. }
  42. }
  43. m.PayIn = make([]loan.PayIn, 0, 10)
  44. switch m.getFileType() {
  45. case "pdf":
  46. e = m.decodePdf()
  47. break
  48. case "excel", "opensheet":
  49. e = m.decodeXls()
  50. break
  51. default:
  52. e = errors.New("unknown format")
  53. }
  54. if e == nil {
  55. eJson := m.WriteJson()
  56. if eJson != nil {
  57. log.Error("failed to write analysis to json", eJson.Error())
  58. }
  59. }
  60. return
  61. }
  62. func (m *AiDecodeIncome) ReadJson() (e error) {
  63. if !fileExists(m.ul.jsonPath()) {
  64. return errors.New(m.ul.jsonPath() + " not found")
  65. }
  66. f, e := os.Open(m.ul.jsonPath())
  67. if e != nil {
  68. return
  69. }
  70. decoder := json.NewDecoder(f)
  71. data := AiDecodeJson{}
  72. e = decoder.Decode(&data)
  73. if e != nil {
  74. log.Error("failed load existing decode json", e.Error())
  75. return
  76. }
  77. return
  78. }
  79. func (m *AiDecodeIncome) WriteJson() (e error) {
  80. b, e := json.Marshal(m.DecodeIncomeDetails)
  81. if e != nil {
  82. return
  83. }
  84. ioutil.WriteFile(m.ul.jsonPath(), b, 0644)
  85. return
  86. }
  87. func (m *AiDecodeIncome) getFileType() (ret string) {
  88. strMime, e := GetFileContentType(m.ul.filePath())
  89. if e != nil {
  90. return
  91. }
  92. m.Mime = strMime
  93. ret, e = m.ul.GetFileType()
  94. if e != nil {
  95. ret = ""
  96. }
  97. return
  98. }
  99. func (m *AiDecodeIncome) decodePdf() (e error) {
  100. cmd := exec.Command("pdftotext", "-layout", m.ul.filePath(), "-")
  101. out, e := cmd.Output()
  102. if e != nil {
  103. log.Error("cannot convert pdf to text ", e)
  104. }
  105. raw := string(out)
  106. switch m.detectFunder(raw) {
  107. case loan.Lender_AAA:
  108. m.Lender = loan.Lender_AAA
  109. e = m.decodeAAAPdf(raw)
  110. // regardless of error, we pump in all available row successed so far
  111. for _, row := range m.AAA {
  112. pi := loan.PayIn{}
  113. pi.Id = 0
  114. pi.Ts = row.Period
  115. pi.Amount = row.LoanFacility
  116. pi.Lender = loan.Lender_AAA
  117. pi.Settlement = row.Settlement
  118. pi.Balance = row.Balance
  119. pi.OffsetBalance = -1
  120. pi.IncomeAmount = row.InTrail
  121. pi.IncomeType = "Trail"
  122. m.PayIn = append(m.PayIn, pi)
  123. }
  124. // log.Println("AAA final result", m.AAA)
  125. break
  126. case loan.Lender_Connective:
  127. m.Lender = loan.Lender_Connective
  128. e = m.decodeConnectivePdf(out)
  129. break
  130. case loan.Lender_Unknown:
  131. e = errors.New(loan.Lender_Unknown)
  132. break // not able to detect Funder
  133. }
  134. return
  135. }
  136. func (m *AiDecodeIncome) decodeXls() (e error) {
  137. e = errors.New("not implemented yet")
  138. return
  139. }
  140. func (m *AiDecodeIncome) detectFunder(raw string) loan.LenderType {
  141. if m.isAAA(raw) {
  142. return loan.Lender_AAA
  143. }
  144. if m.isConnective(raw) {
  145. return loan.Lender_Connective
  146. }
  147. return loan.Lender_Unknown
  148. }
  149. func (m *AiDecodeIncome) isAAA(raw string) bool {
  150. keyword := "AAA Financial Trail Report"
  151. lines := strings.Split(raw, "\n")
  152. return m.checkFunderKeyword(keyword, lines, 0, 3)
  153. }
  154. func (m *AiDecodeIncome) isConnective(raw string) bool {
  155. keyword := "connective.com.au"
  156. lines := strings.Split(raw, "\n")
  157. return m.checkFunderKeyword(keyword, lines, 0, 3)
  158. }
  159. func (m *AiDecodeIncome) checkFunderKeyword(keyword string, lines []string, start int, end int) bool {
  160. for _, line := range lines {
  161. // as long as it has it
  162. if strings.Contains(line, keyword) {
  163. return true
  164. }
  165. }
  166. return false
  167. }