Do not use cached skills if content updated

Add candidate/job route to job-analysis
This commit is contained in:
James Ketr 2025-07-08 13:48:47 -07:00
parent a0cb23df28
commit 4a7a72812f
4 changed files with 90 additions and 1 deletions

View File

@ -199,6 +199,9 @@ RUN pip install openapi-python-client
# QR code generator
RUN pip install pyqrcode pypng
# Anthropic and other backends
RUN pip install anthropic pydantic_ai
# Automatic type conversion pydantic -> typescript
RUN pip install pydantic typing-inspect jinja2
RUN apt-get update \

View File

@ -52,7 +52,7 @@ export const navigationConfig: NavigationConfig = {
{
id: 'job-analysis',
label: 'Job Analysis',
path: '/job-analysis',
path: '/job-analysis/:candidateId?/:jobId?/:stepId?',
variant: 'fullWidth',
icon: <WorkIcon />,
component: <JobAnalysisPage />,

View File

@ -29,6 +29,7 @@ import { JobCreator } from 'components/JobCreator';
import { LoginRestricted } from 'components/ui/LoginRestricted';
import { ResumeGenerator } from 'components/ResumeGenerator';
import { JobsView } from 'components/ui/JobsView';
import { Navigate, useNavigate, useParams } from 'react-router-dom';
function WorkAddIcon(): JSX.Element {
return (
@ -78,7 +79,13 @@ const capitalize = (str: string): string => {
// Main component
const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
const theme = useTheme();
const navigate = useNavigate();
const { user, guest, apiClient } = useAuth();
const { candidateId, jobId, stepId } = useParams<{
candidateId?: string;
jobId?: string;
stepId?: string;
}>();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const { selectedJob, setSelectedJob } = useSelectedJob();
@ -95,6 +102,61 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
const [activeStep, setActiveStep] = useState<number>(user === null ? 0 : 1);
const maxStep = 4;
useEffect(() => {
if (jobId && candidateId && stepId && activeStep !== parseFloat(stepId)) {
setActiveStep(parseFloat(stepId));
}
}, [jobId, candidateId, activeStep, stepId]);
useEffect(() => {
let routeCandidateId = candidateId || '';
let routeJobId = jobId || '';
const routeStepId = stepId ? `/${stepId}` : '';
if (routeCandidateId && routeCandidateId !== selectedCandidate?.id) {
apiClient
.getCandidate(routeCandidateId)
.then((candidate: Candidate | null) => {
if (candidate) {
setSelectedCandidate(candidate);
routeCandidateId = candidate.id || '';
} else {
setError('Candidate not found.');
}
})
.catch((err: Error) => {
console.error('Error fetching candidate:', err);
setError('Failed to fetch candidate information.');
});
} else {
routeCandidateId = selectedCandidate?.id || '';
}
if (routeJobId && routeJobId !== selectedJob?.id) {
apiClient
.getJob(routeJobId)
.then((job: Job | null) => {
if (job) {
setSelectedJob(job);
routeJobId = job.id || '';
} else {
setError('Job not found.');
}
})
.catch((err: Error) => {
console.error('Error fetching job:', err);
setError('Failed to fetch job information.');
});
} else {
routeJobId = selectedJob?.id || '';
}
if (routeCandidateId && routeJobId) {
navigate(`/job-analysis/${routeCandidateId}/${routeJobId}${routeStepId}`, {
replace: true,
});
}
}, [candidateId, jobId, setSelectedCandidate, setSelectedJob]);
useEffect(() => {
if (selectedCandidate === null && user !== null) {
apiClient
@ -209,6 +271,12 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
return;
}
setActiveStep(step);
if (selectedCandidate?.id && selectedJob?.id) {
const routeStep = step ? `/${step.toString()}` : '';
navigate(`/job-analysis/${selectedCandidate.id}/${selectedJob.id}${routeStep}`, {
replace: true,
});
}
};
const onCandidateSelect = (candidate: Candidate): void => {

View File

@ -1482,6 +1482,24 @@ async def post_job_analysis(
continue
cache_key = get_skill_cache_key(candidate.id, skill)
assessment: SkillAssessment | None = await database.get_cached_skill_match(cache_key)
# Determine if we need to regenerate the assessment
if assessment:
# Get the latest RAG data update time for the current user
user_rag_update_time = await database.get_user_rag_update_time(candidate.id)
updated = assessment.updated_at
# Check if cached result is still valid
# Regenerate if user's RAG data was updated after cache date
if user_rag_update_time and user_rag_update_time >= updated:
logger.info(f"🔄 Out-of-date cached entry for {candidate.username} skill {assessment.skill}")
assessment = None
else:
logger.info(
f"✅ Using cached skill match for {candidate.username} skill {assessment.skill}: {cache_key}"
)
else:
logger.info(f"💾 No cached skill match data: {cache_key}, {candidate.id}, {skill}")
if not assessment:
logger.info(f"💾 No cached skill match data: {cache_key}, {candidate.id}, {skill}")
continue