feat: Add AI enrichment approval workflow

- Remove confusing "Zweryfikowano 2x | Jakość: 100%" badge
- Create AiEnrichmentProposal model for pending AI suggestions
- Modify AI enrichment to create proposals instead of direct saves
- Add approve/reject API endpoints for proposals
- Update frontend to show approval buttons after AI analysis
- Proposals expire after 30 days if not reviewed

The workflow now requires owner/admin approval before AI-generated
data is applied to company profiles. This prevents unwanted data
from being automatically added.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-01 11:03:53 +01:00
parent 69e71f1f05
commit f166668f22
5 changed files with 486 additions and 62 deletions

View File

@ -18,8 +18,9 @@ from flask import jsonify, request, current_app
from flask_login import current_user, login_required
from database import (
SessionLocal, Company, User, Person, CompanyPerson, CompanyAIInsights
SessionLocal, Company, User, Person, CompanyPerson, CompanyAIInsights, AiEnrichmentProposal
)
from datetime import timedelta
import gemini_service
import krs_api_service
from . import bp
@ -668,43 +669,40 @@ WAZNE:
'error': 'Blad parsowania odpowiedzi AI. Sprobuj ponownie.'
}), 500
# Save or update AI insights
existing_insights = db.query(CompanyAIInsights).filter_by(company_id=company.id).first()
# Create AI enrichment PROPOSAL (requires approval before applying)
# Instead of directly saving, we create a proposal that needs to be reviewed
if existing_insights:
# Update existing
existing_insights.business_summary = ai_data.get('business_summary')
existing_insights.services_list = ai_data.get('services_list', [])
existing_insights.target_market = ai_data.get('target_market')
existing_insights.unique_selling_points = ai_data.get('unique_selling_points', [])
existing_insights.company_values = ai_data.get('company_values', [])
existing_insights.certifications = ai_data.get('certifications', [])
existing_insights.industry_tags = ai_data.get('industry_tags', [])
existing_insights.suggested_category = ai_data.get('suggested_category')
existing_insights.category_confidence = ai_data.get('category_confidence')
existing_insights.ai_confidence_score = 0.85 # Default confidence
existing_insights.processing_time_ms = processing_time
existing_insights.analyzed_at = datetime.utcnow()
else:
# Create new
new_insights = CompanyAIInsights(
# Check for existing pending proposals
existing_pending = db.query(AiEnrichmentProposal).filter_by(
company_id=company.id,
business_summary=ai_data.get('business_summary'),
services_list=ai_data.get('services_list', []),
target_market=ai_data.get('target_market'),
unique_selling_points=ai_data.get('unique_selling_points', []),
company_values=ai_data.get('company_values', []),
certifications=ai_data.get('certifications', []),
industry_tags=ai_data.get('industry_tags', []),
suggested_category=ai_data.get('suggested_category'),
category_confidence=ai_data.get('category_confidence'),
ai_confidence_score=0.85,
processing_time_ms=processing_time,
analyzed_at=datetime.utcnow()
status='pending'
).first()
if existing_pending:
# Update existing pending proposal
existing_pending.proposed_data = ai_data
existing_pending.data_source = company.website
existing_pending.confidence_score = 0.85
existing_pending.ai_explanation = f"AI przeanalizowało dane z {len(sources_used)} źródeł: {', '.join(sources_used)}"
existing_pending.created_at = datetime.utcnow()
existing_pending.expires_at = datetime.utcnow() + timedelta(days=30)
proposal = existing_pending
else:
# Create new proposal
proposal = AiEnrichmentProposal(
company_id=company.id,
status='pending',
proposal_type='ai_enrichment',
data_source=company.website,
proposed_data=ai_data,
ai_explanation=f"AI przeanalizowało dane z {len(sources_used)} źródeł: {', '.join(sources_used)}",
confidence_score=0.85,
expires_at=datetime.utcnow() + timedelta(days=30)
)
db.add(new_insights)
db.add(proposal)
db.commit()
proposal_id = proposal.id
# Count sources used
sources_used = ['database']
@ -713,16 +711,19 @@ WAZNE:
if website_content:
sources_used.append('website')
logger.info(f"AI enrichment completed for {company.name}. Processing time: {processing_time}ms. Sources: {sources_used}")
logger.info(f"AI enrichment proposal created for {company.name}. Proposal ID: {proposal_id}. Sources: {sources_used}")
return jsonify({
'success': True,
'message': f'Dane firmy "{company.name}" zostaly wzbogacone przez AI',
'message': f'Propozycja wzbogacenia danych dla "{company.name}" została utworzona i oczekuje na akceptację',
'proposal_id': proposal_id,
'status': 'pending',
'processing_time_ms': processing_time,
'sources_used': sources_used,
'brave_results_count': len(brave_results['news']) + len(brave_results['web']),
'website_content_length': len(website_content),
'insights': ai_data
'proposed_data': ai_data,
'requires_approval': True
})
except Exception as e:
@ -736,6 +737,213 @@ WAZNE:
db.close()
# ============================================================
# AI ENRICHMENT PROPOSALS API ROUTES
# ============================================================
@bp.route('/company/<int:company_id>/proposals', methods=['GET'])
@login_required
def api_get_proposals(company_id):
"""
API: Get AI enrichment proposals for a company.
Returns pending, approved, and rejected proposals.
"""
db = SessionLocal()
try:
company = db.query(Company).filter_by(id=company_id).first()
if not company:
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
# Check permissions
if not current_user.is_admin and current_user.company_id != company.id:
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
proposals = db.query(AiEnrichmentProposal).filter_by(
company_id=company_id
).order_by(AiEnrichmentProposal.created_at.desc()).all()
return jsonify({
'success': True,
'proposals': [{
'id': p.id,
'status': p.status,
'proposal_type': p.proposal_type,
'proposed_data': p.proposed_data,
'ai_explanation': p.ai_explanation,
'confidence_score': float(p.confidence_score) if p.confidence_score else None,
'created_at': p.created_at.isoformat() if p.created_at else None,
'reviewed_at': p.reviewed_at.isoformat() if p.reviewed_at else None,
'reviewed_by': p.reviewed_by.email if p.reviewed_by else None,
'review_comment': p.review_comment,
'approved_fields': p.approved_fields
} for p in proposals]
})
finally:
db.close()
@bp.route('/company/<int:company_id>/proposals/<int:proposal_id>/approve', methods=['POST'])
@login_required
def api_approve_proposal(company_id, proposal_id):
"""
API: Approve an AI enrichment proposal.
Optionally accepts 'fields' parameter to approve only specific fields.
When approved, the data is applied to CompanyAIInsights.
"""
db = SessionLocal()
try:
company = db.query(Company).filter_by(id=company_id).first()
if not company:
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
# Check permissions - only admin or company owner
if not current_user.is_admin and current_user.company_id != company.id:
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
proposal = db.query(AiEnrichmentProposal).filter_by(
id=proposal_id,
company_id=company_id
).first()
if not proposal:
return jsonify({'success': False, 'error': 'Propozycja nie istnieje'}), 404
if proposal.status != 'pending':
return jsonify({'success': False, 'error': f'Propozycja ma status: {proposal.status}'}), 400
data = request.get_json() or {}
approved_fields = data.get('fields') # Optional: only approve specific fields
comment = data.get('comment', '')
ai_data = proposal.proposed_data
# Apply to CompanyAIInsights
existing_insights = db.query(CompanyAIInsights).filter_by(company_id=company.id).first()
# Determine which fields to apply
if approved_fields:
# Partial approval
fields_to_apply = approved_fields
else:
# Full approval - all fields
fields_to_apply = list(ai_data.keys())
if existing_insights:
# Update existing
if 'business_summary' in fields_to_apply:
existing_insights.business_summary = ai_data.get('business_summary')
if 'services_list' in fields_to_apply:
existing_insights.services_list = ai_data.get('services_list', [])
if 'target_market' in fields_to_apply:
existing_insights.target_market = ai_data.get('target_market')
if 'unique_selling_points' in fields_to_apply:
existing_insights.unique_selling_points = ai_data.get('unique_selling_points', [])
if 'company_values' in fields_to_apply:
existing_insights.company_values = ai_data.get('company_values', [])
if 'certifications' in fields_to_apply:
existing_insights.certifications = ai_data.get('certifications', [])
if 'industry_tags' in fields_to_apply:
existing_insights.industry_tags = ai_data.get('industry_tags', [])
if 'suggested_category' in fields_to_apply:
existing_insights.suggested_category = ai_data.get('suggested_category')
existing_insights.ai_confidence_score = proposal.confidence_score
existing_insights.analyzed_at = datetime.utcnow()
else:
# Create new
new_insights = CompanyAIInsights(
company_id=company.id,
business_summary=ai_data.get('business_summary') if 'business_summary' in fields_to_apply else None,
services_list=ai_data.get('services_list', []) if 'services_list' in fields_to_apply else [],
target_market=ai_data.get('target_market') if 'target_market' in fields_to_apply else None,
unique_selling_points=ai_data.get('unique_selling_points', []) if 'unique_selling_points' in fields_to_apply else [],
company_values=ai_data.get('company_values', []) if 'company_values' in fields_to_apply else [],
certifications=ai_data.get('certifications', []) if 'certifications' in fields_to_apply else [],
industry_tags=ai_data.get('industry_tags', []) if 'industry_tags' in fields_to_apply else [],
suggested_category=ai_data.get('suggested_category') if 'suggested_category' in fields_to_apply else None,
ai_confidence_score=proposal.confidence_score,
analyzed_at=datetime.utcnow()
)
db.add(new_insights)
# Update proposal status
proposal.status = 'approved'
proposal.reviewed_at = datetime.utcnow()
proposal.reviewed_by_id = current_user.id
proposal.review_comment = comment
proposal.approved_fields = fields_to_apply
proposal.applied_at = datetime.utcnow()
db.commit()
logger.info(f"AI proposal {proposal_id} approved for company {company.name} by {current_user.email}")
return jsonify({
'success': True,
'message': f'Propozycja została zaakceptowana i dane zastosowane do profilu',
'approved_fields': fields_to_apply
})
except Exception as e:
db.rollback()
logger.error(f"Error approving proposal {proposal_id}: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/company/<int:company_id>/proposals/<int:proposal_id>/reject', methods=['POST'])
@login_required
def api_reject_proposal(company_id, proposal_id):
"""
API: Reject an AI enrichment proposal.
"""
db = SessionLocal()
try:
company = db.query(Company).filter_by(id=company_id).first()
if not company:
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
# Check permissions
if not current_user.is_admin and current_user.company_id != company.id:
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
proposal = db.query(AiEnrichmentProposal).filter_by(
id=proposal_id,
company_id=company_id
).first()
if not proposal:
return jsonify({'success': False, 'error': 'Propozycja nie istnieje'}), 404
if proposal.status != 'pending':
return jsonify({'success': False, 'error': f'Propozycja ma status: {proposal.status}'}), 400
data = request.get_json() or {}
comment = data.get('comment', '')
# Update proposal status
proposal.status = 'rejected'
proposal.reviewed_at = datetime.utcnow()
proposal.reviewed_by_id = current_user.id
proposal.review_comment = comment
db.commit()
logger.info(f"AI proposal {proposal_id} rejected for company {company.name} by {current_user.email}")
return jsonify({
'success': True,
'message': 'Propozycja została odrzucona'
})
except Exception as e:
db.rollback()
logger.error(f"Error rejecting proposal {proposal_id}: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
# ============================================================
# UTILITY API ROUTES
# ============================================================

View File

@ -3,5 +3,9 @@
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
*No recent activity*
### Jan 31, 2026
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #180 | 6:25 PM | 🔵 | Nordabiz project architecture analyzed revealing 16+ Flask blueprints with modular organization | ~831 |
</claude-mem-context>

View File

@ -764,6 +764,7 @@ class Company(Base):
# Website scraping and AI analysis
website_content = relationship('CompanyWebsiteContent', back_populates='company', cascade='all, delete-orphan')
ai_insights = relationship('CompanyAIInsights', back_populates='company', uselist=False)
ai_enrichment_proposals = relationship('AiEnrichmentProposal', back_populates='company', cascade='all, delete-orphan')
class Service(Base):
@ -1136,6 +1137,73 @@ class CompanyAIInsights(Base):
company = relationship('Company', back_populates='ai_insights')
class AiEnrichmentProposal(Base):
"""
Propozycje wzbogacenia danych przez AI - wymagają akceptacji właściciela/admina.
Workflow:
1. AI analizuje dane ze strony WWW
2. Tworzy propozycję (status: pending)
3. Właściciel/admin przegląda i akceptuje lub odrzuca
4. Po akceptacji dane dodawane do profilu firmy
"""
__tablename__ = 'ai_enrichment_proposals'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('companies.id'), nullable=False, index=True)
# Status: pending, approved, rejected, expired
status = Column(String(20), default='pending', nullable=False, index=True)
# Typ propozycji (website_extraction, krs_update, manual_suggestion)
proposal_type = Column(String(50), default='website_extraction', nullable=False)
# Źródło danych (URL strony, API, itp.)
data_source = Column(String(500))
# Proponowane dane jako JSON
proposed_data = Column(JSONB, nullable=False)
# Struktura proposed_data:
# {
# "services": ["usługa 1", "usługa 2"],
# "products": ["produkt 1"],
# "keywords": ["keyword 1"],
# "specializations": ["spec 1"],
# "brands": ["marka 1"],
# "target_customers": ["klient 1"],
# "regions": ["region 1"],
# "summary": "Opis firmy..."
# }
# Komentarz AI wyjaśniający propozycję
ai_explanation = Column(Text)
# Wskaźnik pewności AI (0.0 - 1.0)
confidence_score = Column(Numeric(3, 2))
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
reviewed_at = Column(DateTime)
applied_at = Column(DateTime)
expires_at = Column(DateTime) # Propozycje wygasają po X dniach
# Kto przeglądał
reviewed_by_id = Column(Integer, ForeignKey('users.id'))
# Komentarz przy akceptacji/odrzuceniu
review_comment = Column(Text)
# Które pola zostały zaakceptowane (jeśli częściowa akceptacja)
approved_fields = Column(JSONB) # ["services", "keywords"]
# Relationships
company = relationship('Company', back_populates='ai_enrichment_proposals')
reviewed_by = relationship('User', foreign_keys=[reviewed_by_id])
def __repr__(self):
return f"<AiEnrichmentProposal {self.id} for Company {self.company_id} ({self.status})>"
class MaturityAssessment(Base):
"""Historical tracking of digital maturity scores over time"""
__tablename__ = 'maturity_assessments'

View File

@ -0,0 +1,58 @@
-- ============================================================
-- 041_ai_enrichment_proposals.sql
-- System akceptacji propozycji wzbogacenia danych przez AI
-- ============================================================
-- Tabela propozycji wzbogacenia AI
CREATE TABLE IF NOT EXISTS ai_enrichment_proposals (
id SERIAL PRIMARY KEY,
company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
-- Status workflow
status VARCHAR(20) NOT NULL DEFAULT 'pending',
-- Typ propozycji
proposal_type VARCHAR(50) NOT NULL DEFAULT 'website_extraction',
-- Źródło danych (URL strony, API, itp.)
data_source VARCHAR(500),
-- Proponowane dane jako JSON
proposed_data JSONB NOT NULL,
-- Komentarz AI wyjaśniający propozycję
ai_explanation TEXT,
-- Wskaźnik pewności AI (0.00 - 1.00)
confidence_score NUMERIC(3, 2),
-- Timestamps
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
reviewed_at TIMESTAMP,
applied_at TIMESTAMP,
expires_at TIMESTAMP,
-- Kto przeglądał
reviewed_by_id INTEGER REFERENCES users(id),
-- Komentarz przy akceptacji/odrzuceniu
review_comment TEXT,
-- Które pola zostały zaakceptowane (częściowa akceptacja)
approved_fields JSONB
);
-- Indeksy
CREATE INDEX IF NOT EXISTS idx_ai_proposals_company ON ai_enrichment_proposals(company_id);
CREATE INDEX IF NOT EXISTS idx_ai_proposals_status ON ai_enrichment_proposals(status);
CREATE INDEX IF NOT EXISTS idx_ai_proposals_created ON ai_enrichment_proposals(created_at DESC);
-- Komentarze
COMMENT ON TABLE ai_enrichment_proposals IS 'Propozycje wzbogacenia danych przez AI wymagające akceptacji';
COMMENT ON COLUMN ai_enrichment_proposals.status IS 'Status: pending, approved, rejected, expired';
COMMENT ON COLUMN ai_enrichment_proposals.proposed_data IS 'JSON z proponowanymi danymi (services, keywords, summary, etc.)';
COMMENT ON COLUMN ai_enrichment_proposals.approved_fields IS 'Lista zaakceptowanych pól przy częściowej akceptacji';
-- Grant permissions
GRANT ALL ON TABLE ai_enrichment_proposals TO nordabiz_app;
GRANT USAGE, SELECT ON SEQUENCE ai_enrichment_proposals_id_seq TO nordabiz_app;

View File

@ -568,15 +568,7 @@
<span class="company-category-badge">{{ company.category.name }}</span>
{% endif %}
{% if quality_data %}
<span class="quality-badge {% if quality_data.quality_score >= 95 %}verified{% elif quality_data.quality_score < 80 %}needs-review{% endif %}">
✓ Zweryfikowano {{ quality_data.verification_count }}x | Jakość: {{ quality_data.quality_score }}%
</span>
{% else %}
<span class="quality-badge needs-review">
⏳ Niezweryfikowana
</span>
{% endif %}
{# Badge jakości usunięty - zbędny i mylący dla użytkowników #}
<h1 class="company-name">{{ company.name }}</h1>
@ -3664,6 +3656,101 @@ function finishAiEnrichment(success) {
}
}
function showProposalApprovalButtons(companyId, proposalId, proposedData) {
// Update modal title
document.getElementById('aiModalTitle').textContent = 'Propozycja wymaga akceptacji';
document.getElementById('aiCancelBtn').style.display = 'none';
document.getElementById('aiSpinner').style.display = 'none';
// Create approval buttons container
const footer = document.getElementById('aiProgressFooter');
footer.style.display = 'flex';
footer.innerHTML = `
<div style="display: flex; gap: var(--spacing-md); width: 100%; justify-content: center; flex-wrap: wrap;">
<button id="approveProposalBtn" class="btn btn-success" style="padding: 12px 24px; font-weight: 600;">
✓ Akceptuj i dodaj do profilu
</button>
<button id="rejectProposalBtn" class="btn btn-danger" style="padding: 12px 24px; font-weight: 600;">
✕ Odrzuć propozycję
</button>
</div>
`;
// Handle approval
document.getElementById('approveProposalBtn').addEventListener('click', async () => {
const btn = document.getElementById('approveProposalBtn');
btn.disabled = true;
btn.textContent = 'Zapisywanie...';
try {
const response = await fetch(`/api/company/${companyId}/proposals/${proposalId}/approve`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
}
});
const data = await response.json();
if (data.success) {
addAiLogEntry('✓ Propozycja ZAAKCEPTOWANA - dane dodane do profilu!', 'success', '★');
footer.innerHTML = `
<div style="text-align: center; color: #10b981; font-weight: 600;">
✓ Dane zostały dodane do profilu firmy
<br><small style="font-weight: normal;">Odśwież stronę aby zobaczyć zmiany</small>
</div>
`;
setTimeout(() => location.reload(), 2000);
} else {
addAiLogEntry('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
btn.disabled = false;
btn.textContent = '✓ Akceptuj i dodaj do profilu';
}
} catch (error) {
addAiLogEntry('Błąd połączenia: ' + error.message, 'error');
btn.disabled = false;
btn.textContent = '✓ Akceptuj i dodaj do profilu';
}
});
// Handle rejection
document.getElementById('rejectProposalBtn').addEventListener('click', async () => {
const btn = document.getElementById('rejectProposalBtn');
btn.disabled = true;
btn.textContent = 'Odrzucanie...';
try {
const response = await fetch(`/api/company/${companyId}/proposals/${proposalId}/reject`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
}
});
const data = await response.json();
if (data.success) {
addAiLogEntry('✕ Propozycja ODRZUCONA - dane nie zostały dodane', 'info');
footer.innerHTML = `
<div style="text-align: center; color: #ef4444;">
✕ Propozycja odrzucona
<br><small>Możesz spróbować ponownie później</small>
</div>
`;
setTimeout(() => closeAiModal(), 2000);
} else {
addAiLogEntry('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
btn.disabled = false;
btn.textContent = '✕ Odrzuć propozycję';
}
} catch (error) {
addAiLogEntry('Błąd połączenia: ' + error.message, 'error');
btn.disabled = false;
btn.textContent = '✕ Odrzuć propozycję';
}
});
}
document.addEventListener('DOMContentLoaded', function() {
const aiEnrichBtn = document.getElementById('aiEnrichBtn');
if (aiEnrichBtn && !aiEnrichBtn.disabled) {
@ -3723,38 +3810,37 @@ document.addEventListener('DOMContentLoaded', function() {
const data = await response.json();
if (data.success) {
updateAiProgress(85, 'Zapisywanie wynikow...');
updateAiProgress(85, 'Propozycja utworzona...');
addAiLogEntry('Parsowanie odpowiedzi JSON', 'info');
await sleep(200);
// Show what was generated
const insights = data.insights;
addAiLogEntry('Wygenerowane dane:', 'step');
// Show what was proposed (not yet saved!)
const insights = data.proposed_data;
const proposalId = data.proposal_id;
addAiLogEntry('AI przygotowało propozycję danych:', 'step');
if (insights.business_summary) {
addAiLogEntry(`Opis biznesowy: ${insights.business_summary.substring(0, 60)}...`, 'success');
addAiLogEntry(`Opis biznesowy: ${insights.business_summary.substring(0, 80)}...`, 'info');
}
if (insights.services_list && insights.services_list.length > 0) {
addAiLogEntry(`Uslugi: ${insights.services_list.length} pozycji`, 'success');
addAiLogEntry(`Usługi (${insights.services_list.length}): ${insights.services_list.slice(0, 5).join(', ')}${insights.services_list.length > 5 ? '...' : ''}`, 'info');
}
if (insights.unique_selling_points && insights.unique_selling_points.length > 0) {
addAiLogEntry(`Wyrozniki: ${insights.unique_selling_points.length} pozycji`, 'success');
addAiLogEntry(`Wyróżniki (${insights.unique_selling_points.length}): ${insights.unique_selling_points.slice(0, 3).join(', ')}`, 'info');
}
if (insights.industry_tags && insights.industry_tags.length > 0) {
addAiLogEntry(`Tagi branzowe: ${insights.industry_tags.join(', ')}`, 'success');
}
if (insights.suggested_category) {
addAiLogEntry(`Sugerowana kategoria: ${insights.suggested_category}`, 'success');
addAiLogEntry(`Tagi branżowe: ${insights.industry_tags.join(', ')}`, 'info');
}
updateAiProgress(95, 'Finalizacja...');
addAiLogEntry('Zapisano do bazy danych', 'info');
await sleep(300);
updateAiProgress(100, 'Oczekuje na akceptację');
addAiLogEntry(`Czas przetwarzania: ${data.processing_time_ms}ms`, 'info');
addAiLogEntry('Wzbogacanie zakonczone pomyslnie!', 'success', '★');
addAiLogEntry('⚠️ PROPOZYCJA WYMAGA AKCEPTACJI', 'step');
addAiLogEntry('Kliknij "Akceptuj" aby dodać dane do profilu lub "Odrzuć" aby porzucić', 'info');
finishAiEnrichment(true);
// Show approval buttons
showProposalApprovalButtons(companyId, proposalId, insights);
return; // Don't close modal yet
} else {
addAiLogEntry('Blad: ' + (data.error || 'Nieznany blad'), 'error');
finishAiEnrichment(false);