swissco 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- swissco-0.1.0/PKG-INFO +206 -0
- swissco-0.1.0/README.md +180 -0
- swissco-0.1.0/pyproject.toml +43 -0
- swissco-0.1.0/src/swissco/__init__.py +5 -0
- swissco-0.1.0/src/swissco/_sparql.py +85 -0
- swissco-0.1.0/src/swissco/cli.py +458 -0
- swissco-0.1.0/src/swissco/companies.py +232 -0
- swissco-0.1.0/src/swissco/config.py +67 -0
- swissco-0.1.0/src/swissco/events.py +203 -0
- swissco-0.1.0/src/swissco/publications.py +316 -0
- swissco-0.1.0/src/swissco/py.typed +0 -0
- swissco-0.1.0/src/swissco/render.py +177 -0
- swissco-0.1.0/src/swissco/sources.py +54 -0
- swissco-0.1.0/src/swissco/watch.py +216 -0
swissco-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swissco
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Swiss company data from the shell: Zefix and SHAB in one command
|
|
5
|
+
Keywords: swiss,zefix,shab,sogc,commercial-register,handelsregister,cli,lindas
|
|
6
|
+
Author: Prospex
|
|
7
|
+
Author-email: Prospex <hello@prospex.ch>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: zefix-parser[http]>=0.1
|
|
18
|
+
Requires-Dist: shab-parser[http]>=0.2
|
|
19
|
+
Requires-Dist: pytest>=8 ; extra == 'dev'
|
|
20
|
+
Requires-Python: >=3.14
|
|
21
|
+
Project-URL: Homepage, https://prospex.ch
|
|
22
|
+
Project-URL: Repository, https://github.com/prospex-ch/swissco-cli
|
|
23
|
+
Project-URL: Documentation, https://swissco.readthedocs.io
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# swissco
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/swissco/)
|
|
30
|
+
[](https://swissco.readthedocs.io/en/latest/)
|
|
31
|
+
|
|
32
|
+
Swiss company data from the shell. Look a company up by UID, search 790,000 of them
|
|
33
|
+
by name or by what they say they do, list gazette publications, or watch a list of
|
|
34
|
+
companies for change.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uvx swissco lookup CHE-444.420.929
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
No signup, no API key, no config file. Two federal open-data sources do the work:
|
|
41
|
+
[Zefix on LINDAS](https://ld.admin.ch/) for the commercial register, and the
|
|
42
|
+
[Amtsblattportal](https://amtsblattportal.ch) for the Swiss Official Gazette of Commerce.
|
|
43
|
+
|
|
44
|
+
Built and maintained by [Prospex](https://prospex.ch), a Swiss B2B sales intelligence platform.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uvx swissco --help # run it without installing
|
|
50
|
+
pip install swissco # or install it
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Python 3.14 or newer.
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
Every command takes `--format table|json|csv`, `--limit` and `--quiet`.
|
|
58
|
+
|
|
59
|
+
### `swissco lookup`
|
|
60
|
+
|
|
61
|
+
Everything the register publishes about one company.
|
|
62
|
+
|
|
63
|
+
```console
|
|
64
|
+
$ swissco lookup CHE-444.420.929
|
|
65
|
+
legal name Baumberger Bau AG
|
|
66
|
+
uid CHE-444.420.929
|
|
67
|
+
chid CH-036.9.103.786-9
|
|
68
|
+
ehra id 1702823
|
|
69
|
+
legal form code 0151
|
|
70
|
+
legal form Schweizerische Zweigniederlassung im Handelsregister eingetragen
|
|
71
|
+
municipality Koppigen
|
|
72
|
+
canton BE
|
|
73
|
+
address Hauptstrasse 6, 3425 Koppigen
|
|
74
|
+
purpose Anbieten von Kleintransporte aller Art.
|
|
75
|
+
zefix uri https://register.ld.admin.ch/zefix/company/1702823
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Accepts `CHE-444.420.929`, `CHE444420929`, or the UID buried in other text. The check
|
|
79
|
+
digit is verified before any request goes out.
|
|
80
|
+
|
|
81
|
+
### `swissco search`
|
|
82
|
+
|
|
83
|
+
Companies by legal name **or by statutory purpose**: how a company describes what it
|
|
84
|
+
does, in its own words, in the register.
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
$ swissco search "usinage" --canton VD --limit 5
|
|
88
|
+
LEGAL_NAME UID LEGAL_FORM MUNICIPALITY CANTON PURPOSE
|
|
89
|
+
------------------------------------- --------------- ------------------------------------------------- ------------ ------ ------------------------------------------------------------
|
|
90
|
+
Atelier roue libre S.A. CHE-261.821.895 Aktiengesellschaft Penthalaz VD La société a pour but l'usinage, la réparation, la révision…
|
|
91
|
+
Cute Cut Sàrl CHE-403.577.924 Gesellschaft mit beschränkter Haftung GMBH / SARL Lausanne VD la société a pour but toutes activités, notamment la fabric…
|
|
92
|
+
DecoupART CNC Sàrl CHE-342.723.896 Gesellschaft mit beschränkter Haftung GMBH / SARL Paudex VD la société a pour but tous types de travaux dans les domain…
|
|
93
|
+
LAVA Technologies Sàrl en liquidation CHE-217.431.384 Gesellschaft mit beschränkter Haftung GMBH / SARL Nyon VD la société a pour but: développement de machines d'usinage …
|
|
94
|
+
Pousaz Mécanique SA CHE-166.712.190 Aktiengesellschaft Oron VD La société a pour but l'exploitation d'un atelier mécanique…
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`--canton` takes a two-letter code, `--legal-form` an eCH-0097 code (`0106` is an AG,
|
|
98
|
+
`0107` a GmbH).
|
|
99
|
+
|
|
100
|
+
### `swissco publications`
|
|
101
|
+
|
|
102
|
+
Commercial-register publications from the gazette, in a date range.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
swissco publications --canton ZH --since 2026-08-01
|
|
106
|
+
swissco publications --canton ZH --since 2026-08-01 --type CAPITAL_INCREASED
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Without `--type`, only the list pages are read: one request per 2,000 publications.
|
|
110
|
+
With `--type`, each surviving publication's body is fetched and classified into the
|
|
111
|
+
[eleven event types](https://shab-parser.readthedocs.io/en/latest/events.html)
|
|
112
|
+
`shab-parser` recognises, so narrow the range and the canton first.
|
|
113
|
+
|
|
114
|
+
Ranges past about ten days are split into windows automatically. The API rejects any
|
|
115
|
+
request whose page offset reaches 10,000, and the gazette publishes around a thousand
|
|
116
|
+
commercial-register entries a day.
|
|
117
|
+
|
|
118
|
+
### `swissco events`
|
|
119
|
+
|
|
120
|
+
One company's registry history.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
swissco events CHE-444.420.929 --since 2024-01-01
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The gazette's list pages carry no UID, only a title, so this resolves the UID to a
|
|
127
|
+
legal name, keeps the publications whose title looks like that name, then fetches
|
|
128
|
+
those bodies and keeps the events whose own UID matches. The title match decides
|
|
129
|
+
what is worth downloading; the body's UID decides what is reported. Fetched bodies
|
|
130
|
+
are cached under the state directory, so an overlapping re-run costs nothing.
|
|
131
|
+
|
|
132
|
+
### `swissco watch`
|
|
133
|
+
|
|
134
|
+
What changed since last time.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
printf 'CHE-444.420.929\nCHE-105.943.826\n' > uids.txt
|
|
138
|
+
swissco watch uids.txt --state ~/.swissco/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Each company is compared by fingerprint, a digest over its identity, address and
|
|
142
|
+
purpose fields. Exits `10` when something changed and `0` when nothing did, so cron
|
|
143
|
+
can branch on it:
|
|
144
|
+
|
|
145
|
+
```crontab
|
|
146
|
+
0 7 * * * swissco watch ~/uids.txt --quiet --format json > ~/changes.json \
|
|
147
|
+
|| mail -s "registry changes" me@example.com < ~/changes.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A UID that has left the dataset is reported as **no longer in the dataset**, never as
|
|
151
|
+
deleted. LINDAS carries active entities and is not a historical export, so a UID can
|
|
152
|
+
leave it for reasons that have nothing to do with the company ending.
|
|
153
|
+
|
|
154
|
+
## Output
|
|
155
|
+
|
|
156
|
+
`--format` is the only thing that changes the output. A command piped into `jq` and
|
|
157
|
+
the same command watched by a person produce identical bytes, so a script that works
|
|
158
|
+
in your terminal works in CI.
|
|
159
|
+
|
|
160
|
+
`table` and `csv` are rendered from the same rows that `json` serialises, so a column
|
|
161
|
+
cannot appear in one format and be missing from another. Progress notes go to stderr,
|
|
162
|
+
where `--quiet` silences them; errors go to stderr as a single JSON object.
|
|
163
|
+
|
|
164
|
+
## Access and terms
|
|
165
|
+
|
|
166
|
+
**SHAB / Amtsblattportal.** The REST API is the channel the operator offers for
|
|
167
|
+
machine access: freely accessible, no authentication for published data, no
|
|
168
|
+
documented rate limit, page size capped at 2,000. The website UI is disallowed by
|
|
169
|
+
`robots.txt`, which does not reach the API. The operator disclaims completeness, and
|
|
170
|
+
only the signed PDF is legally binding.
|
|
171
|
+
|
|
172
|
+
**Zefix on LINDAS.** Published on [opendata.swiss](https://opendata.swiss), no
|
|
173
|
+
credentials. Commercial-use terms have never been settled; the
|
|
174
|
+
[dataset page](https://opendata.swiss/en/dataset/zefix-lindas) states what applies.
|
|
175
|
+
|
|
176
|
+
**Zefix PublicREST.** Requires credentials issued by `zefix@bj.admin.ch`. Every
|
|
177
|
+
command here works without them. Supplying them through `--user`/`--password` or
|
|
178
|
+
`ZEFIX_USER`/`ZEFIX_PASSWORD` adds capital, status, deletion date, former names and
|
|
179
|
+
corporate relations to `lookup`, and a name-prefix search to `search`.
|
|
180
|
+
|
|
181
|
+
## Being a good citizen
|
|
182
|
+
|
|
183
|
+
These are small public services run by federal offices. `swissco` sends one request
|
|
184
|
+
every 0.5 seconds at most, backs off exponentially on failure, and identifies itself
|
|
185
|
+
with a real `User-Agent` carrying this repository's URL. `--interval` can raise that
|
|
186
|
+
floor and cannot lower it.
|
|
187
|
+
|
|
188
|
+
## Built on
|
|
189
|
+
|
|
190
|
+
| Package | Does |
|
|
191
|
+
|---|---|
|
|
192
|
+
| [`zefix-parser`](https://pypi.org/project/zefix-parser/) | Zefix: LINDAS SPARQL, PublicREST, UID validation |
|
|
193
|
+
| [`shab-parser`](https://pypi.org/project/shab-parser/) | SHAB: discovery, fetch, parse, eleven-type event classification |
|
|
194
|
+
|
|
195
|
+
Both are MIT and maintained alongside this one. `swissco` is the shell over them.
|
|
196
|
+
|
|
197
|
+
## Watching more than a list
|
|
198
|
+
|
|
199
|
+
`swissco watch` from cron is the free version of what [Prospex](https://prospex.ch)
|
|
200
|
+
sells. Prospex watches the whole register continuously, joins it to hiring, funding,
|
|
201
|
+
tenders and web signals, and tells you which of those changes is worth a call. If a
|
|
202
|
+
cron job and a UID list cover it, this tool is all you need.
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT
|
swissco-0.1.0/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# swissco
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/swissco/)
|
|
4
|
+
[](https://swissco.readthedocs.io/en/latest/)
|
|
5
|
+
|
|
6
|
+
Swiss company data from the shell. Look a company up by UID, search 790,000 of them
|
|
7
|
+
by name or by what they say they do, list gazette publications, or watch a list of
|
|
8
|
+
companies for change.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uvx swissco lookup CHE-444.420.929
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No signup, no API key, no config file. Two federal open-data sources do the work:
|
|
15
|
+
[Zefix on LINDAS](https://ld.admin.ch/) for the commercial register, and the
|
|
16
|
+
[Amtsblattportal](https://amtsblattportal.ch) for the Swiss Official Gazette of Commerce.
|
|
17
|
+
|
|
18
|
+
Built and maintained by [Prospex](https://prospex.ch), a Swiss B2B sales intelligence platform.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uvx swissco --help # run it without installing
|
|
24
|
+
pip install swissco # or install it
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Python 3.14 or newer.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
Every command takes `--format table|json|csv`, `--limit` and `--quiet`.
|
|
32
|
+
|
|
33
|
+
### `swissco lookup`
|
|
34
|
+
|
|
35
|
+
Everything the register publishes about one company.
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
$ swissco lookup CHE-444.420.929
|
|
39
|
+
legal name Baumberger Bau AG
|
|
40
|
+
uid CHE-444.420.929
|
|
41
|
+
chid CH-036.9.103.786-9
|
|
42
|
+
ehra id 1702823
|
|
43
|
+
legal form code 0151
|
|
44
|
+
legal form Schweizerische Zweigniederlassung im Handelsregister eingetragen
|
|
45
|
+
municipality Koppigen
|
|
46
|
+
canton BE
|
|
47
|
+
address Hauptstrasse 6, 3425 Koppigen
|
|
48
|
+
purpose Anbieten von Kleintransporte aller Art.
|
|
49
|
+
zefix uri https://register.ld.admin.ch/zefix/company/1702823
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Accepts `CHE-444.420.929`, `CHE444420929`, or the UID buried in other text. The check
|
|
53
|
+
digit is verified before any request goes out.
|
|
54
|
+
|
|
55
|
+
### `swissco search`
|
|
56
|
+
|
|
57
|
+
Companies by legal name **or by statutory purpose**: how a company describes what it
|
|
58
|
+
does, in its own words, in the register.
|
|
59
|
+
|
|
60
|
+
```console
|
|
61
|
+
$ swissco search "usinage" --canton VD --limit 5
|
|
62
|
+
LEGAL_NAME UID LEGAL_FORM MUNICIPALITY CANTON PURPOSE
|
|
63
|
+
------------------------------------- --------------- ------------------------------------------------- ------------ ------ ------------------------------------------------------------
|
|
64
|
+
Atelier roue libre S.A. CHE-261.821.895 Aktiengesellschaft Penthalaz VD La société a pour but l'usinage, la réparation, la révision…
|
|
65
|
+
Cute Cut Sàrl CHE-403.577.924 Gesellschaft mit beschränkter Haftung GMBH / SARL Lausanne VD la société a pour but toutes activités, notamment la fabric…
|
|
66
|
+
DecoupART CNC Sàrl CHE-342.723.896 Gesellschaft mit beschränkter Haftung GMBH / SARL Paudex VD la société a pour but tous types de travaux dans les domain…
|
|
67
|
+
LAVA Technologies Sàrl en liquidation CHE-217.431.384 Gesellschaft mit beschränkter Haftung GMBH / SARL Nyon VD la société a pour but: développement de machines d'usinage …
|
|
68
|
+
Pousaz Mécanique SA CHE-166.712.190 Aktiengesellschaft Oron VD La société a pour but l'exploitation d'un atelier mécanique…
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`--canton` takes a two-letter code, `--legal-form` an eCH-0097 code (`0106` is an AG,
|
|
72
|
+
`0107` a GmbH).
|
|
73
|
+
|
|
74
|
+
### `swissco publications`
|
|
75
|
+
|
|
76
|
+
Commercial-register publications from the gazette, in a date range.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
swissco publications --canton ZH --since 2026-08-01
|
|
80
|
+
swissco publications --canton ZH --since 2026-08-01 --type CAPITAL_INCREASED
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Without `--type`, only the list pages are read: one request per 2,000 publications.
|
|
84
|
+
With `--type`, each surviving publication's body is fetched and classified into the
|
|
85
|
+
[eleven event types](https://shab-parser.readthedocs.io/en/latest/events.html)
|
|
86
|
+
`shab-parser` recognises, so narrow the range and the canton first.
|
|
87
|
+
|
|
88
|
+
Ranges past about ten days are split into windows automatically. The API rejects any
|
|
89
|
+
request whose page offset reaches 10,000, and the gazette publishes around a thousand
|
|
90
|
+
commercial-register entries a day.
|
|
91
|
+
|
|
92
|
+
### `swissco events`
|
|
93
|
+
|
|
94
|
+
One company's registry history.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
swissco events CHE-444.420.929 --since 2024-01-01
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The gazette's list pages carry no UID, only a title, so this resolves the UID to a
|
|
101
|
+
legal name, keeps the publications whose title looks like that name, then fetches
|
|
102
|
+
those bodies and keeps the events whose own UID matches. The title match decides
|
|
103
|
+
what is worth downloading; the body's UID decides what is reported. Fetched bodies
|
|
104
|
+
are cached under the state directory, so an overlapping re-run costs nothing.
|
|
105
|
+
|
|
106
|
+
### `swissco watch`
|
|
107
|
+
|
|
108
|
+
What changed since last time.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
printf 'CHE-444.420.929\nCHE-105.943.826\n' > uids.txt
|
|
112
|
+
swissco watch uids.txt --state ~/.swissco/
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Each company is compared by fingerprint, a digest over its identity, address and
|
|
116
|
+
purpose fields. Exits `10` when something changed and `0` when nothing did, so cron
|
|
117
|
+
can branch on it:
|
|
118
|
+
|
|
119
|
+
```crontab
|
|
120
|
+
0 7 * * * swissco watch ~/uids.txt --quiet --format json > ~/changes.json \
|
|
121
|
+
|| mail -s "registry changes" me@example.com < ~/changes.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A UID that has left the dataset is reported as **no longer in the dataset**, never as
|
|
125
|
+
deleted. LINDAS carries active entities and is not a historical export, so a UID can
|
|
126
|
+
leave it for reasons that have nothing to do with the company ending.
|
|
127
|
+
|
|
128
|
+
## Output
|
|
129
|
+
|
|
130
|
+
`--format` is the only thing that changes the output. A command piped into `jq` and
|
|
131
|
+
the same command watched by a person produce identical bytes, so a script that works
|
|
132
|
+
in your terminal works in CI.
|
|
133
|
+
|
|
134
|
+
`table` and `csv` are rendered from the same rows that `json` serialises, so a column
|
|
135
|
+
cannot appear in one format and be missing from another. Progress notes go to stderr,
|
|
136
|
+
where `--quiet` silences them; errors go to stderr as a single JSON object.
|
|
137
|
+
|
|
138
|
+
## Access and terms
|
|
139
|
+
|
|
140
|
+
**SHAB / Amtsblattportal.** The REST API is the channel the operator offers for
|
|
141
|
+
machine access: freely accessible, no authentication for published data, no
|
|
142
|
+
documented rate limit, page size capped at 2,000. The website UI is disallowed by
|
|
143
|
+
`robots.txt`, which does not reach the API. The operator disclaims completeness, and
|
|
144
|
+
only the signed PDF is legally binding.
|
|
145
|
+
|
|
146
|
+
**Zefix on LINDAS.** Published on [opendata.swiss](https://opendata.swiss), no
|
|
147
|
+
credentials. Commercial-use terms have never been settled; the
|
|
148
|
+
[dataset page](https://opendata.swiss/en/dataset/zefix-lindas) states what applies.
|
|
149
|
+
|
|
150
|
+
**Zefix PublicREST.** Requires credentials issued by `zefix@bj.admin.ch`. Every
|
|
151
|
+
command here works without them. Supplying them through `--user`/`--password` or
|
|
152
|
+
`ZEFIX_USER`/`ZEFIX_PASSWORD` adds capital, status, deletion date, former names and
|
|
153
|
+
corporate relations to `lookup`, and a name-prefix search to `search`.
|
|
154
|
+
|
|
155
|
+
## Being a good citizen
|
|
156
|
+
|
|
157
|
+
These are small public services run by federal offices. `swissco` sends one request
|
|
158
|
+
every 0.5 seconds at most, backs off exponentially on failure, and identifies itself
|
|
159
|
+
with a real `User-Agent` carrying this repository's URL. `--interval` can raise that
|
|
160
|
+
floor and cannot lower it.
|
|
161
|
+
|
|
162
|
+
## Built on
|
|
163
|
+
|
|
164
|
+
| Package | Does |
|
|
165
|
+
|---|---|
|
|
166
|
+
| [`zefix-parser`](https://pypi.org/project/zefix-parser/) | Zefix: LINDAS SPARQL, PublicREST, UID validation |
|
|
167
|
+
| [`shab-parser`](https://pypi.org/project/shab-parser/) | SHAB: discovery, fetch, parse, eleven-type event classification |
|
|
168
|
+
|
|
169
|
+
Both are MIT and maintained alongside this one. `swissco` is the shell over them.
|
|
170
|
+
|
|
171
|
+
## Watching more than a list
|
|
172
|
+
|
|
173
|
+
`swissco watch` from cron is the free version of what [Prospex](https://prospex.ch)
|
|
174
|
+
sells. Prospex watches the whole register continuously, joins it to hiring, funding,
|
|
175
|
+
tenders and web signals, and tells you which of those changes is worth a call. If a
|
|
176
|
+
cron job and a UID list cover it, this tool is all you need.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "swissco"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Swiss company data from the shell: Zefix and SHAB in one command"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Prospex", email = "hello@prospex.ch" },
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
keywords = ["swiss", "zefix", "shab", "sogc", "commercial-register", "handelsregister", "cli", "lindas"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Topic :: Office/Business :: Financial",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"httpx>=0.27",
|
|
23
|
+
"zefix-parser[http]>=0.1",
|
|
24
|
+
"shab-parser[http]>=0.2",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=8"]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
swissco = "swissco.cli:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://prospex.ch"
|
|
35
|
+
Repository = "https://github.com/prospex-ch/swissco-cli"
|
|
36
|
+
Documentation = "https://swissco.readthedocs.io"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build>=0.11.32,<0.12.0"]
|
|
40
|
+
build-backend = "uv_build"
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""The SELECT list and graph pattern a searched entity is read back with.
|
|
2
|
+
|
|
3
|
+
``zefix-parser`` owns the shape of a :class:`~zefix_parser.RegistryEntity` and
|
|
4
|
+
the parser that folds SPARQL rows into one. It builds queries for the three
|
|
5
|
+
accesses it supports — count, keyset page, detail by URI or UID — but a
|
|
6
|
+
filtered search is not among them, so the pattern has to be restated here.
|
|
7
|
+
|
|
8
|
+
It is a restatement rather than an import because ``_ENTITY_FIELDS`` and
|
|
9
|
+
``_ENTITY_PATTERN`` are private to that library: reaching into them would tie
|
|
10
|
+
this CLI to an internal name, and a rename upstream would break the search with
|
|
11
|
+
no warning. The coupling that remains is the honest one — every variable below
|
|
12
|
+
must keep the name ``zefix_parser.parse_entity_page`` reads it under, which the
|
|
13
|
+
tests assert against a recorded response.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
#: One row per language-tagged literal, folded down by ``parse_entity_page``.
|
|
19
|
+
ENTITY_FIELDS = """\
|
|
20
|
+
SELECT ?entity ?legalName ?name ?uid ?chid ?ehraId
|
|
21
|
+
?legalFormUri ?legalFormLabel
|
|
22
|
+
?municipalityUri ?municipalityLabel
|
|
23
|
+
?canton
|
|
24
|
+
?streetAddress ?postalCode ?locality
|
|
25
|
+
?purpose
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
ENTITY_PATTERN = """\
|
|
29
|
+
?entity schema:legalName ?legalName .
|
|
30
|
+
|
|
31
|
+
OPTIONAL { ?entity schema:name ?name . }
|
|
32
|
+
|
|
33
|
+
OPTIONAL {
|
|
34
|
+
?entity schema:identifier ?uidNode .
|
|
35
|
+
?uidNode schema:name "CompanyUID" ;
|
|
36
|
+
schema:value ?uid .
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
OPTIONAL {
|
|
40
|
+
?entity schema:identifier ?chidNode .
|
|
41
|
+
?chidNode schema:name "CompanyCHID" ;
|
|
42
|
+
schema:value ?chid .
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
OPTIONAL {
|
|
46
|
+
?entity schema:identifier ?ehraIdNode .
|
|
47
|
+
?ehraIdNode schema:name "CompanyEHRAID" ;
|
|
48
|
+
schema:value ?ehraId .
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
OPTIONAL {
|
|
52
|
+
?entity schema:additionalType ?legalFormUri .
|
|
53
|
+
OPTIONAL { ?legalFormUri (schema:name|rdfs:label) ?legalFormLabel . }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
OPTIONAL {
|
|
57
|
+
?entity admin:municipality ?municipalityUri .
|
|
58
|
+
?municipalityUri schema:name ?municipalityLabel .
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
OPTIONAL { ?entity schema:address/schema:addressRegion ?canton . }
|
|
62
|
+
OPTIONAL { ?entity schema:address/schema:streetAddress ?streetAddress . }
|
|
63
|
+
OPTIONAL { ?entity schema:address/schema:postalCode ?postalCode . }
|
|
64
|
+
OPTIONAL { ?entity schema:address/schema:addressLocality ?locality . }
|
|
65
|
+
|
|
66
|
+
OPTIONAL { ?entity schema:description ?purpose . }
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def escape_literal(value: str) -> str:
|
|
71
|
+
"""*value* made safe to sit inside a double-quoted SPARQL literal.
|
|
72
|
+
|
|
73
|
+
Nothing upstream does this: every query builder in ``zefix-parser`` takes
|
|
74
|
+
values it produced itself, where this CLI takes them from a shell argument.
|
|
75
|
+
A search term containing a quote would close the literal and let the rest
|
|
76
|
+
of the term be read as query syntax, so the two characters that can do that
|
|
77
|
+
— the quote and the backslash that would smuggle one back in — are escaped,
|
|
78
|
+
along with the newlines a literal may not contain.
|
|
79
|
+
"""
|
|
80
|
+
return (
|
|
81
|
+
value.replace("\\", "\\\\")
|
|
82
|
+
.replace('"', '\\"')
|
|
83
|
+
.replace("\n", "\\n")
|
|
84
|
+
.replace("\r", "\\r")
|
|
85
|
+
)
|