<commit_before>import {getOutboxInitialState, Outbox} from './outbox.js'
import {Duration, Time} from './time.js'
import {
  getLast,
  lowerBound,
  makeObject,
  mod,
  range,
  switchOnKey,
  upperBound,
} from './util.js'

function getInitialState() {
  const initialTarget = target => ({target, mtime: new Time(-Infinity)})
  return {
    outbox: getOutboxInitialState(),
    monitored: true,
    targets: {
      weekly: initialTarget(Duration.hours(40)),
      daily: initialTarget(Duration.hours(8)),
      session: initialTarget(Duration.hours(1)),
      rest: initialTarget(Duration.minutes(5)),
    },
    lastActives: {},
    timelines: {},
    sharedTimeline: [],
    firstWeek: Infinity,
    dailyValues: {},
  }
}

function dayOfTime(time) {
  return Math.floor(time.sinceEpoch.plus(time.zone).minus({hours: 4}).days)
}

function timelineLowerBound(timeline, start) {
  return lowerBound(timeline, start.milliseconds, ({end}) => end.milliseconds)
}

function markTimeline(timeline, start, end) {
  const lower = timelineLowerBound(timeline, start)
  const upper = upperBound(
    timeline,
    end.milliseconds,
    ({start}) => start.milliseconds,
  )
  if (lower < upper) {
    start = start.clampHigh(timeline[lower].start)
    end = end.clampLow(timeline[upper - 1].end)
  }
  timeline.splice(lower, upper - lower, {start, end})
}

function clearTimeline(timeline, start) {
  const lower = timelineLowerBound(timeline, start)
  let cleared = Duration.sum(
    timeline.slice(lower).map(({start, end}) => end.minus(start)),
  )
  const newSpans = []
  if (lower < timeline.length) {
    const lowerStart = timeline[lower].start
    if (lowerStart.lessEqual(start)) {
      newSpans.push({start: lowerStart, end: start})
      cleared = cleared.minus(start.minus(lowerStart))
    }
  }
  timeline.splice(lower, timeline.length - lower, ...newSpans)
  return cleared
}

export function restAdvised({session, rest}) {
  const restBenefit = session.value.dividedBy(session.target) * 0.75
  const restCost = rest.target.minus(rest.value).dividedBy(rest.target) - 0.25
  return !rest.attained && restBenefit >= restCost
}

export class Metric {
  constructor({name, value = null, target}) {
    this.name = name
    this.value = value
    this.target = target
    this.advised = true
  }

  get attained() {
    return this.value.greaterEqual(this.target)
  }

  get ratio() {
    return this.value.dividedBy(this.target)
  }
}

export class Model {<commit_msg>Don't advise rest if it's zero.<commit_after>import {getOutboxInitialState, Outbox} from './outbox.js'
import {Duration, Time} from './time.js'
import {
  getLast,
  lowerBound,
  makeObject,
  mod,
  range,
  switchOnKey,
  upperBound,
} from './util.js'

function getInitialState() {
  const initialTarget = target => ({target, mtime: new Time(-Infinity)})
  return {
    outbox: getOutboxInitialState(),
    monitored: true,
    targets: {
      weekly: initialTarget(Duration.hours(40)),
      daily: initialTarget(Duration.hours(8)),
      session: initialTarget(Duration.hours(1)),
      rest: initialTarget(Duration.minutes(5)),
    },
    lastActives: {},
    timelines: {},
    sharedTimeline: [],
    firstWeek: Infinity,
    dailyValues: {},
  }
}

function dayOfTime(time) {
  return Math.floor(time.sinceEpoch.plus(time.zone).minus({hours: 4}).days)
}

function timelineLowerBound(timeline, start) {
  return lowerBound(timeline, start.milliseconds, ({end}) => end.milliseconds)
}

function markTimeline(timeline, start, end) {
  const lower = timelineLowerBound(timeline, start)
  const upper = upperBound(
    timeline,
    end.milliseconds,
    ({start}) => start.milliseconds,
  )
  if (lower < upper) {
    start = start.clampHigh(timeline[lower].start)
    end = end.clampLow(timeline[upper - 1].end)
  }
  timeline.splice(lower, upper - lower, {start, end})
}

function clearTimeline(timeline, start) {
  const lower = timelineLowerBound(timeline, start)
  let cleared = Duration.sum(
    timeline.slice(lower).map(({start, end}) => end.minus(start)),
  )
  const newSpans = []
  if (lower < timeline.length) {
    const lowerStart = timeline[lower].start
    if (lowerStart.lessEqual(start)) {
      newSpans.push({start: lowerStart, end: start})
      cleared = cleared.minus(start.minus(lowerStart))
    }
  }
  timeline.splice(lower, timeline.length - lower, ...newSpans)
  return cleared
}

export function restAdvised({session, rest}) {
  const restBenefit = session.value.dividedBy(session.target) * 0.75
  const restCost = rest.target.minus(rest.value).dividedBy(rest.target) - 0.25
  return !rest.attained && rest.ratio && restBenefit >= restCost
}

export class Metric {
  constructor({name, value = null, target}) {
    this.name = name
    this.value = value
    this.target = target
    this.advised = true
  }

  get attained() {
    return this.value.greaterEqual(this.target)
  }

  get ratio() {
    return this.value.dividedBy(this.target)
  }
}

export class Model {
{'fraction_non_alphanumeric': 0.13860988348734432, 'fraction_numerical': 0.005222981116914423, 'mean_word_length': 3.7691570881226055, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1229124', 'n_tokens_mistral': 1668, 'n_tokens_neox': 1619, 'n_words': 340}