Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
bb5e52a
Add TypeScript type definitions and utility files for mobile analysis…
Copilot Apr 12, 2026
a162321
Add TypeScript detector ports: fiscal, constitutional, surveillance
Copilot Apr 12, 2026
7641c61
Add TypeScript detector ports: procurement timeline, governance gap, …
Copilot Apr 12, 2026
d539ce7
Add TypeScript detector ports: administrativeIntegrity, scopeExpansio…
Copilot Apr 12, 2026
920bf9c
Fix incomplete sanitization in parseDollarAmount
Copilot Apr 12, 2026
098d4c1
Add mobile storage layer and OCR module
Copilot Apr 12, 2026
bf1c7c8
Add React Native UI components and screens for ODIA mobile app
Copilot Apr 12, 2026
82fbb40
Add comprehensive unit tests for all mobile detectors
Copilot Apr 12, 2026
d982e8f
Add mobile test files for detectors, textUtils, and storage
Copilot Apr 12, 2026
a14d952
Add mobile documentation: README, development guide, and porting guide
Copilot Apr 12, 2026
c079d45
feat: add React Native mobile app with TypeScript-ported analysis det…
Copilot Apr 12, 2026
8187c90
fix: resolve pre-commit linting and schema validation errors
Copilot Apr 12, 2026
0e4fdc7
fix: add E501 exception for fetch_documents.py function signature
Copilot Apr 12, 2026
ee650c7
fix: shorten legistar_adapter docstring to comply with E501
Copilot Apr 12, 2026
c194bfa
fix: remove unused Depends import from auth_middleware
Copilot Apr 12, 2026
b5876f0
fix: resolve all pre-commit issues - unused imports and optional depe…
Copilot Apr 12, 2026
7db038b
Merge branch 'master' into copilot/featuremobile-app
SynTechRev Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ lib/
lib64/
# Allow frontend/lib/ (Next.js source — not a Python virtualenv)
!frontend/lib/
# Allow mobile/lib/ (React Native source)
!mobile/lib/
parts/
sdist/
var/
Expand Down Expand Up @@ -77,6 +79,15 @@ data/ledger.db
.DS_Store
Thumbs.db

# Mobile / React Native
mobile/node_modules/
mobile/.expo/
mobile/ios/
mobile/android/
mobile/dist/
mobile/coverage/
mobile/.env*.local

# Frontend / Node.js
frontend/node_modules/
frontend/.next/
Expand Down
111 changes: 111 additions & 0 deletions mobile/__tests__/detectors/administrativeIntegrity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { detectAdministrativeAnomalies } from '../../lib/analysis/detectors/administrativeIntegrity';
import { NormalizedDocument } from '../../lib/analysis/types';

describe('detectAdministrativeAnomalies', () => {
it('returns empty for non-object input', () => {
expect(detectAdministrativeAnomalies(null as any)).toEqual([]);
});

it('detects missing final_action with approval signal', () => {
const doc: NormalizedDocument = {
raw_text: 'The motion was approved by a unanimous vote.',
final_action: null,
};
const result = detectAdministrativeAnomalies(doc);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'admin:missing-final-action',
severity: 'high',
layer: 'administrative',
}),
])
);
});

it('does not flag final_action when present', () => {
const doc: NormalizedDocument = {
raw_text: 'The motion was approved.',
final_action: 'Approved',
};
const result = detectAdministrativeAnomalies(doc);
expect(result.find((a) => a.id === 'admin:missing-final-action')).toBeUndefined();
});

it('detects blank required fields', () => {
const doc: NormalizedDocument = {
raw_text: 'Some text',
final_action: 'Done',
status: '',
vote_result: null,
meeting_date: '2024-01-15',
agenda_number: '5',
};
const result = detectAdministrativeAnomalies(doc);
const anomaly = result.find((a) => a.id === 'admin:blank-required-fields');
expect(anomaly).toBeDefined();
expect(anomaly!.details.blank_fields).toEqual(expect.arrayContaining(['status', 'vote_result']));
});

it('detects retroactive authorization', () => {
const doc: NormalizedDocument = {
raw_text: 'The contract was ratified after the execution date.',
final_action: 'Done',
status: 'Complete',
vote_result: 'Yes',
meeting_date: '2024-01-01',
agenda_number: '1',
};
const result = detectAdministrativeAnomalies(doc);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'admin:retroactive-authorization',
severity: 'high',
}),
])
);
});

it('detects nunc pro tunc language', () => {
const doc: NormalizedDocument = {
raw_text: 'The order is entered nunc pro tunc to the original filing date.',
final_action: 'Entered',
status: 'Active',
vote_result: 'Yes',
meeting_date: '2024-01-01',
agenda_number: '1',
};
const result = detectAdministrativeAnomalies(doc);
expect(result.find((a) => a.id === 'admin:retroactive-authorization')).toBeDefined();
});

it('detects misfiling indicators', () => {
const doc: NormalizedDocument = {
raw_text: 'This item was misfiled under the wrong agenda category.',
final_action: 'Filed',
status: 'Active',
vote_result: 'NA',
meeting_date: '2024-01-01',
agenda_number: '1',
};
const result = detectAdministrativeAnomalies(doc);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'admin:potential-misfiling',
severity: 'medium',
}),
])
);
});

it('treats whitespace-only as blank', () => {
const doc: NormalizedDocument = {
raw_text: 'Meeting notes with approved actions.',
final_action: ' ',
};
const result = detectAdministrativeAnomalies(doc);
expect(result.find((a) => a.id === 'admin:missing-final-action')).toBeDefined();
});
});
72 changes: 72 additions & 0 deletions mobile/__tests__/detectors/auditEngine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { analyzeDocument } from '../../lib/analysis/auditEngine';
import { NormalizedDocument } from '../../lib/analysis/types';

describe('analyzeDocument', () => {
it('returns expected structure', () => {
const doc: NormalizedDocument = { raw_text: 'Sample text' };
const result = analyzeDocument(doc);
expect(result).toHaveProperty('count');
expect(result).toHaveProperty('score');
expect(result).toHaveProperty('anomalies');
expect(typeof result.count).toBe('number');
expect(typeof result.score).toBe('number');
expect(Array.isArray(result.anomalies)).toBe(true);
});

it('returns perfect score for clean document', () => {
const doc: NormalizedDocument = {
raw_text: 'A simple document with no issues.',
provenance: { hash: 'sha256-valid' },
};
const result = analyzeDocument(doc);
expect(result.count).toBe(0);
expect(result.score).toBe(1.0);
});

it('detects fiscal anomalies', () => {
const doc: NormalizedDocument = {
raw_text: 'The cost is $1,000,000 for services.',
};
const result = analyzeDocument(doc);
expect(result.anomalies.some((a) => a.layer === 'fiscal')).toBe(true);
});

it('detects constitutional anomalies', () => {
const doc: NormalizedDocument = {
raw_text: 'The Secretary may determine the appropriate measures at their discretion.',
provenance: { hash: 'abc' },
};
const result = analyzeDocument(doc);
expect(result.anomalies.some((a) => a.layer === 'constitutional')).toBe(true);
});

it('detects surveillance anomalies', () => {
const doc: NormalizedDocument = {
raw_text: 'The surveillance monitoring system is operated by a contractor vendor.',
provenance: { hash: 'abc' },
};
const result = analyzeDocument(doc);
expect(result.anomalies.some((a) => a.layer === 'surveillance')).toBe(true);
});

it('score decreases with more anomalies', () => {
const cleanDoc: NormalizedDocument = {
raw_text: 'Clean document.',
provenance: { hash: 'abc' },
};
const dirtyDoc: NormalizedDocument = {
raw_text: 'The Secretary may determine at their discretion surveillance monitoring contractor.',
};
const cleanResult = analyzeDocument(cleanDoc);
const dirtyResult = analyzeDocument(dirtyDoc);
expect(dirtyResult.score).toBeLessThan(cleanResult.score);
});

it('handles empty document', () => {
const doc: NormalizedDocument = {};
const result = analyzeDocument(doc);
expect(result.count).toBeGreaterThanOrEqual(0);
expect(result.score).toBeLessThanOrEqual(1.0);
expect(result.score).toBeGreaterThanOrEqual(0.0);
});
});
55 changes: 55 additions & 0 deletions mobile/__tests__/detectors/constitutional.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { detectConstitutionalAnomalies } from '../../lib/analysis/detectors/constitutional';
import { NormalizedDocument } from '../../lib/analysis/types';

