A cheapest-first, safety-first monitoring system for public feedback about BNI Sekuritas and the BIONS app.
The project collects public app reviews and social comments, normalizes them into one canonical data model, classifies sentiment, then exposes results through API, dashboard, and exports.
BNI/BIONS teams need a repeatable way to see:
Cheapest-first validation.
Approved MVP sources:
| Source | Target | Status | Risk |
|---|---|---|---|
| Google Play | id.bions.bnis.android | enabled | low/medium |
| Apple App Store | app id 6736508566 | enabled | low |
| YouTube | @BNI1946, @bnisekuritas46 | enabled, needs YOUTUBE_API_KEY | low |
| Stockbit | BNI/BIONS terms | disabled | high |
| X/Twitter | BNI/BIONS terms | disabled | high |
| TikTok | BNI/BIONS terms | disabled | high |
| BNI/BIONS terms | disabled | high | |
| Threads | BNI/BIONS terms | disabled | high |
keywords + schedules
-> collector adapters
-> RawSocialItem normalization
-> dedupe + preprocessing
-> sentiment classification
-> MongoDB
-> FastAPI
-> dashboard + exports
Core modules:
| Path | Purpose |
|---|---|
apps/api/ | FastAPI service for health, comments, sentiment, exports, keywords, schedules. |
apps/dashboard/ | Next.js dashboard scaffold. |
pipeline/collector/ | Platform adapters, canonical data model, normalization, dedupe, backfill runner. |
pipeline/storage/ | MongoDB persistence — canonical, raw, processed, labeled, sentiment, run, export, dashboard, model registry collections. |
pipeline/sentiment/ | Indonesian text preprocessing, topic tagging, quality flags, model/OpenRouter sentiment classification. |
pipeline/scheduler/ | Scheduled collector/analyzer jobs. |
pipeline/export/ | CSV/XLSX export helpers. |
db/ | MongoDB init, indexes, seed keywords, legacy SQL notes. |
docs/ | Human-facing strategy, architecture, contracts, research. |
.agents/ | AI-agent project context and operating rules. |
cd /home/hp/dev/work/bni-bions-sentiment-analysis
python3 -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'
make mongo-up
pytest -q
Optional collector deps:
pip install -e '.[collectors]'
Run pipeline pieces:
make collect
make backfill
make sentiment-backfill
make analyze
make export
make api
Equivalent direct commands:
python3 -m pipeline.collector.run
python3 -m pipeline.collector.backfill
python3 -c "from pipeline.sentiment.run import backfill_sentiment; print(backfill_sentiment())"
python3 -c "from pipeline.sentiment.run import backfill_sentiment; print(backfill_sentiment(reprocess=True))" # re-run all analyzable items
python3 -c "from pipeline.storage.datasets import build_dataset_layers; print(build_dataset_layers())" # raw + processed training layers
python3 -c "from pipeline.storage.datasets import export_training_jsonl; print(export_training_jsonl())" # labeled training JSONL
python3 -m pipeline.sentiment.run
python3 -m pipeline.export.csv_export
python3 -m uvicorn apps.api.app.main:app --reload
Local secrets belong in .env. Do not commit .env.
Required when using YouTube collector:
YOUTUBE_API_KEY=...
Default runtime settings are currently defined in apps/api/app/config.py:
APP_TIMEZONE=Asia/Jakarta
SENTIMENT_METHOD=openrouter
OPENROUTER_MODEL=openrouter/free
OPENROUTER_BATCH_SIZE=5
OPENROUTER_BATCH_DELAY_SECONDS=2
MODEL_NAME=indobenchmark/indobert-base-p2
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=bni_bions_sentiment
Local MongoDB:
make mongo-up
make mongo-status
Use these until there are 14 clean days of successful runs:
global:
schedule: "1x/day"
timezone: Asia/Jakarta
initial_backfill_days: 30
rolling_backfill_days: 7
raw_retention_days: 180
aggregate_retention_days: 365
google_play:
max_items_per_run: 100
rate_limit: "1 request / 5-10 sec"
apple_app_store:
max_items_per_run: 100
rate_limit: "1 request / 5-10 sec"
youtube:
max_comments_per_run: 200
max_videos_per_run: 50
relevance_filter: "enabled: keeps BIONS/sekuritas/saham/trading/RDN/app comments; drops spam/off-topic comments"
daily_quota_budget_units: 500
Stop collection on auth errors, rate limits, CAPTCHA, login walls, quota budget exhaustion, private/deleted content, or repeated server failures.
All collectors should emit RawSocialItem from pipeline/collector/base.py.
Important fields:
platform
source_type
source_id
keyword
target_entity
text
author_username
author_display_name
posted_at
collected_at
metrics
raw_payload
content_hash
collection_method
access_risk
collector_version
User approved saving public usernames/display names. Still redact secrets, emails, phone numbers, NIK/KTP-like values, account/card-like numbers, and passwords from stored/exported data.
Start here:
| Document | Use |
|---|---|
AGENTS.md | AI coding agent entrypoint. |
.agents/README.md | AI-agent doc map. |
docs/architecture.md | System architecture explanation. |
docs/data-contract.md | Canonical item reference. |
docs/collector-strategy.md | Collector plan and source priorities. |
docs/provider-decision-matrix.md | Source/vendor tradeoffs. |
docs/human-approval-checklist.md | Approved sources and risk gates. |
docs/labeling-guideline.md | Sentiment labeling guide. |
Step-by-step guides for running each collector.
| Guide | What it covers |
|---|---|
| 00 - Prerequisites | Setup: Python, venv, install, .env config |
| 01 - Google Play | Collect app reviews (no API key) |
| 02 - App Store | Collect app reviews (no API key) |
| 03 - YouTube | Collect video comments (needs API key) |
| 04 - X/Twitter | Collect mentions + replies (needs bearer token) |
| 05 - TikTok | Collect video comments (needs research token) |
| 06 - Instagram | Collect post comments (needs graph token) |
| 07 - Threads | Collect thread replies (needs access token) |
| 08 - Stockbit | Collect discussions (public scrape, high risk) |
| 09 - Run All | Run all collectors at once |
| 10 - Troubleshooting | Common errors and fixes |
| 11 - Docker MongoDB | Local MongoDB infra for collector storage |
| 12 - Backfill Retention | Fill 1 year of historical collector data into MongoDB |
Before claiming work complete:
. .venv/bin/activate
pytest -q
node --check db/mongo-init.js
git diff --check
Before commit, scan staged diff for accidental secrets:
git diff --staged | grep -Ei 'password|secret|api[_-]?key|token|cookie|authorization|bearer' || true
OpenRouter labels sentiment and BIONS topics in one batch call. Default batch size is 5 items for the free OpenRouter pool. That keeps responses fast, preserves JSON/id alignment, lowers truncation/topic drift risk, and reduces rate-limit pressure with a 2s inter-batch delay. Larger requested batch sizes are clamped to 5.
Allowed topics are the canonical 8: app_experience, trading_execution, account_kyc, fees_pricing, market_data_features, customer_service, trust_reliability, competition_comparison.
Rating remains context only. OpenRouter prompt explicitly forbids 1★ -> negative unless text supports it.
Use text-first labels.
raw_social_items: original source payload snapshot for reproducible reprocessing.processed_social_items: normalized text, tokens/features, model label, quality flags.labeled_examples: human labels for training/eval. ratingContext is context only, not label truth.Rating policy: 1★ or 5★ never overrides sentiment. If rating conflicts with text/model output, mark needsReview; human label decides final training label.
Recommended loop:
backfill_sentiment(reprocess=True).build_dataset_layers().quality.needsReview=true + balanced samples first.data/training/bions_sentiment.jsonl.db/schema.sql and db/seed_keywords.sql are legacy SQL references while the active database target is MongoDB.Internal project scaffold unless a license is added.
Python
84.0%
TypeScript
12.2%
JavaScript
2.6%
A cheapest-first, safety-first monitoring system for public feedback about BNI Sekuritas and the BIONS app.
The project collects public app reviews and social comments, normalizes them into one canonical data model, classifies sentiment, then exposes results through API, dashboard, and exports.
BNI/BIONS teams need a repeatable way to see:
Cheapest-first validation.
Approved MVP sources:
| Source | Target | Status | Risk |
|---|---|---|---|
| Google Play | id.bions.bnis.android | enabled | low/medium |
| Apple App Store | app id 6736508566 | enabled | low |
| YouTube | @BNI1946, @bnisekuritas46 | enabled, needs YOUTUBE_API_KEY | low |
| Stockbit | BNI/BIONS terms | disabled | high |
| X/Twitter | BNI/BIONS terms | disabled | high |
| TikTok | BNI/BIONS terms | disabled | high |
| BNI/BIONS terms | disabled | high | |
| Threads | BNI/BIONS terms | disabled | high |
keywords + schedules
-> collector adapters
-> RawSocialItem normalization
-> dedupe + preprocessing
-> sentiment classification
-> MongoDB
-> FastAPI
-> dashboard + exports
Core modules:
| Path | Purpose |
|---|---|
apps/api/ | FastAPI service for health, comments, sentiment, exports, keywords, schedules. |
apps/dashboard/ | Next.js dashboard scaffold. |
pipeline/collector/ | Platform adapters, canonical data model, normalization, dedupe, backfill runner. |
pipeline/storage/ | MongoDB persistence — canonical, raw, processed, labeled, sentiment, run, export, dashboard, model registry collections. |
pipeline/sentiment/ | Indonesian text preprocessing, topic tagging, quality flags, model/OpenRouter sentiment classification. |
pipeline/scheduler/ | Scheduled collector/analyzer jobs. |
pipeline/export/ | CSV/XLSX export helpers. |
db/ | MongoDB init, indexes, seed keywords, legacy SQL notes. |
docs/ | Human-facing strategy, architecture, contracts, research. |
.agents/ | AI-agent project context and operating rules. |
cd /home/hp/dev/work/bni-bions-sentiment-analysis
python3 -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'
make mongo-up
pytest -q
Optional collector deps:
pip install -e '.[collectors]'
Run pipeline pieces:
make collect
make backfill
make sentiment-backfill
make analyze
make export
make api
Equivalent direct commands:
python3 -m pipeline.collector.run
python3 -m pipeline.collector.backfill
python3 -c "from pipeline.sentiment.run import backfill_sentiment; print(backfill_sentiment())"
python3 -c "from pipeline.sentiment.run import backfill_sentiment; print(backfill_sentiment(reprocess=True))" # re-run all analyzable items
python3 -c "from pipeline.storage.datasets import build_dataset_layers; print(build_dataset_layers())" # raw + processed training layers
python3 -c "from pipeline.storage.datasets import export_training_jsonl; print(export_training_jsonl())" # labeled training JSONL
python3 -m pipeline.sentiment.run
python3 -m pipeline.export.csv_export
python3 -m uvicorn apps.api.app.main:app --reload
Local secrets belong in .env. Do not commit .env.
Required when using YouTube collector:
YOUTUBE_API_KEY=...
Default runtime settings are currently defined in apps/api/app/config.py:
APP_TIMEZONE=Asia/Jakarta
SENTIMENT_METHOD=openrouter
OPENROUTER_MODEL=openrouter/free
OPENROUTER_BATCH_SIZE=5
OPENROUTER_BATCH_DELAY_SECONDS=2
MODEL_NAME=indobenchmark/indobert-base-p2
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=bni_bions_sentiment
Local MongoDB:
make mongo-up
make mongo-status
Use these until there are 14 clean days of successful runs:
global:
schedule: "1x/day"
timezone: Asia/Jakarta
initial_backfill_days: 30
rolling_backfill_days: 7
raw_retention_days: 180
aggregate_retention_days: 365
google_play:
max_items_per_run: 100
rate_limit: "1 request / 5-10 sec"
apple_app_store:
max_items_per_run: 100
rate_limit: "1 request / 5-10 sec"
youtube:
max_comments_per_run: 200
max_videos_per_run: 50
relevance_filter: "enabled: keeps BIONS/sekuritas/saham/trading/RDN/app comments; drops spam/off-topic comments"
daily_quota_budget_units: 500
Stop collection on auth errors, rate limits, CAPTCHA, login walls, quota budget exhaustion, private/deleted content, or repeated server failures.
All collectors should emit RawSocialItem from pipeline/collector/base.py.
Important fields:
platform
source_type
source_id
keyword
target_entity
text
author_username
author_display_name
posted_at
collected_at
metrics
raw_payload
content_hash
collection_method
access_risk
collector_version
User approved saving public usernames/display names. Still redact secrets, emails, phone numbers, NIK/KTP-like values, account/card-like numbers, and passwords from stored/exported data.
Start here:
| Document | Use |
|---|---|
AGENTS.md | AI coding agent entrypoint. |
.agents/README.md | AI-agent doc map. |
docs/architecture.md | System architecture explanation. |
docs/data-contract.md | Canonical item reference. |
docs/collector-strategy.md | Collector plan and source priorities. |
docs/provider-decision-matrix.md | Source/vendor tradeoffs. |
docs/human-approval-checklist.md | Approved sources and risk gates. |
docs/labeling-guideline.md | Sentiment labeling guide. |
Step-by-step guides for running each collector.
| Guide | What it covers |
|---|---|
| 00 - Prerequisites | Setup: Python, venv, install, .env config |
| 01 - Google Play | Collect app reviews (no API key) |
| 02 - App Store | Collect app reviews (no API key) |
| 03 - YouTube | Collect video comments (needs API key) |
| 04 - X/Twitter | Collect mentions + replies (needs bearer token) |
| 05 - TikTok | Collect video comments (needs research token) |
| 06 - Instagram | Collect post comments (needs graph token) |
| 07 - Threads | Collect thread replies (needs access token) |
| 08 - Stockbit | Collect discussions (public scrape, high risk) |
| 09 - Run All | Run all collectors at once |
| 10 - Troubleshooting | Common errors and fixes |
| 11 - Docker MongoDB | Local MongoDB infra for collector storage |
| 12 - Backfill Retention | Fill 1 year of historical collector data into MongoDB |
Before claiming work complete:
. .venv/bin/activate
pytest -q
node --check db/mongo-init.js
git diff --check
Before commit, scan staged diff for accidental secrets:
git diff --staged | grep -Ei 'password|secret|api[_-]?key|token|cookie|authorization|bearer' || true
OpenRouter labels sentiment and BIONS topics in one batch call. Default batch size is 5 items for the free OpenRouter pool. That keeps responses fast, preserves JSON/id alignment, lowers truncation/topic drift risk, and reduces rate-limit pressure with a 2s inter-batch delay. Larger requested batch sizes are clamped to 5.
Allowed topics are the canonical 8: app_experience, trading_execution, account_kyc, fees_pricing, market_data_features, customer_service, trust_reliability, competition_comparison.
Rating remains context only. OpenRouter prompt explicitly forbids 1★ -> negative unless text supports it.
Use text-first labels.
raw_social_items: original source payload snapshot for reproducible reprocessing.processed_social_items: normalized text, tokens/features, model label, quality flags.labeled_examples: human labels for training/eval. ratingContext is context only, not label truth.Rating policy: 1★ or 5★ never overrides sentiment. If rating conflicts with text/model output, mark needsReview; human label decides final training label.
Recommended loop:
backfill_sentiment(reprocess=True).build_dataset_layers().quality.needsReview=true + balanced samples first.data/training/bions_sentiment.jsonl.db/schema.sql and db/seed_keywords.sql are legacy SQL references while the active database target is MongoDB.Internal project scaffold unless a license is added.
Python
84.0%
TypeScript
12.2%
JavaScript
2.6%