Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.pyc
output
*~
.idea/*
63 changes: 36 additions & 27 deletions common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,58 @@
Common and other crappy code used throughout git by a bus.
"""


def safe_author_name(author):
if author:
return author.replace(',', '_').replace(':', '_')
else:
return author

def safe_int(i):

def safe_int(i: str | None) -> int | None:
if i is None or i == '':
return None
else:
return int(i)

def safe_str(s):

def safe_str(s) -> str:
if s is None:
return ''
else:
return str(s)


def parse_dev_shared(s, num_func):
dev_shared = []
if not s:
return dev_shared
for ddv in s.split(','):
segs = ddv.split(':')
k = segs[:-1]
v = float(segs[-1])
dev_shared.append((k, v))
return [(ddv.split(':')[:-1], float(ddv.split(':')[-1])) for ddv in s.split(',')]

return dev_shared

def dev_shared_to_str(dev_shared):
return ','.join([':'.join([':'.join(devs), str(shared)]) for devs, shared in dev_shared])

def parse_dev_exp_str(s, num_func):

def parse_dev_exp_str(s: str, num_func) -> list[tuple[str, str, str]]:
if not s:
return []
return [(dd[0], num_func(dd[1]), num_func(dd[2])) for dd in [d.split(':') for d in s.split(',')]]
return [
(dd[0], num_func(dd[1]), num_func(dd[2]))
for dd in [d.split(':') for d in s.split(',')]
]


def dev_exp_to_str(devs):
return ','.join([':'.join([str(x) for x in d]) for d in devs])


def project_name(fname):
if not fname:
return None
return fname.split(':')[0]


class FileData(object):
"""
Represents a single line of data about a single file, can encode / parse to / from tsv.
Expand All @@ -74,7 +79,7 @@ def __init__(self, line):
if line is None:
line = ''
line = line.strip('\n\r')
fields = line.split('\t')
fields: list[str | None] = line.split('\t')
n_missing_fields = FileData.num_fields - len(fields)
fields.extend(n_missing_fields * [None])

Expand All @@ -90,23 +95,27 @@ def __init__(self, line):
self.project = project_name(self.fname)

def as_line(self):
return '\t'.join(map(safe_str, [self.fname,
self.cnt_lines,
dev_exp_to_str(self.dev_experience),
self.tot_knowledge,
dev_shared_to_str(self.dev_uniq),
dev_shared_to_str(self.dev_risk)]))
return '\t'.join(
[
safe_str(s)
for s in [
self.fname, self.cnt_lines, dev_exp_to_str(self.dev_experience),
self.tot_knowledge, dev_shared_to_str(self.dev_uniq),
dev_shared_to_str(self.dev_risk)
]
]
)

def __str__(self):
s = ("fname: %s, cnt_lines: %s, dev_experience: %s, tot_knowledge: %s, dev_uniq: %s, " + \
"risk: %s ") % (self.fname,
str(self.cnt_lines),
dev_exp_to_str(self.dev_experience),
str(self.tot_knowledge),
dev_shared_to_str(self.dev_uniq),
dev_shared_to_str(self.dev_risk))
s = (
f"fname: {self.fname}, cnt_lines: {self.cnt_lines}, "
f"dev_experience: {dev_exp_to_str(self.dev_experience)}, "
f"tot_knowledge: {self.tot_knowledge}, dev_uniq: {dev_shared_to_str(self.dev_uniq)}, "
f"risk: {dev_shared_to_str(self.dev_risk)}"
)
return s



def is_interesting(f, interesting, not_interesting):
if f.strip() == '':
return False
Expand All @@ -115,6 +124,7 @@ def is_interesting(f, interesting, not_interesting):
has_my_interest = not any([n.search(f) for n in not_interesting])
return has_my_interest


def parse_departed_devs(dd_file, departed_devs):
fil = open(dd_file, 'r')
for line in fil:
Expand All @@ -124,4 +134,3 @@ def parse_departed_devs(dd_file, departed_devs):
continue
departed_devs.append(line)
fil.close()

50 changes: 29 additions & 21 deletions estimate_file_risk.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
"""

import sys
import optparse

from argparse import ArgumentParser

from common import FileData, safe_author_name


def get_bus_risk(dev, bus_risks, def_risk):
if dev not in bus_risks:
return def_risk
else:
return bus_risks[dev]


def estimate_file_risks(lines, bus_risks, def_bus_risk):
"""
Estimate the risk in the file as:
Expand All @@ -41,28 +44,33 @@ def estimate_file_risks(lines, bus_risks, def_bus_risk):
fd.dev_risk = dev_risk
yield fd.as_line()


def parse_risk_file(risk_file, bus_risks):
risk_f = open(risk_file, 'r')
for line in risk_f:
line = line.strip()
if not line:
continue
dev, risk = line.split('=')
dev = safe_author_name(dev)
bus_risks[dev] = float(risk)
risk_f.close()
with open(risk_file, 'r') as risk_f:
for line in risk_f:
line = line.strip()
if not line:
continue
dev, risk = line.split('=')
dev = safe_author_name(dev)
bus_risks[dev] = float(risk)


if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option('-b', '--bus-risk', dest='bus_risk', metavar='FLOAT', default=0.1,
help='The estimated probability that a dev will be hit by a bus in your analysis timeframe')
parser.add_option('-r', '--risk-file', dest='risk_file', metavar='FILE',
help='File of dev=float lines (e.g. ejorgensen=0.4) with dev bus likelihoods')
options, args = parser.parse_args()
parser = ArgumentParser()
parser.add_argument(
"-b", "--bus-risk", dest="bus_risk", metavar="FLOAT", default=0.1,
help="The estimated probability that a dev will be hit by a bus in your analysis timeframe"
)
parser.add_argument(
"-r", "--risk-file", dest="risk_file", metavar="FILE",
help="File of dev=float lines (e.g. ejorgensen=0.4) with dev bus likelihoods"
)
args = parser.parse_args()

bus_risks = {}
if options.risk_file:
parse_risk_file(options.risk_file, bus_risks)
bus_risks_options = {}
if args.risk_file:
parse_risk_file(args.risk_file, bus_risks_options)

for line in estimate_file_risks(sys.stdin, bus_risks, float(options.bus_risk)):
print line
for line in estimate_file_risks(sys.stdin, bus_risks_options, float(args.bus_risk)):
print(line)
36 changes: 22 additions & 14 deletions estimate_unique_knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@
"""

import sys
import math
import copy

from optparse import OptionParser
from argparse import ArgumentParser

from common import FileData


def sequential_create_knowledge(dev_uniq, dev, adjustment):
"""
Create adjustment lines of knowledge in dev's account.
Expand All @@ -42,6 +41,7 @@ def sequential_create_knowledge(dev_uniq, dev, adjustment):
dev_uniq[dev] = 0
dev_uniq[dev] += adjustment


def sequential_destroy_knowledge(adjustment, tot_knowledge, dev_uniq):
"""
Find the percentage of tot_knowledge the adjustment represents and
Expand All @@ -57,6 +57,7 @@ def sequential_destroy_knowledge(adjustment, tot_knowledge, dev_uniq):
k -= k * pct_to_destroy
dev_uniq[devs] = k


def sequential_share_knowledge_group(dev, shared_key_exploded, pct_to_share, dev_uniq):
"""
Share pct_to_share knowledge from all accounts that dev doesn't
Expand All @@ -74,6 +75,7 @@ def sequential_share_knowledge_group(dev, shared_key_exploded, pct_to_share, dev
dev_uniq[new_shared_key] = 0
dev_uniq[new_shared_key] += amt_to_share


def sequential_distribute_shared_knowledge(dev, shared_knowledge, tot_knowledge, dev_uniq):
"""
Share the percent of knowledge represented by shared_knowledge of
Expand All @@ -83,11 +85,13 @@ def sequential_distribute_shared_knowledge(dev, shared_knowledge, tot_knowledge,
pct_to_share = 0
if tot_knowledge:
pct_to_share = float(shared_knowledge) / float(tot_knowledge)
for shared_key in dev_uniq.keys():
# list is needed, since dictionary is being modified during the loop
for shared_key in list(dev_uniq.keys()):
shared_key_exploded = shared_key.split('\0')
if dev not in shared_key_exploded:
sequential_share_knowledge_group(dev, shared_key_exploded, pct_to_share, dev_uniq)


def sequential_estimate_uniq(fd, knowledge_churn_constant):
"""
Estimate the amounts of unique knowledge for each developer who
Expand Down Expand Up @@ -119,7 +123,8 @@ def sequential_estimate_uniq(fd, knowledge_churn_constant):
dev_uniq = [(shared_key.split('\0'), shared) for shared_key, shared in dev_uniq.items()]

return dev_uniq, int(tot_knowledge)



def sequential(lines, model_args):
"""
Entry point for the sequential algorithm.
Expand All @@ -130,22 +135,25 @@ def sequential(lines, model_args):
tot_knowledge fields filled in.
"""
knowledge_churn_constant = float(model_args[0])
for line in lines:
fd = FileData(line)
for ln in lines:
fd = FileData(ln)
dev_uniq, tot_knowledge = sequential_estimate_uniq(fd, knowledge_churn_constant)
fd.dev_uniq = dev_uniq
fd.tot_knowledge = tot_knowledge
yield fd.as_line()

if __name__ == '__main__':
parser = OptionParser()
parser.add_option('--model', dest='model', metavar='MODEL[:MARG1[:MARG2]...]', default="sequential:0.1",
help='Knowledge model to use, with arguments.')
options, args = parser.parse_args()

model = options.model.split(':')
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument(
"--model", dest="model", metavar="MODEL[:MARG1[:MARG2]...]",
default="sequential:0.1", help='Knowledge model to use, with arguments.'
)
args = parser.parse_args()

model = args.model.split(':')
model_func = locals()[model[0]]
model_args = model[1:]

for line in model_func(sys.stdin, model_args):
print line
print(line)
Loading