describe('detectConstitutionalAnomalies', () => {
it('returns empty array for non-object input', () => {
expect(detectConstitutionalAnomalies(null as any)).toEqual([]);
});

it('returns empty for document with no text', () => {
const doc: NormalizedDocument = {};
expect(detectConstitutionalAnomalies(doc)).toEqual([]);
});

it('detects broad delegation without standards', () => {
const doc: NormalizedDocument = {
raw_text: 'The Secretary may determine the appropriate levels and the Director shall establish new rules.',
};
const result = detectConstitutionalAnomalies(doc);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'constitutional:broad-delegation',
severity: 'medium',
layer: 'constitutional',
}),
])
);
});

it('does not flag delegation when standards are present', () => {
const doc: NormalizedDocument = {
raw_text: 'The Secretary may determine levels subject to the standard criteria established by Congress.',
};
const result = detectConstitutionalAnomalies(doc);
expect(result.find((a) => a.id === 'constitutional:broad-delegation')).toBeUndefined();
});

it('detects "in their discretion" pattern', () => {
const doc: NormalizedDocument = {
raw_text: 'The Commissioner may act in their discretion to implement new policies.',
};
const result = detectConstitutionalAnomalies(doc);
expect(result.find((a) => a.id === 'constitutional:broad-delegation')).toBeDefined();
});

it('reports delegation_count in details', () => {
const doc: NormalizedDocument = {
raw_text: 'The Secretary may determine X. The Administrator shall prescribe Y.',
};
const result = detectConstitutionalAnomalies(doc);
const anomaly = result.find((a) => a.id === 'constitutional:broad-delegation');
expect(anomaly).toBeDefined();
expect(anomaly!.details.delegation_count).toBeGreaterThanOrEqual(2);
});
});
81 changes: 81 additions & 0 deletions mobile/__tests__/detectors/crossReference.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { detectCrossJurisdictionRefs, crossReferenceAudit } from '../../lib/analysis/detectors/crossReference';

describe('detectCrossJurisdictionRefs', () => {
it('returns empty for text with no citations', () => {
expect(detectCrossJurisdictionRefs('No legal references here.')).toEqual([]);
});

it('detects USC + California cross-reference', () => {
const text = '42 U.S.C. § 1983 and Cal. Penal Code provisions apply.';
const result = detectCrossJurisdictionRefs(text);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'federal_state_cross_reference',
severity: 'info',
}),
])
);
});

it('detects CFR + California cross-reference', () => {
const text = '21 CFR § 50 and Cal. Civil Code provisions.';
const result = detectCrossJurisdictionRefs(text);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'cfr_state_cross_reference',
}),
])
);
});

it('does not flag only federal references', () => {
const text = '42 U.S.C. § 1983 and 18 U.S.C. § 1001.';
expect(detectCrossJurisdictionRefs(text)).toEqual([]);
});
});

describe('crossReferenceAudit', () => {
it('returns empty for empty docs list', () => {
expect(crossReferenceAudit([])).toEqual([]);
});

it('detects jurisdiction mismatch for federal doc with more state refs', () => {
const docs = [
{
id: 'doc-1',
text: 'Cal. Penal Code and Cal. Civil Code and Cal. Health Code but only 42 U.S.C. § 1983.',
jurisdiction: 'federal',
},
];
const result = crossReferenceAudit(docs as any);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
issue: 'jurisdiction_mismatch',
severity: 'warning',
}),
])
);
});

it('detects jurisdiction mismatch for state doc with more federal refs', () => {
const docs = [
{
id: 'doc-2',
text: '42 U.S.C. § 1983, 18 U.S.C. § 1001, 21 CFR § 50, no state refs.',
jurisdiction: 'california',
},
];
const result = crossReferenceAudit(docs as any);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
issue: 'jurisdiction_mismatch',
severity: 'warning',
}),
])
);
});
});
Loading
Loading