← back to drummonds__gobank

Function bodies 204 total

All specs Real LLM only Function bodies
main function · go · L48-L90 (43 LOC)
cmd/boefetch/main.go
func main() {
	outFile := flag.String("o", "boe_rates.csv", "output CSV file path")
	flag.Parse()

	entries, err := fetchFromAPI()
	if err != nil {
		fmt.Fprintf(os.Stderr, "API fetch failed (%v), using fallback data\n", err)
		entries = parseFallback()
	}

	// Prepend the initial rate at 2020-01-01 (0.75%)
	start := rateEntry{
		date: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		rate: 0.75,
	}
	entries = append([]rateEntry{start}, entries...)

	// Sort by date
	sort.Slice(entries, func(i, j int) bool { return entries[i].date.Before(entries[j].date) })

	// Deduplicate by date
	var deduped []rateEntry
	seen := map[string]bool{}
	for _, e := range entries {
		key := e.date.Format("2006-01-02")
		if !seen[key] {
			seen[key] = true
			deduped = append(deduped, e)
		}
	}

	// Write CSV
	var sb strings.Builder
	for _, e := range deduped {
		fmt.Fprintf(&sb, "%s,%.4f\n", e.date.Format("2006-01-02"), e.rate/100.0)
	}

	if err := os.WriteFile(*outFile, []byte(sb.String()), 0644); err != nil
parseAPIResponse function · go · L114-L154 (41 LOC)
cmd/boefetch/main.go
func parseAPIResponse(body string) ([]rateEntry, error) {
	lines := strings.Split(strings.TrimSpace(body), "\n")
	var entries []rateEntry
	for i, line := range lines {
		if i == 0 {
			continue // skip header
		}
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		parts := strings.SplitN(line, ",", 2)
		if len(parts) != 2 {
			continue
		}
		dateStr := strings.TrimSpace(parts[0])
		rateStr := strings.TrimSpace(parts[1])

		t, err := time.Parse("02/Jan/2006", dateStr)
		if err != nil {
			// Try alternative format
			t, err = time.Parse("02 Jan 2006", dateStr)
			if err != nil {
				continue
			}
		}

		var rate float64
		_, err = fmt.Sscanf(rateStr, "%f", &rate)
		if err != nil {
			continue
		}

		entries = append(entries, rateEntry{date: t, rate: rate})
	}

	if len(entries) == 0 {
		return nil, fmt.Errorf("no valid entries parsed from API response")
	}
	return entries, nil
}
parseFallback function · go · L156-L159 (4 LOC)
cmd/boefetch/main.go
func parseFallback() []rateEntry {
	entries, _ := parseAPIResponse(fallbackData)
	return entries
}
customerData function · go · L23-L32 (10 LOC)
cmd/chartcompare/data.go
func customerData() []Point {
	pts := make([]Point, 50)
	for i := range pts {
		pts[i] = Point{
			Date:  startDate.AddDate(0, 0, i),
			Value: 5 + float64(i) + 2*math.Sin(float64(i)/5),
		}
	}
	return pts
}
balanceData function · go · L35-L46 (12 LOC)
cmd/chartcompare/data.go
func balanceData() []DualPoint {
	pts := make([]DualPoint, 50)
	for i := range pts {
		day := float64(i)
		pts[i] = DualPoint{
			Date: startDate.AddDate(0, 0, i),
			A:    10000 + day*200 + 500*math.Sin(day/7), // savings
			B:    5000 + day*100 + 300*math.Cos(day/10), // lending
		}
	}
	return pts
}
nimData function · go · L49-L59 (11 LOC)
cmd/chartcompare/data.go
func nimData() []Point {
	pts := make([]Point, 50)
	for i := range pts {
		day := float64(i)
		pts[i] = Point{
			Date:  startDate.AddDate(0, 0, i),
			Value: 200 + 80*math.Sin(day/4) + 30*math.Cos(day/7),
		}
	}
	return pts
}
boeRateData function · go · L62-L91 (30 LOC)
cmd/chartcompare/data.go
func boeRateData() []Point {
	steps := []struct {
		dayOffset int
		rate      float64
	}{
		{0, 0.0010},
		{8, 0.0025},
		{16, 0.0050},
		{22, 0.0100},
		{28, 0.0175},
		{32, 0.0250},
		{36, 0.0350},
		{40, 0.0425},
		{44, 0.0500},
		{47, 0.0525},
	}

	pts := make([]Point, 50)
	stepIdx := 0
	for i := range pts {
		if stepIdx+1 < len(steps) && i >= steps[stepIdx+1].dayOffset {
			stepIdx++
		}
		pts[i] = Point{
			Date:  startDate.AddDate(0, 0, i),
			Value: steps[stepIdx].rate,
		}
	}
	return pts
}
About: code-quality intelligence by Repobility · https://repobility.com
thinLabels function · go · L9-L22 (14 LOC)
cmd/chartcompare/gocharts.go
func thinLabels(labels []string, max int) []string {
	if len(labels) <= max {
		return labels
	}
	out := make([]string, len(labels))
	copy(out, labels)
	step := len(out) / max
	for i := range out {
		if i%step != 0 && i != len(out)-1 {
			out[i] = ""
		}
	}
	return out
}
gcCustomerCount function · go · L24-L53 (30 LOC)
cmd/chartcompare/gocharts.go
func gcCustomerCount(data []Point) string {
	values := make([]float64, len(data))
	labels := make([]string, len(data))
	for i, p := range data {
		values[i] = p.Value
		labels[i] = p.Date.Format("2 Jan")
	}
	labels = thinLabels(labels, 6)

	p, err := charts.LineRender(
		[][]float64{values},
		charts.SVGOutputOptionFunc(),
		charts.DimensionsOptionFunc(svgWidth, svgHeight),
		charts.XAxisLabelsOptionFunc(labels),
		charts.LegendOptionFunc(charts.LegendOption{Show: charts.Ptr(false)}),
		charts.PaddingOptionFunc(charts.Box{Left: 60, Right: 10, Top: 10, Bottom: 10, IsSet: true}),
		func(opt *charts.ChartOption) {
			opt.Symbol = charts.SymbolNone
			opt.LineStrokeWidth = 2
		},
	)
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Chart error: %v</p>`, err)
	}
	b, err := p.Bytes()
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Render error: %v</p>`, err)
	}
	return string(b)
}
gcBalances function · go · L55-L89 (35 LOC)
cmd/chartcompare/gocharts.go
func gcBalances(data []DualPoint) string {
	savings := make([]float64, len(data))
	lending := make([]float64, len(data))
	labels := make([]string, len(data))
	for i, p := range data {
		savings[i] = p.A
		lending[i] = p.B
		labels[i] = p.Date.Format("2 Jan")
	}
	labels = thinLabels(labels, 6)

	p, err := charts.LineRender(
		[][]float64{savings, lending},
		charts.SVGOutputOptionFunc(),
		charts.DimensionsOptionFunc(svgWidth, svgHeight+25),
		charts.XAxisLabelsOptionFunc(labels),
		charts.LegendOptionFunc(charts.LegendOption{
			Show:        charts.Ptr(true),
			SeriesNames: []string{"Savings", "Lending"},
		}),
		charts.PaddingOptionFunc(charts.Box{Left: 60, Right: 10, Top: 10, Bottom: 10, IsSet: true}),
		func(opt *charts.ChartOption) {
			opt.Symbol = charts.SymbolNone
			opt.LineStrokeWidth = 2
		},
	)
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Chart error: %v</p>`, err)
	}
	b, err := p.Bytes()
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger"
gcNIM function · go · L91-L120 (30 LOC)
cmd/chartcompare/gocharts.go
func gcNIM(data []Point) string {
	values := make([]float64, len(data))
	labels := make([]string, len(data))
	for i, p := range data {
		values[i] = p.Value
		labels[i] = p.Date.Format("2 Jan")
	}
	labels = thinLabels(labels, 6)

	p, err := charts.LineRender(
		[][]float64{values},
		charts.SVGOutputOptionFunc(),
		charts.DimensionsOptionFunc(svgWidth, svgHeight),
		charts.XAxisLabelsOptionFunc(labels),
		charts.LegendOptionFunc(charts.LegendOption{Show: charts.Ptr(false)}),
		charts.PaddingOptionFunc(charts.Box{Left: 60, Right: 10, Top: 10, Bottom: 10, IsSet: true}),
		func(opt *charts.ChartOption) {
			opt.Symbol = charts.SymbolNone
			opt.LineStrokeWidth = 2
		},
	)
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Chart error: %v</p>`, err)
	}
	b, err := p.Bytes()
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Render error: %v</p>`, err)
	}
	return string(b)
}
gcBoERate function · go · L122-L152 (31 LOC)
cmd/chartcompare/gocharts.go
func gcBoERate(data []Point) string {
	// Convert rate to percentage for display
	values := make([]float64, len(data))
	labels := make([]string, len(data))
	for i, p := range data {
		values[i] = p.Value * 100
		labels[i] = p.Date.Format("2 Jan")
	}
	labels = thinLabels(labels, 6)

	p, err := charts.LineRender(
		[][]float64{values},
		charts.SVGOutputOptionFunc(),
		charts.DimensionsOptionFunc(svgWidth, svgHeight),
		charts.XAxisLabelsOptionFunc(labels),
		charts.LegendOptionFunc(charts.LegendOption{Show: charts.Ptr(false)}),
		charts.PaddingOptionFunc(charts.Box{Left: 60, Right: 10, Top: 10, Bottom: 10, IsSet: true}),
		func(opt *charts.ChartOption) {
			opt.Symbol = charts.SymbolNone
			opt.LineStrokeWidth = 2
		},
	)
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Chart error: %v</p>`, err)
	}
	b, err := p.Bytes()
	if err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">Render error: %v</p>`, err)
	}
	return string(b)
}
hrSingleLine function · go · L21-L250 (230 LOC)
cmd/chartcompare/handrolled.go
func hrSingleLine(data []Point, color, yFmt string) string {
	if len(data) == 0 {
		return ""
	}

	minV, maxV := data[0].Value, data[0].Value
	for _, p := range data {
		if p.Value < minV {
			minV = p.Value
		}
		if p.Value > maxV {
			maxV = p.Value
		}
	}
	vRange := maxV - minV
	if vRange < 1e-9 {
		vRange = 1
		minV -= 0.5
	} else {
		minV -= vRange * 0.1
		maxV += vRange * 0.1
		vRange = maxV - minV
	}
	if minV < 0 {
		minV = 0
		vRange = maxV - minV
	}

	var s strings.Builder
	s.WriteString(fmt.Sprintf(`<svg viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" style="width:100%%;height:auto">`, svgWidth, svgHeight))
	s.WriteString(`<style>text{font-family:Arial,Helvetica,sans-serif}</style>`)

	// Background
	s.WriteString(fmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="%d" fill="#fafafa" stroke="#dbdbdb" stroke-width="1"/>`,
		chartPadL, chartPadT, chartW, chartH))

	// Y-axis
	for i := 0; i <= 4; i++ {
		val := minV + vRange*float64(i)/4.0
		y := float64(chartPadT+chartH)
xLabels function · go · L253-L267 (15 LOC)
cmd/chartcompare/handrolled.go
func xLabels(data []Point, s *strings.Builder) {
	if len(data) < 2 {
		return
	}
	numLabels := 5
	if len(data) < numLabels {
		numLabels = len(data)
	}
	for i := 0; i < numLabels; i++ {
		idx := i * (len(data) - 1) / (numLabels - 1)
		x := float64(chartPadL) + float64(chartW)*float64(idx)/float64(len(data)-1)
		s.WriteString(fmt.Sprintf(`<text x="%.0f" y="%d" text-anchor="middle" font-size="9" fill="#7a7a7a">%s</text>`,
			x, chartPadT+chartH+15, data[idx].Date.Format("2 Jan")))
	}
}
hrCustomerCount function · go · L271-L273 (3 LOC)
cmd/chartcompare/handrolled.go
func hrCustomerCount(data []Point) string {
	return hrSingleLine(data, "#00947e", "%.0f")
}
Repobility · open methodology · https://repobility.com/research/
hrBalances function · go · L275-L277 (3 LOC)
cmd/chartcompare/handrolled.go
func hrBalances(data []DualPoint) string {
	return hrDualLine(data, "#48c78e", "#3e8ed0", "Savings", "Lending")
}
hrNIM function · go · L279-L281 (3 LOC)
cmd/chartcompare/handrolled.go
func hrNIM(data []Point) string {
	return hrSingleLine(data, "#f59e0b", "%.0f")
}
hrBoERate function · go · L283-L285 (3 LOC)
cmd/chartcompare/handrolled.go
func hrBoERate(data []Point) string {
	return hrStepLine(data, "#48c78e", "%.2f%%")
}
main function · go · L10-L120 (111 LOC)
cmd/chartcompare/main.go
func main() {
	// Find repo root (walk up from executable location)
	outDir := filepath.Join("..", "..", "docs", "research")
	if err := os.MkdirAll(outDir, 0o755); err != nil {
		fmt.Fprintf(os.Stderr, "mkdir: %v\n", err)
		os.Exit(1)
	}

	// Generate datasets
	custData := customerData()
	balData := balanceData()
	nimPts := nimData()
	boePts := boeRateData()

	// Chart sections: title, 3 SVGs (hand-rolled, go-charts, margaid)
	type section struct {
		title                    string
		handrolled, gochart, mar string
	}

	sections := []section{
		{
			title:      "Customer Count",
			handrolled: hrCustomerCount(custData),
			gochart:    gcCustomerCount(custData),
			mar:        mgCustomerCount(custData),
		},
		{
			title:      "Balance History (dual line)",
			handrolled: hrBalances(balData),
			gochart:    gcBalances(balData),
			mar:        mgBalances(balData),
		},
		{
			title:      "NIM (bps)",
			handrolled: hrNIM(nimPts),
			gochart:    gcNIM(nimPts),
			mar:        mgNIM(nimPts)
mgSingleLine function · go · L10-L33 (24 LOC)
cmd/chartcompare/margaid.go
func mgSingleLine(data []Point, title string) string {
	series := m.NewSeries(m.Titled(title))
	for _, p := range data {
		series.Add(m.MakeValue(m.SecondsFromTime(p.Date), p.Value))
	}

	diagram := m.New(svgWidth, svgHeight,
		m.WithAutorange(m.XAxis, series),
		m.WithAutorange(m.YAxis, series),
		m.WithInset(60),
		m.WithColorScheme(160),
	)

	diagram.Line(series, m.UsingStrokeWidth(2))
	diagram.Axis(series, m.XAxis, diagram.TimeTicker("2 Jan"), false, "")
	diagram.Axis(series, m.YAxis, diagram.ValueTicker('f', 0, 10), true, "")
	diagram.Frame()

	var buf bytes.Buffer
	if err := diagram.Render(&buf); err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">margaid error: %v</p>`, err)
	}
	return buf.String()
}
mgDualLine function · go · L35-L63 (29 LOC)
cmd/chartcompare/margaid.go
func mgDualLine(data []DualPoint, labelA, labelB string) string {
	seriesA := m.NewSeries(m.Titled(labelA))
	seriesB := m.NewSeries(m.Titled(labelB))
	for _, p := range data {
		t := m.SecondsFromTime(p.Date)
		seriesA.Add(m.MakeValue(t, p.A))
		seriesB.Add(m.MakeValue(t, p.B))
	}

	diagram := m.New(svgWidth, svgHeight+25,
		m.WithAutorange(m.XAxis, seriesA),
		m.WithAutorange(m.YAxis, seriesA, seriesB),
		m.WithInset(60),
		m.WithColorScheme(160),
	)

	diagram.Line(seriesA, m.UsingStrokeWidth(2))
	diagram.Line(seriesB, m.UsingStrokeWidth(2))
	diagram.Axis(seriesA, m.XAxis, diagram.TimeTicker("2 Jan"), false, "")
	diagram.Axis(seriesA, m.YAxis, diagram.ValueTicker('f', 0, 10), true, "")
	diagram.Frame()
	diagram.Legend(m.BottomLeft)

	var buf bytes.Buffer
	if err := diagram.Render(&buf); err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">margaid error: %v</p>`, err)
	}
	return buf.String()
}
mgStepLine function · go · L66-L94 (29 LOC)
cmd/chartcompare/margaid.go
func mgStepLine(data []Point, title string) string {
	series := m.NewSeries(m.Titled(title))
	for i, p := range data {
		t := m.SecondsFromTime(p.Date)
		if i > 0 {
			// Horizontal segment: previous value at current x
			series.Add(m.MakeValue(t, data[i-1].Value*100))
		}
		series.Add(m.MakeValue(t, p.Value*100))
	}

	diagram := m.New(svgWidth, svgHeight,
		m.WithAutorange(m.XAxis, series),
		m.WithAutorange(m.YAxis, series),
		m.WithInset(60),
		m.WithColorScheme(160),
	)

	diagram.Line(series, m.UsingStrokeWidth(2))
	diagram.Axis(series, m.XAxis, diagram.TimeTicker("2 Jan"), false, "")
	diagram.Axis(series, m.YAxis, diagram.ValueTicker('f', 2, 10), true, "")
	diagram.Frame()

	var buf bytes.Buffer
	if err := diagram.Render(&buf); err != nil {
		return fmt.Sprintf(`<p class="has-text-danger">margaid error: %v</p>`, err)
	}
	return buf.String()
}
mgCustomerCount function · go · L96-L98 (3 LOC)
cmd/chartcompare/margaid.go
func mgCustomerCount(data []Point) string {
	return mgSingleLine(data, "Customers")
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
mgBalances function · go · L100-L102 (3 LOC)
cmd/chartcompare/margaid.go
func mgBalances(data []DualPoint) string {
	return mgDualLine(data, "Savings", "Lending")
}
mgNIM function · go · L104-L106 (3 LOC)
cmd/chartcompare/margaid.go
func mgNIM(data []Point) string {
	return mgSingleLine(data, "NIM (bps)")
}
mgBoERate function · go · L108-L110 (3 LOC)
cmd/chartcompare/margaid.go
func mgBoERate(data []Point) string {
	return mgStepLine(data, "BoE Rate")
}
BuildRuntimeHTML method · go · L13-L101 (89 LOC)
cmd/demo/about.go
func (ds *DemoState) BuildRuntimeHTML() string {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)

	ds.mu.Lock()
	currentDay := ds.currentDay.Format("2 Jan 2006")
	dayCount := ds.dayCount
	customerCount := len(ds.customers)
	productCount := len(ds.products)
	paymentCount := len(ds.payments)
	boeRate := ds.settings.BoEBaseRate * 100
	piiCount := ds.custStoreCount()
	var dbStats sql.DBStats
	if ds.db != nil {
		dbStats = ds.db.Stats()
	}
	ds.mu.Unlock()

	var s strings.Builder

	s.WriteString(`<h2 class="title is-4">Runtime</h2>`)
	s.WriteString(`<p class="subtitle is-6 has-text-grey">Runtime stats for the Model Bank simulation</p>`)

	// Environment
	s.WriteString(`<div class="box">`)
	s.WriteString(`<h3 class="title is-5">Environment</h3>`)
	s.WriteString(`<table class="table is-fullwidth">`)
	s.WriteString(fmt.Sprintf(`<tr><th>Version</th><td>%s</td></tr>`, version))
	s.WriteString(fmt.Sprintf(`<tr><th>Runtime</th><td>%s</td></tr>`, runtimeEnv))
	s.WriteString(fmt.Sprintf(`<tr><th>Go
formatBytes function · go · L103-L114 (12 LOC)
cmd/demo/about.go
func formatBytes(b uint64) string {
	const unit = 1024
	if b < unit {
		return fmt.Sprintf("%d B", b)
	}
	div, exp := uint64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMG"[exp])
}
BuildPnLHTML method · go · L11-L57 (47 LOC)
cmd/demo/accounting.go
func (ds *DemoState) BuildPnLHTML() string {
	ds.mu.Lock()
	dayCount := ds.dayCount
	opCostPerDay := ds.opCostPerDay

	loanInterestIncome := 0.0
	depositInterestExpense := 0.0
	for _, c := range ds.customers {
		for _, a := range c.Accounts {
			if a.Family == gbp.FamilyLending {
				loanInterestIncome += a.Interest
			} else {
				depositInterestExpense += a.Interest
			}
		}
	}
	boeInterestIncome := ds.boeInterestAccum
	ds.mu.Unlock()

	opCosts := opCostPerDay * float64(dayCount)
	netInterest := loanInterestIncome + boeInterestIncome - depositInterestExpense
	profit := netInterest - opCosts

	var s strings.Builder
	s.WriteString(`<h2 class="title is-4">Profit &amp; Loss</h2>`)
	s.WriteString(fmt.Sprintf(`<p class="subtitle is-6 has-text-grey">For %d simulated days</p>`, dayCount))

	s.WriteString(`<div class="box">`)
	s.WriteString(`<table class="table is-fullwidth">`)
	s.WriteString(`<tbody>`)
	s.WriteString(fmt.Sprintf(`<tr><td><strong>Loan Interest Income</strong></td><td class="h
Label method · go · L19-L32 (14 LOC)
cmd/demo/auth.go
func (r Role) Label() string {
	switch r {
	case RoleAdmin:
		return "Admin"
	case RoleAuditor:
		return "Auditor"
	case RoleCustomerService:
		return "Customer Service"
	case RoleReadOnly:
		return "Read Only"
	default:
		return "Admin"
	}
}
Can method · go · L35-L52 (18 LOC)
cmd/demo/auth.go
func (r Role) Can(action string) bool {
	switch action {
	case "sim_controls":
		return r == RoleAdmin
	case "settings":
		return r == RoleAdmin
	case "export":
		return r == RoleAdmin
	case "send_payment":
		return r == RoleAdmin || r == RoleCustomerService
	case "view_pii":
		return r != RoleReadOnly
	case "buy_gilt":
		return r == RoleAdmin
	default:
		return false
	}
}
Repobility analyzer · published findings · https://repobility.com
ValidRole function · go · L55-L61 (7 LOC)
cmd/demo/auth.go
func ValidRole(s string) bool {
	switch Role(s) {
	case RoleAdmin, RoleAuditor, RoleCustomerService, RoleReadOnly:
		return true
	}
	return false
}
NewAuthStore function · go · L75-L80 (6 LOC)
cmd/demo/auth.go
func NewAuthStore(ttl time.Duration) *AuthStore {
	return &AuthStore{
		sessions: make(map[string]*sessionData),
		ttl:      ttl,
	}
}
getOrCreate method · go · L82-L89 (8 LOC)
cmd/demo/auth.go
func (as *AuthStore) getOrCreate(sessionID string) *sessionData {
	sd, ok := as.sessions[sessionID]
	if !ok {
		sd = &sessionData{Role: RoleAdmin}
		as.sessions[sessionID] = sd
	}
	return sd
}
Authorize method · go · L91-L96 (6 LOC)
cmd/demo/auth.go
func (as *AuthStore) Authorize(sessionID string) {
	as.mu.Lock()
	sd := as.getOrCreate(sessionID)
	sd.PIIExpiry = time.Now().Add(as.ttl)
	as.mu.Unlock()
}
Revoke method · go · L98-L104 (7 LOC)
cmd/demo/auth.go
func (as *AuthStore) Revoke(sessionID string) {
	as.mu.Lock()
	if sd, ok := as.sessions[sessionID]; ok {
		sd.PIIExpiry = time.Time{}
	}
	as.mu.Unlock()
}
IsAuthorized method · go · L106-L117 (12 LOC)
cmd/demo/auth.go
func (as *AuthStore) IsAuthorized(sessionID string) bool {
	as.mu.Lock()
	defer as.mu.Unlock()
	sd, ok := as.sessions[sessionID]
	if !ok {
		return false
	}
	if sd.PIIExpiry.IsZero() || time.Now().After(sd.PIIExpiry) {
		return false
	}
	return true
}
SetRole method · go · L119-L124 (6 LOC)
cmd/demo/auth.go
func (as *AuthStore) SetRole(sessionID string, role Role) {
	as.mu.Lock()
	sd := as.getOrCreate(sessionID)
	sd.Role = role
	as.mu.Unlock()
}
GetRole method · go · L126-L134 (9 LOC)
cmd/demo/auth.go
func (as *AuthStore) GetRole(sessionID string) Role {
	as.mu.Lock()
	defer as.mu.Unlock()
	sd, ok := as.sessions[sessionID]
	if !ok {
		return RoleAdmin
	}
	return sd.Role
}
About: code-quality intelligence by Repobility · https://repobility.com
EffectivePII method · go · L138-L156 (19 LOC)
cmd/demo/auth.go
func (as *AuthStore) EffectivePII(sessionID string) bool {
	as.mu.Lock()
	defer as.mu.Unlock()
	sd, ok := as.sessions[sessionID]
	if !ok {
		return false
	}
	switch sd.Role {
	case RoleAuditor, RoleCustomerService:
		return true
	case RoleReadOnly:
		return false
	default: // Admin
		if sd.PIIExpiry.IsZero() || time.Now().After(sd.PIIExpiry) {
			return false
		}
		return true
	}
}
registerBankAppAPI function · go · L14-L90 (77 LOC)
cmd/demo/bankapp_api.go
func registerBankAppAPI(state *DemoState) {
	http.HandleFunc("/api/customers", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(state.bankAppCustomerList())
	})

	http.HandleFunc("/api/customer/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
		// Parse: /api/customer/{id}/accounts or /api/customer/{id}/transactions
		path := strings.TrimPrefix(r.URL.Path, "/api/customer/")
		parts := strings.SplitN(path, "/", 2)
		if len(parts) == 0 || parts[0] == "" {
			http.NotFound(w, r)
			return
		}
		custID := parts[0]
		sub := ""
		if len(parts) > 1 {
			sub = parts[1]
		}

		w.Header().Set("Content-Type", "application/json")
		switch {
		case sub == "accounts":
			resp := state.bankAppAccounts(custID)
		
bankAppCustomerList method · go · L66-L80 (15 LOC)
cmd/demo/bankapp_data.go
func (ds *DemoState) bankAppCustomerList() []apiCustomer {
	ds.mu.Lock()
	customers := make([]CustomerRecord, len(ds.customers))
	copy(customers, ds.customers)
	ds.mu.Unlock()

	result := make([]apiCustomer, len(customers))
	for i, c := range customers {
		result[i] = apiCustomer{
			ID:   c.ID,
			Name: ds.lookupName(c.ID),
		}
	}
	return result
}
bankAppAccounts method · go · L82-L117 (36 LOC)
cmd/demo/bankapp_data.go
func (ds *DemoState) bankAppAccounts(custID string) *apiAccountsResponse {
	ds.mu.Lock()
	var cust *CustomerRecord
	for i := range ds.customers {
		if ds.customers[i].ID == custID {
			c := ds.customers[i]
			cust = &c
			break
		}
	}
	ds.mu.Unlock()

	if cust == nil {
		return nil
	}

	resp := &apiAccountsResponse{
		CustomerID:   cust.ID,
		CustomerName: ds.lookupName(cust.ID),
	}
	for _, a := range cust.Accounts {
		resp.Accounts = append(resp.Accounts, apiAccount{
			ProductName: a.ProductName,
			Family:      string(a.Family),
			Rate:        a.Rate,
			Balance:     a.Balance,
			Interest:    a.Interest,
		})
		if a.Family == gbp.FamilySavings {
			resp.TotalSavings += a.Balance
		} else {
			resp.TotalLending += a.Balance
		}
	}
	return resp
}
bankAppTransactions method · go · L119-L140 (22 LOC)
cmd/demo/bankapp_data.go
func (ds *DemoState) bankAppTransactions(custID string, page int) apiTransactionsResponse {
	entries, total := ds.CustomerTransactions(custID, page, txPerPage)
	apiEntries := make([]apiTxEntry, len(entries))
	for i, tx := range entries {
		apiEntries[i] = apiTxEntry{
			ID:          tx.ID,
			Date:        tx.Date.Format("2006-01-02"),
			ProductName: tx.ProductName,
			Type:        tx.Type.String(),
			Amount:      tx.Amount,
			Balance:     tx.Balance,
			Reference:   tx.Reference,
		}
	}
	return apiTransactionsResponse{
		CustomerID: custID,
		Page:       page,
		TotalCount: total,
		PerPage:    txPerPage,
		Entries:    apiEntries,
	}
}
bankAppProductDetail method · go · L142-L172 (31 LOC)
cmd/demo/bankapp_data.go
func (ds *DemoState) bankAppProductDetail(custID string, accountIdx int) *apiProductDetail {
	ds.mu.Lock()
	var cust *CustomerRecord
	for i := range ds.customers {
		if ds.customers[i].ID == custID {
			c := ds.customers[i]
			cust = &c
			break
		}
	}
	ds.mu.Unlock()

	if cust == nil || accountIdx < 0 || accountIdx >= len(cust.Accounts) {
		return nil
	}

	a := cust.Accounts[accountIdx]
	return &apiProductDetail{
		CustomerID:   cust.ID,
		CustomerName: ds.lookupName(cust.ID),
		AccountIndex: accountIdx,
		ProductName:  a.ProductName,
		Family:       string(a.Family),
		Rate:         a.Rate,
		Balance:      a.Balance,
		Interest:     a.Interest,
		SortCode:     a.SortCode,
		AccountNum:   a.AccountNum,
		OpenDate:     a.OpenDate.Format("2006-01-02"),
	}
}
bankAppProductTransactions method · go · L174-L195 (22 LOC)
cmd/demo/bankapp_data.go
func (ds *DemoState) bankAppProductTransactions(custID string, accountIdx, page int) apiTransactionsResponse {
	entries, total := ds.ProductTransactions(custID, accountIdx, page, txPerPage)
	apiEntries := make([]apiTxEntry, len(entries))
	for i, tx := range entries {
		apiEntries[i] = apiTxEntry{
			ID:          tx.ID,
			Date:        tx.Date.Format("2006-01-02"),
			ProductName: tx.ProductName,
			Type:        tx.Type.String(),
			Amount:      tx.Amount,
			Balance:     tx.Balance,
			Reference:   tx.Reference,
		}
	}
	return apiTransactionsResponse{
		CustomerID: custID,
		Page:       page,
		TotalCount: total,
		PerPage:    txPerPage,
		Entries:    apiEntries,
	}
}
registerBankAppRoutes function · go · L15-L91 (77 LOC)
cmd/demo/bankapp.go
func registerBankAppRoutes(state *DemoState, appCtrl *lofigui.Controller) {
	appPage := func(w http.ResponseWriter, content string) {
		appCtrl.RenderTemplate(w, pongo2.Context{
			"results": content,
		})
	}

	http.HandleFunc("/app/", func(w http.ResponseWriter, r *http.Request) {
		path := strings.TrimPrefix(r.URL.Path, "/app/")

		// POST /app/login — simulated login redirect
		if path == "login" && r.Method == "POST" {
			r.ParseForm()
			custID := r.FormValue("customer_id")
			if custID == "" {
				http.Redirect(w, r, "/app/", http.StatusSeeOther)
				return
			}
			http.Redirect(w, r, "/app/customer/"+custID, http.StatusSeeOther)
			return
		}

		if r.Method != "GET" {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}

		// /app/ — login screen
		if path == "" {
			appPage(w, state.buildAppLoginHTML())
			return
		}

		// /app/customer/{id}[/sub...]
		if !strings.HasPrefix(path, "customer/") {
			http.NotFound(w, r)
			return
		}
		rest := strings.
Repobility · open methodology · https://repobility.com/research/
phoneHeader function · go · L9-L18 (10 LOC)
cmd/demo/bankapp_render.go
func phoneHeader(title, subtitle string) string {
	var s strings.Builder
	s.WriteString(`<div class="phone-header">`)
	fmt.Fprintf(&s, `<p class="title">%s</p>`, title)
	if subtitle != "" {
		fmt.Fprintf(&s, `<p class="subtitle">%s</p>`, subtitle)
	}
	s.WriteString(`</div>`)
	return s.String()
}
phoneNav function · go · L21-L41 (21 LOC)
cmd/demo/bankapp_render.go
func phoneNav(custID, activeTab string) string {
	var s strings.Builder
	s.WriteString(`<div class="phone-nav">`)

	tabs := []struct {
		Icon, Label, Href, Key string
	}{
		{"&#127968;", "Home", "/app/", "home"},
		{"&#128179;", "Accounts", fmt.Sprintf("/app/customer/%s", custID), "accounts"},
		{"&#128196;", "Activity", fmt.Sprintf("/app/customer/%s/transactions", custID), "transactions"},
	}
	for _, t := range tabs {
		cls := ""
		if t.Key == activeTab {
			cls = ` class="is-active"`
		}
		fmt.Fprintf(&s, `<a href="%s"%s><span>%s</span>%s</a>`, t.Href, cls, t.Icon, t.Label)
	}
	s.WriteString(`</div>`)
	return s.String()
}
buildAppLoginHTML method · go · L45-L79 (35 LOC)
cmd/demo/bankapp_render.go
func (ds *DemoState) buildAppLoginHTML() string {
	customers := ds.bankAppCustomerList()

	var s strings.Builder
	s.WriteString(`<div class="phone-header" style="padding:24px 16px 20px;text-align:center">`)
	s.WriteString(`<div style="width:56px;height:56px;border-radius:50%;background:rgba(255,255,255,0.2);margin:0 auto 8px;display:flex;align-items:center;justify-content:center;font-size:1.8rem">&#127974;</div>`)
	s.WriteString(`<p class="title" style="font-size:1.3rem">Model Bank</p>`)
	s.WriteString(`<p class="subtitle" style="font-size:0.75rem">Internet Banking</p>`)
	s.WriteString(`</div>`)
	s.WriteString(`<div class="phone-body">`)

	if len(customers) == 0 {
		s.WriteString(`<p style="color:#7a7a7a;text-align:center;margin-top:2rem">No customers yet. Start the simulation in the admin dashboard.</p>`)
	} else {
		// Quick-access customer cards (skip login form in WASM — just show customer list)
		limit := len(customers)
		if limit > 50 {
			limit = 50
		}
		for _, c := range custo
page 1 / 5next ›