update_proposals.rkt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. #lang racket/base
  2. ;; This program updates an entry in a proposals database.
  3. (require racket/cmdline
  4. racket/date
  5. racket/list
  6. db
  7. "config.rkt") ; load configuration file
  8. (define progname "update_proposals.rkt")
  9. ; give us the date in YYYY-MM-DD format
  10. (date-display-format 'iso-8601)
  11. ; parameters
  12. ; if #t, use proposal type, submitting organiation, solicitation/call, and
  13. ; telescope name from the most recently submitted (i.e., highest ID) proposal
  14. (define reuse-params (make-parameter #f))
  15. ; set up command line arguments
  16. (define mode (command-line
  17. #:program "update_proposals"
  18. #:once-each
  19. [("-r" "--reuse-parameters") "Reuse/auto-fill proposal type, submitting organization, solicitation/call and telescope name from the most recently added proposal."
  20. (reuse-params #t)]
  21. #:args ([updatetype "help"]) ; (add, update, list-open, list-closed, help)
  22. updatetype))
  23. ; print some help
  24. (define (printhelp)
  25. (displayln (string-append "Usage: "
  26. progname " MODE"))
  27. (newline)
  28. (displayln "Where MODE is one of:")
  29. (displayln " add\t\t - add new proposal to database.")
  30. (displayln " update\t\t - update a proposal with results.")
  31. (displayln " stats\t\t - print summary statistics.")
  32. (displayln " list-open\t - Show all submitted (but not resolved) proposals.")
  33. (displayln " list-closed\t - Show all resolved (accepted and rejected) proposals.")
  34. (displayln " list-accepted\t - Show accepted proposals.")
  35. (displayln " list-rejected\t - Show rejected proposals.")
  36. (displayln " help\t\t - Show this help message.")
  37. (newline)
  38. (displayln "Copyright 2019-2020, 2022-2023 George C. Privon"))
  39. ; set up a condensed prompt for getting information
  40. (define (getinput prompt)
  41. (write-string prompt)
  42. (write-string ": ")
  43. (read-line))
  44. ; take an input result from the SQL search and write it out nicely
  45. (define (printentry entry issub)
  46. (displayln (string-append
  47. (number->string (vector-ref entry 0))
  48. ": "
  49. (vector-ref entry 1)
  50. "("
  51. (vector-ref entry 2)
  52. "; PI: "
  53. (vector-ref entry 4)
  54. (if (not issub)
  55. (string-append "; "
  56. (vector-ref entry 5))
  57. "")
  58. ") \""
  59. (vector-ref entry 3)
  60. "\"")))
  61. (define (get-last-proposal-call conn)
  62. (println "Adopting proposal information from last submission")
  63. (last-proposal-call conn))
  64. ; get information from the most recent proposal submission
  65. (define (last-proposal-call conn)
  66. (query-list conn "SELECT type, organization, solicitation, telescope FROM proposals ORDER BY id DESC LIMIT 1"))
  67. ; add a new proposal to the database
  68. (define (addnew conn)
  69. ; full list of input fileds that we will need (these will be the prompts
  70. ; to the user)
  71. (define input-fields (list "Proposal type"
  72. "Submitting Organization"
  73. "Solicitation/Call"
  74. "Telescope"
  75. "Proposal Title"
  76. "PI"
  77. "CoIs"
  78. "Submit date (YYYY-MM-DD)"
  79. "Organization's propsal ID"))
  80. (displayln "Adding new proposal to database.")
  81. ; assume all these proposals are submitted, don't ask the user
  82. (define status "submitted")
  83. ; get the proposal information
  84. (define propinfo
  85. (cond
  86. ; if we're re-using parameters, get info from the most recent submission
  87. ; and append the user input for the remaining fields
  88. [(reuse-params) (append (get-last-proposal-call conn)
  89. (map getinput (list-tail input-fields 4)))]
  90. ; if not using previous information, ask the user for all inputs
  91. [else (map getinput input-fields)]))
  92. ; do the INSERT into the Sqlite database
  93. (let* ([add-proposal-info
  94. (prepare conn "INSERT INTO proposals (type, organization, solicitation, telescope, title, PI, CoI, submitdate, orgpropID, status) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")])
  95. (query-exec conn (bind-prepared-statement add-proposal-info
  96. (flatten (list propinfo status))))))
  97. ; update an entry with new status (accepted, rejected, etc.)
  98. (define (update conn ID)
  99. (displayln (string-append "Updating entry " (number->string ID)))
  100. (define entry (query-maybe-row conn "SELECT * FROM proposals WHERE ID=?" ID))
  101. (cond
  102. [(eq? #f entry) (error "Invalid ID. Row not found")])
  103. (displayln (string-append "Current status is: "
  104. (vector-ref entry 9)
  105. " ("
  106. (vector-ref entry 10)
  107. ")"))
  108. (write-string "Please enter new status: ")
  109. (define newstatus (read-line))
  110. ;(write-string "Please enter date of updated status (leave blank to use current date): ")
  111. ;(define resdate (read-line))
  112. (define resdate (date->string (seconds->date (current-seconds))))
  113. ; now update that entry
  114. (query-exec conn "UPDATE proposals SET status=?, resultdate=? WHERE ID=?"
  115. newstatus
  116. resdate
  117. ID)
  118. (displayln "Entry updated."))
  119. ; retrieve and print proposals based on status
  120. (define (printprop conn
  121. #:submitted issub
  122. #:accepted [isaccept #f]
  123. #:rejected [isrej #f])
  124. (define selclause (string-append
  125. (if issub
  126. "status='submitted'"
  127. "status!='submitted'")
  128. ; find things that are "accepted" or "funded"
  129. (if isaccept
  130. " AND status LIKE '%Accepted%' OR status LIKE '%Funded%'"
  131. "")
  132. ; find things that are "rejected"
  133. (if isrej
  134. " AND status LIKE '%Rejected%'"
  135. "")))
  136. (define props (query-rows conn (string-append "SELECT ID,telescope,solicitation,title,PI,status FROM proposals WHERE "
  137. selclause)))
  138. (display (string-append (number->string (length props))))
  139. (if issub
  140. (displayln " pending proposals found.")
  141. (displayln " resolved proposals found."))
  142. (newline)
  143. ; print all the unresolved proposals to the screen
  144. (map (lambda (prop)
  145. (printentry prop issub))
  146. props))
  147. ; find proposals waiting for updates
  148. (define (findpending conn)
  149. (write-string "Updating proposals. ")
  150. (printprop conn #:submitted #t)
  151. (write-string "Please enter a proposal number to edit (enter 0 or nothing to exit): ")
  152. (define upID (read-line))
  153. (cond
  154. [(eq? (string->number upID) 0) (exit)]
  155. [(string->number upID) (update conn (string->number upID))]
  156. [else (exit)]))
  157. ; compute and print some statistics about proposals:
  158. ; - total number of proposals (since earliest date)
  159. ; - number of pending proposals
  160. ; - number of successful proposals and corresponding fraction of the total that are not pending
  161. ; - number of rejected proposals and corresponding fraction of the total that are not pending
  162. ; - do the above two for all proposals and for proposals that I PI'ed. (TODO: PI'ed separation not yet implemented)
  163. (define (proposal-stats conn)
  164. (displayln "Proposal statistics to date.\n")
  165. ; do statistics for all proposals
  166. (displayln "\tAll proposals")
  167. (let-values ([(Nprop Npending Nrejected) (get-stats conn)])
  168. (print-stats Nprop Npending Nrejected))
  169. ; do statistics for proposals as PI
  170. (displayln (string-append "\n\tPI'ed Proposals (by "
  171. PIname
  172. ")"))
  173. (let-values ([(Nprop Npending Nrejected) (get-stats conn #:selclause (string-append "PI LIKE '%"
  174. PIname
  175. "%'"))])
  176. (print-stats Nprop Npending Nrejected))
  177. )
  178. ; given numbers, format somewhat pretty output of proposal statistics
  179. (define (print-stats Nprop Npending Nrejected)
  180. (display (number->string Nprop))
  181. (display "\ttotal proposals entered (")
  182. (display (number->string (- Nprop Npending)))
  183. (display " proposals resolved; ")
  184. (display (number->string Npending))
  185. (displayln " proposals pending).")
  186. (define Naccepted (- Nprop Npending Nrejected))
  187. (display (number->string Naccepted))
  188. (display "\tproposals accepted (f=")
  189. (display (number->string (/ Naccepted
  190. (- Nprop Npending))))
  191. (displayln " of resolved proposals).")
  192. (display (number->string Nrejected))
  193. (display "\tproposals rejected (f=")
  194. (display (number->string (/ Nrejected
  195. (- Nprop Npending))))
  196. (displayln " of resolved proposals)."))
  197. ; retrieve proposal numbers from the database, for statistics
  198. (define (get-stats conn #:selclause [extrasel ""])
  199. (define mysel (if (eq? 0 (string-length extrasel))
  200. ""
  201. (string-append " AND "
  202. extrasel)))
  203. (define mysel-one (if (eq? 0 (string-length extrasel))
  204. ""
  205. (string-append " WHERE "
  206. extrasel)))
  207. (values
  208. ; total number of proposals
  209. (length (query-rows conn
  210. (string-append "SELECT ID FROM proposals"
  211. mysel-one)))
  212. ; Number of pending proposals
  213. (length (query-rows conn
  214. (string-append "SELECT ID FROM proposals WHERE status='submitted'"
  215. mysel)))
  216. ; Number of rejected proposals
  217. (length (query-rows conn
  218. (string-append "SELECT ID FROM proposals WHERE status LIKE '%rejected%'"
  219. mysel)))))
  220. ; make sure we can use the sqlite3 connection
  221. (define checkdblib
  222. (cond (not (sqlite3-available?))
  223. (error "Sqlite3 library not available.")))
  224. ; catch-all routine for when we need to access the database
  225. (define (querysys mode)
  226. ; first see if we need write access or if we can use read only
  227. (define dbmode (if (or (regexp-match "add" mode)
  228. (regexp-match "update" mode))
  229. 'read/write
  230. 'read-only))
  231. ; open the database with the specified mode
  232. (define conn (sqlite3-connect #:database dbloc
  233. #:mode dbmode))
  234. ; now handle the user's request
  235. (cond
  236. [(regexp-match "add" mode) (addnew conn)]
  237. [(regexp-match "update" mode) (findpending conn)]
  238. [(regexp-match "stats" mode) (proposal-stats conn)]
  239. [(regexp-match "list-open" mode) (printprop conn #:submitted #t)]
  240. [(regexp-match "list-closed" mode) (printprop conn #:submitted #f)]
  241. [(regexp-match "list-accepted" mode) (printprop conn #:submitted #f #:accepted #t)]
  242. [(regexp-match "list-rejected" mode) (printprop conn #:submitted #f #:rejected #t)]
  243. [else (error (string-append "Unknown mode. Try " progname " help\n\n"))])
  244. ; close the databse
  245. (disconnect conn))
  246. ; First see if the user wants help or if we need to pass to one of the other
  247. ; procedures
  248. (cond
  249. [(regexp-match "help" mode) (printhelp)]
  250. [else (querysys mode)])