pyproc 0.2__tar.gz → 0.3__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.
- {pyproc-0.2 → pyproc-0.3}/.github/workflows/pyproc-pypi.yml +24 -4
- pyproc-0.3/.github/workflows/test.yml +32 -0
- pyproc-0.3/PKG-INFO +592 -0
- pyproc-0.3/README.md +557 -0
- pyproc-0.3/docs/assets/README_ASSET_PROMPTS.md +153 -0
- pyproc-0.3/docs/assets/logo.png +0 -0
- pyproc-0.3/docs/assets/pyproc-mcp-banner.png +0 -0
- pyproc-0.3/plan/API_AND_CLI_WRAPPER_OPTIMIZATION_PLAN.md +1216 -0
- pyproc-0.3/plan/PYPROC_MCP_REBRAND_AND_IMPLEMENTATION_PLAN.md +1808 -0
- pyproc-0.3/pyproc/__init__.py +13 -0
- pyproc-0.3/pyproc/cache.py +145 -0
- {pyproc-0.2 → pyproc-0.3}/pyproc/cli.py +207 -146
- {pyproc-0.2 → pyproc-0.3}/pyproc/lpse.py +404 -33
- pyproc-0.3/pyproc/mcp/__init__.py +22 -0
- pyproc-0.3/pyproc/mcp/errors.py +44 -0
- pyproc-0.3/pyproc/mcp/hosts.py +205 -0
- pyproc-0.3/pyproc/mcp/resources.py +186 -0
- pyproc-0.3/pyproc/mcp/schemas.py +1007 -0
- pyproc-0.3/pyproc/mcp/search_index.py +256 -0
- pyproc-0.3/pyproc/mcp/server.py +208 -0
- pyproc-0.3/pyproc/mcp/tools.py +991 -0
- {pyproc-0.2 → pyproc-0.3}/pyproc/text.py +3 -1
- {pyproc-0.2 → pyproc-0.3}/pyproc/utils.py +38 -32
- {pyproc-0.2 → pyproc-0.3}/pyproject.toml +13 -3
- pyproc-0.3/tests/__init__.py +0 -0
- pyproc-0.3/tests/fixtures/README.md +43 -0
- pyproc-0.3/tests/fixtures/darurat_pemenang.html +195 -0
- pyproc-0.3/tests/fixtures/darurat_pengumuman.html +182 -0
- pyproc-0.3/tests/fixtures/dt_darurat_list.json +37 -0
- pyproc-0.3/tests/fixtures/dt_lelang.json +9 -0
- pyproc-0.3/tests/fixtures/dt_lelang_data_only.json +4 -0
- pyproc-0.3/tests/fixtures/dt_nonspk.json +282 -0
- pyproc-0.3/tests/fixtures/dt_swakelola.json +257 -0
- pyproc-0.3/tests/fixtures/error_page.html +10 -0
- pyproc-0.3/tests/fixtures/hasil_evaluasi.html +52 -0
- pyproc-0.3/tests/fixtures/jadwal.html +29 -0
- pyproc-0.3/tests/fixtures/lelang_page.html +16 -0
- pyproc-0.3/tests/fixtures/nonspk_pemenang_berkontrak.html +207 -0
- pyproc-0.3/tests/fixtures/nonspk_pemenang_bug_10941898000.html +266 -0
- pyproc-0.3/tests/fixtures/nonspk_pengumuman.html +197 -0
- pyproc-0.3/tests/fixtures/not_found_page.html +9 -0
- pyproc-0.3/tests/fixtures/pemenang.html +36 -0
- pyproc-0.3/tests/fixtures/pengumuman_lelang.html +93 -0
- pyproc-0.3/tests/fixtures/peserta.html +28 -0
- pyproc-0.3/tests/fixtures/swakelola_pelaksana.html +223 -0
- pyproc-0.3/tests/fixtures/swakelola_pengumuman.html +192 -0
- pyproc-0.3/tests/test_cache.py +201 -0
- pyproc-0.3/tests/test_cli_unit.py +525 -0
- {pyproc-0.2 → pyproc-0.3}/tests/test_downloader.py +4 -1
- {pyproc-0.2 → pyproc-0.3}/tests/test_lpse.py +5 -14
- pyproc-0.3/tests/test_lpse_unit.py +652 -0
- pyproc-0.3/tests/test_mcp_hosts.py +115 -0
- pyproc-0.3/tests/test_mcp_schemas.py +495 -0
- pyproc-0.3/tests/test_mcp_search_index.py +69 -0
- pyproc-0.3/tests/test_mcp_tools.py +671 -0
- pyproc-0.3/tests/test_utils_unit.py +58 -0
- pyproc-0.2/PKG-INFO +0 -210
- pyproc-0.2/README.md +0 -184
- pyproc-0.2/pyproc/__init__.py +0 -10
- pyproc-0.2/requirements.txt +0 -5
- {pyproc-0.2 → pyproc-0.3}/.gitignore +0 -0
- {pyproc-0.2 → pyproc-0.3}/CHANGELOG.md +0 -0
- {pyproc-0.2 → pyproc-0.3}/LICENSE +0 -0
- {pyproc-0.2 → pyproc-0.3}/Makefile +0 -0
- {pyproc-0.2 → pyproc-0.3}/pyproc/exceptions.py +0 -0
- /pyproc-0.2/tests/__init__.py → /pyproc-0.3/pyproc/mcp/prompts.py +0 -0
- {pyproc-0.2 → pyproc-0.3}/tests/.gitignore +0 -0
- {pyproc-0.2 → pyproc-0.3}/tests/supporting_files/list-host-with-filename.txt +0 -0
- {pyproc-0.2 → pyproc-0.3}/tests/supporting_files/list-host.txt +0 -0
|
@@ -9,13 +9,33 @@ on:
|
|
|
9
9
|
- v*
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: '3.x'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
python -m pip install -e ".[test]"
|
|
26
|
+
|
|
27
|
+
- name: Run unit tests
|
|
28
|
+
run: |
|
|
29
|
+
python -m pytest tests/test_lpse_unit.py tests/test_cli_unit.py tests/test_cache.py -v
|
|
30
|
+
|
|
12
31
|
release-build:
|
|
13
32
|
runs-on: ubuntu-latest
|
|
33
|
+
needs: test
|
|
14
34
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v5
|
|
16
36
|
|
|
17
37
|
- name: Set up Python
|
|
18
|
-
uses: actions/setup-python@
|
|
38
|
+
uses: actions/setup-python@v6
|
|
19
39
|
with:
|
|
20
40
|
python-version: '3.x'
|
|
21
41
|
|
|
@@ -31,7 +51,7 @@ jobs:
|
|
|
31
51
|
python -m build
|
|
32
52
|
|
|
33
53
|
- name: Upload distributions
|
|
34
|
-
uses: actions/upload-artifact@
|
|
54
|
+
uses: actions/upload-artifact@v5
|
|
35
55
|
with:
|
|
36
56
|
name: release-dists
|
|
37
57
|
path: dist/
|
|
@@ -48,7 +68,7 @@ jobs:
|
|
|
48
68
|
|
|
49
69
|
steps:
|
|
50
70
|
- name: Retrieve release distributions
|
|
51
|
-
uses: actions/download-artifact@
|
|
71
|
+
uses: actions/download-artifact@v5
|
|
52
72
|
with:
|
|
53
73
|
name: release-dists
|
|
54
74
|
path: dist/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
tags: [v*]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [master]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
python -m pip install -e ".[test]"
|
|
29
|
+
|
|
30
|
+
- name: Run unit tests
|
|
31
|
+
run: |
|
|
32
|
+
python -m pytest tests/test_lpse_unit.py tests/test_cli_unit.py tests/test_cache.py -v
|
pyproc-0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyproc
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Summary: MCP tools for real-time Indonesian public procurement data from SPSE/Inaproc
|
|
5
|
+
Project-URL: Homepage, https://github.com/wakataw/pyproc
|
|
6
|
+
Project-URL: Issues, https://github.com/wakataw/pyproc/issues
|
|
7
|
+
Author-email: Agung Pratama <workwithagung@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agent,inaproc,indonesia,llm,lpse,mcp,procurement,spse
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Natural Language :: Indonesian
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: backoff
|
|
27
|
+
Requires-Dist: beautifulsoup4
|
|
28
|
+
Requires-Dist: html5lib
|
|
29
|
+
Requires-Dist: requests
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: mcp; extra == 'mcp'
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: pytest; extra == 'test'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# PyProc MCP
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="docs/assets/logo.png"
|
|
40
|
+
alt="PyProc MCP — Real-time Indonesian procurement data for LLM agents"
|
|
41
|
+
width="200">
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<strong>Real-time Indonesian procurement data for LLM agents</strong>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<a href="https://pypi.org/project/pyproc/"><img src="https://img.shields.io/badge/version-v0.3-blue" alt="PyPI version"></a>
|
|
50
|
+
<a href="https://pypi.org/project/pyproc/"><img src="https://img.shields.io/badge/python-≥3.9-yellow" alt="Python ≥3.9"></a>
|
|
51
|
+
<a href="https://github.com/wakataw/pyproc/actions/workflows/test.yml?query=branch%3Amaster"><img src="https://github.com/wakataw/pyproc/actions/workflows/test.yml/badge.svg?branch=master" alt="Test Status"></a>
|
|
52
|
+
<a href="https://github.com/wakataw/pyproc/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT"></a>
|
|
53
|
+
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-compatible-purple" alt="MCP Compatible"></a>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
PyProc MCP turns public **SPSE/Inaproc** procurement data into **MCP tools** that can be used by LLM clients (Claude Desktop, Continue, Cursor), AI agents, automation workflows, Python scripts, and command-line users.
|
|
59
|
+
|
|
60
|
+
## Why PyProc MCP?
|
|
61
|
+
|
|
62
|
+
<p align="center">
|
|
63
|
+
<img src="docs/assets/pyproc-mcp-banner.png"
|
|
64
|
+
alt="PyProc MCP — Real-time Indonesian procurement data for LLM agents"
|
|
65
|
+
width="800">
|
|
66
|
+
</p>
|
|
67
|
+
|
|
68
|
+
Indonesia's public procurement system (SPSE/Inaproc) processes thousands of tender and non-tender packages across hundreds of government institutions (LPSE). This data is public — but not easily accessible to AI agents and automation tools.
|
|
69
|
+
|
|
70
|
+
PyProc MCP bridges this gap:
|
|
71
|
+
|
|
72
|
+
- **LLM-native access** — MCP tools let AI agents search and analyze procurement data directly
|
|
73
|
+
- **Real-time data** — Fetches live data from SPSE/Inaproc, not stale snapshots
|
|
74
|
+
- **Comprehensive** — Full package details: announcements, participants, evaluation results, winners, schedules
|
|
75
|
+
- **Lightweight** — Single command: `pyproc-mcp`. Works with any MCP-compatible client
|
|
76
|
+
- **Multi-interface** — Use as MCP server, Python library, or CLI tool — same codebase, no lock-in
|
|
77
|
+
- **Respectful defaults** — Built-in rate limiting, caching guidance, and responsible-use policy
|
|
78
|
+
|
|
79
|
+
## What You Can Do
|
|
80
|
+
|
|
81
|
+
- **Discover opportunities** — Search tender and non-tender packages across any LPSE host
|
|
82
|
+
- **Deep-dive analysis** — Retrieve full package details: requirements, HPS value, location, schedule
|
|
83
|
+
- **Competitive intelligence** — See who's bidding, who's winning, and at what price
|
|
84
|
+
- **Timeline tracking** — Monitor procurement schedules from announcement to contract
|
|
85
|
+
- **Vendor research** — Find which companies win which types of contracts where
|
|
86
|
+
- **AI-powered insights** — Let LLMs analyze procurement patterns, compare packages, and generate reports
|
|
87
|
+
- **Export and automate** — Use the Python library or CLI to build automated procurement monitoring pipelines
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Usage Modes
|
|
92
|
+
|
|
93
|
+
PyProc can be used in three ways. Choose the one that fits your workflow:
|
|
94
|
+
|
|
95
|
+
### 1. 🧠 MCP Server for LLM Clients (Recommended)
|
|
96
|
+
|
|
97
|
+
Give your LLM direct access to Indonesian procurement data through MCP tools.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install pyproc[mcp]
|
|
101
|
+
pyproc-mcp
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**MCP Client Configuration:**
|
|
105
|
+
|
|
106
|
+
Add to your MCP client's configuration file (e.g., `claude_desktop_config.json`):
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"pyproc": {
|
|
112
|
+
"command": "pyproc-mcp",
|
|
113
|
+
"args": [],
|
|
114
|
+
"env": {
|
|
115
|
+
"PYPROC_TIMEOUT": "30"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Restart your MCP client. PyProc tools will appear in the tool list.
|
|
123
|
+
|
|
124
|
+
### 2. 📦 Python Library
|
|
125
|
+
|
|
126
|
+
Use PyProc as a Python library for custom automation and data analysis.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from pyproc import Lpse, JenisPengadaan
|
|
130
|
+
from pyproc.lpse import By
|
|
131
|
+
|
|
132
|
+
# Initialize client for a specific LPSE host
|
|
133
|
+
lpse = Lpse('kemenkeu')
|
|
134
|
+
|
|
135
|
+
# Search for tender packages
|
|
136
|
+
packages = lpse.get_paket_tender(
|
|
137
|
+
start=0,
|
|
138
|
+
length=20,
|
|
139
|
+
search_keyword='mobil dinas',
|
|
140
|
+
tahun=2025,
|
|
141
|
+
kategori=JenisPengadaan.PENGADAAN_BARANG
|
|
142
|
+
)
|
|
143
|
+
print(packages['data'])
|
|
144
|
+
|
|
145
|
+
# Get full detail of a package
|
|
146
|
+
detail = lpse.detil_paket_tender('10080116000')
|
|
147
|
+
detail.get_all_detil()
|
|
148
|
+
print(detail.todict())
|
|
149
|
+
|
|
150
|
+
# Get only the winner
|
|
151
|
+
winners = detail.get_pemenang()
|
|
152
|
+
print(winners)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [Python Library Usage](#python-library-usage) for complete API documentation.
|
|
156
|
+
|
|
157
|
+
### 3. ⌨️ CLI Tool
|
|
158
|
+
|
|
159
|
+
Download procurement data in bulk from the command line.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Download tender data from Kemenkeu LPSE, export as JSON
|
|
163
|
+
pyproc kemenkeu --keyword "mobil dinas" --tahun-anggaran 2025 --output-format json
|
|
164
|
+
|
|
165
|
+
# Download non-tender data from multiple LPSE hosts
|
|
166
|
+
pyproc jakarta,sumbarprov --jenis-paket non_tender --tahun-anggaran 2025
|
|
167
|
+
|
|
168
|
+
# Download pencatatan non-tender data. This is distinct from non_tender.
|
|
169
|
+
pyproc nasional --jenis-paket pencatatan_non_tender --tahun-anggaran 2026
|
|
170
|
+
|
|
171
|
+
# Download with custom output filename
|
|
172
|
+
pyproc "kemenkeu;output_kemenkeu" --output-format csv --separator ";"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
See [CLI Usage](#cli-usage) for the full argument reference.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## MCP Tools
|
|
180
|
+
|
|
181
|
+
The MCP server exposes the following tools:
|
|
182
|
+
|
|
183
|
+
| Tool | Description |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `search_lpse_hosts` | Find LPSE host slugs from institution names such as "kementerian keuangan" |
|
|
186
|
+
| `get_lpse_host_detail` | Confirm metadata and canonical URL for a known LPSE host slug |
|
|
187
|
+
| `get_procurement_search_options` | Explain direct keyword search vs local full-text indexing |
|
|
188
|
+
| `search_tender_packages` | Search tender procurement packages by one or more exact SPSE keywords |
|
|
189
|
+
| `search_non_tender_packages` | Search non-tender packages by one or more exact SPSE keywords |
|
|
190
|
+
| `search_pencatatan_non_tender_packages` | Search pencatatan non-tender packages, a distinct `/dt/nonspk` entity |
|
|
191
|
+
| `search_swakelola_packages` | Search swakelola packages |
|
|
192
|
+
| `search_pengadaan_darurat_packages` | Search pengadaan darurat packages |
|
|
193
|
+
| `get_tender_detail` | Get full detail for a tender package — announcement, participants, evaluations, winner, schedule |
|
|
194
|
+
| `get_non_tender_detail` | Get full detail for a non-tender package |
|
|
195
|
+
| `get_pencatatan_non_tender_detail` | Get detail for a pencatatan non-tender package |
|
|
196
|
+
| `get_swakelola_detail` | Get detail for a swakelola package |
|
|
197
|
+
| `get_pengadaan_darurat_detail` | Get detail for a pengadaan darurat package |
|
|
198
|
+
| `get_tender_details_bulk` | Get details for multiple tender packages in one tool call |
|
|
199
|
+
| `get_non_tender_details_bulk` | Get details for multiple non-tender packages in one tool call |
|
|
200
|
+
| `get_pencatatan_non_tender_details_bulk` | Get details for multiple pencatatan non-tender packages |
|
|
201
|
+
| `get_swakelola_details_bulk` | Get details for multiple swakelola packages |
|
|
202
|
+
| `get_pengadaan_darurat_details_bulk` | Get details for multiple pengadaan darurat packages |
|
|
203
|
+
| `get_procurement_categories` | List supported procurement categories (no network call) |
|
|
204
|
+
| `validate_lpse_host` | Check if an LPSE host is accessible |
|
|
205
|
+
| `create_procurement_search_index` | Download a bounded package set into a local SQLite full-text index |
|
|
206
|
+
| `search_procurement_index` | Search a local SQLite full-text index |
|
|
207
|
+
| `list_procurement_indexes` | List local full-text indexes |
|
|
208
|
+
| `delete_procurement_index` | Delete a local full-text index |
|
|
209
|
+
|
|
210
|
+
Each tool includes LLM-friendly descriptions with parameter documentation, rate limit notes, and data source attribution.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## LPSE Host Discovery
|
|
215
|
+
|
|
216
|
+
Users do not need to know LPSE host slugs in advance. When a user names an institution, the LLM should resolve the host first, then search procurement packages.
|
|
217
|
+
|
|
218
|
+
There are two procurement executor scopes:
|
|
219
|
+
|
|
220
|
+
- Agency-specific hosts, such as `kemenkeu`, `jakarta`, or `pu`.
|
|
221
|
+
- The nationwide host `nasional`, used for national-wide/lintas instansi data and pencatatan nasional sources.
|
|
222
|
+
|
|
223
|
+
If the user asks for national, nationwide, all-Indonesia, lintas instansi, or pencatatan nasional data, use `lpse_host="nasional"` directly.
|
|
224
|
+
|
|
225
|
+
Host discovery uses the maintained Gist host metadata and the `newUrlPath` field for agency hosts. The `nasional` host is built in and does not depend on the Gist metadata. Legacy `oldUrl` values are ignored. Canonical URLs are always built as:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
https://spse.inaproc.id/{newUrlPath}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Example user request:
|
|
232
|
+
|
|
233
|
+
> cari data lelang pengadaan laptop pada kementerian keuangan
|
|
234
|
+
|
|
235
|
+
Expected MCP flow:
|
|
236
|
+
|
|
237
|
+
1. Call `search_lpse_hosts` with `query="kementerian keuangan"`.
|
|
238
|
+
2. Select the best returned `host`, usually `kemenkeu`.
|
|
239
|
+
3. Call `search_tender_packages` with `lpse_host="kemenkeu"` and `keyword="laptop"`.
|
|
240
|
+
|
|
241
|
+
If multiple host candidates look plausible, the LLM should ask the user to choose the intended LPSE before searching package data.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Search Modes
|
|
246
|
+
|
|
247
|
+
SPSE/Inaproc package search is keyword-based. PyProc MCP therefore exposes two search strategies:
|
|
248
|
+
|
|
249
|
+
- **Direct keyword search**: fast and lightweight. Use `search_tender_packages` or `search_non_tender_packages` with `keyword` or `keywords`, for example `["laptop", "notebook", "komputer"]`. The MCP server runs bounded SPSE searches and merges duplicate package IDs.
|
|
250
|
+
- **Local full-text search**: broader but slower. Use `create_procurement_search_index` only after the user agrees to download and index a bounded package set locally; the tool requires `confirm_download=true`. Then use `search_procurement_index` to search downloaded package details with SQLite FTS.
|
|
251
|
+
|
|
252
|
+
The LLM should start with direct keyword search. If results are weak or the user asks for a broader full-text search, it should explain the tradeoff before creating a local index.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Example LLM Workflows
|
|
257
|
+
|
|
258
|
+
Here are examples of what you can ask an LLM when PyProc MCP tools are connected:
|
|
259
|
+
|
|
260
|
+
**"Cari data lelang pengadaan laptop pada kementerian keuangan"**
|
|
261
|
+
→ LLM uses `search_lpse_hosts` to resolve "kementerian keuangan" to `kemenkeu`, then calls `search_tender_packages` with keywords like `["laptop", "notebook", "komputer"]`.
|
|
262
|
+
|
|
263
|
+
**"Kalau keyword biasa kurang lengkap, cari full text di detail paket"**
|
|
264
|
+
→ LLM explains that local indexing downloads package details, then uses `create_procurement_search_index` followed by `search_procurement_index` if the user agrees.
|
|
265
|
+
|
|
266
|
+
**"Find active procurement packages related to cybersecurity in 2025"**
|
|
267
|
+
→ LLM uses `search_tender_packages` with keyword "keamanan siber" or "cybersecurity" across relevant LPSE hosts.
|
|
268
|
+
|
|
269
|
+
**"Summarize tender package 10080116000 on kemenkeu — what's the HPS, who won, and when?"**
|
|
270
|
+
→ LLM uses `get_tender_detail` to retrieve the full package and summarizes key fields.
|
|
271
|
+
|
|
272
|
+
**"Ambil detail untuk 5 paket tender pertama dari hasil pencarian"**
|
|
273
|
+
→ LLM uses `get_tender_details_bulk` with the selected package IDs instead of calling `get_tender_detail` repeatedly.
|
|
274
|
+
|
|
275
|
+
**"Compare the top 5 infrastructure tenders on pu LPSE by HPS value"**
|
|
276
|
+
→ LLM uses `search_tender_packages` with `kategori=PEKERJAAN_KONSTRUKSI`, sorts by HPS, and presents a comparison table.
|
|
277
|
+
|
|
278
|
+
**"Generate a vendor research checklist for a company bidding on IT procurement"**
|
|
279
|
+
→ LLM analyzes recent IT-related tenders and generates a checklist of requirements, certifications, and typical bid values.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Installation
|
|
284
|
+
|
|
285
|
+
### Stable release
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
pip install pyproc
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### With MCP support
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
pip install pyproc[mcp]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Development version
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
pip install git+https://github.com/wakataw/pyproc.git
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Uninstall
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
pip uninstall pyproc
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Configuration
|
|
312
|
+
|
|
313
|
+
The MCP server is configured via environment variables:
|
|
314
|
+
|
|
315
|
+
| Variable | Default | Description |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| `PYPROC_TIMEOUT` | `30` | HTTP request timeout in seconds |
|
|
318
|
+
| `PYPROC_RATE_LIMIT_DELAY` | `1.0` | Minimum seconds between requests |
|
|
319
|
+
| `PYPROC_LOG_LEVEL` | `INFO` | Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Cache and Rate Limiting
|
|
324
|
+
|
|
325
|
+
- **MCP server**: Does not use persistent caching. Each tool call makes a fresh request to SPSE/Inaproc. Rate limiting enforces a minimum 1-second delay between requests to respect SPSE servers.
|
|
326
|
+
- **CLI tool**: Uses a local SQLite database (`.idx` file) for download progress tracking and resume support. The cache is disposable per-download.
|
|
327
|
+
- **Python library**: No built-in caching. You control the request lifecycle through the `Lpse` class methods.
|
|
328
|
+
|
|
329
|
+
Data freshness depends on SPSE/Inaproc source availability. For the most up-to-date information, visit [spse.inaproc.id](https://spse.inaproc.id).
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Python Library Usage
|
|
334
|
+
|
|
335
|
+
### Initialization
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from pyproc import Lpse
|
|
339
|
+
|
|
340
|
+
lpse = Lpse('kemenkeu', timeout=30) # timeout in seconds
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The `Lpse` class supports context manager usage:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
with Lpse('kemenkeu') as lpse:
|
|
347
|
+
packages = lpse.get_paket_tender(length=10)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Search Tender Packages
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from pyproc import Lpse, JenisPengadaan
|
|
354
|
+
from pyproc.lpse import By
|
|
355
|
+
|
|
356
|
+
lpse = Lpse('kemenkeu')
|
|
357
|
+
|
|
358
|
+
# Basic search
|
|
359
|
+
packages = lpse.get_paket_tender(start=0, length=25)
|
|
360
|
+
|
|
361
|
+
# Search by keyword
|
|
362
|
+
packages = lpse.get_paket_tender(search_keyword='sekolah', length=10)
|
|
363
|
+
|
|
364
|
+
# Filter by category
|
|
365
|
+
packages = lpse.get_paket_tender(
|
|
366
|
+
kategori=JenisPengadaan.PENGADAAN_BARANG,
|
|
367
|
+
length=10
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Filter by budget year
|
|
371
|
+
packages = lpse.get_paket_tender(tahun=2025, length=10)
|
|
372
|
+
|
|
373
|
+
# Sort by HPS
|
|
374
|
+
packages = lpse.get_paket_tender(order=By.HPS, length=10)
|
|
375
|
+
|
|
376
|
+
# Raw data only (list instead of dict)
|
|
377
|
+
data = lpse.get_paket_tender(data_only=True, length=10)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Search Non-Tender Packages
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
packages = lpse.get_paket_non_tender(start=0, length=25)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Search Pencatatan Packages
|
|
387
|
+
|
|
388
|
+
Pencatatan non-tender is a separate entity from ordinary non-tender.
|
|
389
|
+
|
|
390
|
+
```python
|
|
391
|
+
packages = lpse.get_paket_pencatatan_non_tender(
|
|
392
|
+
kategori=JenisPengadaan.JASA_LAINNYA,
|
|
393
|
+
rekanan='PT Test',
|
|
394
|
+
tahun=2026,
|
|
395
|
+
instansi_id='L112',
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
swakelola = lpse.get_paket_swakelola(
|
|
399
|
+
tipe_swakelola=1,
|
|
400
|
+
rekanan='Direktorat Arsitektur dan Desain',
|
|
401
|
+
tahun=2026,
|
|
402
|
+
instansi_id='K68',
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
darurat = lpse.get_paket_pengadaan_darurat(
|
|
406
|
+
kategori=JenisPengadaan.PEKERJAAN_KONSTRUKSI,
|
|
407
|
+
tahun=2026,
|
|
408
|
+
instansi_id='D267',
|
|
409
|
+
)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Get Package Detail
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# Get all detail sections
|
|
416
|
+
detail = lpse.detil_paket_tender('10080116000')
|
|
417
|
+
detail.get_all_detil()
|
|
418
|
+
|
|
419
|
+
# Access individual sections
|
|
420
|
+
detail.get_pengumuman() # Announcement
|
|
421
|
+
detail.get_peserta() # Participants
|
|
422
|
+
detail.get_hasil_evaluasi() # Evaluation results
|
|
423
|
+
detail.get_pemenang() # Winner
|
|
424
|
+
detail.get_pemenang_berkontrak() # Contracted winner
|
|
425
|
+
detail.get_jadwal() # Schedule
|
|
426
|
+
|
|
427
|
+
# Serialize to dict
|
|
428
|
+
data = detail.todict()
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Non-Tender Detail
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
detail = lpse.detil_paket_non_tender('10080116000')
|
|
435
|
+
detail.get_all_detil()
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Pencatatan Detail
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
detail = lpse.detil_paket_pencatatan_non_tender('10942236000')
|
|
442
|
+
detail.get_all_detil()
|
|
443
|
+
|
|
444
|
+
swakelola = lpse.detil_paket_swakelola('10336514000')
|
|
445
|
+
swakelola.get_all_detil()
|
|
446
|
+
|
|
447
|
+
darurat = lpse.detil_paket_pengadaan_darurat('106802')
|
|
448
|
+
darurat.get_all_detil()
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Procurement Categories
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
from pyproc import JenisPengadaan
|
|
455
|
+
|
|
456
|
+
# List all categories
|
|
457
|
+
for cat in JenisPengadaan:
|
|
458
|
+
print(cat.name, cat.value)
|
|
459
|
+
|
|
460
|
+
# Use as filter
|
|
461
|
+
JenisPengadaan.PENGADAAN_BARANG # Goods Procurement
|
|
462
|
+
JenisPengadaan.PEKERJAAN_KONSTRUKSI # Construction Works
|
|
463
|
+
JenisPengadaan.JASA_LAINNYA # Other Services
|
|
464
|
+
# ... and more
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## CLI Usage
|
|
470
|
+
|
|
471
|
+
### Basic Download
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
pyproc kemenkeu
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
This downloads tender data from the Kemenkeu LPSE and exports it as `kemenkeu.csv`.
|
|
478
|
+
|
|
479
|
+
### Arguments
|
|
480
|
+
|
|
481
|
+
| Argument | Example | Default | Description |
|
|
482
|
+
|---|---|---|---|
|
|
483
|
+
| `lpse_host` | `pyproc kemenkeu` | Required | LPSE host or text file with host list |
|
|
484
|
+
| `-k, --keyword` | `--keyword "mobil dinas"` | `""` | Search keyword filter |
|
|
485
|
+
| `-t, --tahun-anggaran` | `--tahun-anggaran 2025` | Current year | Budget year (single, comma-separated, or range) |
|
|
486
|
+
| `--kategori` | `--kategori PEKERJAAN_KONSTRUKSI` | None | Procurement category |
|
|
487
|
+
| `--jenis-paket` | `--jenis-paket pencatatan_non_tender` | `tender` | One of `tender`, `non_tender`, `pencatatan_non_tender`, `swakelola`, `darurat` |
|
|
488
|
+
| `--rekanan` | `--rekanan "PT MAJU"` | None | Provider/rekanan name filter |
|
|
489
|
+
| `--instansi-id` | `--instansi-id K68` | None | K/L/PD code from master KLPD |
|
|
490
|
+
| `--tipe-swakelola-id` | `--tipe-swakelola-id 1` | None | Swakelola-only type filter |
|
|
491
|
+
| `-c, --chunk-size` | `--chunk-size 50` | `100` | Records per page |
|
|
492
|
+
| `-x, --timeout` | `--timeout 60` | `30` | Request timeout (seconds) |
|
|
493
|
+
| `-d, --index-download-delay` | `--index-download-delay 5` | `1` | Delay between index pages (seconds) |
|
|
494
|
+
| `-o, --output-format` | `--output-format json` | `csv` | Output format: `csv` or `json` |
|
|
495
|
+
| `--keep-index` | `--keep-index` | `False` | Keep SQLite index file after download |
|
|
496
|
+
| `-r, --resume` | `--resume` | `False` | Resume failed download |
|
|
497
|
+
| `-s, --separator` | `--separator "|"` | `;` | CSV delimiter |
|
|
498
|
+
| `--log` | `--log DEBUG` | `INFO` | Log level |
|
|
499
|
+
|
|
500
|
+
### Multi-Host Download
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
# Download from multiple LPSE hosts
|
|
504
|
+
pyproc jakarta,kemenkeu,sumbarprov
|
|
505
|
+
|
|
506
|
+
# With custom output filenames
|
|
507
|
+
pyproc "jakarta;file_jakarta,kemenkeu;file_kemenkeu"
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Download Host List
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
# Export LPSE host list as CSV from Gist metadata
|
|
514
|
+
pyproc daftarlpse
|
|
515
|
+
|
|
516
|
+
# Export LPSE host list as sanitized Gist-backed JSON
|
|
517
|
+
pyproc daftarhost
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Responsible Use and Disclaimer
|
|
523
|
+
|
|
524
|
+
**This project is not affiliated with LKPP, LPSE, SPSE, Inaproc, or any Indonesian government institution.**
|
|
525
|
+
|
|
526
|
+
PyProc MCP accesses publicly available procurement data from SPSE/Inaproc systems. Users are responsible for:
|
|
527
|
+
|
|
528
|
+
- **Respectful usage** — Do not overwhelm SPSE servers with excessive requests. The tool includes built-in rate limiting.
|
|
529
|
+
- **Data accuracy** — Procurement data may be incomplete, delayed, or changed by source systems. LLM-generated analysis should be verified against official sources at [spse.inaproc.id](https://spse.inaproc.id).
|
|
530
|
+
- **Compliance** — Ensure your use complies with applicable laws and regulations in Indonesia.
|
|
531
|
+
- **No disruption** — Do not use this tool to disrupt public e-procurement services.
|
|
532
|
+
|
|
533
|
+
### Bahasa Indonesia
|
|
534
|
+
|
|
535
|
+
> Penulis tidak terafiliasi dengan pengembang SPSE atau pemilik aplikasi SPSE. Software ini dikembangkan dengan tujuan akademis, bentuk pengawasan oleh masyarakat, dan membantu pengusaha untuk mempermudah otomasi perolehan informasi pengadaan dari pemerintah.
|
|
536
|
+
>
|
|
537
|
+
> Penggunaan yang tidak wajar dan mengganggu sebagian atau seluruh fungsi aplikasi SPSE pada satuan kerja menjadi tanggung jawab masing-masing pengguna.
|
|
538
|
+
>
|
|
539
|
+
> PyProc ada karena SPSE ada, jadi gunakanlah dengan bijak dan secukupnya.
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## Development
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
# Clone the repository
|
|
547
|
+
git clone https://github.com/wakataw/pyproc.git
|
|
548
|
+
cd pyproc
|
|
549
|
+
|
|
550
|
+
# Install with dev dependencies
|
|
551
|
+
pip install -e ".[test]"
|
|
552
|
+
pip install -e ".[mcp]" # for MCP development
|
|
553
|
+
|
|
554
|
+
# Run tests
|
|
555
|
+
python -m pytest tests/ -v
|
|
556
|
+
|
|
557
|
+
# Run unit tests only (no network required)
|
|
558
|
+
python -m pytest tests/ --ignore=tests/test_lpse.py --ignore=tests/test_downloader.py -v
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Project Structure
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
pyproc/
|
|
565
|
+
__init__.py # Public API: Lpse, JenisPengadaan
|
|
566
|
+
lpse.py # API wrapper + HTML parsers
|
|
567
|
+
cli.py # CLI downloader pipeline
|
|
568
|
+
cache.py # SQLite cache store
|
|
569
|
+
utils.py # Token parsing, host list
|
|
570
|
+
exceptions.py # Exception classes
|
|
571
|
+
text.py # UI strings
|
|
572
|
+
mcp/ # MCP server adapter layer
|
|
573
|
+
server.py # MCP server entry point
|
|
574
|
+
tools.py # Tool handlers
|
|
575
|
+
schemas.py # Validation and normalization
|
|
576
|
+
resources.py # MCP resources
|
|
577
|
+
errors.py # Error mapping
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## License
|
|
583
|
+
|
|
584
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Donatur ☕️
|
|
589
|
+
|
|
590
|
+
Orang-orang yang berjasa menyediakan kopi sehingga pengembangan paket tetap berjalan:
|
|
591
|
+
|
|
592
|
+
- Angga Rinaldi Rizal (50 cangkir ☕️)
|