feat(entity-review): add 'provides match' toggle for source URLs
All checks were successful
Deploy Frontend / build-and-deploy (push) Successful in 2m23s
DSPy RAG Evaluation / Layer 1 - Unit Tests (push) Successful in 5m37s
DSPy RAG Evaluation / Layer 2 - DSPy Module Tests (push) Successful in 7m24s
DSPy RAG Evaluation / Layer 3 - Integration Tests (push) Successful in 5m47s
DSPy RAG Evaluation / Layer 4 - Comprehensive Evaluation (push) Successful in 6m52s
DSPy RAG Evaluation / Quality Gate (push) Successful in 1s
All checks were successful
Deploy Frontend / build-and-deploy (push) Successful in 2m23s
DSPy RAG Evaluation / Layer 1 - Unit Tests (push) Successful in 5m37s
DSPy RAG Evaluation / Layer 2 - DSPy Module Tests (push) Successful in 7m24s
DSPy RAG Evaluation / Layer 3 - Integration Tests (push) Successful in 5m47s
DSPy RAG Evaluation / Layer 4 - Comprehensive Evaluation (push) Successful in 6m52s
DSPy RAG Evaluation / Quality Gate (push) Successful in 1s
- Add toggle in source URL form to indicate when a source provides sufficient information to create a person profile without LinkedIn - Store provides_match boolean in source observation data - Display green badge on existing sources that have provides_match: true - Include bilingual tooltip (EN/NL) explaining the toggle purpose
This commit is contained in:
parent
b11223277c
commit
6812524ae5
3 changed files with 116 additions and 11 deletions
|
|
@ -2604,6 +2604,67 @@
|
|||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* Source URL Header (link + badge) */
|
||||
.source-url-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Provides Match Toggle */
|
||||
.provides-match-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.provides-match-toggle .toggle-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary, #64748b);
|
||||
}
|
||||
|
||||
.provides-match-toggle input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #10b981;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.provides-match-toggle .toggle-text {
|
||||
color: var(--text-primary, #1e293b);
|
||||
}
|
||||
|
||||
.dark .provides-match-toggle .toggle-text {
|
||||
color: var(--text-primary, #e2e8f0);
|
||||
}
|
||||
|
||||
.provides-match-toggle svg {
|
||||
color: var(--text-secondary, #94a3b8);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* Provides Match Badge on existing source URLs */
|
||||
.provides-match-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 2px 6px;
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: #10b981;
|
||||
border-radius: 4px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dark .provides-match-badge {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
/* WCMS-Only Profiles Styles */
|
||||
.wcms-only-search {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ interface SourceUrlItem {
|
|||
source_type?: string;
|
||||
source_domain?: string;
|
||||
comment?: string;
|
||||
provides_match?: boolean;
|
||||
added_at?: string;
|
||||
added_manually?: boolean;
|
||||
}
|
||||
|
|
@ -402,6 +403,7 @@ export default function EntityReviewPage() {
|
|||
const [showSourceUrlInput, setShowSourceUrlInput] = useState(false);
|
||||
const [sourceUrl, setSourceUrl] = useState('');
|
||||
const [sourceComment, setSourceComment] = useState('');
|
||||
const [sourceProvidesMatch, setSourceProvidesMatch] = useState(false);
|
||||
const [addingSourceUrl, setAddingSourceUrl] = useState(false);
|
||||
const [sourceUrlError, setSourceUrlError] = useState<string | null>(null);
|
||||
const [sourceUrlSuccess, setSourceUrlSuccess] = useState<string | null>(null);
|
||||
|
|
@ -714,6 +716,7 @@ export default function EntityReviewPage() {
|
|||
source_url: sourceUrl.trim(),
|
||||
comment: sourceComment.trim() || undefined,
|
||||
source_type: 'webpage',
|
||||
provides_match: sourceProvidesMatch,
|
||||
};
|
||||
|
||||
// For WCMS-only profiles, include WCMS metadata
|
||||
|
|
@ -747,6 +750,7 @@ export default function EntityReviewPage() {
|
|||
// Clear inputs and show success
|
||||
setSourceUrl('');
|
||||
setSourceComment('');
|
||||
setSourceProvidesMatch(false);
|
||||
setSourceUrlSuccess(result.message || (language === 'nl' ? 'Bron toegevoegd' : 'Source added'));
|
||||
|
||||
// Hide success message after 3 seconds
|
||||
|
|
@ -765,7 +769,7 @@ export default function EntityReviewPage() {
|
|||
} finally {
|
||||
setAddingSourceUrl(false);
|
||||
}
|
||||
}, [selectedProfile, sourceUrl, sourceComment, language, fetchProfileDetail, fetchWcmsOnlyProfileDetail]);
|
||||
}, [selectedProfile, sourceUrl, sourceComment, sourceProvidesMatch, language, fetchProfileDetail, fetchWcmsOnlyProfileDetail]);
|
||||
|
||||
// Save review decision
|
||||
const saveDecision = useCallback(async (decision: 'match' | 'not_match' | 'uncertain') => {
|
||||
|
|
@ -2143,16 +2147,27 @@ export default function EntityReviewPage() {
|
|||
}
|
||||
return (
|
||||
<div key={source.source_id} className="source-url-item">
|
||||
<a
|
||||
href={source.source_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="source-url-link"
|
||||
title={source.source_url}
|
||||
>
|
||||
<Globe size={12} />
|
||||
{displayUrl}
|
||||
</a>
|
||||
<div className="source-url-header">
|
||||
<a
|
||||
href={source.source_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="source-url-link"
|
||||
title={source.source_url}
|
||||
>
|
||||
<Globe size={12} />
|
||||
{displayUrl}
|
||||
</a>
|
||||
{source.provides_match && (
|
||||
<span
|
||||
className="provides-match-badge"
|
||||
title={language === 'nl' ? 'Bron volstaat als match' : 'Provides match'}
|
||||
>
|
||||
<CheckCircle size={10} />
|
||||
{language === 'nl' ? 'Match' : 'Match'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{source.comment && (
|
||||
<div className="source-url-comment">
|
||||
"{source.comment}"
|
||||
|
|
@ -2204,6 +2219,32 @@ export default function EntityReviewPage() {
|
|||
disabled={addingSourceUrl}
|
||||
rows={2}
|
||||
/>
|
||||
<div className="provides-match-toggle">
|
||||
<label className="toggle-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sourceProvidesMatch}
|
||||
onChange={(e) => setSourceProvidesMatch(e.target.checked)}
|
||||
disabled={addingSourceUrl}
|
||||
/>
|
||||
<span className="toggle-text">
|
||||
{language === 'nl' ? 'Bron volstaat als match' : 'Provides match'}
|
||||
</span>
|
||||
<Tooltip
|
||||
position="top"
|
||||
maxWidth={300}
|
||||
content={
|
||||
<div>
|
||||
{language === 'nl'
|
||||
? 'Wanneer ingeschakeld, biedt deze bron voldoende informatie om deze persoon te identificeren en een volledig persoonsprofiel aan te maken zonder LinkedIn-match.'
|
||||
: 'When enabled, this source provides sufficient information to identify this person and create a complete person profile without needing a LinkedIn match.'}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Info size={14} />
|
||||
</Tooltip>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
className="add-source-url-btn"
|
||||
onClick={addSourceUrl}
|
||||
|
|
|
|||
|
|
@ -424,6 +424,7 @@ class AddSourceUrlRequest(BaseModel):
|
|||
source_url: str
|
||||
comment: Optional[str] = None # User comment explaining the source, e.g., "De lessen worden gegeven door Mala Sardjoepersad"
|
||||
source_type: Optional[str] = None # Optional type: "webpage", "social_media", "news_article", etc.
|
||||
provides_match: bool = False # When True, this source provides sufficient info to identify the person without LinkedIn
|
||||
# Optional WCMS data for profiles that aren't in the candidates file
|
||||
wcms_name: Optional[str] = None
|
||||
wcms_email: Optional[str] = None
|
||||
|
|
@ -2634,6 +2635,7 @@ async def add_source_url(request: AddSourceUrlRequest):
|
|||
wcms_ppid = request.wcms_ppid
|
||||
comment = request.comment.strip() if request.comment else None
|
||||
source_type = request.source_type or "webpage"
|
||||
provides_match = request.provides_match
|
||||
|
||||
# Load candidates from the aggregated file
|
||||
load_candidates()
|
||||
|
|
@ -2681,6 +2683,7 @@ async def add_source_url(request: AddSourceUrlRequest):
|
|||
"source_type": source_type,
|
||||
"source_domain": parsed.netloc,
|
||||
"comment": comment,
|
||||
"provides_match": provides_match,
|
||||
"added_at": datetime.now(timezone.utc).isoformat(),
|
||||
"added_manually": True
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue