diff --git a/Finance-Treasury.cabal b/Finance-Treasury.cabal
new file mode 100644
--- /dev/null
+++ b/Finance-Treasury.cabal
@@ -0,0 +1,22 @@
+Name:                Finance-Treasury
+Version:             0.1
+Description:         Obtain Treasury yield curve data from Department of Treasury website
+Synopsis:            Obtain Treasury yield curve data
+Category:            Web
+Stability:           experimental
+Homepage:            http://www.ecoin.net/haskell/Finance-Treasury.html
+License:             BSD3
+License-file:        LICENSE
+Author:              Stephen Lihn
+Maintainer:          stevelihn@gmail.com
+cabal-version:       >=1.2
+build-type:          Simple
+Flag splitBase
+  Description: Choose the new smaller, split-up base package.
+Library 
+  if flag(splitBase)
+    Build-Depends: base >= 3,containers, network, HTTP, HTTP-Simple, time>=1.1.1, old-locale, hxt>=7.3
+  else
+    Build-Depends: base < 3, network, HTTP, HTTP-Simple, time>=1.1.1, hxt>=7.3
+  GHC-Options:         -O
+  Exposed-modules:     Finance.Treasury
diff --git a/Finance/Treasury.hs b/Finance/Treasury.hs
new file mode 100644
--- /dev/null
+++ b/Finance/Treasury.hs
@@ -0,0 +1,326 @@
+{- | 
+Module     : Finance.Treasury
+Copyright  : Copyright (c) 2008, Steve lihn <stevelihn@gmail.com>
+License    : BSD3
+Maintainer : Steve lihn <stevelihn@gmail.com>
+Stability  : experimental
+Tested with: GHC 6.6.1
+
+Finance.Treasury is a module to obtain yield curve data from Department
+of Treasury website.
+
+Error reporting is somewhat of a mixed model in this module. More
+improvement is desired in future releases.
+
+Here is a small complete program illustrating the use of this module:
+
+@
+
+    module Main where
+    import Finance.Treasury
+    import qualified Data.Map as M
+    import Data.List (sort)
+    import Text.Printf
+    main = do
+        putStrLn "**************************************"
+        m <- getYieldCurveThisMonth 
+        putStrLn "*** pretty print 10y rates for all dates"
+        prettyYieldCurve m (Just "10y")
+        --
+        d <- getLatestYieldCurve 
+        putStrLn "*** pretty print the latest daily yield curve"
+        prettyYieldCurve (M.fromList [d]) Nothing
+        --
+        putStrLn "*** show some yield curves of past year"
+        h <- getYieldCurveHist
+        prettyYieldCurve (minmax h) (Just "10y")
+        mapM_ prt yrs 
+        putStrLn "**************************************"
+        return ()
+        where minmax h = M.fromList [ M.findMin h, M.findMax h ]
+              yrs = reverse [ 1992..2007 ]
+              prt :: Int -> IO ()
+              prt yr = do putStrLn $ "*** show some yield curves of "++(show yr)
+                          yc <- getYieldCurveYyyy yr
+                          prettyYieldCurve (minmax yc) (Just "10y")
+                          return ()
+
+
+@
+
+-}
+{- 
+  License info:
+  The license is a simple BSD3-style license.  
+-}
+
+module Finance.Treasury ( 
+    DailyYieldCurve, 
+    DailyYieldCurveList,
+    yieldCurveHash, 
+    getLatestYieldCurve, 
+    getYieldCurveThisMonth,
+    getYieldCurveHist,
+    getYieldCurveYyyy,
+    prettyYieldCurve
+  ) where
+
+import Network.URI (parseURI, escapeURIString, isUnescapedInURI)
+-- import Network.HTTP
+import Network.HTTP.Simple (httpGet)
+import Data.List (intersperse, sortBy, sort, null)
+import Data.Maybe (listToMaybe, fromJust)
+import Data.Char (isDigit)
+
+import qualified Data.Time.Calendar as C
+import qualified Data.Map as M
+import qualified Data.Tree.Class as DT
+import qualified Text.XML.HXT.Parser as XP
+import qualified Control.Exception as E
+import Text.Printf
+
+baseURL = "http://www.treas.gov/offices/domestic-finance/debt-management/interest-rate/"
+
+-- yield.xml (current month if traded)
+-- yield_historical.xml for this year or last year (if January)
+-- yield_historical_YYYY.xml for YYYY is last year or earlier
+-- up to 1990
+
+yieldURL     = baseURL++"yield.xml"
+yieldHistURL = baseURL++"yield_historical.xml"
+yieldYyyyURL :: Int -> String
+yieldYyyyURL yr = baseURL++"yield_historical_"++s2++".xml"
+    where s1 = show yr
+          s2 = if length s1 == 4 then s1
+                  else error "yieldYyyyURL: "++s1++" is not YYYYY"
+         
+
+{- yieldCurveHash translates maturity from XML names to abbreviations.
+   E.g. "BC_1MONTH" becomes "1m".
+   List of all maturities: 1m 3m 6m 1y 2y 3y 5y 7y 10y 20y 30y 
+-}
+yieldCurveHash :: M.Map String String
+yieldCurveHash = M.fromList
+    [ ( "BC_1MONTH", "1m" ),
+      ( "BC_3MONTH", "3m" ),
+      ( "BC_6MONTH", "6m" ), 
+      ( "BC_1YEAR",  "1y" ), 
+      ( "BC_2YEAR",  "2y" ), 
+      ( "BC_3YEAR",  "3y" ), 
+      ( "BC_5YEAR",  "5y" ), 
+      ( "BC_7YEAR",  "7y" ), 
+      ( "BC_10YEAR", "10y" ),
+      ( "BC_20YEAR", "20y" ),
+      ( "BC_30YEAR", "30y" ) ] 
+
+
+type DailyYieldCurve     = M.Map String Float
+type DailyYieldCurveList = [ (String, Float) ]
+type YieldCurveMap       = M.Map C.Day DailyYieldCurve
+
+getLatestYieldCurve :: IO (C.Day, DailyYieldCurve)
+getLatestYieldCurve =
+    do ycs <- getYieldCurveThisMonth
+       return $ M.findMax ycs
+
+-- There is no reason to fail, so it is an error if there is no XML returned
+getYieldCurveThisMonth :: IO YieldCurveMap
+getYieldCurveThisMonth =
+    do jstr <- fetchXML yieldURL
+       parseRawWrapper parseRawXML1 jstr
+
+-- There is no reason to fail, so it is an error if there is no XML returned
+getYieldCurveHist :: IO YieldCurveMap
+getYieldCurveHist =
+    do jstr <- fetchXML yieldHistURL
+       parseRawWrapper parseRawXML2 jstr
+
+-- If YYYY is not in range, this call may fail
+-- Otherwise, there is no reason to fail
+getYieldCurveYyyy :: Int -> IO YieldCurveMap
+getYieldCurveYyyy yr =
+    do s <- fetchXML $ yieldYyyyURL yr
+       parseRawWrapper parseRawXML2 s
+
+--------------------------------------------------------------------
+--  helper function
+parseRawWrapper parseFunc s =
+       case s of
+       Nothing -> return M.empty
+       Just ss -> do -- FIXME: May need to check if ss =~ <?xml..., error if not
+                     -- debug: putStrLn $ take 3000 $ skiphead ss
+                     return $ parseFunc $ skiphead ss
+
+parseRawXML1 xmlstr = parseRawXML getGNewDateNodes   getBcNodes   xmlstr
+parseRawXML2 xmlstr = parseRawXML getGCurveDateNodes getBc30Nodes xmlstr
+
+parseRawXML getnodes getbc xmlstr = 
+    if (not $ M.null m) then m else error "parseRawXML: error empty result" 
+    where m = M.fromList $ dtlist $ getnodes xmlstr 
+          dtlist :: [XP.XmlTree] -> [(C.Day,DailyYieldCurve)]
+          dtlist [] = []
+          dtlist (s:xs) = 
+              let d  = showdt . getdt $ s
+                  yc = parseYieldCurve $ DT.getChildren $ justhead s $ getbc $ s in 
+              if ( length d == 10 ) then (mktuple d yc):(dtlist xs)
+              else dtlist xs
+          mktuple d yc = ( makeDay d , M.fromList yc )
+          getdt node = let dt1 = nodematch $ map getNewDateNode $ DT.getChildren node
+                           bc  = justhead node $ getbc $ node
+                           dt2 = nodematch $ map getNewDateNode $ DT.getChildren $ bc 
+                           justdt sx = justhead node sx in
+                       if not (null dt1) then justdt dt1
+                       else if not (null dt2) then justdt dt2
+                            else error $ "getNewDateNode error at \n"++(XP.formatXmlTree node)
+          showdt dt = showText $ justhead dt $ DT.getChildren dt
+--------------------------------------------------------------------
+
+justhead node (s:sx) = s
+justhead node [] = error $ "parse error at \n"++(XP.formatXmlTree node)
+
+-- a quick test function
+prettyYieldCurve :: YieldCurveMap -> Maybe String -> IO ()
+prettyYieldCurve ycm mmat =
+    do let ds = sort $ M.keys ycm
+           prt1 mat d = do curv <- M.lookup d ycm
+                           rate <- M.lookup mat curv
+                           printf fmt (show d) mat rate
+           prt2 d = do curv <- M.lookup d ycm
+                       mapM_ (prt4 d) $ sorted curv
+           prt0 d = case mmat of
+                        Nothing  -> prt2 d
+                        Just mat -> prt1 mat d
+       mapM_ prt0 ds
+    where prt4 d (a,b) = printf fmt (show d) a b
+          sorted yc = sortByMaturity $ M.toList yc
+          fmt = "%s, %3s, %5.2f\n"
+-- -----------------------------------------------------------------
+-- -----------------------------------------------------------------
+-- Private helper functions
+
+-- expect MM-DD-YYYY
+makeDay :: String -> C.Day
+makeDay s = 
+    let a = splitc '-' s in
+    case (length s == 10 && length a == 3 && validint a a) of 
+    False -> error("date field " ++ s ++ " malformed")
+    True  -> let y = read (a!!2) :: Integer
+                 m = read (a!!0) :: Int
+                 d = read (a!!1) :: Int in
+             C.fromGregorian y m d
+
+fetchXML :: String -> IO (Maybe String)
+fetchXML url = 
+    case parseURI url of
+        Nothing  -> error("uri malformed:" ++ url)
+        Just uri -> httpGet uri
+
+-- helper function to sort keys (maturity) in yield curve
+sortByMaturity :: DailyYieldCurveList -> DailyYieldCurveList
+sortByMaturity yc = sortBy yc_sort yc
+
+-- helper function, sort by maturity in correct order (month then year)
+yc_sort (a1,a2) (b1,b2) = compare (convkey a1) (convkey b1)
+
+-- helper function for yc_sort
+convkey :: String -> Int
+convkey k = 
+    if not $ validint [k1] [k1] then 0
+    else k2 * (if d == 'y' then 1000 else 100)
+    where k1 = reverse $ tail $ reverse k
+          k2 = read k1 :: Int
+          d = head $ reverse k
+  
+
+parseYieldCurve :: [ XP.XmlTree ] -> [(String,Float)]
+parseYieldCurve [] = []
+parseYieldCurve (s:sx) =
+    case (M.lookup lp mx) of
+      Nothing -> parseYieldCurve sx
+      Just p  -> if (ts == [] || ls == "N/A" || (not $ validnum ls ls)) then parseYieldCurve sx
+                 else if yd > 0 then (p,yd):(parseYieldCurve sx)
+                      else parseYieldCurve sx
+    where mx = yieldCurveHash 
+          lp = XP.localPartOf s
+          ts = DT.getChildren s
+          ls = rmspace $ showText $ justhead s $ ts 
+          yd = read (fixnum ls) :: Float 
+
+-- HXT XML Parser combinators
+
+getGNewDateNodes :: String -> [ XP.XmlTree ]
+getGNewDateNodes s = gettags $ head $ if sx == [] then error err else sx
+    where gettags = XP.deep ( XP.isTag "G_NEW_DATE")
+          sx = XP.xread s
+          err = "getGNewDateNodes: error empty result"
+          
+getGCurveDateNodes :: String -> [ XP.XmlTree ]
+getGCurveDateNodes s = gettags $ head $ if sx == [] then error err else sx
+    where gettags = XP.deep ( XP.isTag "G_BID_CURVE_DATE")
+          sx = XP.xread s
+          err = "getGCurveDateNodes: error empty result"
+
+getNewDateNode = XP.isTag "NEW_DATE" 
+
+-- FIXME: I am just lazy, this is quick, but may have performance cost
+getBcNodes   = XP.deep ( XP.isTag "G_BC_CAT" )
+
+getBc30Nodes = XP.deep ( XP.isTag "G_BC_30YEAR" )
+
+-- FIXME: This is a hack to XHT, I just can't find the right function to show text
+showText :: XP.XmlTree -> String
+showText t = showt n
+    where n = DT.getNode t
+          showt (XP.XText a) = a
+          showt _ = ""
+
+-- FIXME: There is probably a better way to do this!
+nodematch :: [[a]] -> [a]
+nodematch [] = []
+nodematch xs@(n:nx) = case n of
+    []  -> nodematch $ nx
+    [a] -> a:(nodematch $ nx)
+    _   -> error "nodematch error, there is a problem with XML syntax"
+
+
+------------------------------------------------------------
+-- auxillary functions --
+
+-- skip <?xml> and <!-- tags, HXT does not take them well
+skiphead :: String -> String
+skiphead [] = [] 
+skiphead [a,b] = [a,b] 
+skiphead s@(c1:c2:sx) = 
+    if c1 == '<' && not ( c2 == '?' || c2 == '!' ) then s
+        else skiphead (c2:sx)
+
+splitc :: Char -> String -> [String]
+splitc delim s = 
+    if null rest then [token]
+        else token : splitc delim (tail rest)
+    where (token,rest) = span (/=delim) s
+
+join :: String -> [String] -> String
+join sep = concat . intersperse sep
+
+validint y [] = True 
+validint y x@(s:sx) = 
+    if validc s then validint y sx
+                else error $ "invalid date list: "++(show y)
+    where validc [] = True
+          validc (s:sx) = isDigit s && validc sx
+
+validnum y x = 
+    if validc x then True
+                else error $ "invalid number: "++(show y)
+    where validc [] = True
+          validc (s:sx) = (isDigit s || s == '.') && validc sx
+
+-- prepend 0 in numbers such as .9, otherwise read will fail
+fixnum [] = [] 
+fixnum s@(c:cx) = if c=='.' then ('0':s) else s
+
+-- remove leading spaces
+rmspace :: String -> String
+rmspace [] = []
+rmspace s@(c:sx) = if c == ' ' then rmspace sx else s
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2008, Stephen Lihn
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of b7j0c.org nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY BRAD CLAWSIE ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BRAD CLAWSIE BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/tests/clean.sh b/tests/clean.sh
new file mode 100644
--- /dev/null
+++ b/tests/clean.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+  pwd=`pwd`
+  if [ `basename $pwd` != 'tests' ] ; then
+    exit 1
+  fi
+
+  cd ../
+  rm -f */*.hi */*.o
+  echo "*.hi *.o cleaned up"
+  rm -rf dist/
+  rm -f tests/treas
+  echo "dists/ treas cleaned up"
+
diff --git a/tests/test.sh b/tests/test.sh
new file mode 100644
--- /dev/null
+++ b/tests/test.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# to clean up, 
+# rm treas
+# rm -Rf *.hi *.o
+
+  pwd=`pwd`
+  if [ `basename $pwd` != 'tests' ] ; then
+    exit 1
+  fi
+
+mk_treas ()
+{
+    cp treas.hs ../
+    cd ../
+    ghc treas.hs --make
+    mv treas* tests/
+    cd tests/
+    ./treas
+}
+
+  [ -f "./treas" ] && rm ./treas
+  cd ../
+  Setup.lhs configure --user && \
+  Setup.lhs build && \
+  cd tests && \
+  mk_treas
+
+
+
diff --git a/tests/treas.hs b/tests/treas.hs
new file mode 100644
--- /dev/null
+++ b/tests/treas.hs
@@ -0,0 +1,31 @@
+
+
+    module Main where
+    import Finance.Treasury
+    import qualified Data.Map as M
+    import Data.List (sort)
+    import Text.Printf
+    main = do
+        putStrLn "**************************************"
+        m <- getYieldCurveThisMonth 
+        putStrLn "*** pretty print 10y rates for all dates"
+        prettyYieldCurve m (Just "10y")
+        --
+        d <- getLatestYieldCurve 
+        putStrLn "*** pretty print the latest daily yield curve"
+        prettyYieldCurve (M.fromList [d]) Nothing
+        --
+        putStrLn "*** show some yield curves of past year"
+        h <- getYieldCurveHist
+        prettyYieldCurve (minmax h) (Just "10y")
+        mapM_ prt yrs 
+        putStrLn "**************************************"
+        return ()
+        where minmax h = M.fromList [ M.findMin h, M.findMax h ]
+              yrs = reverse [ 1992..2007 ]
+              prt :: Int -> IO ()
+              prt yr = do putStrLn $ "*** show some yield curves of "++(show yr)
+                          yc <- getYieldCurveYyyy yr
+                          prettyYieldCurve (minmax yc) (Just "10y")
+                          return ()
+
