|
|
|
(import chicken scheme srfi-1)
|
|
|
|
(use awful
|
|
|
|
(only waffle waffle-sxml->html add-widget get-widget widgets widget-rules)
|
|
|
|
numbers scss spiffy-request-vars format)
|
|
|
|
|
|
|
|
;; Widgets are like composable HTML templates but with SXML
|
|
|
|
(include "widgets") ; common web app widgets
|
|
|
|
|
|
|
|
;; Needed for interactive development because widgets are thread local
|
|
|
|
(define (waffle-sxml->html* sxml)
|
|
|
|
(parameterize ((widgets *widgets*) (widget-rules *widget-rules*))
|
|
|
|
(waffle-sxml->html sxml)))
|
|
|
|
|
|
|
|
;; Standardize all pages
|
|
|
|
(define (defpage path thunk)
|
|
|
|
(define-page path (lambda ()
|
|
|
|
(with-output-to-string
|
|
|
|
(lambda ()
|
|
|
|
(waffle-sxml->html* (thunk)))))
|
|
|
|
headers: (with-output-to-string
|
|
|
|
(lambda ()
|
|
|
|
(waffle-sxml->html*
|
|
|
|
`((stylesheet (@ (path "normalize.css")))
|
|
|
|
(stylesheet (@ (path "foundation.min.css")))
|
|
|
|
(stylesheet (@ (path "app.css")))
|
|
|
|
))))
|
|
|
|
no-session: #t))
|
|
|
|
|
|
|
|
(widget
|
|
|
|
'menu
|
|
|
|
``(group-box (@ (title ,title))
|
|
|
|
,@(map (lambda (n)
|
|
|
|
`((a (@ (href ,(conc "/?type=" type "&n=" n))) ,n)
|
|
|
|
(br) (br)))
|
|
|
|
(iota len)))
|
|
|
|
'((title "") (type "none") (len 0)))
|
|
|
|
|
|
|
|
(widget
|
|
|
|
'menu-bar
|
|
|
|
``(div
|
|
|
|
(menu (@ (title "Investors") (type i) (len ,len-i)))
|
|
|
|
(menu (@ (title "Companies") (type c) (len ,len-c))))
|
|
|
|
'((len-i 0) (len-c 0)))
|
|
|
|
|
|
|
|
(widget
|
|
|
|
'schedule-table
|
|
|
|
``(group-box
|
|
|
|
(@ (title ,title))
|
|
|
|
(cols (div
|
|
|
|
(h5 "Schedule")
|
|
|
|
(table (@ (class "schedule"))
|
|
|
|
(tr (th "Time") (th "Meeting with"))
|
|
|
|
,@contents))))
|
|
|
|
'((title "")))
|
|
|
|
|
|
|
|
(widget
|
|
|
|
'shuffle-bar
|
|
|
|
``(cols (center
|
|
|
|
(form (@ (action "/shuffle"))
|
|
|
|
(button (@ (type "submit") (class "small")) "Shuffle"))))
|
|
|
|
'())
|
|
|
|
|
|
|
|
;; add support for defaults to request variables
|
|
|
|
(define ((as-number/default default) var vars/vals)
|
|
|
|
(or (as-number var vars/vals) default))
|
|
|
|
(define ((as-symbol/default default) var vars/vals)
|
|
|
|
(or (as-symbol var vars/vals) default))
|
|
|
|
|
|
|
|
;;;;;; PAGES ;;;;;;
|
|
|
|
|
|
|
|
;; NOTE: company and investor relations are stored in a-lists where
|
|
|
|
;; the symbol 'i references an investor, 'c a company, and 's a score.
|
|
|
|
(defpage "/"
|
|
|
|
(lambda ()
|
|
|
|
(define (time t)
|
|
|
|
(let* ((rem (remainder t 60))
|
|
|
|
(hr (/ (- t rem) 60)))
|
|
|
|
(format "~2D:~2,'0D" hr rem)))
|
|
|
|
(define (schedule-for type n)
|
|
|
|
(map (lambda (round) (find (lambda (x) (eq? (alist-ref type x) n)) round)) *schedule*))
|
|
|
|
(define (slot type meeting t)
|
|
|
|
`(tr (td ,(time t)) (td ,(if meeting (alist-ref type meeting) '-))))
|
|
|
|
(define (type-title t) (if (eq? t 'c) "Company" "Investor"))
|
|
|
|
|
|
|
|
(with-request-vars ((n (as-number/default 0)) (type (as-symbol/default 'c)))
|
|
|
|
(let ((start-time (* 9 60)))
|
|
|
|
`((row
|
|
|
|
(col (@ (width 3))
|
|
|
|
(menu-bar (@ (len-i ,(length *investors*))
|
|
|
|
(len-c ,(length *companies*))))
|
|
|
|
(shuffle-bar))
|
|
|
|
(col (@ (width 9))
|
|
|
|
(schedule-table
|
|
|
|
(@ (title ,(conc (type-title type) " " n)))
|
|
|
|
,@(let ((schedules (schedule-for type n)))
|
|
|
|
(map (lambda (schedule t)
|
|
|
|
(slot (if (eq? type 'c) 'i 'c) schedule t))
|
|
|
|
schedules
|
|
|
|
(iota (length schedules) start-time 20)))))))))))
|
|
|
|
|
|
|
|
(define-page "/shuffle"
|
|
|
|
(lambda ()
|
|
|
|
(regen-schedule)
|
|
|
|
(redirect-to "/?type=c&n=0"))
|
|
|
|
no-session: #t)
|
|
|
|
|
|
|
|
(define (run-awful) ; for emacs interactive development
|
|
|
|
(thread-start!
|
|
|
|
(lambda ()
|
|
|
|
(awful-start (lambda () (void)) port: 8080))))
|
|
|
|
|
|
|
|
;;;;;; CSS ;;;;;;
|
|
|
|
(define (scss->file path scss)
|
|
|
|
(with-output-to-file path
|
|
|
|
(lambda () (write-css scss))))
|
|
|
|
|
|
|
|
(scss->file
|
|
|
|
"res/css/app.css"
|
|
|
|
'(css+
|
|
|
|
((.schedule li) (white-space pre))))
|
|
|
|
|
|
|
|
|
|
|
|
;;;;;; Scheduling ;;;;;;
|
|
|
|
(define (shuffle! v)
|
|
|
|
(do ((n (vector-length v) (- n 1)))
|
|
|
|
((zero? n) v)
|
|
|
|
(let* ((r (random n)) (t (vector-ref v r)))
|
|
|
|
(vector-set! v r (vector-ref v (- n 1)))
|
|
|
|
(vector-set! v (- n 1) t))))
|
|
|
|
|
|
|
|
(define (gen-ranks n)
|
|
|
|
(vector->list (shuffle! (list->vector n))))
|
|
|
|
|
|
|
|
;; randomly generate lists of rankings
|
|
|
|
(define (gen-ranks-list n)
|
|
|
|
(map (lambda (i) (gen-ranks (iota n))) (iota n)))
|
|
|
|
|
|
|
|
;; map ranking lists to companies and investors
|
|
|
|
;; score the combination by adding ranks together
|
|
|
|
;; higher is better.
|
|
|
|
;; As noted earlier: 'i == investor and 'c == company,
|
|
|
|
;; 's == score.
|
|
|
|
(define (score companies investors)
|
|
|
|
(map (lambda (c i)
|
|
|
|
(map (lambda (j)
|
|
|
|
`((c . ,i)
|
|
|
|
(i . ,j)
|
|
|
|
(s .
|
|
|
|
,(+ (list-ref (list-ref investors j) i)
|
|
|
|
(list-ref c j)))))
|
|
|
|
(iota (length companies))))
|
|
|
|
companies (iota (length investors))))
|
|
|
|
|
|
|
|
(define (sort-scores companies investors)
|
|
|
|
(map
|
|
|
|
(lambda (company)
|
|
|
|
(sort
|
|
|
|
company
|
|
|
|
(lambda (x1 x2)
|
|
|
|
(> (alist-ref 's x1) (alist-ref 's x2)))))
|
|
|
|
(score companies investors)))
|
|
|
|
|
|
|
|
;; greedily find best match and return unmatched combinations
|
|
|
|
(define (match/remainder taken ranks)
|
|
|
|
(let ((mtch (find (lambda (x) (not (memq (alist-ref 'i x) taken))) ranks)))
|
|
|
|
(receive (head tail) (span (lambda (x) (not (eq? x mtch))) ranks)
|
|
|
|
(if (null? tail)
|
|
|
|
(values #f head) ; no match found
|
|
|
|
(values (car tail) (append head (cdr tail)))))))
|
|
|
|
|
|
|
|
(define (allocate-round ranks)
|
|
|
|
(let loop ((taken '()) (matches '()) (ranks ranks) (left '()))
|
|
|
|
(if (null? ranks)
|
|
|
|
(values (reverse matches) (reverse left))
|
|
|
|
(receive (mtch rem) (match/remainder taken (car ranks))
|
|
|
|
(if mtch
|
|
|
|
(loop (cons (alist-ref 'i mtch) taken)
|
|
|
|
(cons mtch matches) (cdr ranks) (cons rem left))
|
|
|
|
(loop taken matches (cdr ranks) (cons rem left)))))))
|
|
|
|
|
|
|
|
(define (make-schedule ranks)
|
|
|
|
(let loop ((ranks ranks) (matches '()))
|
|
|
|
(if (null? ranks)
|
|
|
|
(reverse matches)
|
|
|
|
(receive (mtchs new-ranks) (allocate-round ranks)
|
|
|
|
(if (null? mtchs)
|
|
|
|
(reverse matches)
|
|
|
|
(loop new-ranks (cons mtchs matches)))))))
|
|
|
|
|
|
|
|
(define *schedule* '())
|
|
|
|
(define *companies* '())
|
|
|
|
(define *investors* '())
|
|
|
|
|
|
|
|
(define (regen-schedule)
|
|
|
|
(set! *companies* (gen-ranks-list 10))
|
|
|
|
(set! *investors* (gen-ranks-list 10))
|
|
|
|
(set! *schedule* (make-schedule (sort-scores *companies* *investors*))))
|
|
|
|
(regen-schedule)
|