Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

231 line
5.2KB

  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. ResimacXls []ResimacRow
  25. ResimacPdf []ResimacPdf
  26. }
  27. type AiDecodeJson struct {
  28. Version string
  29. V1 DecodeIncomeDetails
  30. }
  31. func (m *AiDecodeIncome) decodeUploadToPayIn(ulMeta loan.Uploads, allowCachedResult bool) (e error) {
  32. m.Id = ulMeta.Id
  33. m.Input = ulMeta
  34. m.ul.Upload = ulMeta
  35. m.PayIn = make([]loan.PayIn, 0, 100) // finalized payIns, generated
  36. if allowCachedResult {
  37. e = m.ReadJson()
  38. if e == nil {
  39. return // already decoded
  40. } else {
  41. log.Warn("trying to read existing json failed ", e.Error())
  42. e = nil
  43. }
  44. }
  45. m.PayIn = make([]loan.PayIn, 0, 10)
  46. switch m.getFileType() {
  47. case "pdf":
  48. e = m.decodePdf()
  49. break
  50. case "excel", "opensheet":
  51. e = m.decodeXls()
  52. break
  53. default:
  54. e = errors.New("unknown format")
  55. }
  56. if e == nil {
  57. eJson := m.WriteJson()
  58. if eJson != nil {
  59. log.Error("failed to write analysis to json", eJson.Error())
  60. }
  61. }
  62. return
  63. }
  64. func (m *AiDecodeIncome) ReadJson() (e error) {
  65. if !fileExists(m.ul.jsonPath()) {
  66. return errors.New(m.ul.jsonPath() + " not found")
  67. }
  68. f, e := os.Open(m.ul.jsonPath())
  69. if e != nil {
  70. return
  71. }
  72. decoder := json.NewDecoder(f)
  73. data := AiDecodeJson{}
  74. e = decoder.Decode(&data)
  75. if e != nil {
  76. log.Error("failed load existing decode json", e.Error())
  77. return
  78. }
  79. return
  80. }
  81. func (m *AiDecodeIncome) WriteJson() (e error) {
  82. b, e := json.Marshal(m.DecodeIncomeDetails)
  83. if e != nil {
  84. return
  85. }
  86. ioutil.WriteFile(m.ul.jsonPath(), b, 0644)
  87. return
  88. }
  89. func (m *AiDecodeIncome) getFileType() (ret string) {
  90. strMime, e := GetFileContentType(m.ul.filePath())
  91. if e != nil {
  92. return
  93. }
  94. m.Mime = strMime
  95. ret, e = m.ul.GetFileType()
  96. if e != nil {
  97. ret = ""
  98. }
  99. return
  100. }
  101. func (m *AiDecodeIncome) decodePdf() (e error) {
  102. cmd := exec.Command("pdftotext", "-layout", m.ul.filePath(), "-")
  103. out, e := cmd.Output()
  104. if e != nil {
  105. log.Error("cannot convert pdf to text ", e)
  106. }
  107. raw := string(out)
  108. switch m.detectFunder(raw) {
  109. case loan.Lender_AAA:
  110. m.Lender = loan.Lender_AAA
  111. e = m.decodeAAAPdf(raw)
  112. // regardless of error, we pump in all available row successed so far
  113. for _, row := range m.AAA {
  114. pi := loan.PayIn{}
  115. pi.Id = 0
  116. pi.Ts = row.Period
  117. pi.Amount = row.LoanFacility
  118. pi.Lender = loan.Lender_AAA
  119. pi.Settlement = row.Settlement
  120. pi.Balance = row.Balance
  121. pi.OffsetBalance = -1
  122. pi.IncomeAmount = row.InTrail
  123. pi.IncomeType = "Trail"
  124. m.PayIn = append(m.PayIn, pi)
  125. }
  126. // log.Println("AAA final result", m.AAA)
  127. break
  128. case loan.Lender_Connective:
  129. m.Lender = loan.Lender_Connective
  130. e = m.decodeConnectivePdf(out)
  131. break
  132. case loan.Lender_Resimac:
  133. m.Lender = loan.Lender_Resimac
  134. e = m.decodeResimacPdf(out)
  135. break
  136. case loan.Lender_Unknown:
  137. e = errors.New(loan.Lender_Unknown)
  138. break // not able to detect Funder
  139. }
  140. return
  141. }
  142. func (m *AiDecodeIncome) decodeXls() (e error) {
  143. e = m.ul.convertExcelTo("csv")
  144. if e != nil {
  145. log.Error("cannot convert xls to csv ", e.Error())
  146. }
  147. raw := string(m.ul.tmp)
  148. switch m.detectFunder(raw) {
  149. case loan.Lender_Resimac:
  150. m.Lender = loan.Lender_Resimac
  151. e = m.decodeResimacXls(m.ul.tmp)
  152. break
  153. case loan.Lender_Unknown:
  154. e = errors.New(loan.Lender_Unknown)
  155. break // not able to detect Funder
  156. }
  157. return
  158. }
  159. func (m *AiDecodeIncome) detectFunder(raw string) loan.LenderType {
  160. if m.isAAA(raw) {
  161. return loan.Lender_AAA
  162. }
  163. if m.isConnective(raw) {
  164. return loan.Lender_Connective
  165. }
  166. if m.isResimacXls(raw) {
  167. return loan.Lender_Resimac
  168. }
  169. if m.isResimacPdf(raw) {
  170. return loan.Lender_Resimac
  171. }
  172. return loan.Lender_Unknown
  173. }
  174. func (m *AiDecodeIncome) isAAA(raw string) bool {
  175. keyword := "AAA Financial Trail Report"
  176. lines := strings.Split(raw, "\n")
  177. return m.checkFunderKeyword(keyword, lines)
  178. }
  179. func (m *AiDecodeIncome) isConnective(raw string) bool {
  180. keyword := "connective.com.au"
  181. lines := strings.Split(raw, "\n")
  182. return m.checkFunderKeyword(keyword, lines)
  183. }
  184. func (m *AiDecodeIncome) isResimacXls(raw string) bool {
  185. keyword := "TRSTCD,ORGNTR,LOANNO,PORTNO,BRNAMX,LNAMT,INTRTE,DELRTE,MARGIN,SETLDX,LNBAL,MANFEE,RSINCP,RSSACT,RSSACP,SACAMT,MOFEE,MANTOT,NEWLON,REFADJ,NETNEW,FIXED,LNFORT,ORGNAM,LNEOM"
  186. lines := strings.Split(raw, "\n")
  187. //remove all spaces
  188. for i := 0; i < len(lines); i++ {
  189. lines[i] = strings.ReplaceAll(lines[i], " ", "") // remove all spaces
  190. }
  191. return m.checkFunderKeyword(keyword, lines)
  192. }
  193. func (m *AiDecodeIncome) isResimacPdf(raw string) bool {
  194. keyword := "SuperFinanceMarketsPtyLtd-8779"
  195. lines := strings.Split(raw, "\n")
  196. //remove all spaces
  197. for i := 0; i < len(lines); i++ {
  198. lines[i] = strings.ReplaceAll(lines[i], " ", "") // remove all spaces
  199. }
  200. return m.checkFunderKeyword(keyword, lines)
  201. }
  202. func (m *AiDecodeIncome) checkFunderKeyword(keyword string, lines []string) bool {
  203. for _, line := range lines {
  204. if strings.Contains(line, keyword) {
  205. return true
  206. }
  207. }
  208. return false
  209. }