#!/usr/bin/env python3 """Anonymised demo consignees across Europe from two production samples (never committed, deleted after use): names.txt – one "First Last" per line (sampled in RANDOM order so initials are mixed) locs.txt – "street no|postal|city|c_country_id" per line First names, last names, street names, house numbers and postal/city/country triples are drawn independently, so no output row is a real customer or a real address. Countries without a production sample get built-in city/postal/street pools. Country mix is forced to a European spread (iDempiere C_Country ids of the LogShip instance). build-consignees.py names.txt locs.txt out.json [count] """ import json, random, re, sys from collections import Counter names, locs, out = sys.argv[1], sys.argv[2], sys.argv[3] N = int(sys.argv[4]) if len(sys.argv) > 4 else 200 random.seed(20261023 + N) first, last = [], [] for l in open(names, encoding="utf-8"): p = l.strip().split() if len(p) >= 2: first.append(p[0]); last.append(p[-1]) random.shuffle(first); random.shuffle(last) streets, places = {}, {} for l in open(locs, encoding="utf-8"): a = l.rstrip("\n").split("|") if len(a) < 4: continue st = re.sub(r"\s*\d+\s*[a-zA-Z]?(\s*[-/]\s*\d+\w?)?\s*$", "", a[0]).strip() if 3 < len(st) < 40 and not re.search(r"\d", st): streets.setdefault(a[3], []).append(st) city = a[2].strip().title() if a[1].strip() and 2 < len(city) < 30: places.setdefault(a[3], []).append((a[1].strip(), city)) # built-in pools for countries with no/few production samples: (country id, [(postal, city)], [streets], tld) BUILTIN = { "167": ([("2100", "København Ø"), ("8000", "Aarhus C"), ("5000", "Odense C"), ("9000", "Aalborg"), ("2800", "Kongens Lyngby")], ["Østerbrogade", "Nørregade", "Vesterbrogade", "Strandvejen", "Jernbanegade", "Søndergade"], "dk"), "313": ([("11456", "Stockholm"), ("41118", "Göteborg"), ("21122", "Malmö"), ("75310", "Uppsala"), ("58222", "Linköping")], ["Drottninggatan", "Kungsgatan", "Storgatan", "Vasagatan", "Linnégatan", "Järnvägsgatan"], "se"), "181": ([("00100", "Helsinki"), ("33100", "Tampere"), ("20100", "Turku"), ("90100", "Oulu")], ["Mannerheimintie", "Hämeenkatu", "Kauppakatu", "Rantakatu", "Koulukatu"], "fi"), "269": ([("0150", "Oslo"), ("5003", "Bergen"), ("7010", "Trondheim"), ("4006", "Stavanger")], ["Karl Johans gate", "Storgata", "Kirkegata", "Strandgaten", "Bryggen"], "no"), "280": ([("00-001", "Warszawa"), ("30-001", "Kraków"), ("50-001", "Wrocław"), ("61-001", "Poznań"), ("80-001", "Gdańsk")], ["ul. Marszałkowska", "ul. Długa", "ul. Piotrkowska", "ul. Krakowska", "ul. Floriańska"], "pl"), "166": ([("110 00", "Praha 1"), ("602 00", "Brno"), ("702 00", "Ostrava"), ("301 00", "Plzeň")], ["Václavské náměstí", "Vodičkova", "Masarykova", "Nádražní", "Jiráskova"], "cz"), "206": ([("1051", "Budapest"), ("4024", "Debrecen"), ("6720", "Szeged"), ("9021", "Győr")], ["Váci utca", "Andrássy út", "Piac utca", "Kossuth Lajos utca", "Petőfi utca"], "hu"), "281": ([("1000-001", "Lisboa"), ("4000-001", "Porto"), ("3000-001", "Coimbra"), ("8000-001", "Faro")], ["Rua Augusta", "Avenida da Liberdade", "Rua de Santa Catarina", "Rua do Comércio", "Rua Direita"], "pt"), "212": ([("D02 X285", "Dublin 2"), ("T12 Y8Y7", "Cork"), ("H91 E2K3", "Galway"), ("V94 T2N1", "Limerick")], ["Grafton Street", "O'Connell Street", "Patrick Street", "Shop Street", "Main Street"], "ie"), "192": ([("105 57", "Athina"), ("546 24", "Thessaloniki"), ("262 21", "Patra"), ("712 02", "Heraklion")], ["Ermou", "Egnatia", "Panepistimiou", "Tsimiski", "Kifisias"], "gr"), "285": ([("010011", "București"), ("400001", "Cluj-Napoca"), ("300001", "Timișoara"), ("700001", "Iași")], ["Strada Lipscani", "Bulevardul Unirii", "Calea Victoriei", "Strada Republicii"], "ro"), "107": ([("8001", "Zürich"), ("3011", "Bern"), ("4051", "Basel"), ("1201", "Genève"), ("6003", "Luzern")], ["Bahnhofstrasse", "Marktgasse", "Seestrasse", "Dorfstrasse", "Hauptstrasse"], "ch"), "103": ([("1000", "Bruxelles"), ("2000", "Antwerpen"), ("9000", "Gent"), ("3000", "Leuven"), ("4000", "Liège")], ["Rue Neuve", "Meir", "Veldstraat", "Bondgenotenlaan", "Rue de la Loi"], "be"), "105": ([("1012 AB", "Amsterdam"), ("3011 AA", "Rotterdam"), ("2511 BT", "Den Haag"), ("3511 LX", "Utrecht"), ("5611 AA", "Eindhoven")], ["Kalverstraat", "Coolsingel", "Lange Voorhout", "Oudegracht", "Stratumseind"], "nl"), "233": ([("1009", "Luxembourg"), ("4001", "Esch-sur-Alzette"), ("9001", "Ettelbruck")], ["Grand-Rue", "Avenue de la Liberté", "Rue de Hollerich", "Boulevard Royal"], "lu"), "106": ([("28013", "Madrid"), ("08002", "Barcelona"), ("41004", "Sevilla"), ("46002", "València"), ("48001", "Bilbao")], ["Calle Mayor", "Gran Vía", "Carrer de Pelai", "Calle Sierpes", "Avenida Diagonal"], "es"), "333": ([("SW1A 1AA", "London"), ("M1 1AE", "Manchester"), ("B1 1AA", "Birmingham"), ("EH1 1YZ", "Edinburgh"), ("LS1 1UR", "Leeds")], ["High Street", "Station Road", "Church Lane", "Victoria Road", "King Street"], "co.uk"), "102": ([("75001", "Paris"), ("69001", "Lyon"), ("13001", "Marseille"), ("33000", "Bordeaux"), ("59000", "Lille"), ("67000", "Strasbourg")], ["Rue de Rivoli", "Avenue Jean Jaurès", "Rue Nationale", "Boulevard Victor Hugo", "Rue de la République"], "fr"), "214": ([("00184", "Roma"), ("20121", "Milano"), ("80134", "Napoli"), ("10121", "Torino"), ("40121", "Bologna"), ("50123", "Firenze")], ["Via Roma", "Corso Vittorio Emanuele", "Via Garibaldi", "Via Dante", "Piazza del Duomo"], "it"), "108": ([("1010", "Wien"), ("8010", "Graz"), ("4020", "Linz"), ("5020", "Salzburg"), ("6020", "Innsbruck")], ["Mariahilfer Straße", "Herrengasse", "Landstraße", "Getreidegasse", "Maria-Theresien-Straße"], "at"), "101": ([], [], "de"), } # European mix in percent (DE-heavy like a German shop, but broad) MIX = [("101", 38), ("108", 9), ("107", 5), ("105", 6), ("103", 4), ("233", 1), ("102", 7), ("214", 7), ("106", 5), ("281", 2), ("280", 4), ("166", 3), ("167", 3), ("313", 3), ("181", 1), ("269", 1), ("212", 1), ("206", 1), ("192", 1), ("285", 1), ("333", 2)] pool = [c for c, w in MIX for _ in range(w)] def address(c): bi = BUILTIN.get(c) sample_places, sample_streets = places.get(c, []), streets.get(c, []) if sample_places and (len(sample_places) >= 20 or not bi or not bi[0]): postal, city = random.choice(sample_places) else: postal, city = random.choice(bi[0]) st_pool = (sample_streets if len(sample_streets) >= 20 else []) or (bi[1] if bi else []) or streets.get("101", []) street = f"{random.choice(st_pool)} {random.randint(1, 120)}{random.choice(['', '', '', 'a', 'b'])}" return street, postal, city, (bi[2] if bi else "eu") rows, seen = [], set() while len(rows) < N: f, s = random.choice(first), random.choice(last) if (f, s) in seen: continue seen.add((f, s)) c = random.choice(pool); street, postal, city, tld = address(c) mail = re.sub(r"[^a-z.]", "", f"{f}.{s}".lower().replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")) + "@example." + tld rows.append({"name": f"{f} {s}", "street": street, "postal": postal, "city": city, "countryId": int(c), "email": mail}) random.shuffle(rows) json.dump(rows, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=0) print(len(rows), "consignees; countries:", dict(sorted(Counter(r["countryId"] for r in rows).items()))) print("initials:", "".join(sorted({r["name"][0] for r in rows})))