More models

This commit is contained in:
Matt Soucy 2020-06-27 12:53:58 -04:00
parent ffaf4aed34
commit da895cbc91

View File

@ -1,6 +1,7 @@
package me.msoucy.gbat package me.msoucy.gbat
import java.io.File import java.io.File
import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.io.forEachLine import kotlin.io.forEachLine
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
@ -281,12 +282,10 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
private fun lookupOrCreateKnowledgeAcct(authors : List<String>) = transaction(db) { private fun lookupOrCreateKnowledgeAcct(authors : List<String>) = transaction(db) {
val authorStr = authors.sorted().joinToString("\n") val authorStr = authors.sorted().joinToString("\n")
var newId = -1 var newId = KnowledgeAcctsTable.select {
KnowledgeAcctsTable.select {
KnowledgeAcctsTable.authors eq authorStr KnowledgeAcctsTable.authors eq authorStr
}.fetchSize(1). }.first().let {
forEach { it[KnowledgeAcctsTable.id]
newId = it[KnowledgeAcctsTable.id]
} }
if (newId != -1) { if (newId != -1) {
KnowledgeAcctsTable.insert { KnowledgeAcctsTable.insert {
@ -314,17 +313,17 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
} }
AuthorsTable.select { AuthorsTable.select {
AuthorsTable.author eq authorName AuthorsTable.author eq authorName
}.fetchSize(1).map { }.first().let {
it[AuthorsTable.id] it[AuthorsTable.id]
}.first() }
} }
private fun totalLineKnowledge(linenum : Int) = transaction(db) { private fun totalLineKnowledge(linenum : Int) = transaction(db) {
LineKnowledge.select { LineKnowledge.select {
LineKnowledge.linenum eq linenum LineKnowledge.linenum eq linenum
}.fetchSize(1).map { }.first().let {
it[LineKnowledge.knowledge] it[LineKnowledge.knowledge]
}.first() }
} }
private fun createTables() = transaction(db) { private fun createTables() = transaction(db) {
@ -351,17 +350,17 @@ class SummaryModel(val db : Database) {
} }
object DirsTable : IntIdTable("dirs", "dirid") { object DirsTable : IntIdTable("dirs", "dirid") {
val dir = text("dir") val dir = text("dir")
val parentdirid = integer("parentdirid") val parentdirid = integer("parentdirid").references(DirsTable.id)
val projectid = integer("projectid") val projectid = integer("projectid").references(ProjectTable.id)
val dirsproj_idx = uniqueIndex("dirsproj_idx", dir, parentdirid, projectid) val dirsproj_idx = uniqueIndex("dirsproj_idx", dir, parentdirid, projectid)
} }
object FilesTable : IntIdTable("files", "fileid") { object FilesTable : IntIdTable("files", "fileid") {
val fname = text("fname") val fname = text("fname")
val dirid = integer("dirid").index("filesdir_idx") val dirid = integer("dirid").index("filesdir_idx").references(DirsTable.id)
} }
object LinesTable : IntIdTable("lines", "lineid") { object LinesTable : IntIdTable("lines", "lineid") {
val line = text("line") val line = text("line")
val fileid = integer("fileid").index("linesfile_idx") val fileid = integer("fileid").index("linesfile_idx").references(FilesTable.id)
val linenum = integer("linenum") val linenum = integer("linenum")
val linesnumfile_idx = uniqueIndex("linesnumfile_idx", fileid, linenum) val linesnumfile_idx = uniqueIndex("linesnumfile_idx", fileid, linenum)
} }
@ -372,16 +371,16 @@ class SummaryModel(val db : Database) {
val authors = text("authorsstr").uniqueIndex("authorgroupsstrs_idx") val authors = text("authorsstr").uniqueIndex("authorgroupsstrs_idx")
} }
object AuthorsAuthorGroupsTable : Table("authors_authorgroups") { object AuthorsAuthorGroupsTable : Table("authors_authorgroups") {
val authorid = integer("authorid") val authorid = integer("authorid").references(AuthorsTable.id)
val groupid = integer("authorgroupid") val groupid = integer("authorgroupid").references(AuthorsGroupsTable.id)
override val primaryKey = PrimaryKey(authorid, groupid) override val primaryKey = PrimaryKey(authorid, groupid)
} }
object AllocationsTable : IntIdTable("allocations", "allocationid") { object AllocationsTable : IntIdTable("allocations", "allocationid") {
val knowledge = double("knowledge") val knowledge = double("knowledge")
val risk = double("risk") val risk = double("risk")
val orphaned = double("orphaned") val orphaned = double("orphaned")
val lineid = integer("lineid").index("linealloc_idx") val lineid = integer("lineid").index("linealloc_idx").references(LinesTable.id)
val authorgroupid = integer("authorgroupid") val authorgroupid = integer("authorgroupid").references(AuthorsGroupsTable.id)
} }
val GIT_BY_A_BUS_BELOW_THRESHOLD = "Git by a Bus Safe Author" val GIT_BY_A_BUS_BELOW_THRESHOLD = "Git by a Bus Safe Author"
@ -398,57 +397,165 @@ class SummaryModel(val db : Database) {
row[AllocationsTable.risk.sum()] ?: 0.0, row[AllocationsTable.risk.sum()] ?: 0.0,
row[AllocationsTable.orphaned.sum()] ?: 0.0) {} row[AllocationsTable.orphaned.sum()] ?: 0.0) {}
} }
data class AuthorRisk(var stats : Statistics = Statistics()) class LineDict {
data class LineDict(var stats : Statistics = Statistics(), var authorRisks : MutableMap<String, AuthorRisk> = mutableMapOf()) var stats = Statistics()
class FileTree(var name : String = "", var authorRisks = mutableMapOf<String, Statistics>()
var stats : Statistics = Statistics(), }
var authorRisks : MutableMap<String, AuthorRisk> = mutableMapOf(), class FileTree {
var lines : MutableList<LineDict> = mutableListOf()) var name = ""
var stats = Statistics()
var authorRisks = mutableMapOf<String, Statistics>()
var lines = mutableListOf<LineDict>()
}
class FileEntry(var name : String) {
var stats = Statistics()
var authorRisks = mutableMapOf<String, Statistics>()
}
class ProjectTree {
var name = "root"
var files = mutableMapOf<Int, FileEntry>()
var dirs = mutableListOf<Int>()
}
class ProjectTreeNode {
var name = "root"
var files = mutableListOf<FileEntry>()
var dirs = mutableListOf<ProjectTreeNode>()
}
class ProjectTreeResult(var name : String, var root : ProjectTreeNode) {
var stats = Statistics()
var authorRisks = mutableMapOf<String, Statistics>()
}
class ProjectFilesResult(var fileId : Int, var fname : Path)
private val lineAllocations = (LinesTable leftJoin AllocationsTable)
private val lineAllocationGroups = (lineAllocations leftJoin AuthorsGroupsTable)
private val manyJoined = (lineAllocations leftJoin FilesTable leftJoin DirsTable)
private val allJoined = (manyJoined leftJoin AuthorsGroupsTable)
fun fileidsWithRisk(top : Int? = null) : List<Pair<Int, Double>> = transaction(db) {
var query = (FilesTable leftJoin LinesTable leftJoin AllocationsTable)
.selectAll()
.groupBy(FilesTable.id)
.orderBy(AllocationsTable.risk.sum() to SortOrder.DESC)
if(top != null) {
query = query.limit(top)
}
query.map {
it[FilesTable.id].value to (it[AllocationsTable.risk.sum()] ?: 0.0)
}
}
fun fpath(fileId : Int) : Path = transaction(db) {
FilesTable.select {
FilesTable.id eq fileId
}.first().let { row ->
val dirs = reconsDirs(row[FilesTable.dirid])
Paths.get(dirs.joinToString("/")).normalize()
}
}
fun projectFiles(project : String) : List<ProjectFilesResult> = transaction(db) {
val projectId = findOrCreateProject(project)
return (FilesTable innerJoin DirsTable).select {
(FilesTable.dirid eq DirsTable.id) and
(DirsTable.projectid eq projectId)
}.map { row ->
ProjectFilesResult(row[FilesTable.id].value, reconsDir(row[FilesTable.dirid]).resolve(row[FilesTable.fname]))
}
}
fun projectSummary(project : String) = transaction(db) {
val projectId = findOrCreateProject(project)
val theTree = mutableMapOf<Int, ProjectTree>().withDefault {ProjectTree()}
// First fill in the directory structure, ignoring the files
val parentDirIds = mutableListOf(0)
while(parentDirIds.isNotEmpty()) {
val parentId = parentDirIds.removeAt(0)
DirsTable.select { DirsTable.parentdirid eq parentId }
.forEach { row ->
val dirId = row[DirsTable.id].value
theTree.getOrPut(parentId) { ProjectTree() }.dirs.add(dirId)
theTree.getOrPut(dirId) { ProjectTree() }.name = row[DirsTable.dir]
parentDirIds.add(dirId)
}
}
// Then add the files
theTree.entries.forEach { entry ->
FilesTable.select { FilesTable.dirid eq entry.key }.forEach { row ->
entry.value.files[row[FilesTable.id].value] = FileEntry(row[FilesTable.fname])
}
entry.value.files.entries.forEach { (fileId, fileEntry) ->
lineAllocations.select { LinesTable.fileid eq fileId }
.groupBy(LinesTable.fileid)
.forEach { row ->
fileEntry.stats.totKnowledge = row[AllocationsTable.knowledge.sum()] ?: 0.0
fileEntry.stats.totRisk = row[AllocationsTable.risk.sum()] ?: 0.0
fileEntry.stats.totOrphaned = row[AllocationsTable.orphaned.sum()] ?: 0.0
}
}
entry.value.files.entries.forEach { (fileId, fileEntry) ->
lineAllocationGroups.select { LinesTable.fileid eq fileId }
.groupBy(AllocationsTable.authorgroupid)
.orderBy(AuthorsGroupsTable.authors)
.forEach { row ->
fileEntry.authorRisks[row[AuthorsGroupsTable.authors]] = Statistics(row)
}
}
}
val transformedRoot = transformNode(theTree, 0)
assert(transformedRoot.dirs.size == 1)
val root = transformedRoot.dirs.first()
val projectTree = ProjectTreeResult(project, root)
allJoined.select { DirsTable.projectid eq projectId }
.groupBy(AuthorsGroupsTable.id)
.forEach { row ->
projectTree.authorRisks[row[AuthorsGroupsTable.authors]] = Statistics(row)
}
manyJoined.select {
DirsTable.projectid eq
}.first().let { row ->
projectTree.stats = Statistics(row)
}
projectTree
}
fun fileSummary(fileId : Int) = transaction(db) { fun fileSummary(fileId : Int) = transaction(db) {
var fileTree = FileTree() var fileTree = FileTree()
val joinA = Join(LinesTable, AllocationsTable, lineAllocationGroups.select {
JoinType.LEFT,
LinesTable.id, AllocationsTable.lineid)
val joinB = Join(joinA, AuthorsGroupsTable,
JoinType.LEFT,
AuthorsGroupsTable.id, AllocationsTable.authorgroupid
)
joinB.select {
LinesTable.fileid eq fileId LinesTable.fileid eq fileId
}.groupBy(AuthorsGroupsTable.id).forEach { row -> }.groupBy(AuthorsGroupsTable.id).forEach { row ->
val authors = row[AuthorsGroupsTable.authors] val authors = row[AuthorsGroupsTable.authors]
fileTree.authorRisks[authors] = fileTree.authorRisks[authors] = Statistics(row)
AuthorRisk(Statistics(row))
} }
Join(joinA, FilesTable, Join(lineAllocations, FilesTable,
JoinType.LEFT, JoinType.LEFT,
LinesTable.fileid, FilesTable.id LinesTable.fileid, FilesTable.id
).select { ).select { LinesTable.fileid eq fileId }
LinesTable.fileid eq fileId .first().let { row ->
}.fetchSize(1).forEach { row ->
fileTree.stats = Statistics(row) fileTree.stats = Statistics(row)
} }
fileTree.name = FilesTable.select { fileTree.name = FilesTable.select { FilesTable.id eq fileId }.map { it[FilesTable.fname] }.first()
FilesTable.id eq fileId
}.map { it[FilesTable.fname] }.first()
LinesTable.select { LinesTable.select { LinesTable.fileid eq fileId }
LinesTable.fileid eq fileId .map { it[LinesTable.id].value }
}.map { .forEach { lineId ->
it[LinesTable.id].value
}.forEach { lineId ->
val lineDict = LineDict() val lineDict = LineDict()
joinB.select { lineAllocationGroups.select {
LinesTable.id eq lineId LinesTable.id eq lineId
}.groupBy(AuthorsGroupsTable.id).forEach { lineRow -> }.groupBy(AuthorsGroupsTable.id).forEach { lineRow ->
lineDict.authorRisks[lineRow[AuthorsGroupsTable.authors]] = AuthorRisk(Statistics(lineRow)) lineDict.authorRisks[lineRow[AuthorsGroupsTable.authors]] = Statistics(lineRow)
} }
joinA.select { lineAllocations.select {
LinesTable.id eq lineId LinesTable.id eq lineId
}.fetchSize(1).forEach { }.first().let {
lineDict.stats = Statistics(it) lineDict.stats = Statistics(it)
} }
fileTree.lines.add(lineDict) fileTree.lines.add(lineDict)
@ -464,7 +571,19 @@ class SummaryModel(val db : Database) {
} }
} }
private fun Double?.zeroIfNone() = this ?: 0.0 private fun transformNode(tree : MutableMap<Int, ProjectTree>, dirId : Int) : ProjectTreeNode {
val result = ProjectTreeNode()
tree[dirId]?.let {dirdict ->
result.dirs = mutableListOf<ProjectTreeNode>().apply {
dirdict.dirs.forEach {
add(transformNode(tree, it))
tree.remove(it)
}
}
result.files = dirdict.files.values.toMutableList()
}
return result
}
private fun reconsDir(dirId : Int) = transaction(db) { private fun reconsDir(dirId : Int) = transaction(db) {
val segs = mutableListOf<String>() val segs = mutableListOf<String>()
@ -584,8 +703,9 @@ class SummaryModel(val db : Database) {
while(parentDirId != 0) { while(parentDirId != 0) {
DirsTable.select { DirsTable.select {
DirsTable.id eq parentDirId DirsTable.id eq parentDirId
}.fetchSize(1). }
forEach { .first()
.let {
dirs.add(it[DirsTable.dir]) dirs.add(it[DirsTable.dir])
parentDirId = it[DirsTable.parentdirid] parentDirId = it[DirsTable.parentdirid]
} }