Ballpark Book LOE

by Daniel Lamb

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<div ng-app="Author">
    <form ng-controller="LOE">
        <label>Start date</label>
        <input type="date" ng-model="start" />
        <label>Number of pages to write</label>
        <input type="number" min="1" max="500" ng-model="pages" />
        <label>Number of hours to write one page</label>
        <input type="number" min="1" max="72" ng-model="hoursPerPage" />
        <label>Number of hours in a work day you will write</label>
        <input type="number" min="1" max="24" ng-model="hoursPerDay" />
        <label>Hourly consultant rate $</label>
        <input type="number" min="20" max="900" ng-model="consultantRate" />
        <label>Estimated retail book price $</label>
        <input type="number" min="1" max="100" ng-model="retailPrice" />
        <label>O'Reilly royalty percentage </label>
        <input type="number" min="1" max="20" ng-model="royalty" />
        <label class="heading">
            Your {{chapterCount | number}} chapter book should be done {{relative}} on {{date}}
        </label>
        <label class="subheading">Cost</label>
        <label>
            You could have made {{cost | currency}} consulting for {{hours | number:0}} hours.
        </label>
        <label>
            With O'Reilly you'll make {{royalties | currency}} on each book, and would have to sell {{breakEven | number:0}} books to make that much.
        </label>
        <label>
            With LeanPub you'll make {{lpRoyalties | currency}} on each book, and only have to sell {{lpBreakEven | number:0}} books to make that much.
        </label>
        <label class="subheading">Deadlines</label>
        <ul ng-repeat="chapter in chapters">
            <li>Chapter {{$index + 1}} due {{chapter.relative}} on {{chapter.date}}</li>
        </ul>
    </form>
</div>

CSS

label, input, ul {
    float: left;
}
label {
    clear: left;
    padding-right: 10px;
}
ul {
    clear: left;
    margin: 0;
    padding: 0 0 5px 10px;
    list-style-type: none;
}
.heading {
    margin-top: 20px;
    font-size: 25px;
}
.subheading {
    margin: 20px 0 5px 0;
    font-size: 20px;
}

JavaScript

angular.module('Author', []).controller('LOE', ['$scope', function ($scope) {
    'use strict';

    var PAGES_PER_CHAPTER = 25,
        WHOLESALE_DISCOUNT = 0.52;

    // assume starting today
    $scope.start = new Date();

    // number of pages to write in the book
    $scope.pages = 250;

    // number of hours to write one page
    $scope.hoursPerPage = 2;

    // number of hours in a work day you will write
    $scope.hoursPerDay = 3;

    // consultant hourly rate
    $scope.consultantRate = 80;

    // estimated full retail price
    $scope.retailPrice = 24.93;

    // estimated royalty (typically between 10 and 15 percent)
    $scope.royalty = 12;

    // calculate the number of days based on the work load settings
    function calcDays(pages) {
        return pages * $scope.hoursPerPage / $scope.hoursPerDay;
    }

    // add weekends to the total number of days
    function excludeWeekends(days) {
        return days += (days / 5 * 2);
    }

    // convert days to relative time and estimated date
    function convertDays(days) {
        return [
            moment($scope.start).add(days, 'days').fromNow(),
            moment($scope.start).add(days, 'days').format('MMMM Do YYYY')
        ];
    }

    // calculate royalty income
    function calcNetRoyalty() {
        // estimate wholesale price as 52% off retail
        var wholesalePrice = $scope.retailPrice - ($scope.retailPrice * WHOLESALE_DISCOUNT);
        return wholesalePrice * ($scope.royalty / 100) ;
    }

    // watch for model changes
    $scope.$watchGroup([
        'start', 'pages', 'hoursPerPage', 'hoursPerDay',
        'consultantRate', 'retailPrice', 'royalty'
    ], function () {

        // calculate the number of days
        var days = calcDays($scope.pages);

        // calculate hourly cost
        $scope.hours = (days * $scope.hoursPerDay);
        $scope.cost = $scope.hours * $scope.consultantRate;

        // calculate O'Reilly royalty income
        $scope.royalties =...