update_proposals.rkt 13 KB

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