01 декабря 2011

Common Lisp. Белоруский экономический кризис.

Речь сегодня пойдет о том, что CL очень даже автоматизирует "бытовуху". Инфляция в РБ составила не менее 80% за год. Можно долго обсуждать с чем это связано, но лучше от этого не станет. До этого момента все мелко-крупные импортеры и без того все свои цены вычисляли в долларах, а теперь сюда еще и подтягиваются остальные участники белоруского "чуда".

Есть частное предприятие, оказывающее услуги населению и решившее, что цена часа услуги будет стоить 0.2 доллара. И теперь сответственно нужен журнал курсов валют. БД: postgresql, имеется доступ к интернету.

Задача: наладить sql таблицу postgresql, которая будет содержать данные о курсе доллара и автоматически добавлять в нее данные каждый день.

Создание журнала в БД postgresql с помощью postmodern:

(ql:quickload :postmodern)
(postmodern:connect-top-level "school" "user" "user" "localhost")
(postmodern:query "create table if not exists journal_currency_exchange (
                               _date date primary key,
                               _value decimal(10,2))")

S-sql это интересно, но для повседневной разработки визуальное разделение на хост язык и sql запросы благоразумнее.

Теперь необходимо этот журнал заполнить данными об изменениях курсов валют. Мне повезло: nbrb.by предоставляет xml-ку на запрос по некоторому url-у. Подробнее здесь: http://nbrb.by/statistics/Rates/XML/

Получение курсов доллара к белорусскому рублю за последний месяц, с помощью cl http клиента drakma.

Основной url: http://nbrb.by/Services/XmlExRatesDyn.aspx
Параметры:
curId - внутренний идентификатор валюты
fromDate, toDate - период для отчета

(ql:quickload :drakma)
(defvar xml-response (drakma:http-request "http://nbrb.by/Services/XmlExRatesDyn.aspx?curId=145&fromDate=11/1/2011&toDate=11/30/2011"))

Теперь необходимо разобрать полученную строку. Для этого есть cl xml парсер xmls:

CL-USER> (defvar parsed-xml (xmls:parse xml-response))
PARSED-XML 
CL-USER> parsed-xml
("Currency" (("toDate" "11/30/2011") ("fromDate" "11/01/2011") ("Id" "145"))
 ("Record" (("Date" "11/01/2011")) ("Rate" NIL "8450"))
 ("Record" (("Date" "11/02/2011")) ("Rate" NIL "8530"))
 ("Record" (("Date" "11/03/2011")) ("Rate" NIL "8580"))
 ("Record" (("Date" "11/04/2011")) ("Rate" NIL "8650"))
 ("Record" (("Date" "11/05/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/06/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/07/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/08/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/09/2011")) ("Rate" NIL "8700"))
 ("Record" (("Date" "11/10/2011")) ("Rate" NIL "8790"))
 ("Record" (("Date" "11/11/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/12/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/13/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/14/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/15/2011")) ("Rate" NIL "8770"))
 ("Record" (("Date" "11/16/2011")) ("Rate" NIL "8760"))
 ("Record" (("Date" "11/17/2011")) ("Rate" NIL "8760"))
 ("Record" (("Date" "11/18/2011")) ("Rate" NIL "8760"))
 ("Record" (("Date" "11/19/2011")) ("Rate" NIL "8740"))
 ("Record" (("Date" "11/20/2011")) ("Rate" NIL "8740"))
 ("Record" (("Date" "11/21/2011")) ("Rate" NIL "8740"))
 ("Record" (("Date" "11/22/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/23/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/24/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/25/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/26/2011")) ("Rate" NIL "8670"))
 ("Record" (("Date" "11/27/2011")) ("Rate" NIL "8670"))
 ("Record" (("Date" "11/28/2011")) ("Rate" NIL "8670"))
 ("Record" (("Date" "11/29/2011")) ("Rate" NIL "8640"))
 ("Record" (("Date" "11/30/2011")) ("Rate" NIL "8600")))

