robocop

Checks KYC attributes against sanction lists
Log | Files | Refs | Submodules | README | LICENSE

commit 53ec32cf745a27c4b6a91da79c7774e57b9e3400
parent ec1fdaa08298c0c6bdb108eafd885b98b590cfae
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  5 Jun 2025 11:46:43 +0200

change score printing to match expectations of taler-exchange-sanctionscheck

Diffstat:
Mapp/Main.hs | 10+++++++---
Msrc/Robocop/Check.hs | 43+++++++++++++++++++++++++------------------
2 files changed, 32 insertions(+), 21 deletions(-)

diff --git a/app/Main.hs b/app/Main.hs @@ -31,9 +31,12 @@ import System.IO import Toml (decodeFile) +printScores :: [Score] -> IO () +printScores scores = mapM_ (putStrLn . scoreToString) scores + + readJSON :: Config -> Targets -> IO () readJSON config sanction_list = do - when (verbosity config >= Info) $ hPutStrLn stderr "Sanction list loaded. Ready for your input.\n(Paste subject data into the terminal. Use JSONlines format.)\nType 'CTRL-D' to exit." eof <- isEOF if eof then hPutStrLn stderr "Thank you for using Robocop." @@ -43,8 +46,8 @@ readJSON config sanction_list = do Left err -> hPutStrLn stderr $ "Failed to decode JSON (" ++ show err ++ ")" Right entry -> do case entry of - NP person -> mapM_ print $ checkPersons config (individuals sanction_list) person - LE entity -> mapM_ print $ checkEntity config (entities sanction_list) entity + NP person -> printScores $ checkPersons config (individuals sanction_list) person + LE entity -> printScores $ checkEntity config (entities sanction_list) entity readJSON config sanction_list main :: IO () @@ -83,4 +86,5 @@ main = do Just age -> hPutStrLn stderr $ "Seconds since epoch: " ++ (show (floor $ diffUTCTime start (UTCTime age 0) :: Int)) Nothing -> hPutStrLn stderr $ "Could not find age of sanction list" + when (verbosity config >= Info) $ hPutStrLn stderr "Sanction list loaded. Ready for your input.\n(Paste subject data into the terminal. Use JSONlines format.)\nType 'CTRL-D' to exit." readJSON config tgts diff --git a/src/Robocop/Check.hs b/src/Robocop/Check.hs @@ -16,6 +16,7 @@ module Robocop.Check , compareFullDate , multFloats , Score(..) + , scoreToString ) where import Data.ISO3166_CountryCodes @@ -24,15 +25,17 @@ import Data.Maybe import Data.Map (Map, toList) import Data.Ratio -import Data.Text (intercalate, pack, snoc, Text) +import Data.Text (intercalate, pack, snoc, Text) import qualified Data.Text as T import Data.Text.Metrics import Data.Time.Calendar import Data.Time.Calendar.OrdinalDate +import Text.Printf (printf) + import Robocop.Type -import Robocop.SSL.Type as SSL +import Robocop.SSL.Type as SSL import Robocop.GLS.Type as GLS import Prelude hiding (lines) @@ -44,6 +47,10 @@ data Score = Score , reference :: Int } deriving (Show, Eq) +scoreToString :: Score -> String +scoreToString (Score mq conf expiresec ref) = + printf "%f %f %d %d" mq conf expiresec ref + suspicious_dates :: (Int, Int) suspicious_dates = (3, 75) -- Difference in years and days that will be marked suspicious (exponential) @@ -56,7 +63,7 @@ checkEntity config entities' entity = findMatchingEntities config entity $ toLis findMatchingEntities :: Config -> LegalEntity -> [(Int, Entity)] -> [Score] findMatchingEntities _ _ [] = [] -findMatchingEntities config entity ((ssid,ent):ents) = +findMatchingEntities config entity ((ssid,ent):ents) = let points = compareEntity config entity ent max_points = foldl1 (+) [ if toList (entity_addresses ent) == [] then 0 else weight_address config @@ -67,7 +74,7 @@ findMatchingEntities config entity ((ssid,ent):ents) = then let score = Score { match_quality = if points >= perfect_points config then 1 else points / perfect_points config , confidence = points / max_points - , expiration = 0 + , expiration = 0 , reference = ssid } in @@ -91,7 +98,7 @@ checkPersons :: Config -> Map Int Individual -> NaturalPerson -> [Score] checkPersons config individuals' person = checkPersons' config person $ toList individuals' checkPersons' :: Config -> NaturalPerson -> [(Int, Individual)] -> [Score] -checkPersons' _ _ [] = [] +checkPersons' _ _ [] = [] checkPersons' config person ((ssid,ind):inds) = let points = checkPerson config person ind @@ -108,7 +115,7 @@ checkPersons' config person ((ssid,ind):inds) = , confidence = points / max_points , expiration = 0 , reference = ssid - } + } in score:checkPersons' config person inds else checkPersons' config person inds @@ -119,11 +126,11 @@ checkPerson config person individual = foldl1 (+) [ address_points , multFloats 100 date_score (removeQuality . removeSSID) , 50 * nationality_score , name_points - ] - where address_score = checkAddress config (addresses individual) (residential person) + ] + where address_score = checkAddress config (addresses individual) (residential person) id_score = checkID config (ids individual) (national_id person) nationality_score = checkCountryCode config (nationalities individual) (nationality person) - name_score = checkNames config (names individual) (full_name person) + name_score = checkNames config (names individual) (full_name person) date_score = if name_points == 0 then [] else checkBirthDate config (birth_dates individual) (birthdate person) @@ -148,7 +155,7 @@ checkBirthDate config dates (Day' date) = catMaybes $ map compareDate $ toList d then Just $ WithSSID ssid $ liftQuality (\_ -> ratio) quality else Nothing -compareFullDate :: Config -> Year -> DayOfYear -> Day -> Float +compareFullDate :: Config -> Year -> DayOfYear -> Day -> Float compareFullDate config year day_of_year day = max ratio_text ratio_date where day' = fromOrdinalDate year day_of_year day_to_text d = pack $ showGregorian d @@ -177,7 +184,7 @@ compareYear config year day = if ratio >= threshold then ratio else 0 -checkNames :: Config -> Map Int (Quality [Text]) -> Text -> [WithSSID QualityFloat] +checkNames :: Config -> Map Int (Quality [Text]) -> Text -> [WithSSID QualityFloat] checkNames config names' name = catMaybes $ map compareName $ toList names' where compareName (ssid, quality) = let ratio = ratioToFloat $ compareText name 0 (removeQuality quality) @@ -206,18 +213,18 @@ checkAddress :: Config -> Map Int (Quality SSL.Address) -> GLS.Address -> [WithS checkAddress config addresses' address' = catMaybes $ map (compareAddress config address') $ toList addresses' compareAddress :: Config -> GLS.Address -> (Int, Quality SSL.Address) -> Maybe (WithSSID QualityFloat) -compareAddress config gls_address (ssid, quality) = if country_score >= 0.75 +compareAddress config gls_address (ssid, quality) = if country_score >= 0.75 then Just $ WithSSID ssid $ liftQuality (\_ -> country_score) quality - else Nothing + else Nothing where ssl_address = removeQuality quality total_score = totalFromMaybes [ details_score , area_score - , location_score + , location_score , zip_code_score ] country_score = case SSL.country ssl_address of - Just c -> if c == GLS.country gls_address then 0.5 * (total_score + 1) else 0 - Nothing -> total_score + Just c -> if c == GLS.country gls_address then 0.5 * (total_score + 1) else 0 + Nothing -> total_score details_score = case details ssl_address of Just det -> let possible_numbers = catMaybes [ Just $ street_number gls_address @@ -265,7 +272,7 @@ compareAddress config gls_address (ssid, quality) = if country_score >= 0.75 Just zc -> let ratio = ratioToFloat $ compareText zc 0 [zipcode gls_address] in - if ratio >= threshold_ratio config then Just ratio else Nothing + if ratio >= threshold_ratio config then Just ratio else Nothing Nothing -> Nothing @@ -274,7 +281,7 @@ cleanText text = map (T.foldl (\new_text char -> if char `elem` chars_to_rm then new_text else snoc new_text char ) start_text) text - where chars_to_rm = ".,-" :: String + where chars_to_rm = ".,-" :: String start_text = "" :: Text permutateText :: [Text] -> [Text]