diff --git a/server.js b/server.js index bb0a2e2..1824b8b 100644 --- a/server.js +++ b/server.js @@ -43,7 +43,7 @@ Postgres.FirstRow = function(inSQL,args, inResponse) }; Postgres.connect(); -//----------------------------------------------------------source definitions------------------------------------------------------------ +//----------------------------------------------------------source definitions------------------------------------------------------------------------------------------------------------------------- //returns array of all sources server.get("/source", function (inReq, inRes) @@ -58,7 +58,7 @@ server.post("/source", bodyParser.json(), function (inReq, inRes)// remove body Postgres.FirstRow(sql,[JSON.stringify(inReq.body)], inRes); }); -//----------------------------------------------------------regex instractions-------------------------------------------------------------------------- +//----------------------------------------------------------regex instrUctions------------------------------------------------------------------------------------------------------------------------- //list all regex operations server.get("/regex", function (inReq, inRes) { @@ -73,7 +73,7 @@ server.post("/regex", bodyParser.json(), function (inReq, inRes) Postgres.FirstRow(sql, [JSON.stringify(inReq.body)], inRes); }); -//------------------------------------------------------------mappings------------------------------------------------------------------------------- +//------------------------------------------------------------mappings--------------------------------------------------------------------------------------------------------------------------------- //list unmapped items flagged to be mapped ?srce= server.get("/unmapped", function (inReq, inRes) @@ -90,7 +90,7 @@ server.post("/mapping", bodyParser.json(), function (inReq, inRes) }); -//-------------------------------------------------------------import data-------------------------------------------------------------------------- +//-------------------------------------------------------------import data----------------------------------------------------------------------------------------------------------------------------- server.use("/import", upload.single('upload'), function (inReq, inRes) { @@ -107,7 +107,7 @@ server.use("/import", upload.single('upload'), function (inReq, inRes) { } ); -//-------------------------------------------------------------suggest source def-------------------------------------------------------------------------- +//-------------------------------------------------------------suggest source def---------------------------------------------------------------------------------------------------------------------- server.use("/csv_suggest", upload.single('upload'), function (inReq, inRes) { diff --git a/test/0.deploy/cmd b/test/0.deploy/cmd new file mode 100644 index 0000000..e69de29 diff --git a/test/0.deploy/schema.sql b/test/0.deploy/schema.sql new file mode 100644 index 0000000..0493a4a --- /dev/null +++ b/test/0.deploy/schema.sql @@ -0,0 +1,1986 @@ +------create dev schema and api user----------------------------------------------------------------------------------------------------------------- + +DROP SCHEMA IF EXISTS tps CASCADE; +DROP SCHEMA IF EXISTS tpsv CASCADE; + +CREATE SCHEMA tps; +COMMENT ON SCHEMA tps IS 'third party source data'; + +CREATE SCHEMA tpsv; +COMMENT ON SCHEMA tps IS 'third party source views'; + +DROP USER IF EXISTS api; + +CREATE ROLE api WITH + LOGIN + NOSUPERUSER + NOCREATEDB + NOCREATEROLE + INHERIT + NOREPLICATION + CONNECTION LIMIT -1 + ENCRYPTED PASSWORD 'md56da13b696f737097e0146e47cc0d0985'; + +-----need to setup all database objects and then grant priveledges to api---------------------------------------------------------------------------- + +--grant schema USAGE +GRANT USAGE ON SCHEMA tps TO api; +GRANT USAGE ON SCHEMA tpsv TO api; + +--grant current table privledges +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA tps TO api; +GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA tpsv TO api; + +--grant current sequence privledges +GRANT USAGE ON ALL SEQUENCES IN SCHEMA tps TO api; +GRANT USAGE ON ALL SEQUENCES IN SCHEMA tpsv TO api; + +--grant future table privledges +ALTER DEFAULT PRIVILEGES IN SCHEMA tps GRANT SELECT, UPDATE, INSERT, DELETE ON TABLES TO api; +ALTER DEFAULT PRIVILEGES IN SCHEMA tpsv GRANT SELECT, UPDATE, INSERT, DELETE ON TABLES TO api; + +--grant future sequence privledges +ALTER DEFAULT PRIVILEGES IN SCHEMA tps GRANT USAGE ON SEQUENCES TO api; +ALTER DEFAULT PRIVILEGES IN SCHEMA tpsv GRANT USAGE ON SEQUENCES TO api; + + +-----create tables----------------------------------------------------------------------------------------------------------------------------------- + +-----regex map instructions table + +CREATE TABLE tps.map_rm ( + srce text NOT NULL, + target text NOT NULL, + regex jsonb, + seq integer NOT NULL, + hist jsonb +); +COMMENT ON TABLE tps.map_rm IS 'regex map instructions'; + +-----return value table + +CREATE TABLE tps.map_rv ( + srce text NOT NULL, + target text NOT NULL, + retval jsonb NOT NULL, + map jsonb NOT NULL, + hist jsonb NOT NULL +); +COMMENT ON TABLE tps.map_rv IS 'return value lookup table'; + +-----source definition table + +CREATE TABLE tps.srce ( + srce text NOT NULL, + defn jsonb, + hist jsonb +); +COMMENT ON TABLE tps.srce IS 'source master listing and definition'; + +-----source data table + +CREATE TABLE tps.trans ( + id integer NOT NULL, + srce text, + rec jsonb, + parse jsonb, + map jsonb, + allj jsonb, + ic jsonb, + logid INTEGER +); +COMMENT ON TABLE tps.trans IS 'source records'; +COMMENT ON COLUMN tps.trans.ic IS 'input constraint value'; +ALTER TABLE tps.trans ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME tps.trans_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +-----import log table + +CREATE TABLE tps.trans_log ( + id integer NOT NULL, + info jsonb +); +COMMENT ON TABLE tps.trans_log IS 'import event information'; +ALTER TABLE tps.trans_log ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME tps.trans_log_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-------------primary keys---------------------------------------------------------------------------------------------------------------------------- + +ALTER TABLE ONLY tps.map_rm + ADD CONSTRAINT map_rm_pk PRIMARY KEY (srce, target); + +ALTER TABLE ONLY tps.map_rv + ADD CONSTRAINT map_rv_pk PRIMARY KEY (srce, target, retval); + +ALTER TABLE ONLY tps.srce + ADD CONSTRAINT srce_pkey PRIMARY KEY (srce); + +ALTER TABLE ONLY tps.trans_log + ADD CONSTRAINT trans_log_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY tps.trans + ADD CONSTRAINT trans_pkey PRIMARY KEY (id); + +-------------indexes--------------------------------------------------------------------------------------------------------------------------------- + +CREATE INDEX trans_allj ON tps.trans USING gin (allj); + +CREATE INDEX trans_rec ON tps.trans USING gin (rec); + +CREATE INDEX trans_srce ON tps.trans USING btree (srce); + +-------------foreign keys---------------------------------------------------------------------------------------------------------------------------- + +ALTER TABLE ONLY tps.map_rm + ADD CONSTRAINT map_rm_fk_srce FOREIGN KEY (srce) REFERENCES tps.srce(srce); + +ALTER TABLE ONLY tps.map_rv + ADD CONSTRAINT map_rv_fk_rm FOREIGN KEY (srce, target) REFERENCES tps.map_rm(srce, target); + +ALTER TABLE ONLY tps.trans + ADD CONSTRAINT trans_srce_fkey FOREIGN KEY (srce) REFERENCES tps.srce(srce); + +ALTER TABLE ONLY tps.trans + ADD CONSTRAINT trans_logid_fkey FOREIGN KEY (logid) REFERENCES tps.trans_log(id); + +-------------create functions------------------------------------------------------------------------------------------------------------------------ + +-----set source +DROP FUNCTION IF EXISTS tps.srce_set(jsonb); +CREATE FUNCTION tps.srce_set(_defn jsonb) RETURNS jsonb +AS +$f$ +DECLARE + _message jsonb; + _MESSAGE_TEXT text; + _PG_EXCEPTION_DETAIL text; + _PG_EXCEPTION_HINT text; + _rebuild BOOLEAN; +BEGIN + + ---------test if anythign is changing-------------------------------------------------------------------------------------------- + + IF _defn = (SELECT defn FROM tps.srce WHERE srce = _defn->>'name') THEN + _message:= + ( + $$ + { + "status":"complete", + "message":"source was not different no action taken" + } + $$::jsonb + ); + RETURN _message; + END IF; + + ---------if the constraint definition is changing, rebuild for existing records--------------------------------------------------- + + SELECT + NOT (_defn->'constraint' = (SELECT defn->'constraint' FROM tps.srce WHERE srce = _defn->>'name')) + INTO + _rebuild; + + RAISE NOTICE '%',_rebuild::text; + + ---------do merge----------------------------------------------------------------------------------------------------------------- + + INSERT INTO + tps.srce (srce, defn, hist) + SELECT + --extract name from defintion + _defn->>'name' + --add current timestamp to defintions + ,_defn + --add definition + ,jsonb_build_object( + 'hist_defn',_defn + ,'effective',jsonb_build_array(CURRENT_TIMESTAMP,null::timestamptz) + ) || '[]'::jsonb + ON CONFLICT ON CONSTRAINT srce_pkey DO UPDATE + SET + defn = _defn + ,hist = + --the new definition going to position -0- + jsonb_build_object( + 'hist_defn',_defn + ,'effective',jsonb_build_array(CURRENT_TIMESTAMP,null::timestamptz) + ) + --the previous definition, set upper bound of effective range which was previously null + || jsonb_set( + srce.hist + ,'{0,effective,1}'::text[] + ,to_jsonb(CURRENT_TIMESTAMP) + ); + --rebuild constraint key if necessary--------------------------------------------------------------------------------------- + + IF _rebuild THEN + WITH + rebuild AS ( + SELECT + j.srce + ,j.rec + ,j.id + --aggregate back to the record since multiple paths may be listed in the constraint + ,tps.jsonb_concat_obj( + jsonb_build_object( + --the new json key is the path itself + cons.path->>0 + ,j.rec#>((cons.path->>0)::text[]) + ) + ) json_key + FROM + tps.trans j + INNER JOIN tps.srce s ON + s.srce = j.srce + JOIN LATERAL jsonb_array_elements(s.defn->'constraint') WITH ORDINALITY cons(path, seq) ON TRUE + WHERE + s.srce = _defn->>'name' + GROUP BY + j.rec + ,j.id + ) + UPDATE + tps.trans t + SET + ic = r.json_key + FROM + rebuild r + WHERE + t.id = r.id; + _message:= + ( + $$ + { + "status":"complete", + "message":"source set and constraint rebuilt on existing records" + } + $$::jsonb + ); + ELSE + _message:= + ( + $$ + { + "status":"complete", + "message":"source set" + } + $$::jsonb + ); + END IF; + + RETURN _message; + + + + EXCEPTION WHEN OTHERS THEN + GET STACKED DIAGNOSTICS + _MESSAGE_TEXT = MESSAGE_TEXT, + _PG_EXCEPTION_DETAIL = PG_EXCEPTION_DETAIL, + _PG_EXCEPTION_HINT = PG_EXCEPTION_HINT; + _message:= + ($$ + { + "status":"fail", + "message":"error importing data" + } + $$::jsonb) + ||jsonb_build_object('message_text',_MESSAGE_TEXT) + ||jsonb_build_object('pg_exception_detail',_PG_EXCEPTION_DETAIL); + RETURN _message; +END; +$f$ +LANGUAGE plpgsql; + +-----generate sql to create select based on schema +DROP FUNCTION IF EXISTS tps.build_srce_view_sql(text, text); +CREATE OR REPLACE FUNCTION tps.build_srce_view_sql(_srce text, _schema text) RETURNS TEXT +AS +$f$ +DECLARE + --_schema text; + _path text[]; + --_srce text; + _sql text; +BEGIN + --_schema:= 'default'; + _path:= ARRAY['schemas',_schema]::text[]; + --_srce:= 'dcard'; +SELECT + 'DROP VIEW IF EXISTS tpsv.'||_srce||'_'||_path[2]||'; CREATE VIEW tpsv.'||_srce||'_'||_path[2]||' AS SELECT id, logid, '||string_agg('(allj#>>'''||r.PATH::text||''')::'||r.type||' AS "'||r.column_name||'"',', ')||' FROM tps.trans WHERE srce = '''||_srce||''';' +INTO + _sql +FROM + tps.srce + JOIN LATERAL jsonb_array_elements(defn#>_path) ae(v) ON TRUE + JOIN LATERAL jsonb_to_record (ae.v) AS r(PATH text[], "type" text, column_name text) ON TRUE +WHERE + srce = _srce +GROUP BY + srce.srce; + +RETURN _sql; +RAISE NOTICE '%',_sql; + +END +$f$ +LANGUAGE plpgsql; + +-----set map defintion from json argument +CREATE OR REPLACE FUNCTION tps.srce_map_def_set(_defn jsonb) RETURNS jsonb +AS +$f$ + +DECLARE + _message jsonb; + _MESSAGE_TEXT text; + _PG_EXCEPTION_DETAIL text; + _PG_EXCEPTION_HINT text; + +BEGIN + + BEGIN + + INSERT INTO + tps.map_rm (srce, target, regex, seq, hist) + SELECT + --data source + ae.r->>'srce' + --map name + ,ae.r->>'name' + --map definition + ,ae.r + --map aggregation sequence + ,(ae.r->>'sequence')::INTEGER + --history definition + ,jsonb_build_object( + 'hist_defn',ae.r + ,'effective',jsonb_build_array(CURRENT_TIMESTAMP,null::timestamptz) + ) || '[]'::jsonb + FROM + jsonb_array_elements(_defn) ae(r) + ON CONFLICT ON CONSTRAINT map_rm_pk DO UPDATE SET + srce = excluded.srce + ,target = excluded.target + ,regex = excluded.regex + ,seq = excluded.seq + ,hist = + --the new definition going to position -0- + jsonb_build_object( + 'hist_defn',excluded.regex + ,'effective',jsonb_build_array(CURRENT_TIMESTAMP,null::timestamptz) + ) + --the previous definition, set upper bound of effective range which was previously null + || jsonb_set( + map_rm.hist + ,'{0,effective,1}'::text[] + ,to_jsonb(CURRENT_TIMESTAMP) + ); + + EXCEPTION WHEN OTHERS THEN + + GET STACKED DIAGNOSTICS + _MESSAGE_TEXT = MESSAGE_TEXT, + _PG_EXCEPTION_DETAIL = PG_EXCEPTION_DETAIL, + _PG_EXCEPTION_HINT = PG_EXCEPTION_HINT; + _message:= + ($$ + { + "status":"fail", + "message":"error setting definition" + } + $$::jsonb) + ||jsonb_build_object('message_text',_MESSAGE_TEXT) + ||jsonb_build_object('pg_exception_detail',_PG_EXCEPTION_DETAIL); + return _message; + END; + + _message:= jsonb_build_object('status','complete','message','definition has been set'); + return _message; + +END; +$f$ +language plpgsql; + + +------------build report for unmapped items--------------------------------------------------------------------------------------------------------------------------------------------- + +CREATE OR REPLACE FUNCTION tps.jsonb_concat( + state jsonb, + concat jsonb) + RETURNS jsonb AS +$BODY$ +BEGIN + --RAISE notice 'state is %', state; + --RAISE notice 'concat is %', concat; + RETURN state || concat; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; + +DROP AGGREGATE IF EXISTS tps.jsonb_concat_obj(jsonb); +CREATE AGGREGATE tps.jsonb_concat_obj(jsonb) ( + SFUNC=tps.jsonb_concat, + STYPE=jsonb, + INITCOND='{}' +); + + +DROP FUNCTION IF EXISTS tps.report_unmapped; +CREATE FUNCTION tps.report_unmapped(_srce text) RETURNS TABLE +( + source text, + map text, + ret_val jsonb, + "count" bigint +) +LANGUAGE plpgsql +AS +$f$ +BEGIN + +/* +first get distinct target json values +then apply regex +*/ + +RETURN QUERY +WITH + +--------------------apply regex operations to transactions--------------------------------------------------------------------------------- + +rx AS ( + SELECT + t.srce, + t.id, + t.rec, + m.target, + m.seq, + regex->'regex'->>'function' regex_function, + e.v ->> 'field' result_key_name, + e.v ->> 'key' target_json_path, + e.v ->> 'flag' regex_options_flag, + e.v->>'map' map_intention, + e.v->>'retain' retain_result, + e.v->>'regex' regex_expression, + e.rn target_item_number, + COALESCE(mt.rn,rp.rn,1) result_number, + mt.mt rx_match, + rp.rp rx_replace, + CASE e.v->>'map' + WHEN 'y' THEN + e.v->>'field' + ELSE + null + END map_key, + CASE e.v->>'map' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(mt.mt[1]) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rp.rp) + ELSE + '{}'::jsonb + END + ELSE + NULL + END map_val, + CASE e.v->>'retain' + WHEN 'y' THEN + e.v->>'field' + ELSE + NULL + END retain_key, + CASE e.v->>'retain' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(trim(mt.mt[1])) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rtrim(rp.rp)) + ELSE + '{}'::jsonb + END + ELSE + NULL + END retain_val + FROM + --------------------------start with all regex maps------------------------------------------------------------------------------------ + tps.map_rm m + --------------------------isolate matching basis to limit map to only look at certain json--------------------------------------------- + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'where') w(v) ON TRUE + --------------------------join to main transaction table but only certain key/values are included-------------------------------------- + INNER JOIN tps.trans t ON + t.srce = m.srce AND + t.rec @> w.v + --------------------------break out array of regluar expressions in the map------------------------------------------------------------ + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'defn') WITH ORDINALITY e(v, rn) ON true + --------------------------each regex references a path to the target value, extract the target from the reference and do regex--------- + LEFT JOIN LATERAL regexp_matches(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text,COALESCE(e.v ->> 'flag','')) WITH ORDINALITY mt(mt, rn) ON + m.regex->'regex'->>'function' = 'extract' + --------------------------same as above but for a replacement type function------------------------------------------------------------ + LEFT JOIN LATERAL regexp_replace(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text, e.v ->> 'replace'::text,e.v ->> 'flag') WITH ORDINALITY rp(rp, rn) ON + m.regex->'regex'->>'function' = 'replace' + WHERE + --t.allj IS NULL + t.srce = _srce AND + e.v @> '{"map":"y"}'::jsonb + --rec @> '{"Transaction":"ACH Credits","Transaction":"ACH Debits"}' + --rec @> '{"Description":"CHECK 93013270 086129935"}'::jsonb + ORDER BY + t.id DESC, + m.target, + e.rn, + COALESCE(mt.rn,rp.rn,1) +) + +--SELECT * FROM rx LIMIT 100 + + +, agg_to_target_items AS ( +SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,CASE WHEN map_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + map_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(map_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(map_val ORDER BY result_number) + END + ) + END map_val + ,CASE WHEN retain_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + retain_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(retain_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(retain_val ORDER BY result_number) + END + ) + END retain_val +FROM + rx +GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,map_key + ,retain_key +) + +--SELECT * FROM agg_to_target_items LIMIT 100 + + +, agg_to_target AS ( +SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,tps.jsonb_concat_obj(COALESCE(map_val,'{}'::JSONB)) map_val + ,jsonb_strip_nulls(tps.jsonb_concat_obj(COALESCE(retain_val,'{}'::JSONB))) retain_val +FROM + agg_to_target_items +GROUP BY + srce + ,id + ,target + ,seq + ,map_intention +) + + +, agg_to_ret AS ( +SELECT + srce + ,target + ,seq + ,map_intention + ,map_val + ,retain_val + ,count(*) "count" +FROM + agg_to_target +GROUP BY + srce + ,target + ,seq + ,map_intention + ,map_val + ,retain_val +) + +, link_map AS ( +SELECT + a.srce + ,a.target + ,a.seq + ,a.map_intention + ,a.map_val + ,a."count" + ,a.retain_val + ,v.map mapped_val +FROM + agg_to_ret a + LEFT OUTER JOIN tps.map_rv v ON + v.srce = a.srce AND + v.target = a.target AND + v.retval = a.map_val +) +SELECT + l.srce + ,l.target + ,l.map_val + ,l."count" +FROM + link_map l +WHERE + l.mapped_val IS NULL +ORDER BY + l.srce + ,l.target + ,l."count" desc; +END; +$f$; + + +-------------------create trigger to map imported items------------------------------------------------------------------------------------------------------ + +CREATE OR REPLACE FUNCTION tps.trans_insert_map() RETURNS TRIGGER +AS +$f$ + DECLARE + _cnt INTEGER; + + BEGIN + IF (TG_OP = 'INSERT') THEN + + + WITH + --------------------apply regex operations to transactions----------------------------------------------------------------------------------- + + rx AS ( + SELECT + t.srce, + t.id, + t.rec, + m.target, + m.seq, + regex->'regex'->>'function' regex_function, + e.v ->> 'field' result_key_name, + e.v ->> 'key' target_json_path, + e.v ->> 'flag' regex_options_flag, + e.v->>'map' map_intention, + e.v->>'retain' retain_result, + e.v->>'regex' regex_expression, + e.rn target_item_number, + COALESCE(mt.rn,rp.rn,1) result_number, + mt.mt rx_match, + rp.rp rx_replace, + CASE e.v->>'map' + WHEN 'y' THEN + e.v->>'field' + ELSE + null + END map_key, + CASE e.v->>'map' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(mt.mt[1]) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rp.rp) + ELSE + '{}'::jsonb + END + ELSE + NULL + END map_val, + CASE e.v->>'retain' + WHEN 'y' THEN + e.v->>'field' + ELSE + NULL + END retain_key, + CASE e.v->>'retain' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(trim(mt.mt[1])) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rtrim(rp.rp)) + ELSE + '{}'::jsonb + END + ELSE + NULL + END retain_val + FROM + --------------------------start with all regex maps------------------------------------------------------------------------------------ + tps.map_rm m + --------------------------isolate matching basis to limit map to only look at certain json--------------------------------------------- + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'where') w(v) ON TRUE + --------------------------join to main transaction table but only certain key/values are included-------------------------------------- + INNER JOIN new_table t ON + t.srce = m.srce AND + t.rec @> w.v + --------------------------break out array of regluar expressions in the map------------------------------------------------------------ + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'defn') WITH ORDINALITY e(v, rn) ON true + --------------------------each regex references a path to the target value, extract the target from the reference and do regex--------- + LEFT JOIN LATERAL regexp_matches(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text,COALESCE(e.v ->> 'flag','')) WITH ORDINALITY mt(mt, rn) ON + m.regex->'regex'->>'function' = 'extract' + --------------------------same as above but for a replacement type function------------------------------------------------------------ + LEFT JOIN LATERAL regexp_replace(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text, e.v ->> 'replace'::text,e.v ->> 'flag') WITH ORDINALITY rp(rp, rn) ON + m.regex->'regex'->>'function' = 'replace' + ORDER BY + t.id DESC, + m.target, + e.rn, + COALESCE(mt.rn,rp.rn,1) + ) + + --SELECT count(*) FROM rx LIMIT 100 + + + , agg_to_target_items AS ( + SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,CASE WHEN map_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + map_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(map_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(map_val ORDER BY result_number) + END + ) + END map_val + ,CASE WHEN retain_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + retain_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(retain_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(retain_val ORDER BY result_number) + END + ) + END retain_val + FROM + rx + GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,map_key + ,retain_key + ) + + --SELECT * FROM agg_to_target_items LIMIT 100 + + + , agg_to_target AS ( + SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,tps.jsonb_concat_obj(COALESCE(map_val,'{}'::JSONB)) map_val + ,jsonb_strip_nulls(tps.jsonb_concat_obj(COALESCE(retain_val,'{}'::JSONB))) retain_val + FROM + agg_to_target_items + GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ORDER BY + id + ) + + + --SELECT * FROM agg_to_target + + + , link_map AS ( + SELECT + a.srce + ,a.id + ,a.target + ,a.seq + ,a.map_intention + ,a.map_val + ,a.retain_val retain_value + ,v.map + FROM + agg_to_target a + LEFT OUTER JOIN tps.map_rv v ON + v.srce = a.srce AND + v.target = a.target AND + v.retval = a.map_val + ) + + --SELECT * FROM link_map + + , agg_to_id AS ( + SELECT + srce + ,id + ,tps.jsonb_concat_obj(COALESCE(retain_value,'{}'::jsonb) ORDER BY seq DESC) retain_val + ,tps.jsonb_concat_obj(COALESCE(map,'{}'::jsonb)) map + FROM + link_map + GROUP BY + srce + ,id + ) + + --SELECT agg_to_id.srce, agg_to_id.id, jsonb_pretty(agg_to_id.retain_val) , jsonb_pretty(agg_to_id.map) FROM agg_to_id ORDER BY id desc LIMIT 100 + + --create a complete list of all new inserts assuming some do not have maps (left join) + ,join_all AS ( + SELECT + n.srce + ,n.id + ,n.rec + ,a.retain_val parse + ,a.map + ,n.rec||COALESCE(a.map||a.retain_val,'{}'::jsonb) allj + FROM + new_table n + LEFT OUTER JOIN agg_to_id a ON + a.id = n.id + ) + + --update trans with join_all recs + UPDATE + tps.trans t + SET + parse = a.parse + ,map = a.map + ,allj = a.allj + FROM + join_all a + WHERE + t.id = a.id; + + + END IF; + RETURN NULL; + END; +$f$ LANGUAGE plpgsql; + +CREATE TRIGGER trans_insert + AFTER INSERT ON tps.trans + REFERENCING NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE tps.trans_insert_map(); + + +------------------import data------------------------------------------------------------------------------------------------------------------------------------- + +DROP FUNCTION IF EXISTS tps.srce_import(text, jsonb); +CREATE OR REPLACE FUNCTION tps.srce_import(_srce text, _recs jsonb) RETURNS jsonb + +/*-------------------------------------------------------- +0. test if source exists +1. create pending list +2. get unqiue pending keys +3. see which keys not already in tps.trans +4. insert pending records associated with keys that are not already in trans +5. insert summary to log table +*/--------------------------------------------------------- + +--to-do +--return infomation to a client via json or composite type + + +AS $f$ +DECLARE + _t text; + _c text; + _log_info jsonb; + _log_id text; + _cnt numeric; + _message jsonb; + --_recs jsonb; + --_srce text; + _defn jsonb; + _MESSAGE_TEXT text; + _PG_EXCEPTION_DETAIL text; + _PG_EXCEPTION_HINT text; + +BEGIN + + --_path := 'C:\users\fleet\downloads\discover-recentactivity-20171031.csv'; + --_srce := 'dcard'; + --_recs:= $$[{"Trans. Date":"1/2/2018","Post Date":"1/2/2018","Description":"GOOGLE *YOUTUBE VIDEOS G.CO/HELPPAY#CAP0H07TXV","Amount":4.26,"Category":"Services"},{"Trans. Date":"1/2/2018","Post Date":"1/2/2018","Description":"MICROSOFT *ONEDRIVE 800-642-7676 WA","Amount":4.26,"Category":"Services"},{"Trans. Date":"1/3/2018","Post Date":"1/3/2018","Description":"CLE CLINIC PT PMTS 216-445-6249 OHAK2C57F2F0B3","Amount":200,"Category":"Medical Services"},{"Trans. Date":"1/4/2018","Post Date":"1/4/2018","Description":"AT&T *PAYMENT 800-288-2020 TX","Amount":57.14,"Category":"Services"},{"Trans. Date":"1/4/2018","Post Date":"1/7/2018","Description":"WWW.KOHLS.COM #0873 MIDDLETOWN OH","Amount":-7.9,"Category":"Payments and Credits"},{"Trans. Date":"1/5/2018","Post Date":"1/7/2018","Description":"PIZZA HUT 007946 STOW OH","Amount":9.24,"Category":"Restaurants"},{"Trans. Date":"1/5/2018","Post Date":"1/7/2018","Description":"SUBWAY 00044289255 STOW OH","Amount":10.25,"Category":"Restaurants"},{"Trans. Date":"1/6/2018","Post Date":"1/7/2018","Description":"ACME NO. 17 STOW OH","Amount":103.98,"Category":"Supermarkets"},{"Trans. Date":"1/6/2018","Post Date":"1/7/2018","Description":"DISCOUNT DRUG MART 32 STOW OH","Amount":1.69,"Category":"Merchandise"},{"Trans. Date":"1/6/2018","Post Date":"1/7/2018","Description":"DISCOUNT DRUG MART 32 STOW OH","Amount":2.19,"Category":"Merchandise"},{"Trans. Date":"1/9/2018","Post Date":"1/9/2018","Description":"CIRCLE K 05416 STOW OH00947R","Amount":3.94,"Category":"Gasoline"},{"Trans. Date":"1/9/2018","Post Date":"1/9/2018","Description":"CIRCLE K 05416 STOW OH00915R","Amount":52.99,"Category":"Gasoline"},{"Trans. Date":"1/13/2018","Post Date":"1/13/2018","Description":"AUTOZONE #0722 STOW OH","Amount":85.36,"Category":"Automotive"},{"Trans. Date":"1/13/2018","Post Date":"1/13/2018","Description":"DISCOUNT DRUG MART 32 STOW OH","Amount":26.68,"Category":"Merchandise"},{"Trans. Date":"1/13/2018","Post Date":"1/13/2018","Description":"EL CAMPESINO STOW OH","Amount":6.5,"Category":"Restaurants"},{"Trans. Date":"1/13/2018","Post Date":"1/13/2018","Description":"TARGET STOW OH","Amount":197.9,"Category":"Merchandise"},{"Trans. Date":"1/14/2018","Post Date":"1/14/2018","Description":"DISCOUNT DRUG MART 32 STOW OH","Amount":13.48,"Category":"Merchandise"},{"Trans. Date":"1/15/2018","Post Date":"1/15/2018","Description":"TARGET.COM * 800-591-3869 MN","Amount":22.41,"Category":"Merchandise"},{"Trans. Date":"1/16/2018","Post Date":"1/16/2018","Description":"BUFFALO WILD WINGS KENT KENT OH","Amount":63.22,"Category":"Restaurants"},{"Trans. Date":"1/16/2018","Post Date":"1/16/2018","Description":"PARTA - KCG KENT OH","Amount":4,"Category":"Government Services"},{"Trans. Date":"1/16/2018","Post Date":"1/16/2018","Description":"REMEMBERNHU 402-935-7733 IA","Amount":60,"Category":"Services"},{"Trans. Date":"1/16/2018","Post Date":"1/16/2018","Description":"TARGET.COM * 800-591-3869 MN","Amount":44.81,"Category":"Merchandise"},{"Trans. Date":"1/16/2018","Post Date":"1/16/2018","Description":"TREE CITY COFFEE & PASTR KENT OH","Amount":17.75,"Category":"Restaurants"},{"Trans. Date":"1/17/2018","Post Date":"1/17/2018","Description":"BESTBUYCOM805526794885 888-BESTBUY MN","Amount":343.72,"Category":"Merchandise"},{"Trans. Date":"1/19/2018","Post Date":"1/19/2018","Description":"DISCOUNT DRUG MART 32 STOW OH","Amount":5.98,"Category":"Merchandise"},{"Trans. Date":"1/19/2018","Post Date":"1/19/2018","Description":"U-HAUL OF KENT-STOW KENT OH","Amount":15.88,"Category":"Travel/ Entertainment"},{"Trans. Date":"1/19/2018","Post Date":"1/19/2018","Description":"WALMART GROCERY 800-966-6546 AR","Amount":5.99,"Category":"Supermarkets"},{"Trans. Date":"1/19/2018","Post Date":"1/19/2018","Description":"WALMART GROCERY 800-966-6546 AR","Amount":17.16,"Category":"Supermarkets"},{"Trans. Date":"1/19/2018","Post Date":"1/19/2018","Description":"WALMART GROCERY 800-966-6546 AR","Amount":500.97,"Category":"Supermarkets"},{"Trans. Date":"1/20/2018","Post Date":"1/20/2018","Description":"GOOGLE *GOOGLE PLAY G.CO/HELPPAY#CAP0HFFS7W","Amount":2.12,"Category":"Services"},{"Trans. Date":"1/20/2018","Post Date":"1/20/2018","Description":"LOWE'S OF STOW, OH. STOW OH","Amount":256.48,"Category":"Home Improvement"},{"Trans. Date":"1/23/2018","Post Date":"1/23/2018","Description":"CASHBACK BONUS REDEMPTION PYMT/STMT CRDT","Amount":-32.2,"Category":"Awards and Rebate Credits"},{"Trans. Date":"1/23/2018","Post Date":"1/23/2018","Description":"INTERNET PAYMENT - THANK YOU","Amount":-2394.51,"Category":"Payments and Credits"},{"Trans. Date":"1/27/2018","Post Date":"1/27/2018","Description":"GIANT-EAGLE #4096 STOW OH","Amount":67.81,"Category":"Supermarkets"},{"Trans. Date":"1/27/2018","Post Date":"1/27/2018","Description":"OFFICEMAX/OFFICE DEPOT63 STOW OH","Amount":21.06,"Category":"Merchandise"},{"Trans. Date":"1/27/2018","Post Date":"1/27/2018","Description":"TARGET STOW OH","Amount":71,"Category":"Merchandise"},{"Trans. Date":"1/29/2018","Post Date":"1/29/2018","Description":"NETFLIX.COM NETFLIX.COM CA19899514437","Amount":14.93,"Category":"Services"},{"Trans. Date":"1/30/2018","Post Date":"1/30/2018","Description":"SQ *TWISTED MELTZ KENT OH0002305843011416898511","Amount":16.87,"Category":"Restaurants"},{"Trans. Date":"1/30/2018","Post Date":"1/30/2018","Description":"TARGET STOW OH","Amount":49.37,"Category":"Merchandise"}]$$::jsonb; + +----------------------------------------------------test if source exists---------------------------------------------------------------------------------- + + SELECT + defn + INTO + _defn + FROM + tps.srce + WHERE + srce = _srce; + + IF _defn IS NULL THEN + _message:= + format( + $$ + { + "status":"fail", + "message":"source %L does not exists" + } + $$, + _srce + )::jsonb; + RETURN _message; + END IF; + + -------------unwrap the json record and apply the path(s) of the constraint to build a constraint key per record----------------------------------------------------------------------------------- + + WITH + pending_list AS ( + SELECT + _srce srce + ,j.rec + ,j.id + --aggregate back to the record since multiple paths may be listed in the constraint + --it is unclear why the "->>0" is required to correctly extract the text array from the jsonb + ,tps.jsonb_concat_obj( + jsonb_build_object( + --the new json key is the path itself + cons.path->>0 + ,j.rec#>((cons.path->>0)::text[]) + ) + ) json_key + FROM + jsonb_array_elements(_recs) WITH ORDINALITY j(rec,id) + JOIN LATERAL jsonb_array_elements(_defn->'constraint') WITH ORDINALITY cons(path, seq) ON TRUE + GROUP BY + j.rec + ,j.id + ) + + -----------create a unique list of keys from staged rows------------------------------------------------------------------------------------------ + + , pending_keys AS ( + SELECT DISTINCT + json_key + FROM + pending_list + ) + + -----------list of keys already loaded to tps----------------------------------------------------------------------------------------------------- + + , matched_keys AS ( + SELECT DISTINCT + k.json_key + FROM + pending_keys k + INNER JOIN tps.trans t ON + t.ic = k.json_key + ) + + -----------return unique keys that are not already in tps.trans----------------------------------------------------------------------------------- + + , unmatched_keys AS ( + SELECT + json_key + FROM + pending_keys + + EXCEPT + + SELECT + json_key + FROM + matched_keys + ) + + --------build log record-------------------+------------------------------------------------------------------------------------------------ + + , logged AS ( + INSERT INTO + tps.trans_log (info) + SELECT + JSONB_BUILD_OBJECT('time_stamp',CURRENT_TIMESTAMP) + ||JSONB_BUILD_OBJECT('srce',_srce) + --||JSONB_BUILD_OBJECT('path',_path) + ||JSONB_BUILD_OBJECT('not_inserted', + ( + SELECT + jsonb_agg(json_key) + FROM + matched_keys + ) + ) + ||JSONB_BUILD_OBJECT('inserted', + ( + SELECT + jsonb_agg(json_key) + FROM + unmatched_keys + ) + ) + RETURNING * + ) + + -----------insert pending rows that have key with no trans match----------------------------------------------------------------------------------- + --need to look into mapping the transactions prior to loading + + , inserted AS ( + INSERT INTO + tps.trans (srce, rec, ic, logid) + SELECT + pl.srce + ,pl.rec + ,pl.json_key + ,logged.id + FROM + pending_list pl + INNER JOIN unmatched_keys u ON + u.json_key = pl.json_key + CROSS JOIN logged + ORDER BY + pl.id ASC + ----this conflict is only if an exact duplicate rec json happens, which will be rejected + ----therefore, records may not be inserted due to ay matches with certain json fields, or if the entire json is a duplicate, reason is not specified + RETURNING * + ) + + SELECT + id + ,info + INTO + _log_id + ,_log_info + FROM + logged; + + --RAISE NOTICE 'import logged under id# %, info: %', _log_id, _log_info; + + _message:= + ( + $$ + { + "status":"complete" + } + $$::jsonb + )||jsonb_build_object('details',_log_info); + + RETURN _message; + +EXCEPTION WHEN OTHERS THEN + GET STACKED DIAGNOSTICS + _MESSAGE_TEXT = MESSAGE_TEXT, + _PG_EXCEPTION_DETAIL = PG_EXCEPTION_DETAIL, + _PG_EXCEPTION_HINT = PG_EXCEPTION_HINT; + _message:= + ($$ + { + "status":"fail", + "message":"error importing data" + } + $$::jsonb) + ||jsonb_build_object('message_text',_MESSAGE_TEXT) + ||jsonb_build_object('pg_exception_detail',_PG_EXCEPTION_DETAIL); + return _message; +END; +$f$ +LANGUAGE plpgsql; + + +---------------overwrite maps-------------------------------------------------------------------------------------------------------------- + +CREATE OR REPLACE FUNCTION tps.srce_map_overwrite(_srce text) RETURNS jsonb +AS +$f$ +DECLARE + _message jsonb; + _MESSAGE_TEXT text; + _PG_EXCEPTION_DETAIL text; + _PG_EXCEPTION_HINT text; + +BEGIN + WITH + --------------------apply regex operations to transactions----------------------------------------------------------------------------------- + + rx AS ( + SELECT + t.srce, + t.id, + t.rec, + m.target, + m.seq, + regex->'regex'->>'function' regex_function, + e.v ->> 'field' result_key_name, + e.v ->> 'key' target_json_path, + e.v ->> 'flag' regex_options_flag, + e.v->>'map' map_intention, + e.v->>'retain' retain_result, + e.v->>'regex' regex_expression, + e.rn target_item_number, + COALESCE(mt.rn,rp.rn,1) result_number, + mt.mt rx_match, + rp.rp rx_replace, + CASE e.v->>'map' + WHEN 'y' THEN + e.v->>'field' + ELSE + null + END map_key, + CASE e.v->>'map' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(mt.mt[1]) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rp.rp) + ELSE + '{}'::jsonb + END + ELSE + NULL + END map_val, + CASE e.v->>'retain' + WHEN 'y' THEN + e.v->>'field' + ELSE + NULL + END retain_key, + CASE e.v->>'retain' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(trim(mt.mt[1])) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rtrim(rp.rp)) + ELSE + '{}'::jsonb + END + ELSE + NULL + END retain_val + FROM + --------------------------start with all regex maps------------------------------------------------------------------------------------ + tps.map_rm m + --------------------------isolate matching basis to limit map to only look at certain json--------------------------------------------- + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'where') w(v) ON TRUE + --------------------------join to main transaction table but only certain key/values are included-------------------------------------- + INNER JOIN tps.trans t ON + t.srce = m.srce AND + t.rec @> w.v + --------------------------break out array of regluar expressions in the map------------------------------------------------------------ + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'defn') WITH ORDINALITY e(v, rn) ON true + --------------------------each regex references a path to the target value, extract the target from the reference and do regex--------- + LEFT JOIN LATERAL regexp_matches(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text,COALESCE(e.v ->> 'flag','')) WITH ORDINALITY mt(mt, rn) ON + m.regex->'regex'->>'function' = 'extract' + --------------------------same as above but for a replacement type function------------------------------------------------------------ + LEFT JOIN LATERAL regexp_replace(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text, e.v ->> 'replace'::text,e.v ->> 'flag') WITH ORDINALITY rp(rp, rn) ON + m.regex->'regex'->>'function' = 'replace' + WHERE + --t.allj IS NULL + t.srce = _srce + --rec @> '{"Transaction":"ACH Credits","Transaction":"ACH Debits"}' + --rec @> '{"Description":"CHECK 93013270 086129935"}'::jsonb + ORDER BY + t.id DESC, + m.target, + e.rn, + COALESCE(mt.rn,rp.rn,1) + ) + + --SELECT count(*) FROM rx LIMIT 100 + + + , agg_to_target_items AS ( + SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,CASE WHEN map_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + map_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(map_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(map_val ORDER BY result_number) + END + ) + END map_val + ,CASE WHEN retain_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + retain_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(retain_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(retain_val ORDER BY result_number) + END + ) + END retain_val + FROM + rx + GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,map_key + ,retain_key + ) + + --SELECT * FROM agg_to_target_items LIMIT 100 + + + , agg_to_target AS ( + SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,tps.jsonb_concat_obj(COALESCE(map_val,'{}'::JSONB)) map_val + ,jsonb_strip_nulls(tps.jsonb_concat_obj(COALESCE(retain_val,'{}'::JSONB))) retain_val + FROM + agg_to_target_items + GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ORDER BY + id + ) + + + --SELECT * FROM agg_to_target + + + , link_map AS ( + SELECT + a.srce + ,a.id + ,a.target + ,a.seq + ,a.map_intention + ,a.map_val + ,a.retain_val retain_value + ,v.map + FROM + agg_to_target a + LEFT OUTER JOIN tps.map_rv v ON + v.srce = a.srce AND + v.target = a.target AND + v.retval = a.map_val + ) + + --SELECT * FROM link_map + + , agg_to_id AS ( + SELECT + srce + ,id + ,tps.jsonb_concat_obj(COALESCE(retain_value,'{}'::jsonb) ORDER BY seq DESC) retain_val + ,tps.jsonb_concat_obj(COALESCE(map,'{}'::jsonb)) map + FROM + link_map + GROUP BY + srce + ,id + ) + + --SELECT agg_to_id.srce, agg_to_id.id, jsonb_pretty(agg_to_id.retain_val) , jsonb_pretty(agg_to_id.map) FROM agg_to_id ORDER BY id desc LIMIT 100 + + + + UPDATE + tps.trans t + SET + map = o.map, + parse = o.retain_val, + allj = t.rec||o.map||o.retain_val + FROM + agg_to_id o + WHERE + o.id = t.id; + + _message:= jsonb_build_object('status','complete'); + RETURN _message; + +EXCEPTION WHEN OTHERS THEN + + GET STACKED DIAGNOSTICS + _MESSAGE_TEXT = MESSAGE_TEXT, + _PG_EXCEPTION_DETAIL = PG_EXCEPTION_DETAIL, + _PG_EXCEPTION_HINT = PG_EXCEPTION_HINT; + _message:= + ($$ + { + "status":"fail", + "message":"error setting map value" + } + $$::jsonb) + ||jsonb_build_object('message_text',_MESSAGE_TEXT) + ||jsonb_build_object('pg_exception_detail',_PG_EXCEPTION_DETAIL); + + RETURN _message; +END; +$f$ +language plpgsql; + +---------------------set map values from json array of json objects----------------------------------------------------- + + +DROP FUNCTION IF EXISTS tps.map_rv_set; +CREATE OR REPLACE FUNCTION tps.map_rv_set(_defn jsonb) RETURNS jsonb +AS +$f$ +DECLARE + _message jsonb; + _MESSAGE_TEXT text; + _PG_EXCEPTION_DETAIL text; + _PG_EXCEPTION_HINT text; +BEGIN + INSERT INTO + tps.map_rv (srce, target, retval, map, hist) + SELECT + r.source + ,r.map + ,r.ret_val + ,r.mapped + ,jsonb_build_object( + 'hist_defn',mapped + ,'effective',jsonb_build_array(CURRENT_TIMESTAMP,null::timestamptz) + ) || '[]'::jsonb + FROM + JSONB_ARRAY_ELEMENTS(_defn) WITH ORDINALITY ae(r,s) + JOIN LATERAL jsonb_to_record(ae.r) r(source TEXT,map TEXT, ret_val jsonb, mapped jsonb) ON TRUE + ON CONFLICT ON CONSTRAINT map_rv_pk DO UPDATE + SET + map = excluded.map + ,hist = + --the new definition going to position -0- + jsonb_build_object( + 'hist_defn',excluded.map + ,'effective',jsonb_build_array(CURRENT_TIMESTAMP,null::timestamptz) + ) + --the previous definition, set upper bound of effective range which was previously null + || jsonb_set( + map_rv.hist + ,'{0,effective,1}'::text[] + ,to_jsonb(CURRENT_TIMESTAMP) + ); + + -------return message-------------------------------------------------------------------------------------------------- + _message:= jsonb_build_object('status','complete'); + RETURN _message; + +EXCEPTION WHEN OTHERS THEN + + GET STACKED DIAGNOSTICS + _MESSAGE_TEXT = MESSAGE_TEXT, + _PG_EXCEPTION_DETAIL = PG_EXCEPTION_DETAIL, + _PG_EXCEPTION_HINT = PG_EXCEPTION_HINT; + _message:= + ($$ + { + "status":"fail", + "message":"error setting map value" + } + $$::jsonb) + ||jsonb_build_object('message_text',_MESSAGE_TEXT) + ||jsonb_build_object('pg_exception_detail',_PG_EXCEPTION_DETAIL); + + RETURN _message; +END; +$f$ +LANGUAGE plpgsql; + +------------------------------test regex with only unique results-------------------------------------------------------------- + +DROP FUNCTION IF EXISTS tps.test_regex(jsonb); +CREATE FUNCTION tps.test_regex(_defn jsonb) RETURNS jsonb +LANGUAGE plpgsql +AS +$f$ +DECLARE + _rslt jsonb; +BEGIN + + WITH + + --------------------apply regex operations to transactions--------------------------------------------------------------------------------- + + rx AS ( + SELECT + t.srce, + t.id, + t.rec, + m.target, + m.seq, + regex->'regex'->>'function' regex_function, + e.v ->> 'field' result_key_name, + e.v ->> 'key' target_json_path, + e.v ->> 'flag' regex_options_flag, + e.v->>'map' map_intention, + e.v->>'retain' retain_result, + e.v->>'regex' regex_expression, + e.rn target_item_number, + COALESCE(mt.rn,rp.rn,1) result_number, + mt.mt rx_match, + rp.rp rx_replace, + CASE e.v->>'map' + WHEN 'y' THEN + e.v->>'field' + ELSE + null + END map_key, + CASE e.v->>'map' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(mt.mt[1]) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rp.rp) + ELSE + '{}'::jsonb + END + ELSE + NULL + END map_val, + CASE e.v->>'retain' + WHEN 'y' THEN + e.v->>'field' + ELSE + NULL + END retain_key, + CASE e.v->>'retain' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(trim(mt.mt[1])) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rtrim(rp.rp)) + ELSE + '{}'::jsonb + END + ELSE + NULL + END retain_val + FROM + --------------------------start with all regex maps------------------------------------------------------------------------------------ + (SELECT _defn->>'srce' srce, _defn->>'name' target, _defn->'regex' regex, (_defn->>'sequence')::numeric seq) m + --------------------------isolate matching basis to limit map to only look at certain json--------------------------------------------- + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'where') w(v) ON TRUE + --------------------------break out array of regluar expressions in the map------------------------------------------------------------ + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'defn') WITH ORDINALITY e(v, rn) ON true + --------------------------join to main transaction table but only certain key/values are included-------------------------------------- + INNER JOIN tps.trans t ON + t.srce = m.srce AND + t.rec @> w.v + --------------------------each regex references a path to the target value, extract the target from the reference and do regex--------- + LEFT JOIN LATERAL regexp_matches(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text,COALESCE(e.v ->> 'flag','')) WITH ORDINALITY mt(mt, rn) ON + m.regex->'regex'->>'function' = 'extract' + --------------------------same as above but for a replacement type function------------------------------------------------------------ + LEFT JOIN LATERAL regexp_replace(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text, e.v ->> 'replace'::text,e.v ->> 'flag') WITH ORDINALITY rp(rp, rn) ON + m.regex->'regex'->>'function' = 'replace' + ORDER BY + t.id DESC, + m.target, + e.rn, + COALESCE(mt.rn,rp.rn,1) + ) + + --SELECT * FROM rx LIMIT 100 + + + , agg_to_target_items AS ( + SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,CASE WHEN map_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + map_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(map_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(map_val ORDER BY result_number) + END + ) + END map_val + ,CASE WHEN retain_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + retain_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(retain_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(retain_val ORDER BY result_number) + END + ) + END retain_val + FROM + rx + GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,map_key + ,retain_key + ) + + --SELECT * FROM agg_to_target_items LIMIT 100 + + + , agg_to_target AS ( + SELECT + srce + ,id + ,target + ,seq + ,map_intention + ,tps.jsonb_concat_obj(COALESCE(map_val,'{}'::JSONB)) map_val + ,jsonb_strip_nulls(tps.jsonb_concat_obj(COALESCE(retain_val,'{}'::JSONB))) retain_val + FROM + agg_to_target_items + GROUP BY + srce + ,id + ,target + ,seq + ,map_intention + ) + + + , agg_to_ret AS ( + SELECT + srce + ,target + ,seq + ,map_intention + ,map_val + ,retain_val + ,count(*) "count" + FROM + agg_to_target + GROUP BY + srce + ,target + ,seq + ,map_intention + ,map_val + ,retain_val + ) + ,agg_to_id AS ( + SELECT + l.srce + ,l.target + ,l.map_val + ,l."count" + FROM + agg_to_ret l + ORDER BY + l.srce + ,l.target + ,l."count" desc + ) + SELECT + jsonb_agg(row_to_json(agg_to_id)::jsonb) + INTO + _rslt + FROM + agg_to_id; + + RETURN _rslt; +END; +$f$; + +------------------------------test regex with all original records-------------------------------------------------------------- + +DROP FUNCTION IF EXISTS tps.report_unmapped_recs; +CREATE FUNCTION tps.report_unmapped_recs(_srce text) RETURNS TABLE +( + source text, + map text, + ret_val jsonb, + "count" bigint, + recs jsonb + +) +LANGUAGE plpgsql +AS +$f$ +BEGIN + +/* +first get distinct target json values +then apply regex +*/ + +RETURN QUERY +WITH + +--------------------apply regex operations to transactions--------------------------------------------------------------------------------- + +rx AS ( + SELECT + t.srce, + t.id, + t.rec, + m.target, + m.seq, + regex->'regex'->>'function' regex_function, + e.v ->> 'field' result_key_name, + e.v ->> 'key' target_json_path, + e.v ->> 'flag' regex_options_flag, + e.v->>'map' map_intention, + e.v->>'retain' retain_result, + e.v->>'regex' regex_expression, + e.rn target_item_number, + COALESCE(mt.rn,rp.rn,1) result_number, + mt.mt rx_match, + rp.rp rx_replace, + CASE e.v->>'map' + WHEN 'y' THEN + e.v->>'field' + ELSE + null + END map_key, + CASE e.v->>'map' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(mt.mt[1]) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rp.rp) + ELSE + '{}'::jsonb + END + ELSE + NULL + END map_val, + CASE e.v->>'retain' + WHEN 'y' THEN + e.v->>'field' + ELSE + NULL + END retain_key, + CASE e.v->>'retain' + WHEN 'y' THEN + CASE regex->'regex'->>'function' + WHEN 'extract' THEN + CASE WHEN array_upper(mt.mt,1)=1 + THEN to_json(trim(mt.mt[1])) + ELSE array_to_json(mt.mt) + END::jsonb + WHEN 'replace' THEN + to_jsonb(rtrim(rp.rp)) + ELSE + '{}'::jsonb + END + ELSE + NULL + END retain_val + FROM + --------------------------start with all regex maps------------------------------------------------------------------------------------ + tps.map_rm m + --------------------------isolate matching basis to limit map to only look at certain json--------------------------------------------- + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'where') w(v) ON TRUE + --------------------------join to main transaction table but only certain key/values are included-------------------------------------- + INNER JOIN tps.trans t ON + t.srce = m.srce AND + t.rec @> w.v + --------------------------break out array of regluar expressions in the map------------------------------------------------------------ + LEFT JOIN LATERAL jsonb_array_elements(m.regex->'regex'->'defn') WITH ORDINALITY e(v, rn) ON true + --------------------------each regex references a path to the target value, extract the target from the reference and do regex--------- + LEFT JOIN LATERAL regexp_matches(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text,COALESCE(e.v ->> 'flag','')) WITH ORDINALITY mt(mt, rn) ON + m.regex->'regex'->>'function' = 'extract' + --------------------------same as above but for a replacement type function------------------------------------------------------------ + LEFT JOIN LATERAL regexp_replace(t.rec #>> ((e.v ->> 'key')::text[]), e.v ->> 'regex'::text, e.v ->> 'replace'::text,e.v ->> 'flag') WITH ORDINALITY rp(rp, rn) ON + m.regex->'regex'->>'function' = 'replace' + WHERE + --t.allj IS NULL + t.srce = _srce AND + e.v @> '{"map":"y"}'::jsonb + --rec @> '{"Transaction":"ACH Credits","Transaction":"ACH Debits"}' + --rec @> '{"Description":"CHECK 93013270 086129935"}'::jsonb + ORDER BY + t.id DESC, + m.target, + e.rn, + COALESCE(mt.rn,rp.rn,1) +) + +--SELECT * FROM rx LIMIT 100 + + +, agg_to_target_items AS ( +SELECT + srce + ,id + ,rec + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,CASE WHEN map_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + map_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(map_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(map_val ORDER BY result_number) + END + ) + END map_val + ,CASE WHEN retain_key IS NULL + THEN + NULL + ELSE + jsonb_build_object( + retain_key, + CASE WHEN max(result_number) = 1 + THEN + jsonb_agg(retain_val ORDER BY result_number) -> 0 + ELSE + jsonb_agg(retain_val ORDER BY result_number) + END + ) + END retain_val +FROM + rx +GROUP BY + srce + ,id + ,rec + ,target + ,seq + ,map_intention + ,regex_function + ,target_item_number + ,result_key_name + ,target_json_path + ,map_key + ,retain_key +) + +--SELECT * FROM agg_to_target_items LIMIT 100 + + +, agg_to_target AS ( +SELECT + srce + ,id + ,rec + ,target + ,seq + ,map_intention + ,tps.jsonb_concat_obj(COALESCE(map_val,'{}'::JSONB)) map_val + ,jsonb_strip_nulls(tps.jsonb_concat_obj(COALESCE(retain_val,'{}'::JSONB))) retain_val +FROM + agg_to_target_items +GROUP BY + srce + ,id + ,rec + ,target + ,seq + ,map_intention +) + + +, agg_to_ret AS ( +SELECT + srce + ,target + ,seq + ,map_intention + ,map_val + ,retain_val + ,count(*) "count" + ,jsonb_agg(rec) rec +FROM + agg_to_target +GROUP BY + srce + ,target + ,seq + ,map_intention + ,map_val + ,retain_val +) + +, link_map AS ( +SELECT + a.srce + ,a.target + ,a.seq + ,a.map_intention + ,a.map_val + ,a."count" + ,a.rec + ,a.retain_val + ,v.map mapped_val +FROM + agg_to_ret a + LEFT OUTER JOIN tps.map_rv v ON + v.srce = a.srce AND + v.target = a.target AND + v.retval = a.map_val +) +SELECT + l.srce + ,l.target + ,l.map_val + ,l."count" + ,l.rec +FROM + link_map l +WHERE + l.mapped_val IS NULL +ORDER BY + l.srce + ,l.target + ,l."count" desc; +END; +$f$ \ No newline at end of file diff --git a/test/dcard_source/curl b/test/1.dcard_source/curl similarity index 71% rename from test/dcard_source/curl rename to test/1.dcard_source/curl index b92fbae..631a26e 100644 --- a/test/dcard_source/curl +++ b/test/1.dcard_source/curl @@ -1 +1 @@ -curl -H "Content-Type: application/json" -X POST -d@./srce.json http://localhost/srce_set \ No newline at end of file +curl -H "Content-Type: application/json" -X POST -d@./srce.json http://localhost/source \ No newline at end of file diff --git a/test/dcard_source/srce.json b/test/1.dcard_source/srce.json similarity index 100% rename from test/dcard_source/srce.json rename to test/1.dcard_source/srce.json diff --git a/test/dcard_regex/curl b/test/2.dcard_regex/curl similarity index 100% rename from test/dcard_regex/curl rename to test/2.dcard_regex/curl diff --git a/test/dcard_regex/regex.json b/test/2.dcard_regex/regex.json similarity index 100% rename from test/dcard_regex/regex.json rename to test/2.dcard_regex/regex.json diff --git a/test/3.dcard_maps/curl b/test/3.dcard_maps/curl new file mode 100644 index 0000000..5634e22 --- /dev/null +++ b/test/3.dcard_maps/curl @@ -0,0 +1 @@ +curl -H "Content-Type: application/json" -X POST -d@./mapping.json http://localhost/mapping \ No newline at end of file diff --git a/test/3.dcard_maps/mapping.json b/test/3.dcard_maps/mapping.json new file mode 100644 index 0000000..4dddc7b --- /dev/null +++ b/test/3.dcard_maps/mapping.json @@ -0,0 +1,4299 @@ +[ + { + "map": "First 20", + "ret_val": { + "f20": "DISCOUNT DRUG MART 3" + }, + "source": "dcard", + "mapped": { + "party": "Discount Drug Mart", + "reason": "groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TARGET STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Target", + "reason": "groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CIRCLE K 05416 STOW " + }, + "source": "dcard", + "mapped": { + "party": "Circle K", + "reason": "gasoline" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TARGET.COM * 800-591" + }, + "source": "dcard", + "mapped": { + "party": "Target", + "reason": "home supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BESTBUYCOM8055267948" + }, + "source": "dcard", + "mapped": { + "party": "BestBuy", + "reason": "home supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BUFFALO WILD WINGS K" + }, + "source": "dcard", + "mapped": { + "party": "Buffalo Wild Wings", + "reason": "restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CASHBACK BONUS REDEM" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "financing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CLE CLINIC PT PMTS 2" + }, + "source": "dcard", + "mapped": { + "party": "Cleveland Clinic", + "reason": "medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WWW.KOHLS.COM #0873 " + }, + "source": "dcard", + "mapped": { + "party": "Kohls", + "reason": "clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO73601 " + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "gasoline" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "L A SHISH GRILL STOW" + }, + "source": "dcard", + "mapped": { + "party": "La Shish", + "reason": "restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OFFICEMAX/OFFICE DEP" + }, + "source": "dcard", + "mapped": { + "party": "Office Max", + "reason": "home supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SP * ERIKTHEFLUTEMAK" + }, + "source": "dcard", + "mapped": { + "party": "Erik The Flutemaker", + "reason": "gifts" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *SUGAR RUSH KENT " + }, + "source": "dcard", + "mapped": { + "party": "Sugar Rush", + "reason": "restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MINIMUM INTEREST CHA" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "fees" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CASH ADVANCE FEE" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "fees" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUMMIT CO PARKING GA" + }, + "source": "dcard", + "mapped": { + "party": "Summit County Parking Garage", + "reason": "civic duty" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AUTOZONE #1941 STREE" + }, + "source": "dcard", + "mapped": { + "party": "Autozone", + "reason": "auto maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE KENT STAGE KENT " + }, + "source": "dcard", + "mapped": { + "party": "The Kent Stage", + "reason": "gifts" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MSFT *AZURE 800-642-" + }, + "source": "dcard", + "mapped": { + "party": "Azure", + "reason": "recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHIPOTLE ONLINE 303-" + }, + "source": "dcard", + "mapped": { + "party": "Chipotle", + "reason": "restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DIVERSIFIED MINI STO" + }, + "source": "dcard", + "mapped": { + "party": "Diversified Tree Farm", + "reason": "recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GOOGLE *YOUTUBE VIDE" + }, + "source": "dcard", + "mapped": { + "party": "Youtube", + "reason": "recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHIPOTLE 1115 STREET" + }, + "source": "dcard", + "mapped": { + "party": "Chipotle", + "reason": "restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHILDRENS HOSPITAL M" + }, + "source": "dcard", + "mapped": { + "party": "Akron Children's Hospital", + "reason": "medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BATH & BODY WORKS.CO" + }, + "source": "dcard", + "mapped": { + "party": "Bath and Body Works", + "reason": "home supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "IN *MR. BULKY'S FOOD" + }, + "source": "dcard", + "mapped": { + "party": "Mr. Bulky's", + "reason": "recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BP#954778736210 7-EL" + }, + "source": "dcard", + "mapped": { + "party": "BP", + "reason": "gasoline" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GOOGLE *ASCIIFLOW.CO" + }, + "source": "dcard", + "mapped": { + "party": "ASCII Flow", + "reason": "recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MSFT * E04004X603 80" + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "home supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BIG LOTS #00453 STOW" + }, + "source": "dcard", + "mapped": { + "party": "Big Lots", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ACME NO. 17 STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Acme", + "reason": "groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AT&T *PAYMENT 800-28" + }, + "source": "dcard", + "mapped": { + "party": "AT&T", + "reason": "internet" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AUTOZONE #0722 STOW " + }, + "source": "dcard", + "mapped": { + "party": "Autozone", + "reason": "auto maint" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "1794MOTHERHOOD #1794" + }, + "source": "dcard", + "mapped": { + "party": "Motherhood", + "reason": "Clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "3 PALMS HUDSON OH" + }, + "source": "dcard", + "mapped": { + "province": "Ohio", + "city": "Hudson", + "reason": "Restaurante", + "party": "3 Palms" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "36241 7-ELEVEN STOW " + }, + "source": "dcard", + "mapped": { + "province": "Ohio", + "city": "Stow", + "reason": "Gasoline", + "party": "7-Eleven" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "7-ELEVEN 36241 STOW " + }, + "source": "dcard", + "mapped": { + "province": "Ohio", + "city": "Stow", + "reason": "Gasoline", + "party": "7-Eleven" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "98626 - 200 PUBLIC S" + }, + "source": "dcard", + "mapped": { + "party": "Public Square Parking Garage", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ACE HARDWARE HUDSON " + }, + "source": "dcard", + "mapped": { + "party": "Ace Hardware", + "reason": "Home Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ACH CAFE AND STARBUC" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ACME FRESH MARKET UN" + }, + "source": "dcard", + "mapped": { + "party": "Acme", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ADVANCE AUTO PARTS #" + }, + "source": "dcard", + "mapped": { + "party": "Advance Auto", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ADY*NETFLIX WWW.NETF" + }, + "source": "dcard", + "mapped": { + "party": "Netflix", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AIRBNB INC 415-800-5" + }, + "source": "dcard", + "mapped": { + "party": "Airbnb Inc", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AKRON CHILDRENS HOSP" + }, + "source": "dcard", + "mapped": { + "party": "Akron Childrens", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AKRON GENERAL MEDICA" + }, + "source": "dcard", + "mapped": { + "party": "Akron General", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AKRON ZOOLOGICAL PAR" + }, + "source": "dcard", + "mapped": { + "party": "Akron Zoological Park", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AMAZON MKTPLACE PMTS" + }, + "source": "dcard", + "mapped": { + "party": "Amazon", + "reason": "Amazon" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AMAZON VIDEO ON DEMA" + }, + "source": "dcard", + "mapped": { + "party": "Amazon Video", + "reason": "Amazon" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AMAZON.COM AMZN.COM/" + }, + "source": "dcard", + "mapped": { + "party": "Amazon", + "reason": "Amazon" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ANC*ANCESTRY.COM 800" + }, + "source": "dcard", + "mapped": { + "party": "Ancestry.com", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "APPLEBEES C942048569" + }, + "source": "dcard", + "mapped": { + "party": "Applebees", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "APPLEBEES S942049899" + }, + "source": "dcard", + "mapped": { + "party": "Applebees", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ARBYS #1831 STOW STO" + }, + "source": "dcard", + "mapped": { + "party": "Arbys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ARBYS 1831 STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Arbys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ARBYS 2227 PRINCETON" + }, + "source": "dcard", + "mapped": { + "party": "Arbys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ARBYS 5355 UNIONTOWN" + }, + "source": "dcard", + "mapped": { + "party": "Arbys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ASIAN CHAO FAIRLAWN " + }, + "source": "dcard", + "mapped": { + "party": "Asian Chao", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ATT*BILL PAYMENT 800" + }, + "source": "dcard", + "mapped": { + "party": "AT&T", + "reason": "Internet" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AUTOZONE #0723 AKRON" + }, + "source": "dcard", + "mapped": { + "party": "Autozone", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AUTOZONE #2465 MOUNT" + }, + "source": "dcard", + "mapped": { + "party": "Autozone", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "AUTOZONE #4506 STOW " + }, + "source": "dcard", + "mapped": { + "party": "Autozone", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BATH&BODY AKRON OH" + }, + "source": "dcard", + "mapped": { + "party": "Bath & Body Works", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BATH&BODY STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Bath & Body Works", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BATHANDBODYWORKS.COM" + }, + "source": "dcard", + "mapped": { + "party": "Bath & Body Works", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BEDBATH&BEYOND STOW " + }, + "source": "dcard", + "mapped": { + "party": "Bed Bath & Beyond", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BEDBATH&BEYOND#0060 " + }, + "source": "dcard", + "mapped": { + "party": "Bed Bath & Beyond", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BEDBATH&BEYOND#0360 " + }, + "source": "dcard", + "mapped": { + "party": "Bed Bath & Beyond", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BELLACINOS PIZZA AND" + }, + "source": "dcard", + "mapped": { + "party": "Bellacinos", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BEST BUY 00002782295" + }, + "source": "dcard", + "mapped": { + "party": "Best Buy", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BESTBUY.COM 888-BEST" + }, + "source": "dcard", + "mapped": { + "party": "Best Buy", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BESTBUYCOM7424430059" + }, + "source": "dcard", + "mapped": { + "party": "Best Buy", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BIG DEES TACK STREET" + }, + "source": "dcard", + "mapped": { + "party": "Big Dees Tack & Supply", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BISTRO ON MAIN KENT " + }, + "source": "dcard", + "mapped": { + "party": "Bistro On Main", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BP#93079431210 BUFFA" + }, + "source": "dcard", + "mapped": { + "party": "BP", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BP#954635936241 7-EL" + }, + "source": "dcard", + "mapped": { + "party": "BP", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BREWSTER'S TWINSBURG" + }, + "source": "dcard", + "mapped": { + "party": "Brewsters", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BRICCO KENT KENT OH" + }, + "source": "dcard", + "mapped": { + "party": "Bricco", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BRUEGGERS #209 STOW " + }, + "source": "dcard", + "mapped": { + "party": "Brueggers Bagels", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BUMP FIRE SYSTEMS MI" + }, + "source": "dcard", + "mapped": { + "party": "Bump Fire Systems", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BURGER KING #14183 S" + }, + "source": "dcard", + "mapped": { + "party": "Burger King", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BURGER KING #4422 BR" + }, + "source": "dcard", + "mapped": { + "party": "Burger King", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BURGER KING #5239 NE" + }, + "source": "dcard", + "mapped": { + "party": "Burger King", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BURGER KING #5811 ST" + }, + "source": "dcard", + "mapped": { + "party": "Burger King", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BURGERFI CUYAHOGA FA" + }, + "source": "dcard", + "mapped": { + "party": "BurgerFI", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BV HEGDE,MD 330-6733" + }, + "source": "dcard", + "mapped": { + "party": "Bv Hegde, MD", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BV HEGDE,MD 33067333" + }, + "source": "dcard", + "mapped": { + "party": "Bv Hegde, MD", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "BV HEGDE,MD KENT OH" + }, + "source": "dcard", + "mapped": { + "party": "Bv Hegde, MD", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CAFE O PLAY CUYAHOGA" + }, + "source": "dcard", + "mapped": { + "party": "Café O Play", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CAJUN GRILL CLEVELAN" + }, + "source": "dcard", + "mapped": { + "party": "Cajun Grill", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CARTER'S #907 STOW O" + }, + "source": "dcard", + "mapped": { + "party": "Carters", + "reason": "Clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHARLEYS GRILLED SUB" + }, + "source": "dcard", + "mapped": { + "party": "Charleys Grilled Subs", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHARLEYS PHILLY STEA" + }, + "source": "dcard", + "mapped": { + "party": "Charleys Philly Steaks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHICK-FIL-A #01583 C" + }, + "source": "dcard", + "mapped": { + "party": "Chick-Fil-A", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHICK-FIL-A #02197 C" + }, + "source": "dcard", + "mapped": { + "party": "Chick-Fil-A", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHICK-FIL-A #02216 M" + }, + "source": "dcard", + "mapped": { + "party": "Chick-Fil-A", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHICK-FIL-A #02957 M" + }, + "source": "dcard", + "mapped": { + "party": "Chick-Fil-A", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHICK-FIL-A #03447 P" + }, + "source": "dcard", + "mapped": { + "party": "Chick-Fil-A", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHICK-FIL-A #03490 D" + }, + "source": "dcard", + "mapped": { + "party": "Chick-Fil-A", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHILDRENS HOSP ACCOU" + }, + "source": "dcard", + "mapped": { + "party": "Akron Childrens Hospital", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHIPOTLE 0559 MACEDO" + }, + "source": "dcard", + "mapped": { + "party": "Chipotle", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHIPOTLE 1152 STOW O" + }, + "source": "dcard", + "mapped": { + "party": "Chipotle", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHIPOTLE 1216 HUDSON" + }, + "source": "dcard", + "mapped": { + "party": "Chipotle", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHIPOTLE 2027 TWINSB" + }, + "source": "dcard", + "mapped": { + "party": "Chipotle", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHMCA PARKING LOCUST" + }, + "source": "dcard", + "mapped": { + "party": "Akron General", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CHMC-ER REGISTRATION" + }, + "source": "dcard", + "mapped": { + "party": "Akron General", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CITYOFSTOW 330689283" + }, + "source": "dcard", + "mapped": { + "party": "City of Stow", + "reason": "Sewer" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CITYOFSTOW 402-935-7" + }, + "source": "dcard", + "mapped": { + "party": "City of Stow", + "reason": "Sewer" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "COLDSTONE #1490 HUDS" + }, + "source": "dcard", + "mapped": { + "party": "Coldstone", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "COLLEGE TRANSCRIPT 7" + }, + "source": "dcard", + "mapped": { + "party": "Ashland University", + "reason": "Continuing Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "COOKERY, THE HUDSON " + }, + "source": "dcard", + "mapped": { + "party": "The Cookery", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CVS/PHARMACY #04359 " + }, + "source": "dcard", + "mapped": { + "party": "CVS", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CVS/PHARMACY #06167 " + }, + "source": "dcard", + "mapped": { + "party": "CVS", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "CVS/PHARMACY #08932 " + }, + "source": "dcard", + "mapped": { + "party": "CVS", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DAIRY QUEEN #10722 M" + }, + "source": "dcard", + "mapped": { + "party": "Dairy Queen", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DAIRY QUEEN #17749 S" + }, + "source": "dcard", + "mapped": { + "party": "Dairy Queen", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DAIRY QUEEN #18902 S" + }, + "source": "dcard", + "mapped": { + "party": "Dairy Queen", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DAIRY QUEEN#12711 AK" + }, + "source": "dcard", + "mapped": { + "party": "Dairy Queen", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DAVIS EYE CENTER CUY" + }, + "source": "dcard", + "mapped": { + "party": "Davis Eye Center", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DESTINY RESCUE USA 5" + }, + "source": "dcard", + "mapped": { + "party": "Destiny Rescue", + "reason": "Gift" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DICKS SPORTING GOODS" + }, + "source": "dcard", + "mapped": { + "party": "Dicks Sporting Goods", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DIS*DISNEY MOVIE CLU" + }, + "source": "dcard", + "mapped": { + "party": "Disney Movie Club", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DISCOUNT DRUG MART 6" + }, + "source": "dcard", + "mapped": { + "party": "Discount Drug Mart", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DOLLAR GENERAL #1666" + }, + "source": "dcard", + "mapped": { + "party": "Dollar General", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DOLLAR TREE STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Dollar Tree", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DOLRTREE 23000235568" + }, + "source": "dcard", + "mapped": { + "party": "Dollar Tree", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DUMA MEATS MOGADORE " + }, + "source": "dcard", + "mapped": { + "party": "Duma Meats", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DUNKIN #345001 Q35 C" + }, + "source": "dcard", + "mapped": { + "party": "Dunkin Doughnuts", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DUNKIN #353699 Q35 S" + }, + "source": "dcard", + "mapped": { + "party": "Dunkin Doughnuts", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "DURBIN RAILROAD 402-" + }, + "source": "dcard", + "mapped": { + "party": "Durbin Railroad", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EARTH N WOOD PRODUCT" + }, + "source": "dcard", + "mapped": { + "party": "Earht N Wood", + "reason": "Home Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EBAYS HALF.COM SAN J" + }, + "source": "dcard", + "mapped": { + "party": "Half.Com", + "reason": "Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ECARDIO DIAGNOSTICS " + }, + "source": "dcard", + "mapped": { + "party": "Ecardio Diagnostics", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EDDYS BIKE SHOP STOW" + }, + "source": "dcard", + "mapped": { + "party": "Eddys Bike Shop", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EDDY'S DELI & RESTAU" + }, + "source": "dcard", + "mapped": { + "party": "Eddys Deli & Restaurante", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EL CAMPESINO STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "El Campesino", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ENTERPRISE 101213580" + }, + "source": "dcard", + "mapped": { + "party": "Enterprise", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ETSY.COM BROOKL NY" + }, + "source": "dcard", + "mapped": { + "party": "Etsy.com", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EXXONMOBIL HILLSVILL" + }, + "source": "dcard", + "mapped": { + "party": "Exxonmobil", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EXXONMOBIL SCARBRO W" + }, + "source": "dcard", + "mapped": { + "party": "Exxonmobil", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "EXXONMOBIL SURF CITY" + }, + "source": "dcard", + "mapped": { + "party": "Exxonmobil", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "FAMILY VIDEO #348 ST" + }, + "source": "dcard", + "mapped": { + "party": "Family Video", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "FIVE BELOW 501 STOW " + }, + "source": "dcard", + "mapped": { + "party": "Five Below", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "FIVE BELOW 501 STOWE" + }, + "source": "dcard", + "mapped": { + "party": "Five Below", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "FIVE GUYS OH 1086 QS" + }, + "source": "dcard", + "mapped": { + "party": "Five Guys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "FIXEZ 8662336460 NV" + }, + "source": "dcard", + "mapped": { + "party": "Fixez.com", + "reason": "Phone" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "FRACTURED PRUNE SURF" + }, + "source": "dcard", + "mapped": { + "party": "Fractured Prune", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GANDER MOUNTAIN TWIN" + }, + "source": "dcard", + "mapped": { + "party": "Gander Mountain", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GET GO #3359 STREETS" + }, + "source": "dcard", + "mapped": { + "party": "Get Go", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GET GO #3396 STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Get Go", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GIANT #4419 KENT OH" + }, + "source": "dcard", + "mapped": { + "party": "Giant-Eagle", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GIANT EAGLE #5863 ST" + }, + "source": "dcard", + "mapped": { + "party": "Giant Eagle", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GIANT-EAGLE #0224 TW" + }, + "source": "dcard", + "mapped": { + "party": "Giant-Eagle", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GIANT-EAGLE #4032 ST" + }, + "source": "dcard", + "mapped": { + "party": "Giant-Eagle", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GIANT-EAGLE #4096 ST" + }, + "source": "dcard", + "mapped": { + "party": "Giant-Eagle", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GITHUB.COM 415448667" + }, + "source": "dcard", + "mapped": { + "party": "github.com", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GITHUB.COM 55OO9 415" + }, + "source": "dcard", + "mapped": { + "party": "github.com", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GOOGLE *GOOGLE PLAY " + }, + "source": "dcard", + "mapped": { + "party": "Google Play", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GOOGLE *MEDIOCRE GOO" + }, + "source": "dcard", + "mapped": { + "party": "Google Play", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GOOGLE *MUSIC GOOGLE" + }, + "source": "dcard", + "mapped": { + "party": "Google Play", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GOOGLE *OUTFIT7 GOOG" + }, + "source": "dcard", + "mapped": { + "party": "Google Play", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GREAT HARVEST BREAD " + }, + "source": "dcard", + "mapped": { + "party": "Great Harvest Bread", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GREATER CLEVELAND RT" + }, + "source": "dcard", + "mapped": { + "party": "Greater Cleveland RTA", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GRUBHUBTWISTEDMELTZ " + }, + "source": "dcard", + "mapped": { + "party": "Twisted Meltz", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GUIDOPIZZA3306785505" + }, + "source": "dcard", + "mapped": { + "party": "Guidos Pizza", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GUIDOS ORIGINAL PIZZ" + }, + "source": "dcard", + "mapped": { + "party": "Guidos Pizza", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "GUITAR CENTER #616 F" + }, + "source": "dcard", + "mapped": { + "party": "Guitar Center", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "HANDELS ICE CREAM ST" + }, + "source": "dcard", + "mapped": { + "party": "Handles Ice Cream", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "HARTVILLE HARDWARE I" + }, + "source": "dcard", + "mapped": { + "party": "Hartville Hardware", + "reason": "Home Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "HARTVILLE KITCHEN - " + }, + "source": "dcard", + "mapped": { + "party": "Hartville Kitchen", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "HEGGYS ALLIANCE ALLI" + }, + "source": "dcard", + "mapped": { + "party": "Heggys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "HEINENS VILL MKT19 H" + }, + "source": "dcard", + "mapped": { + "party": "Heinens", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "HOBBY LOBBY #405 STO" + }, + "source": "dcard", + "mapped": { + "party": "Hobby Lobby", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "IKEA HOME SHOPPING B" + }, + "source": "dcard", + "mapped": { + "party": "Ikea", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ILP*INSECT LORE 800-" + }, + "source": "dcard", + "mapped": { + "party": "Insect Lore", + "reason": "Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "IN *FAMILY MEDICAL C" + }, + "source": "dcard", + "mapped": { + "party": "Family Medical Care", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "INTEREST CHARGE ON P" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "Financing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "INTEREST CHARGE REFU" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "Financing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "INTERNET PAYMENT - T" + }, + "source": "dcard", + "mapped": { + "party": "Huntington", + "reason": "Financing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "JAMBERRY NAILS, LLC " + }, + "source": "dcard", + "mapped": { + "party": "Jamberry Nails", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "JERSEY MIKE'S 2063 S" + }, + "source": "dcard", + "mapped": { + "party": "Jersey Mikes", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "JOANN ETC #0171 HUDS" + }, + "source": "dcard", + "mapped": { + "party": "Jo-Ann", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "JUSTICE #0639 STOW O" + }, + "source": "dcard", + "mapped": { + "party": "Justice", + "reason": "Clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "KING DRAGON STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "King Dragon", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "KOHL'S #0235 MACEDON" + }, + "source": "dcard", + "mapped": { + "party": "Kohls", + "reason": "clothing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "KOHL'S #0331 STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Kohls", + "reason": "clothing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "KSU DINING SERVICES " + }, + "source": "dcard", + "mapped": { + "party": "KSU Dining", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LAKE FARMPARK ADMISS" + }, + "source": "dcard", + "mapped": { + "party": "Lake Farmpark", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LATE CHARGE REFUND" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "Financing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LATE FEE" + }, + "source": "dcard", + "mapped": { + "party": "Discover Card", + "reason": "Financing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LAZIZA RESTAURANT KE" + }, + "source": "dcard", + "mapped": { + "party": "La Ziza", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LEMONGRASS GRILLE MU" + }, + "source": "dcard", + "mapped": { + "party": "Lemongrass Grille", + "reason": "Restaurnte" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LITTLE TIKES CO 800-" + }, + "source": "dcard", + "mapped": { + "party": "Little Tikes", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOGOS BOOKSTORE KENT" + }, + "source": "dcard", + "mapped": { + "party": "Logos Bookstore", + "reason": "Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOLA BISTRO CLEVELAN" + }, + "source": "dcard", + "mapped": { + "party": "Lola", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOVES COUNTRY STORE " + }, + "source": "dcard", + "mapped": { + "party": "Loves Country Store", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOWE'S OF MACEDONIA," + }, + "source": "dcard", + "mapped": { + "party": "Lowes", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOWE'S OF MT. AIRY, " + }, + "source": "dcard", + "mapped": { + "party": "Lowes", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOWE'S OF STOW, OH. " + }, + "source": "dcard", + "mapped": { + "party": "Lowes", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "LOWE'S OF STREETSBOR" + }, + "source": "dcard", + "mapped": { + "party": "Lowes", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MACS CONVENIENC STOR" + }, + "source": "dcard", + "mapped": { + "party": "Macs Convenience Store", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MACS CONVENIENCE STO" + }, + "source": "dcard", + "mapped": { + "party": "Macs Convenience Store", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MACY*S .COM #0129 MA" + }, + "source": "dcard", + "mapped": { + "party": "Macys", + "reason": "clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MACY'S EAST #581 STO" + }, + "source": "dcard", + "mapped": { + "party": "Macys", + "reason": "clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MALLEYS CHOCOLATES 3" + }, + "source": "dcard", + "mapped": { + "party": "Malleys", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO ROOTS" + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO TWINS" + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO073601" + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO144667" + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO156059" + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARATHON PETRO175588" + }, + "source": "dcard", + "mapped": { + "party": "Marathon", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARCOS PIZZA - 1097 " + }, + "source": "dcard", + "mapped": { + "party": "Marcos", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARCS ALLIANCE ALLIA" + }, + "source": "dcard", + "mapped": { + "party": "Marcs", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARCS CUYAHOGA CUYAH" + }, + "source": "dcard", + "mapped": { + "party": "Marcs", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARC'S STOW STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Marcs", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MARHOFER CHEVY PARTS" + }, + "source": "dcard", + "mapped": { + "party": "Marhofer Chevy", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MAURICES STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Maurices", + "reason": "Clothes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F11579 ST" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F2542 CUY" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F31878 BR" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F34016 ST" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F34630 SU" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F5863 MAR" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MCDONALD'S F8064 HAR" + }, + "source": "dcard", + "mapped": { + "party": "McDonalds", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "METROPARKS ZOO CLEVE" + }, + "source": "dcard", + "mapped": { + "party": "Cleveland Zoo", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MFW BOOKS LLC 573202" + }, + "source": "dcard", + "mapped": { + "party": "Mfw Books", + "reason": "Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MICROSOFT *OFFICE 36" + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MICROSOFT *ONEDRIVE " + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MICROSOFT *STORE 800" + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MINUTECLINIC #21441 " + }, + "source": "dcard", + "mapped": { + "party": "Minuteclinic", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MOE'S SW GRILL 351 S" + }, + "source": "dcard", + "mapped": { + "party": "Moes Southwest Grille", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MR BULKY'S FOODS CUY" + }, + "source": "dcard", + "mapped": { + "party": "Mr Bulkys", + "reason": "groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MSFT *OFFICE 800-642" + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MSFT *OFFICE OFFICE." + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MSFT *ONEDRIVE 800-6" + }, + "source": "dcard", + "mapped": { + "party": "Microsoft", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MURPHY7219ATWALMRT C" + }, + "source": "dcard", + "mapped": { + "party": "Murphy", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MURPHY7515ATWALMART " + }, + "source": "dcard", + "mapped": { + "party": "Murphy", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "MY LITTLE RED WAGON " + }, + "source": "dcard", + "mapped": { + "party": "My Little Red Wagon", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "NETFLIX.COM LOS GATO" + }, + "source": "dcard", + "mapped": { + "party": "Netflix.Com", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "NETFLIX.COM NETFLIX." + }, + "source": "dcard", + "mapped": { + "party": "Netflix.Com", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OBGYN ASSOCIATES OF " + }, + "source": "dcard", + "mapped": { + "party": "OBGYN Associates of Akron", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OED EDUCATOR LICENSU" + }, + "source": "dcard", + "mapped": { + "party": "OED Educator", + "reason": "Continuing Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OH BUREAU MOTOR VEHI" + }, + "source": "dcard", + "mapped": { + "party": "Ohio BMV", + "reason": "Compliance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OLD CAROLINA BBQ STO" + }, + "source": "dcard", + "mapped": { + "party": "Old Carolina BBQ", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ONCE UPON A CHILD 20" + }, + "source": "dcard", + "mapped": { + "party": "Once Upon A Child", + "reason": "Gift" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OPC COL*SERVICE FEE " + }, + "source": "dcard", + "mapped": { + "party": "Ashland University", + "reason": "Continuing Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OPC*ASHLAND UNIV ASH" + }, + "source": "dcard", + "mapped": { + "party": "Ashland University", + "reason": "Continuing Education" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "OREILLY AUT000239030" + }, + "source": "dcard", + "mapped": { + "party": "OReilly", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAGANINI SCHOOL OF C" + }, + "source": "dcard", + "mapped": { + "party": "Paganini School", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PANERA BREAD #204626" + }, + "source": "dcard", + "mapped": { + "party": "Panera Bread", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PANERA BREAD #4626 S" + }, + "source": "dcard", + "mapped": { + "party": "Panera Bread", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PANERA BREAD #4813 S" + }, + "source": "dcard", + "mapped": { + "party": "Panera Bread", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PANINI'S KENT KENT O" + }, + "source": "dcard", + "mapped": { + "party": "Paninis", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PARTA - KCG KENT OH" + }, + "source": "dcard", + "mapped": { + "party": "Parta", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PARTY STATION STOW O" + }, + "source": "dcard", + "mapped": { + "party": "Party Station", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAT CATANS 8 CUY FAL" + }, + "source": "dcard", + "mapped": { + "party": "Pat Catans", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAY*HOMEAWAY 189997 " + }, + "source": "dcard", + "mapped": { + "party": "HomeAway", + "reason": "recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAY*HOMEAWAY HA-GPDR" + }, + "source": "dcard", + "mapped": { + "party": "HomeAway", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAY*PROPDAMAGEPROTEC" + }, + "source": "dcard", + "mapped": { + "party": "HomeAway", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAYLESS SHO000296459" + }, + "source": "dcard", + "mapped": { + "party": "Payless Shoe Source", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PAYLESS SHOE SOURCE " + }, + "source": "dcard", + "mapped": { + "party": "Payless Shoe Source", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PEDIATRIC AND ADOLES" + }, + "source": "dcard", + "mapped": { + "party": "Pediatric And Adolescent", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PERKINS RES002250949" + }, + "source": "dcard", + "mapped": { + "party": "Perkinds Restaurnate", + "reason": "Restuarnte" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PET SUPPLIES PLUS #1" + }, + "source": "dcard", + "mapped": { + "party": "Pet Supplies Plus", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PET SUPPLIES PLUS #6" + }, + "source": "dcard", + "mapped": { + "party": "Pet Supplies Plus", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PILOT SEVILLE OH" + }, + "source": "dcard", + "mapped": { + "party": "Pilot", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PINE CONE GIFT SHOPP" + }, + "source": "dcard", + "mapped": { + "party": "Pine Cone Gift Ship", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PITA PIT KENT OH" + }, + "source": "dcard", + "mapped": { + "party": "Pita Pit", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PIZZA HUT 007946 STO" + }, + "source": "dcard", + "mapped": { + "party": "Pizza Hut", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PIZZA HUT 009343 ALL" + }, + "source": "dcard", + "mapped": { + "party": "Pizza Hut", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PIZZA HUT 027536 AKR" + }, + "source": "dcard", + "mapped": { + "party": "Pizza Hut", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "POTBELLY #277 CLEVEL" + }, + "source": "dcard", + "mapped": { + "party": "Potbelly", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "PRIMARY PET CARE LLC" + }, + "source": "dcard", + "mapped": { + "party": "Primary Pet Care", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "QDI*QUEST DIAGNOSTIC" + }, + "source": "dcard", + "mapped": { + "party": "QDI Quest Diagnostics", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REAL DEALS - GRANVIL" + }, + "source": "dcard", + "mapped": { + "party": "Real Deals", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "RED ROBIN #601 CANTO" + }, + "source": "dcard", + "mapped": { + "party": "Red Robin", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "RED ROBIN NO 601 CAN" + }, + "source": "dcard", + "mapped": { + "party": "Red Robin", + "reason": "Restaurantes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REDBOX *DVD RENTAL 8" + }, + "source": "dcard", + "mapped": { + "party": "Redbox", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REDBOX *DVD RENTAL O" + }, + "source": "dcard", + "mapped": { + "party": "Redbox", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REGAL CINEMAS HUDSON" + }, + "source": "dcard", + "mapped": { + "party": "Regal Cinemas", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REMEMBERNHU 402-935-" + }, + "source": "dcard", + "mapped": { + "party": "Remember Nhu", + "reason": "Gift" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REPUBLIC WIRELESS 80" + }, + "source": "dcard", + "mapped": { + "party": "Republic Wireless", + "reason": "Phones" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "REPUBLIC WIRELESS 87" + }, + "source": "dcard", + "mapped": { + "party": "Republic Wireless", + "reason": "Phones" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ROCK AUTO 608-661-13" + }, + "source": "dcard", + "mapped": { + "party": "Rock Auto", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ROCKNE'S KENT KENT O" + }, + "source": "dcard", + "mapped": { + "party": "Rocknes", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ROCKNES STEELS CORNE" + }, + "source": "dcard", + "mapped": { + "party": "Rocknes", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ROSEWOOD GRILL HUDSO" + }, + "source": "dcard", + "mapped": { + "party": "Rosewood Grill", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "RSVP NO. 36 STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "RSVP", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "RUFENER HILLTOP FARM" + }, + "source": "dcard", + "mapped": { + "party": "Rufener Hilltop Farms", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SALLY BEAUTY #1996 S" + }, + "source": "dcard", + "mapped": { + "party": "Sally Beauty", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SAMS CLUB - #4750 CU" + }, + "source": "dcard", + "mapped": { + "party": "Sams Club", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SFI*PHOTOSBYSHUTTERF" + }, + "source": "dcard", + "mapped": { + "party": "Shutterfly", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHEETZ 0296000029644" + }, + "source": "dcard", + "mapped": { + "party": "Sheetz", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHEETZ 0328000032854" + }, + "source": "dcard", + "mapped": { + "party": "Sheetz", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHEETZ 0584000058434" + }, + "source": "dcard", + "mapped": { + "party": "Sheetz", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHEETZ 2296000229624" + }, + "source": "dcard", + "mapped": { + "party": "Sheetz", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHEETZ 2452000245214" + }, + "source": "dcard", + "mapped": { + "party": "Sheetz", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHEETZ 2456000245624" + }, + "source": "dcard", + "mapped": { + "party": "Sheetz", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHELL 57544399009 GA" + }, + "source": "dcard", + "mapped": { + "party": "Shell", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHELL 57546559204 LE" + }, + "source": "dcard", + "mapped": { + "party": "Shell", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHERATONHOTELSFB CUY" + }, + "source": "dcard", + "mapped": { + "party": "Sheraton Hotels", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SHUTTERFLY 800-986-1" + }, + "source": "dcard", + "mapped": { + "party": "Shutterfly", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SILVER LAKE CC PRO S" + }, + "source": "dcard", + "mapped": { + "party": "Silver Lake", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SIMPLY SWANK SALON &" + }, + "source": "dcard", + "mapped": { + "party": "Simply Swamk", + "reason": "Hygiene" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SP * WYLEE 888-746-7" + }, + "source": "dcard", + "mapped": { + "party": "Wylee", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPEEDWAY 03403 110 H" + }, + "source": "dcard", + "mapped": { + "party": "Speedway", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPEEDWAY 03680 104 S" + }, + "source": "dcard", + "mapped": { + "party": "Speedway", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPEEDWAY 03686 496 S" + }, + "source": "dcard", + "mapped": { + "party": "Speedway", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPEEDWAY 05120 234 C" + }, + "source": "dcard", + "mapped": { + "party": "Speedway", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPEEDWAY 09303 KEN K" + }, + "source": "dcard", + "mapped": { + "party": "Speedway", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPOTIFY NEW YORK CIT" + }, + "source": "dcard", + "mapped": { + "party": "Spotify", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SPOTIFYAB STOCKHOLM " + }, + "source": "dcard", + "mapped": { + "party": "Spotify", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *CORNER CUP COFF " + }, + "source": "dcard", + "mapped": { + "party": "Corner Cup Coffee", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *CORNER CUP COFFE" + }, + "source": "dcard", + "mapped": { + "party": "Corner Cup Coffee", + "reason": "Restaurantes" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *CRAZYBUTTRUE PO " + }, + "source": "dcard", + "mapped": { + "party": "Crazy But True", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *CRAZYBUTTRUE POP" + }, + "source": "dcard", + "mapped": { + "party": "Crazy But True", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *PEACE,LOVE AND L" + }, + "source": "dcard", + "mapped": { + "party": "Peace Love and Little Doughnuts", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *POPPED KENT OH00" + }, + "source": "dcard", + "mapped": { + "party": "Peace Love and Little Doughnuts", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *THE DAILY BREW, " + }, + "source": "dcard", + "mapped": { + "party": "The Daily Brew", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SQ *TWISTED MELTZ KE" + }, + "source": "dcard", + "mapped": { + "party": "Twisted Meltz", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STAHLS FARM MARKET N" + }, + "source": "dcard", + "mapped": { + "party": "Stahls Farm Market", + "reason": "groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STARBUCKS #02954 KEN" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STARBUCKS #13265 STO" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STARBUCKS #14302 CUY" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STARBUCKS STORE 0971" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STARBUCKS STORE 1368" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STARBUCKS W114115015" + }, + "source": "dcard", + "mapped": { + "party": "Starbucks", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STEAMPOWERED.COM 425" + }, + "source": "dcard", + "mapped": { + "party": "store.steampowered.com", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STOW DENTAL GROUP IN" + }, + "source": "dcard", + "mapped": { + "party": "Stow Dental Group", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "STOWNUT DONUT AND DI" + }, + "source": "dcard", + "mapped": { + "party": "Stownut Donut & Diner", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00044289255 S" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00062380255 U" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00068957255 S" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00114363255 S" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00213843255 S" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00216093255 A" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00221911255 T" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 00366203255 K" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUBWAY 03208014255 P" + }, + "source": "dcard", + "mapped": { + "party": "Subway", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUMMA FOOD SERVICE A" + }, + "source": "dcard", + "mapped": { + "party": "Summa Food Service", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUMMA HEALTH SYSTE C" + }, + "source": "dcard", + "mapped": { + "party": "Summa Health", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUMMA PARKING AKRON " + }, + "source": "dcard", + "mapped": { + "party": "Summa Parking", + "reason": "Hospitality" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUMMA WEST RES HOSP " + }, + "source": "dcard", + "mapped": { + "party": "Summa Western Reserve Hospital", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUNCREST GARDENS PEN" + }, + "source": "dcard", + "mapped": { + "party": "Suncrest Gardens", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SUNOCO 0705503100 BR" + }, + "source": "dcard", + "mapped": { + "party": "Sunoco", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SWEET FROG 200080 ST" + }, + "source": "dcard", + "mapped": { + "party": "Sweet Frog", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "SWENSONS STOW KENT S" + }, + "source": "dcard", + "mapped": { + "party": "Swensons", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TARGET CLEVELAND OH" + }, + "source": "dcard", + "mapped": { + "party": "Target", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TARGET CUYAHOGA FALL" + }, + "source": "dcard", + "mapped": { + "party": "Target", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TARGET STREETSBORO O" + }, + "source": "dcard", + "mapped": { + "party": "Target", + "reason": "Groceries" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TERRY LUMBER AND SUP" + }, + "source": "dcard", + "mapped": { + "party": "Terry Lumber and Supply", + "reason": "Home Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THANOS FAMILY RESTAU" + }, + "source": "dcard", + "mapped": { + "party": "Thanos Family Restaurante", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE DISTRICT BOUTIQU" + }, + "source": "dcard", + "mapped": { + "party": "The District Boutique", + "reason": "Clothing" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE HOME DEPOT #3809" + }, + "source": "dcard", + "mapped": { + "party": "The Home Depot", + "reason": "Home Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE HOME DEPOT #3859" + }, + "source": "dcard", + "mapped": { + "item": "Sand Paper", + "reason": "Home Maintenance", + "party": "The Home Depot" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE HOME DEPOT 3859 " + }, + "source": "dcard", + "mapped": { + "party": "The Home Depot", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE MENS WEARHOUSE #" + }, + "source": "dcard", + "mapped": { + "party": "The Mens Warehouse", + "reason": "Hospitality" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE OLDE PICKET FENC" + }, + "source": "dcard", + "mapped": { + "party": "The Olde Picket Fence", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "THE PETS PAJAMAS AKR" + }, + "source": "dcard", + "mapped": { + "party": "The Pets Pajamas", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TOYS R US #9203 CUYA" + }, + "source": "dcard", + "mapped": { + "party": "Toys R' Us", + "reason": "Gifts" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TOYS 'R' US CUYAHOGA" + }, + "source": "dcard", + "mapped": { + "party": "Toys R' Us", + "reason": "Gifts" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TOYSRUS-BABIESRUS 61" + }, + "source": "dcard", + "mapped": { + "party": "Toys R' Us", + "reason": "Gifts" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TOYSRUS-BABIESRUS 80" + }, + "source": "dcard", + "mapped": { + "party": "Toys R' Us", + "reason": "Gifts" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TRACTOR SUPPLY #1215" + }, + "source": "dcard", + "mapped": { + "party": "Tractor Supply", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TRACTOR-SUPPLY-CO #0" + }, + "source": "dcard", + "mapped": { + "party": "Tractor Supply", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TREE CITY COFFEE & P" + }, + "source": "dcard", + "mapped": { + "party": "Tree City Coffee", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TURF TRIMMERS INC KE" + }, + "source": "dcard", + "mapped": { + "party": "Turf Trimmers", + "reason": "Home Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "TWISTED MELTZ - KENT" + }, + "source": "dcard", + "mapped": { + "party": "Twisted Meltz", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "UH EVANS MEDICAL CEN" + }, + "source": "dcard", + "mapped": { + "party": "UH Evans Medical Center", + "reason": "Medical" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "U-HAUL OF KENT-STOW " + }, + "source": "dcard", + "mapped": { + "party": "U-Haul", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "ULTA #565 STOW OH" + }, + "source": "dcard", + "mapped": { + "party": "Ulta", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "VIDANGEL.COM 801-921" + }, + "source": "dcard", + "mapped": { + "party": "VidAngel", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "VTECHELECTR 402-935-" + }, + "source": "dcard", + "mapped": { + "party": "V-Tech Electronics", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WALGREENS #4295 STOW" + }, + "source": "dcard", + "mapped": { + "party": "Walgreens", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WAL-MART SC - #1039 " + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WAL-MART SC - #1499 " + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WAL-MART SC - #2313 " + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WAL-MART SC - #2323 " + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WAL-MART SC - #2506 " + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WAL-MART SC - #7162 " + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WALMART.COM 800-966-" + }, + "source": "dcard", + "mapped": { + "party": "Wal-Mart", + "reason": "Home Supplies" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WENDY'S BRIMFIELD #1" + }, + "source": "dcard", + "mapped": { + "party": "Wendys", + "reason": "Restaurante" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WIKIMEDIA 8776009454" + }, + "source": "dcard", + "mapped": { + "party": "Wikimedia", + "reason": "Gift" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WILLIS GAP GENERAL S" + }, + "source": "dcard", + "mapped": { + "party": "Willis Gap General Store", + "reason": "Gas" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WOODSYS MUSIC INC KE" + }, + "source": "dcard", + "mapped": { + "party": "Woodsys Music", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WOODSY'S MUSIC, INC." + }, + "source": "dcard", + "mapped": { + "party": "Woodsys Music", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WWW.ADVANCEAUTOPARTS" + }, + "source": "dcard", + "mapped": { + "party": "Advance Auto", + "reason": "Auto Maintenance" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WYOGA VETERINARY HOS" + }, + "source": "dcard", + "mapped": { + "party": "Wyoga Veterinary", + "reason": "Recreation" + } + }, + { + "map": "First 20", + "ret_val": { + "f20": "WALMART GROCERY 800-" + }, + "source": "dcard", + "mapped": { + "party": "Walmart", + "reason": "groceries" + } + } +] \ No newline at end of file diff --git a/test/4.dcard_import/curl b/test/4.dcard_import/curl new file mode 100644 index 0000000..78a2a32 --- /dev/null +++ b/test/4.dcard_import/curl @@ -0,0 +1 @@ +curl -v -F upload=@./d.csv http://localhost/import?srce=dcard \ No newline at end of file diff --git a/test/4.dcard_import/d.csv b/test/4.dcard_import/d.csv new file mode 100644 index 0000000..f0151f9 --- /dev/null +++ b/test/4.dcard_import/d.csv @@ -0,0 +1,139 @@ +Trans. Date,Post Date,Description,Amount,Category +01/02/2018,01/02/2018,"GOOGLE *YOUTUBE VIDEOS G.CO/HELPPAY#CAP0H07TXV",4.26,"Services" +01/02/2018,01/02/2018,"MICROSOFT *ONEDRIVE 800-642-7676 WA",4.26,"Services" +01/03/2018,01/03/2018,"CLE CLINIC PT PMTS 216-445-6249 OHAK2C57F2F0B3",200.00,"Medical Services" +01/04/2018,01/04/2018,"AT&T *PAYMENT 800-288-2020 TX",57.14,"Services" +01/04/2018,01/07/2018,"WWW.KOHLS.COM #0873 MIDDLETOWN OH",-7.90,"Payments and Credits" +01/05/2018,01/07/2018,"PIZZA HUT 007946 STOW OH",9.24,"Restaurants" +01/05/2018,01/07/2018,"SUBWAY 00044289255 STOW OH",10.25,"Restaurants" +01/06/2018,01/07/2018,"ACME NO. 17 STOW OH",103.98,"Supermarkets" +01/06/2018,01/07/2018,"DISCOUNT DRUG MART 32 STOW OH",1.69,"Merchandise" +01/06/2018,01/07/2018,"DISCOUNT DRUG MART 32 STOW OH",2.19,"Merchandise" +01/09/2018,01/09/2018,"CIRCLE K 05416 STOW OH00947R",3.94,"Gasoline" +01/09/2018,01/09/2018,"CIRCLE K 05416 STOW OH00915R",52.99,"Gasoline" +01/13/2018,01/13/2018,"AUTOZONE #0722 STOW OH",85.36,"Automotive" +01/13/2018,01/13/2018,"DISCOUNT DRUG MART 32 STOW OH",26.68,"Merchandise" +01/13/2018,01/13/2018,"EL CAMPESINO STOW OH",6.50,"Restaurants" +01/13/2018,01/13/2018,"TARGET STOW OH",197.90,"Merchandise" +01/14/2018,01/14/2018,"DISCOUNT DRUG MART 32 STOW OH",13.48,"Merchandise" +01/15/2018,01/15/2018,"TARGET.COM * 800-591-3869 MN",22.41,"Merchandise" +01/16/2018,01/16/2018,"BUFFALO WILD WINGS KENT KENT OH",63.22,"Restaurants" +01/16/2018,01/16/2018,"PARTA - KCG KENT OH",4.00,"Government Services" +01/16/2018,01/16/2018,"REMEMBERNHU 402-935-7733 IA",60.00,"Services" +01/16/2018,01/16/2018,"TARGET.COM * 800-591-3869 MN",44.81,"Merchandise" +01/16/2018,01/16/2018,"TREE CITY COFFEE & PASTR KENT OH",17.75,"Restaurants" +01/17/2018,01/17/2018,"BESTBUYCOM805526794885 888-BESTBUY MN",343.72,"Merchandise" +01/19/2018,01/19/2018,"DISCOUNT DRUG MART 32 STOW OH",5.98,"Merchandise" +01/19/2018,01/19/2018,"U-HAUL OF KENT-STOW KENT OH",15.88,"Travel/ Entertainment" +01/19/2018,01/19/2018,"WALMART GROCERY 800-966-6546 AR",5.99,"Supermarkets" +01/19/2018,01/19/2018,"WALMART GROCERY 800-966-6546 AR",17.16,"Supermarkets" +01/19/2018,01/19/2018,"WALMART GROCERY 800-966-6546 AR",500.97,"Supermarkets" +01/20/2018,01/20/2018,"GOOGLE *GOOGLE PLAY G.CO/HELPPAY#CAP0HFFS7W",2.12,"Services" +01/20/2018,01/20/2018,"LOWE'S OF STOW, OH. STOW OH",256.48,"Home Improvement" +01/22/2018,01/22/2018,"HOBBY LOBBY #405 STOW OHITEM TRANSFERRED FROM PREV ACCOUNT",38.49,"Merchandise" +01/23/2018,01/23/2018,"CASHBACK BONUS REDEMPTION PYMT/STMT CRDT",-32.20,"Awards and Rebate Credits" +01/23/2018,01/23/2018,"INTERNET PAYMENT - THANK YOU",-2394.51,"Payments and Credits" +01/27/2018,01/27/2018,"GIANT-EAGLE #4096 STOW OH",67.81,"Supermarkets" +01/27/2018,01/27/2018,"OFFICEMAX/OFFICE DEPOT63 STOW OH",21.06,"Merchandise" +01/27/2018,01/27/2018,"TARGET STOW OH",71.00,"Merchandise" +01/29/2018,01/29/2018,"NETFLIX.COM NETFLIX.COM CA19899514437",14.93,"Services" +01/30/2018,01/30/2018,"PARTA - KCG KENT OH",1.00,"Government Services" +01/30/2018,01/30/2018,"SPEEDWAY 09303 KEN KENT OH",46.57,"Gasoline" +01/30/2018,01/30/2018,"SQ *TWISTED MELTZ KENT OH0002305843011416898511",16.87,"Restaurants" +01/30/2018,01/30/2018,"TARGET STOW OH",49.37,"Merchandise" +01/31/2018,01/31/2018,"TARGET STOW OH",4.14,"Merchandise" +01/31/2018,01/31/2018,"TARGET STREETSBORO OH",14.28,"Merchandise" +01/31/2018,02/01/2018,"TARGET STOW OH",-21.34,"Payments and Credits" +01/31/2018,02/01/2018,"TARGET STREETSBORO OH",-9.60,"Payments and Credits" +02/01/2018,02/01/2018,"EL CAMPESINO STOW OH",42.24,"Restaurants" +02/02/2018,02/02/2018,"CASH ADVANCE FEE",5.00,"Fees" +02/02/2018,02/02/2018,"GOOGLE *ASCIIFLOW.COM G.CO/HELPPAY#CAP0HQTYN5",5.00,"Cash Advances" +02/03/2018,02/03/2018,"TARGET STREETSBORO OH",71.69,"Merchandise" +02/03/2018,02/07/2018,"SAMS CLUB - #4750 CUYAHOGA FALLOH",371.90,"Warehouse Clubs" +02/04/2018,02/04/2018,"ACME NO. 17 STOW OH",8.98,"Supermarkets" +02/04/2018,02/04/2018,"MICROSOFT *ONEDRIVE 800-642-7676 WA",4.26,"Services" +02/06/2018,02/06/2018,"MINIMUM INTEREST CHARGE FEE",0.50,"Fees" +02/06/2018,02/07/2018,"BP#954778736210 7-ELEVEN STOW OH",52.80,"Gasoline" +02/06/2018,02/07/2018,"CVS/PHARMACY #08932 TWINSBURG OH",13.87,"Merchandise" +02/07/2018,02/07/2018,"AT&T *PAYMENT 800-288-2020 TXX51Z5QX7SMT2U01",57.14,"Services" +02/07/2018,02/07/2018,"TOYS R US #9203 CUYAHOGA FALLOH",193.32,"Merchandise" +02/08/2018,02/08/2018,"GIANT-EAGLE #4096 STOW OH",66.13,"Supermarkets" +02/08/2018,02/08/2018,"TARGET STOW OH",121.32,"Merchandise" +02/09/2018,02/09/2018,"GUIDOS ORIGINAL PIZZA KENT OH",11.75,"Restaurants" +02/09/2018,02/09/2018,"MARATHON PETRO73601 TWINSBURG OH",44.30,"Gasoline" +02/10/2018,02/10/2018,"RSVP NO. 36 STOW OH",14.43,"Supermarkets" +02/10/2018,02/10/2018,"TARGET STOW OH",77.90,"Merchandise" +02/11/2018,02/11/2018,"SUBWAY 00044289255 STOW OH",21.00,"Restaurants" +02/13/2018,02/13/2018,"CHICK-FIL-A #02197 CUYAHOGA FLS OH",12.79,"Restaurants" +02/13/2018,02/13/2018,"IN *MR. BULKY'S FOODS AKRON OHAJ16V8Q6",3.39,"Supermarkets" +02/13/2018,02/13/2018,"TARGET CUYAHOGA FALLOH",5.33,"Supermarkets" +02/14/2018,02/14/2018,"DISCOUNT DRUG MART 32 STOW OH",4.29,"Merchandise" +02/14/2018,02/14/2018,"HANDELS ICE CREAM STOW STOW OH",7.95,"Supermarkets" +02/15/2018,02/15/2018,"BATH&BODY STOW OH",47.19,"Merchandise" +02/15/2018,02/15/2018,"TARGET STOW OH",76.35,"Merchandise" +02/17/2018,02/17/2018,"EL CAMPESINO STOW OH",6.50,"Restaurants" +02/17/2018,02/17/2018,"WALMART GROCERY 800-966-6546 AR",461.36,"Supermarkets" +02/18/2018,02/18/2018,"ACME NO. 17 STOW OH",32.68,"Supermarkets" +02/18/2018,02/18/2018,"CHIPOTLE ONLINE 303-595-4000 CO",20.75,"Restaurants" +02/19/2018,02/19/2018,"GIANT EAGLE #5863 STREETSBORO OH",25.00,"Supermarkets" +02/20/2018,02/20/2018,"REMEMBERNHU 402-935-7733 IA",60.00,"Services" +02/21/2018,02/21/2018,"BP#954635936241 7-ELEVEN STOW OH",30.04,"Gasoline" +02/22/2018,02/22/2018,"CHICK-FIL-A #02197 CUYAHOGA FLS OH",3.19,"Restaurants" +02/22/2018,02/22/2018,"CHICK-FIL-A #02197 CUYAHOGA FLS OH",18.22,"Restaurants" +02/22/2018,02/22/2018,"PET SUPPLIES PLUS #68 STOW OH",45.88,"Merchandise" +02/22/2018,02/22/2018,"TOYS R US #9203 CUYAHOGA FALLOH",21.31,"Merchandise" +02/23/2018,02/23/2018,"SUMMIT CO PARKING GAR AKRON OH",6.00,"Services" +02/24/2018,02/24/2018,"GET GO #3396 STOW OH",26.46,"Gasoline" +02/25/2018,02/25/2018,"DISCOUNT DRUG MART 32 STOW OH",19.70,"Merchandise" +02/25/2018,02/25/2018,"EL CAMPESINO STOW OH",6.50,"Restaurants" +02/25/2018,02/25/2018,"SQ *CORNER CUP COFFEEH STOW OH0001152921507942036274",2.30,"Supermarkets" +02/25/2018,02/25/2018,"TARGET STOW OH",18.49,"Merchandise" +02/28/2018,02/28/2018,"NETFLIX.COM NETFLIX.COM CA20475539512",14.93,"Services" +03/01/2018,03/01/2018,"GIANT-EAGLE #4032 STOW OH",2.99,"Supermarkets" +03/01/2018,03/01/2018,"TARGET STOW OH",72.46,"Merchandise" +03/02/2018,03/02/2018,"LATE FEE",27.00,"Fees" +03/02/2018,03/02/2018,"MICROSOFT *ONEDRIVE 800-642-7676 WA",4.26,"Services" +03/02/2018,03/02/2018,"PIZZA HUT 007946 STOW OH",9.89,"Restaurants" +03/03/2018,03/03/2018,"CASHBACK BONUS REDEMPTION PYMT/STMT CRDT",-23.28,"Awards and Rebate Credits" +03/03/2018,03/03/2018,"INTERNET PAYMENT - THANK YOU",-2451.43,"Payments and Credits" +03/03/2018,03/03/2018,"LOWE'S OF STOW, OH. STOW OH",6.93,"Home Improvement" +03/03/2018,03/07/2018,"WAL-MART SC - #2323 STOW OH",150.32,"Merchandise" +03/04/2018,03/04/2018,"OLD NAVY ON-LINE 800-OLDNAVY OH",13.66,"Merchandise" +03/06/2018,03/07/2018,"MFW BOOKS LLC 5732022000 MO",86.90,"Education" +03/06/2018,03/07/2018,"OLD NAVY ON-LINE 800-OLDNAVY OH",127.46,"Merchandise" +03/08/2018,03/08/2018,"ACME NO. 17 STOW OH",8.58,"Supermarkets" +03/08/2018,03/08/2018,"CHICK-FIL-A #02197 CUYAHOGA FLS OH",2.12,"Restaurants" +03/08/2018,03/08/2018,"EL CAMPESINO STOW OH",6.50,"Restaurants" +03/08/2018,03/08/2018,"SPEEDWAY 03686 496 STOW OH",45.24,"Gasoline" +03/08/2018,03/08/2018,"SWENSONS STOW KENT STOW OH",8.30,"Restaurants" +03/08/2018,03/08/2018,"TOYS R US #9203 CUYAHOGA FALLOH",5.33,"Merchandise" +03/09/2018,03/09/2018,"SPEEDWAY 03686 496 STOW OH",48.29,"Gasoline" +03/10/2018,03/10/2018,"WALMART GROCERY 800-966-6546 AR",522.22,"Supermarkets" +03/11/2018,03/11/2018,"AT&T *PAYMENT 800-288-2020 TXQ8F55RY7SMT2N04",57.14,"Services" +03/11/2018,03/11/2018,"SQ *CORNER CUP COFFEEH STOW OH0002305843011470140810",2.30,"Supermarkets" +03/12/2018,03/12/2018,"MICROSOFT *STORE 800-642-7676 WA",1.06,"Services" +03/15/2018,03/15/2018,"SQ *CORNER CUP COFFEEH STOW OH0002305843011475075512",2.30,"Supermarkets" +03/16/2018,03/16/2018,"ACME NO. 17 STOW OH",15.85,"Supermarkets" +03/16/2018,03/16/2018,"CHIPOTLE 1152 STOW OH",3.85,"Restaurants" +03/16/2018,03/16/2018,"EL CAMPESINO STOW OH",6.50,"Restaurants" +03/16/2018,03/16/2018,"PIZZA HUT 007946 STOW OH",13.98,"Restaurants" +03/17/2018,03/17/2018,"CHIPOTLE ONLINE 303-595-4000 CO",15.75,"Restaurants" +03/17/2018,03/17/2018,"DISCOUNT DRUG MART 32 STOW OH",9.89,"Merchandise" +03/17/2018,03/17/2018,"MFW BOOKS LLC 5732022000 MO",66.75,"Education" +03/18/2018,03/18/2018,"ACME NO. 17 STOW OH",27.78,"Supermarkets" +03/18/2018,03/18/2018,"GIANT-EAGLE #4032 STOW OH",28.34,"Supermarkets" +03/20/2018,03/20/2018,"REMEMBERNHU 402-935-7733 IA",60.00,"Services" +03/20/2018,03/20/2018,"SONLIGHT CURRICULUM LTD 303-730-8193 CO",762.87,"Education" +03/21/2018,03/21/2018,"BP#954635936241 7-ELEVEN STOW OH",8.87,"Gasoline" +03/21/2018,03/21/2018,"DISCOUNT DRUG MART 32 STOW OH",18.07,"Merchandise" +03/21/2018,03/21/2018,"SQ *CORNER CUP COFFEEH STOW OH0002305843011484061091",2.30,"Supermarkets" +03/21/2018,03/21/2018,"TARGET STOW OH",1.95,"Merchandise" +03/21/2018,03/21/2018,"TARGET STOW OH",224.85,"Merchandise" +03/22/2018,03/22/2018,"JUSTICE #0639 STOW OH",16.01,"Merchandise" +03/22/2018,03/22/2018,"SPEEDWAY 03686 496 STOW OH",32.54,"Gasoline" +03/22/2018,03/22/2018,"SQ *TWISTED MELTZ KENT OH0002305843011486528725",6.74,"Restaurants" +03/22/2018,03/22/2018,"TARGET STOW OH",6.60,"Merchandise" +03/25/2018,03/25/2018,"ACME NO. 17 STOW OH",95.42,"Supermarkets" +03/25/2018,03/25/2018,"ASIAN-GREEK CUISINES STOW OH",70.25,"Restaurants" +03/25/2018,03/25/2018,"MARATHON PETRO73601 TWINSBURG OH",11.09,"Gasoline" +03/25/2018,03/25/2018,"SPEEDWAY 09303 KEN KENT OH",53.28,"Gasoline"