Теперь надо занятся тем, для чего лисп Маккарти и придумывал - обработкой списков. Фильтруем, оставляя только Record:

CL-USER> (defvar ya-parsed-xml (remove-if-not (lambda (value) (and (listp value) (stringp (car value)) (string= (car value) "Record"))) parsed-xml))
(("Record" (("Date" "11/01/2011")) ("Rate" NIL "8450"))
 ("Record" (("Date" "11/02/2011")) ("Rate" NIL "8530"))
 ("Record" (("Date" "11/03/2011")) ("Rate" NIL "8580"))
 ("Record" (("Date" "11/04/2011")) ("Rate" NIL "8650"))
 ("Record" (("Date" "11/05/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/06/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/07/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/08/2011")) ("Rate" NIL "8750"))
 ("Record" (("Date" "11/09/2011")) ("Rate" NIL "8700"))
 ("Record" (("Date" "11/10/2011")) ("Rate" NIL "8790"))
 ("Record" (("Date" "11/11/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/12/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/13/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/14/2011")) ("Rate" NIL "8850"))
 ("Record" (("Date" "11/15/2011")) ("Rate" NIL "8770"))
 ("Record" (("Date" "11/16/2011")) ("Rate" NIL "8760"))
 ("Record" (("Date" "11/17/2011")) ("Rate" NIL "8760"))
 ("Record" (("Date" "11/18/2011")) ("Rate" NIL "8760"))
 ("Record" (("Date" "11/19/2011")) ("Rate" NIL "8740"))
 ("Record" (("Date" "11/20/2011")) ("Rate" NIL "8740"))
 ("Record" (("Date" "11/21/2011")) ("Rate" NIL "8740"))
 ("Record" (("Date" "11/22/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/23/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/24/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/25/2011")) ("Rate" NIL "8720"))
 ("Record" (("Date" "11/26/2011")) ("Rate" NIL "8670"))
 ("Record" (("Date" "11/27/2011")) ("Rate" NIL "8670"))
 ("Record" (("Date" "11/28/2011")) ("Rate" NIL "8670"))
 ("Record" (("Date" "11/29/2011")) ("Rate" NIL "8640"))
 ("Record" (("Date" "11/30/2011")) ("Rate" NIL "8600")))

Сокращаем полученное дерево до списка с элементами (дата значение):

CL-USER> (defvar data (mapcar (lambda (value) (list (nth 1 (nth 0 (nth 1 value))) (nth 2 (nth 2 value)))) ya-parsed-xml))
(("11/01/2011" "8450") ("11/02/2011" "8530") ("11/03/2011" "8580")
 ("11/04/2011" "8650") ("11/05/2011" "8750") ("11/06/2011" "8750")
 ("11/07/2011" "8750") ("11/08/2011" "8750") ("11/09/2011" "8700")
 ("11/10/2011" "8790") ("11/11/2011" "8850") ("11/12/2011" "8850")
 ("11/13/2011" "8850") ("11/14/2011" "8850") ("11/15/2011" "8770")
 ("11/16/2011" "8760") ("11/17/2011" "8760") ("11/18/2011" "8760")
 ("11/19/2011" "8740") ("11/20/2011" "8740") ("11/21/2011" "8740")
 ("11/22/2011" "8720") ("11/23/2011" "8720") ("11/24/2011" "8720")
 ("11/25/2011" "8720") ("11/26/2011" "8670") ("11/27/2011" "8670")
 ("11/28/2011" "8670") ("11/29/2011" "8640") ("11/30/2011" "8600"))

Записываем в базу данных. Postgresql по умолчанию ожидает дату в формате dmy, функция to_date служит для явного задания формата даты mdy:

(mapcar (lambda (value) (postmodern:query 
                                  "insert into journal_currency_exchange(_date, _value) values (to_date($1, 'mm/dd/yyyy'), $2)" (car value) (cadr value))) data) 

Осталось все это оформить в функции и обернуть потоком. Думаю не стоит на этом заострять внимание.

P.S. Может кто-то уже делал систему построения отчетов на CL?

Комментариев нет:

Отправить комментарий