Skip to content

Add Markdown Report Generation to Civic Intelligence Engine#533

Open
RohanExploit wants to merge 1 commit intomainfrom
feature/intelligence-markdown-reports-4688680061861365670
Open

Add Markdown Report Generation to Civic Intelligence Engine#533
RohanExploit wants to merge 1 commit intomainfrom
feature/intelligence-markdown-reports-4688680061861365670

Conversation

@RohanExploit
Copy link
Copy Markdown
Owner

@RohanExploit RohanExploit commented Mar 12, 2026

The Daily Civic Intelligence Refinement Engine now auto-generates a human-readable Markdown report alongside its standard JSON snapshot, providing easily accessible insights into the generated civic intelligence indices and trending keyword metrics. Included corresponding unit test updates to ensure code coverage and functional correctness.


PR created automatically by Jules for task 4688680061861365670 started by @RohanExploit


Summary by cubic

Adds Markdown report generation to the Daily Civic Intelligence Refinement Engine, producing REPORT_YYYY-MM-DD.md alongside the JSON snapshot for clearer, human-readable insights. Aligns with task 4688680061861365670.

  • New Features
    • Generate REPORT_<date>.md with index score and delta, top 5 keywords, emerging concern spikes, and highest-severity region.
    • Show clear empty-state messages when sections have no data.
    • Update tests to expect both JSON and Markdown files and verify key report content.

Written for commit 0a2d2e3. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Daily intelligence reports now generated in human-readable Markdown format, complementing existing JSON snapshots
    • Markdown reports include comprehensive sections: index scores, trending keywords, emerging concerns with spike detection, and highest severity regional analysis
    • Report files automatically named with dates for straightforward tracking and organization

…nement engine

- Updated IntelligenceIndex to generate a human-readable markdown report (`REPORT_YYYY-MM-DD.md`) alongside the JSON snapshot.
- Updated related unit tests in `dailyRefinement.test.ts` to assert markdown report generation and content structure.
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings March 12, 2026 06:32
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 12, 2026

Deploy Preview for fixmybharat canceled.

Name Link
🔨 Latest commit 0a2d2e3
🔍 Latest deploy log https://app.netlify.com/projects/fixmybharat/deploys/69b25e18499b820008a76dcb

@github-actions
Copy link
Copy Markdown

🙏 Thank you for your contribution, @RohanExploit!

PR Details:

Quality Checklist:
Please ensure your PR meets the following criteria:

  • Code follows the project's style guidelines
  • Self-review of code completed
  • Code is commented where necessary
  • Documentation updated (if applicable)
  • No new warnings generated
  • Tests added/updated (if applicable)
  • All tests passing locally
  • No breaking changes to existing functionality

Review Process:

  1. Automated checks will run on your code
  2. A maintainer will review your changes
  3. Address any requested changes promptly
  4. Once approved, your PR will be merged! 🎉

Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

The pull request adds markdown report generation to the intelligence index service. A new private method generates human-readable markdown reports for daily snapshots, which are written alongside existing JSON snapshots. Tests are updated to validate both file formats are created with appropriate content.

Changes

Cohort / File(s) Summary
Markdown Report Generation
services/intelligenceIndex.ts
Adds private saveMarkdownReport() method to generate markdown reports with index score, keywords, emerging concerns, and region severity. Integrates into saveSnapshot() to write formatted REPORT_<date>.md files alongside JSON snapshots.
Test Updates
tests/dailyRefinement.test.ts
Updates snapshot validation tests to expect both .json and .md file outputs. Verifies markdown files contain expected headers ("Civic Intelligence Index") and category identifiers, confirming dual report generation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A daily snapshot, neat and bright,
Now joins with markdown in dual might—
JSON partners with formatted prose,
Readable reports that clearly disclose,
Intelligence blooming where data flows! 📊✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description provides clear context about the changes and includes auto-generated summaries, but lacks structured compliance with the repository's required template. Fill out the standard PR template sections including Type of Change, Related Issue, Testing Done, and Checklist to ensure consistency with repository guidelines.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding Markdown report generation to the Civic Intelligence Engine, which is the primary focus of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/intelligence-markdown-reports-4688680061861365670

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a human-readable Markdown report output to the Daily Civic Intelligence Refinement Engine’s snapshot persistence, so each daily run produces both a JSON snapshot (machine-friendly) and a Markdown report (human-friendly).

Changes:

  • Extend IntelligenceIndex.saveSnapshot() to also generate a REPORT_<date>.md Markdown report alongside <date>.json.
  • Add Markdown report formatting for index score/delta, keywords, spikes, and severity region.
  • Update the IntelligenceIndex integration test to validate both files are produced and the Markdown contains expected content.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
tests/dailyRefinement.test.ts Updates the integration test to expect both JSON + Markdown outputs and validates basic report content.
services/intelligenceIndex.ts Implements Markdown report generation and writes it during snapshot saving.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +68 to +69
// Also save a human-readable markdown report
this.saveMarkdownReport(snapshot);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saveSnapshot now always writes a second file synchronously, so any filesystem error while creating the Markdown report will throw and make the daily refinement job look like it failed even though the JSON snapshot has already been persisted. Consider isolating Markdown generation failures (e.g., try/catch around saveMarkdownReport and log/continue) or writing both outputs in a way that keeps failure behavior consistent/intentional.

