SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
2 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%

Projects: automated re-qualification of hidden projects on re-extraction; news parser v4

Simon-Pierre Boucher committed 20 days ago (Sep 12, 2026) parent 1148759

3 changed files +15 −1

modified apps/worker/src/connectors/news/extract-project.ts +1 −1
@@ -52,7 +52,7 @@ export interface Announcement {
52 52 hqGuarded: boolean;
53 53 }
54 54
55 −export const EXTRACTOR_VERSION = "news_v3";
55 +export const EXTRACTOR_VERSION = "news_v4";
56 56 /** Operators are looked up in the title and the first 4 000 characters of the story only — never in trailing teasers or footers. */
57 57 export const OPERATOR_SCOPE_CHARS = 4000;
58 58
modified apps/worker/src/ingest/contract.ts +2 −0
@@ -52,6 +52,8 @@ export interface IngestStats {
52 52 projectsVetoed?: number;
53 53 /** building records linked to their campus record instead of being flagged as duplicates */
54 54 campusLinks?: number;
55 + /** hidden projects restored because a re-extraction qualified them again */
56 + projectsRequalified?: number;
55 57 }
56 58
57 59 export type IngestFn = (run: IngestRun, entities: NormalizedEntity[], doc: IngestDocRef | null) => Promise<IngestStats>;
modified apps/worker/src/ingest/projects.ts +12 −0
@@ -237,6 +237,18 @@ export async function ingestProject(tx: Tx, ctx: IngestContext, p: NormalizedPro
237 237
238 238 const prov = existing ? await loadCurrentProvenance(tx, "project", existingId!) : [];
239 239 const id = existing ? String(existing.id) : newId("project");
240 + // a project hidden by the automated repair re-qualifies when a re-extraction yields a physical class with strong evidence
241 + // (human decisions — resolved_by <> 'quality-apply' / 'system' — are never reversed)
242 + if (existing && existing.hidden && physical && p.evidenceLevel === "strong" && !ctx.run.dryRun) {
243 + const auto = await tx.execute(sql`select 1 from quality_flags where entity_type = 'project' and entity_id = ${id} and code = 'project_false_positive' and resolved_by in ('quality-apply', 'system') limit 1`);
244 + const human = await tx.execute(sql`select 1 from quality_flags where entity_type = 'project' and entity_id = ${id} and code = 'project_false_positive' and resolved_by not in ('quality-apply', 'system') limit 1`);
245 + if (auto.length && !human.length) {
246 + await tx.execute(sql`update projects set hidden = false, updated_at = now() where id = ${id}`);
247 + await tx.execute(sql`update quality_flags set resolution = 'auto: re-qualified by re-extraction (physical class, strong evidence)', resolved_at = now() where entity_type = 'project' and entity_id = ${id} and code = 'project_false_positive'`);
248 + existing.hidden = false;
249 + ctx.stats.projectsRequalified = (ctx.stats.projectsRequalified ?? 0) + 1;
250 + }
251 + }
240 252 const before: Row = existing ? { ...existing } : {};
241 253 const next: Row = existing ? { ...existing } : { id, status: "announced", geoPrecision: "unknown", isAi: false, confidence: "moderate", externalIds: {}, aiEvidence: "unknown", hidden: false, reviewPriority: 0 };
242 254 const observed: ObservedField[] = [];
243 255