Skip to content

Commit b6b3d66

Browse files
committed
Add batch check digit
1 parent 2629101 commit b6b3d66

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

api-model/src/main/scala/hmda/api/model/public/ULIModel.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package hmda.api.model.public
33
object ULIModel {
44

55
case class Loan(loanId: String)
6-
case class ULI(loanId: String, checkDigit: Int, uli: String)
6+
case class ULI(loanId: String, checkDigit: Int, uli: String) {
7+
def toCSV: String = s"$loanId,$checkDigit,$uli"
8+
}
9+
case class LoanCheckDigitResponse(loanIds: Seq[ULI])
710
case class ULICheck(uli: String)
811
case class ULIValidated(isValid: Boolean)
912
case class ULIBatchValidated(uli: String, isValid: Boolean) {

api-model/src/main/scala/hmda/api/protocol/public/ULIProtocol.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ trait ULIProtocol extends DefaultJsonProtocol {
77

88
implicit val loanFormat = jsonFormat1(Loan.apply)
99
implicit val uliFormat = jsonFormat3(ULI.apply)
10+
implicit val loanCheckDigitResponse = jsonFormat1(LoanCheckDigitResponse.apply)
1011
implicit val uliCheckFormat = jsonFormat1(ULICheck.apply)
1112
implicit val uliValidatedFormat = jsonFormat1(ULIValidated.apply)
1213
implicit val uliBatchValidatedFormat = jsonFormat2(ULIBatchValidated.apply)

api/src/main/scala/hmda/api/http/public/PublicHttpApi.scala

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import akka.stream.scaladsl.{ Sink, Source }
1616
import hmda.api.protocol.processing.ApiErrorProtocol
1717
import hmda.api.protocol.public.ULIProtocol
1818
import hmda.api.util.FlowUtils
19+
1920
import scala.concurrent.ExecutionContext
2021
import scala.util.{ Failure, Success }
2122

@@ -41,9 +42,39 @@ trait PublicHttpApi extends PublicLarHttpApi with HmdaCustomDirectives with ApiE
4142
val check = checkDigit(loanId)
4243
val uli = ULI(loanId, check.toInt, loanId + check)
4344
complete(ToResponseMarshallable(uli))
44-
}
45+
} ~
46+
fileUpload("file") {
47+
case (_, byteSource) =>
48+
val checkDigitF = processLoanIdFile(byteSource).runWith(Sink.seq)
49+
onComplete(checkDigitF) {
50+
case Success(checkDigits) => {
51+
complete(ToResponseMarshallable(LoanCheckDigitResponse(checkDigits)))
52+
}
53+
case Failure(error) =>
54+
log.error(error.getLocalizedMessage)
55+
complete(ToResponseMarshallable(StatusCodes.InternalServerError))
56+
}
57+
case _ =>
58+
complete(ToResponseMarshallable(StatusCodes.BadRequest))
59+
}
4560
}
4661
} ~
62+
path("check-digit" / "csv") {
63+
timedPost { _ =>
64+
fileUpload("file") {
65+
case (_, byteSource) =>
66+
val checkDigit = processLoanIdFile(byteSource)
67+
.map(l => l.toCSV)
68+
.map(l => l + "\n")
69+
.map(s => ByteString(s))
70+
71+
complete(HttpEntity.Chunked.fromData(`text/csv`.toContentType(HttpCharsets.`UTF-8`), checkDigit))
72+
73+
case _ =>
74+
complete(ToResponseMarshallable(StatusCodes.BadRequest))
75+
}
76+
}
77+
} ~
4778
path("validate") {
4879
timedPost { _ =>
4980
entity(as[ULICheck]) { uc =>
@@ -88,6 +119,13 @@ trait PublicHttpApi extends PublicLarHttpApi with HmdaCustomDirectives with ApiE
88119
}
89120
}
90121

122+
private def processLoanIdFile(byteSource: Source[ByteString, Any]) = {
123+
byteSource
124+
.via(framing)
125+
.map(_.utf8String)
126+
.map(loanId => ULI(loanId, checkDigit(loanId).toInt, loanId + checkDigit(loanId)))
127+
}
128+
91129
private def processUliFile(byteSource: Source[ByteString, Any]) = {
92130
byteSource
93131
.via(framing)

api/src/test/scala/hmda/api/http/public/PublicHttpApiSpec.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class PublicHttpApiSpec extends WordSpec with MustMatchers with BeforeAndAfterAl
3232
"10Bx939c5543TqA1144M999143X38\n" +
3333
"10Bx939c5543TqA1144M999133X38\n"
3434

35+
val loanTxt = "10Bx939c5543TqA1144M999143X\n" +
36+
"10Cx939c5543TqA1144M999143X"
37+
3538
"Modified LAR Http API" must {
3639
"return list of modified LARs in proper format" in {
3740
Get("/institutions/0/filings/2017/lar") ~> publicHttpRoutes ~> check {
@@ -45,6 +48,7 @@ class PublicHttpApiSpec extends WordSpec with MustMatchers with BeforeAndAfterAl
4548

4649
"ULI API" must {
4750
val uliFile = multiPartFile(uliTxt, "ulis.txt")
51+
val loanFile = multiPartFile(loanTxt, "loanIds.txt")
4852
val loanId = "10Bx939c5543TqA1144M999143X"
4953
val checkDigit = 38
5054
val uli = "10Bx939c5543TqA1144M999143X" + checkDigit
@@ -56,6 +60,23 @@ class PublicHttpApiSpec extends WordSpec with MustMatchers with BeforeAndAfterAl
5660
responseAs[ULI] mustBe ULI(loanId, checkDigit, uli)
5761
}
5862
}
63+
"return check digit and ULI from file of loan ids" in {
64+
Post("/uli/check-digit", loanFile) ~> publicHttpRoutes ~> check {
65+
status mustBe StatusCodes.OK
66+
responseAs[LoanCheckDigitResponse].loanIds mustBe Seq(
67+
ULI("10Bx939c5543TqA1144M999143X", 38, "10Bx939c5543TqA1144M999143X38"),
68+
ULI("10Cx939c5543TqA1144M999143X", 10, "10Cx939c5543TqA1144M999143X10")
69+
)
70+
}
71+
}
72+
"return csv with check digit and ULI from file of loan ids" in {
73+
Post("/uli/check-digit/csv", loanFile) ~> publicHttpRoutes ~> check {
74+
status mustBe StatusCodes.OK
75+
val csv = responseAs[String]
76+
csv must include("10Bx939c5543TqA1144M999143X,38,10Bx939c5543TqA1144M999143X38")
77+
csv must include("10Cx939c5543TqA1144M999143X,10,10Cx939c5543TqA1144M999143X10")
78+
}
79+
}
5980
"Validate ULI" in {
6081
Post("/uli/validate", uliCheck) ~> publicHttpRoutes ~> check {
6182
responseAs[ULIValidated] mustBe ULIValidated(true)

0 commit comments

Comments
 (0)