Suggested change
// Also save a human-readable markdown report
this.saveMarkdownReport(snapshot);
// Also save a human-readable markdown report, but don't fail the snapshot
// operation if markdown generation/writing encounters an error.
try {
this.saveMarkdownReport(snapshot);
} catch (err) {
// Log and continue; the primary JSON snapshot has already been persisted.
console.error(
`Failed to save markdown report for snapshot ${snapshot.date}:`,
err,
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +181 to +190
const jsonFile = files.find(f => f.endsWith('.json'));
const mdFile = files.find(f => f.endsWith('.md'));

expect(jsonFile).toBeDefined();
expect(jsonFile).toContain(snapshot.date);

expect(mdFile).toBeDefined();
expect(mdFile).toContain(`REPORT_${snapshot.date}`);

const mdContent = fs.readFileSync(path.join(testSnapshotsDir, mdFile!), 'utf-8');
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts mdFile/jsonFile are defined, but Jest assertions don’t short-circuit execution; if either is undefined, the following toContain/readFileSync will throw a secondary error and obscure the real failure. Prefer making the values non-optional before use (e.g., explicit if (!mdFile) throw … / expect(mdFile).toBeTruthy() plus narrowing) so failures stay clear and the test doesn’t crash mid-way.

Suggested change
const jsonFile = files.find(f => f.endsWith('.json'));
const mdFile = files.find(f => f.endsWith('.md'));
expect(jsonFile).toBeDefined();
expect(jsonFile).toContain(snapshot.date);
expect(mdFile).toBeDefined();
expect(mdFile).toContain(`REPORT_${snapshot.date}`);
const mdContent = fs.readFileSync(path.join(testSnapshotsDir, mdFile!), 'utf-8');
const jsonFile = files.find(f => f.endsWith(".json"));
if (!jsonFile) {
throw new Error("Expected JSON snapshot file to be created, but none was found.");
}
const mdFile = files.find(f => f.endsWith(".md"));
if (!mdFile) {
throw new Error("Expected markdown snapshot file to be created, but none was found.");
}
expect(jsonFile).toContain(snapshot.date);
expect(mdFile).toContain(`REPORT_${snapshot.date}`);
const mdContent = fs.readFileSync(path.join(testSnapshotsDir, mdFile), "utf-8");

Copilot uses AI. Check for mistakes.
Comment on lines +179 to +182
expect(files.length).toBe(2); // Should have the JSON and the MD file

const jsonFile = files.find(f => f.endsWith('.json'));
const mdFile = files.find(f => f.endsWith('.md'));
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expect(files.length).toBe(2) makes this test brittle if snapshot saving later adds any additional artifacts (e.g., future metadata files). Since you already locate files by extension below, consider asserting the presence of one .json and one .md instead of an exact directory size.

Suggested change
expect(files.length).toBe(2); // Should have the JSON and the MD file
const jsonFile = files.find(f => f.endsWith('.json'));
const mdFile = files.find(f => f.endsWith('.md'));
const jsonFile = files.find(f => f.endsWith('.json'));
const mdFile = files.find(f => f.endswith('.md'));

Copilot uses AI. Check for mistakes.

markdown += `## Top 5 Emerging Keywords\n`;
if (snapshot.topKeywords.length > 0) {
snapshot.topKeywords.forEach((kw, i) => {
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The report section header says "Top 5 Emerging Keywords", but the code iterates over all snapshot.topKeywords without limiting to 5. Either cap the list (e.g., slice to 5) or change the header so the generated report doesn’t claim a top-5 when more items may be printed (especially if saveSnapshot is called with a snapshot built outside TrendAnalyzer.getTopKeywords(…, 5)).

Suggested change
snapshot.topKeywords.forEach((kw, i) => {
snapshot.topKeywords.slice(0, 5).forEach((kw, i) => {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/intelligenceIndex.ts`:
- Around line 77-81: The "Top 5 Emerging Keywords" heading is out of sync with
the loop that writes all entries from snapshot.topKeywords; update the logic in
services/intelligenceIndex.ts so the output matches the heading by iterating
only the first five items (e.g., use snapshot.topKeywords.slice(0, 5) or loop up
to Math.min(5, snapshot.topKeywords.length) when building markdown), or
alternatively change the heading text to reflect the actual count (e.g., "Top
Emerging Keywords" or include the count). Adjust the loop that references
snapshot.topKeywords to use the chosen approach so the section title and listed
items remain consistent.
- Around line 68-73: The code builds file paths using DailySnapshot.date without
validation (used in saveSnapshot and saveMarkdownReport), allowing path
traversal; validate snapshot.date against a strict YYYY-MM-DD regex before
constructing any path and reject (throw or return an error) on mismatch, and
then use path.join(this.snapshotsDir, `REPORT_${snapshot.date}.md`) only after
validation; ensure the same validation is applied where saveSnapshot is public
so no untrusted snapshot.date can escape snapshotsDir.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9144a7c-74e4-4689-8428-b9e2895f4ba3

📥 Commits

Reviewing files that changed from the base of the PR and between 4e6c0b3 and 0a2d2e3.

📒 Files selected for processing (2)
  • services/intelligenceIndex.ts
  • tests/dailyRefinement.test.ts

Comment on lines +68 to +73
// Also save a human-readable markdown report
this.saveMarkdownReport(snapshot);
}

private saveMarkdownReport(snapshot: DailySnapshot): void {
const filePath = path.join(this.snapshotsDir, `REPORT_${snapshot.date}.md`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Validate snapshot.date before building filenames.

saveSnapshot is public, and DailySnapshot.date is just a string. A value like ../../outside will make these writes escape snapshotsDir. Reject anything outside the expected YYYY-MM-DD format before constructing either path.

🔐 Suggested fix
  public saveSnapshot(snapshot: DailySnapshot): void {
-    const filePath = path.join(this.snapshotsDir, `${snapshot.date}.json`);
+    const safeDate = this.validateSnapshotDate(snapshot.date);
+    const filePath = path.join(this.snapshotsDir, `${safeDate}.json`);
     fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2), "utf-8");

     // Also save a human-readable markdown report
-    this.saveMarkdownReport(snapshot);
+    this.saveMarkdownReport({ ...snapshot, date: safeDate });
   }

+  private validateSnapshotDate(date: string): string {
+    if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
+      throw new Error("snapshot.date must use YYYY-MM-DD format");
+    }
+    return date;
+  }
+
   private saveMarkdownReport(snapshot: DailySnapshot): void {
     const filePath = path.join(this.snapshotsDir, `REPORT_${snapshot.date}.md`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/intelligenceIndex.ts` around lines 68 - 73, The code builds file
paths using DailySnapshot.date without validation (used in saveSnapshot and
saveMarkdownReport), allowing path traversal; validate snapshot.date against a
strict YYYY-MM-DD regex before constructing any path and reject (throw or return
an error) on mismatch, and then use path.join(this.snapshotsDir,
`REPORT_${snapshot.date}.md`) only after validation; ensure the same validation
is applied where saveSnapshot is public so no untrusted snapshot.date can escape
snapshotsDir.

Comment on lines +77 to +81
markdown += `## Top 5 Emerging Keywords\n`;
if (snapshot.topKeywords.length > 0) {
snapshot.topKeywords.forEach((kw, i) => {
markdown += `${i + 1}. ${kw}\n`;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep the section title and output in sync.

The heading says Top 5, but this loop writes every keyword in snapshot.topKeywords. Either cap the list here or rename the section so the report stays accurate when callers pass more than five keywords.

📝 Suggested fix
     markdown += `## Top 5 Emerging Keywords\n`;
     if (snapshot.topKeywords.length > 0) {
-      snapshot.topKeywords.forEach((kw, i) => {
+      snapshot.topKeywords.slice(0, 5).forEach((kw, i) => {
         markdown += `${i + 1}. ${kw}\n`;
       });
     } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
markdown += `## Top 5 Emerging Keywords\n`;
if (snapshot.topKeywords.length > 0) {
snapshot.topKeywords.forEach((kw, i) => {
markdown += `${i + 1}. ${kw}\n`;
});
markdown += `## Top 5 Emerging Keywords\n`;
if (snapshot.topKeywords.length > 0) {
snapshot.topKeywords.slice(0, 5).forEach((kw, i) => {
markdown += `${i + 1}. ${kw}\n`;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/intelligenceIndex.ts` around lines 77 - 81, The "Top 5 Emerging
Keywords" heading is out of sync with the loop that writes all entries from
snapshot.topKeywords; update the logic in services/intelligenceIndex.ts so the
output matches the heading by iterating only the first five items (e.g., use
snapshot.topKeywords.slice(0, 5) or loop up to Math.min(5,
snapshot.topKeywords.length) when building markdown), or alternatively change
the heading text to reflect the actual count (e.g., "Top Emerging Keywords" or
include the count). Adjust the loop that references snapshot.topKeywords to use
the chosen approach so the section title and listed items remain consistent.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="services/intelligenceIndex.ts">

<violation number="1" location="services/intelligenceIndex.ts:79">
P3: Limit the rendered keywords to 5 to match the report section title.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


markdown += `## Top 5 Emerging Keywords\n`;
if (snapshot.topKeywords.length > 0) {
snapshot.topKeywords.forEach((kw, i) => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Limit the rendered keywords to 5 to match the report section title.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/intelligenceIndex.ts, line 79:

<comment>Limit the rendered keywords to 5 to match the report section title.</comment>

<file context>
@@ -64,6 +64,43 @@ export class IntelligenceIndex {
+
+    markdown += `## Top 5 Emerging Keywords\n`;
+    if (snapshot.topKeywords.length > 0) {
+      snapshot.topKeywords.forEach((kw, i) => {
+        markdown += `${i + 1}. ${kw}\n`;
+      });
</file context>
Suggested change
snapshot.topKeywords.forEach((kw, i) => {
snapshot.topKeywords.slice(0, 5).forEach((kw, i) => {
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants